精华内容
下载资源
问答
  • 多线程带来问题什么需要多线程 其实说白了,时代变了,现在的机器都是多核的了,为了榨干机器最后的性能我们引入单线程。 为了充分利用CPU资源,为了提高CPU的使用率,采用多线程的方式去同时完成几件事情而不...

    多线程带来的问题

    为什么需要多线程

    其实说白了,时代变了,现在的机器都是多核的了,为了榨干机器最后的性能我们引入单线程。

    为了充分利用CPU资源,为了提高CPU的使用率,采用多线程的方式去同时完成几件事情而不互相干扰,为了处理大量的IO操作时或处理的情况需要花费大量的时间等等,比如:读写文件,视频图像的采集,处理,显示,保存等。

    性能问题

    上下文切换

    Java 中的线程与 CPU 单核执行是一对一的,即单个处理器同一时间只能处理一个线程的执行;而 CPU 是通过时间片算法来执行任务的,不同的线程活跃状态不同,CPU 会在多个线程间切换执行,在切换时会保存上一个任务的状态,以便下次切换回这个任务时可以再加载到这个任务的状态,这种任务的保存到加载就是一次上下文切换。线程数越多,带来的上下文切换越严重,上下文切换会带来 CPU 系统态使用率占用,这就是为什么当我们开启大量线程,系统反而更慢的原因

    其实你从这个表述中看到,其实整个切换的过程是有线程停止运行的,假设有这样一个工作有10个相同的步骤,每个线程处处理每一个步骤用的时间都是一样的,而且我们同时只能让一个线程工作,那这个时候多个线程之间的协调,也就是这里的调度就会占用很多时间,在公共量相等的情况下,我们的单线程肯定是比多线程要快的,但是现在我们的服务器都是多核,所以说多线程可以加快我们的处理速度,但是这是由前提的,就是线程数和我们的cpu 的核数的关系。

    我们要减少上下文切换,有几种手段:

    • 减少锁等待:锁等待意味着,线程频繁在活跃与等待状态之间切换,增加上下文切换,锁等待是由对同一份资源竞争激烈引起的,在一些场景我们可以用一些手段减轻锁竞争,比如数据分片或者数据快照等方式。
    • CAS 算法:利用 Compare and Swap, 即比较再交换可以避免加锁。后续章节会介绍 CAS 算法。
    • 使用合适的线程数或者协程:使用合适的线程数而不是越多越好,在 CPU 密集的系统中,比如我们倾向于启动最多 2 倍处理器核心数量的线程;协程由于天然在单线程实现多任务的调度,所以协程实际上避免了上下文切换。

    缓存失效

    不仅上下文切换会带来性能问题,缓存失效也有可能带来性能问题。由于程序有很大概率会再次访问刚才访问过的数据,所以为了加速整个程序的运行,会使用缓存,这样我们在使用相同数据时就可以很快地获取数据。可一旦进行了线程调度,切换到其他线程,CPU就会去执行不同的代码,原有的缓存就很可能失效了,需要重新缓存新的数据,这也会造成一定的开销,所以线程调度器为了避免频繁地发生上下文切换,通常会给被调度到的线程设置最小的执行时间,也就是只有执行完这段时间之后,才可能进行下一次的调度,由此减少上下文切换的次数。

    这里的缓存指的是CPU 缓存,关于cup 缓存大致如下,有多级缓存,和主存,所谓的主存就是我们的内存

    image-20201126105755840

    • L1 缓存很小但很快,并且紧靠着在使用它的 CPU 内核。
    • L2 大一些,也慢一些,并且仍然只能被一个单独的 CPU 核使用
    • L3 在现代多核机器中更普遍,仍然更大,更慢,并且被单个插槽上的所有 CPU 核共享
    • 主存保存着程序运行的所有数据,它更大,更慢,由全部插槽上的所有 CPU 核共享
    • 当 CPU 执行运算的时候,它先去 L1 查找所需的数据,再去 L2,然后是 L3,最后如果这些缓存中都没有,所需的数据就要去主内存拿

    下面我们通过一段代码来演示一下缓存失效,我们知道Cache line 的大小一般是64 个字节,如果所以每次读取的时候如果在cpu 缓存里面有数据的话则将Cache line这一行全部读取,而不是读取Cache line里的某一个数据,也就是说Cache line 是我们的基本单位

    public class CacheLineEffect {
        //考虑一般缓存行大小是64字节,一个 long 类型占8字节
        static long[][] arr;
    
        public static void main(String[] args) {
            // 创建一个数组
            arr = new long[1024 * 1024][8];
            for (int i = 0; i < 1024 * 1024; i++) {
                for (int j = 0; j < 8; j++) {
                    arr[i][j] = 1L;
                }
            }
            //第一次累加 读取数组的全部数据进行累加
            long sum = 0L;
            long marked = System.currentTimeMillis();
            for (int i = 0; i < 1024 * 1024; i += 1) {
                for (int j = 0; j < 8; j++) {
                    sum += arr[i][j];
                }
            }
            System.out.println("Loop times:" + (System.currentTimeMillis() - marked) + "ms sum result: " + sum);
            sum = 0L;
            //第二次累加 读取数组的全部数据进行累加
            marked = System.currentTimeMillis();
            for (int i = 0; i < 8; i += 1) {
                for (int j = 0; j < 1024 * 1024; j++) {
                    sum += arr[j][i];
                }
            }
            System.out.println("Loop times:" + (System.currentTimeMillis() - marked) + "ms sum result: " + sum);
        }
    }
    

    这个代码的的特殊之处就是在遍历数组的方式不一样,第一次累加采用的是按行读取,第二次累加采用的是按列读取,而我们的第一次累加因为数组的大小正好是64个字节可以很好的利用cpu 缓存,也就是说一次从主存读取,然后后面7次就可以从cpu 缓存读取了也就是说总共需要读取主存1024 * 1024 次,但是第二次因为没法使用缓存,所以需要读取 1024 * 1024 * 8 次,下面就是输出结果

    Loop times:12ms sum result: 8388608
    Loop times:40ms sum result: 8388608
    

    我们看到这之间的差异,还是比较大的,这里我们看到了CPU 缓存的重要性,同理多线程之间的切换也会导致CPU 缓存失效。

    协作开销

    线程协作同样也有可能带来性能问题。因为线程之间如果有共享数据,为了避免数据错乱,为了保证线程安全,就有可能禁止编译器和 CPU 对其进行重排序等优化,也可能出于同步的目的,反复把线程工作内存的数据 flush 到主存中,然后再从主内存 refresh 到其他线程的工作内存中,等等。这些问题在单线程中并不存在,但在多线程中为了确保数据的正确性,就不得不采取上述方法,因为线程安全的优先级要比性能优先级更高,这也间接降低了我们的性能。

    还有就是你在自己的代码实现中,为了线程安全添加了相应的逻辑,从而打来了相应的开销。

    什么时候要考虑线程安全问题

    访问共享变量或资源

    第一种场景是访问共享变量或共享资源的时候,典型的场景有访问共享对象的属性,访问 static 静态变量,访问共享的缓存,等等。因为这些信息不仅会被一个线程访问到,还有可能被多个线程同时访问,那么就有可能在并发读写的情况下发生线程安全问题。

    依赖时序的操作

    第二个需要我们注意的场景是依赖时序的操作,如果我们操作的正确性是依赖时序的,而在多线程的情况下又不能保障执行的顺序和我们预想的一致,这个时候就会发生线程安全问题,如下面的代码所示:

    if (map.containsKey(key)) {
    
        map.remove(obj)
    
    }
    

    代码中首先检查 map 中有没有 key 对应的元素,如果有则继续执行 remove 操作。此时,这个组合操作就是危险的,因为它是先检查后操作,而执行过程中可能会被打断。如果此时有两个线程同时进入 if() 语句,然后它们都检查到存在 key 对应的元素,于是都希望执行下面的 remove 操作,随后一个线程率先把 obj 给删除了,而另外一个线程它刚已经检查过存在 key 对应的元素,if 条件成立,所以它也会继续执行删除 obj 的操作,但实际上,集合中的 obj 已经被前面的线程删除了,这种情况下就可能导致线程安全问题。

    类似的情况还有很多,比如我们先检查 x=1,如果 x=1 就修改 x 的值,代码如下所示:

    if (x == 1) {
        x = 7 * x;
    }
    

    这样类似的场景都是同样的道理,“检查与执行”并非原子性操作,在中间可能被打断,而检查之后的结果也可能在执行时已经过期、无效,换句话说,获得正确结果取决于幸运的时序。这种情况下,我们就需要对它进行加锁等保护措施来保障操作的原子性。

    对方没有声明自己是线程安全的

    值得注意的场景是在我们使用其他类时,如果对方没有声明自己是线程安全的,那么这种情况下对其他类进行多线程的并发操作,就有可能会发生线程安全问题。举个例子,比如说我们定义了 ArrayList,它本身并不是线程安全的,如果此时多个线程同时对 ArrayList 进行并发读/写,那么就有可能会产生线程安全问题,造成数据出错,而这个责任并不在 ArrayList,因为它本身并不是并发安全的,正如源码注释所写的:

    Note that this implementation is not synchronized. If multiple threads
    access an ArrayList instance concurrently, and at least one of the threads
    modifies the list structurally, it must be synchronized externally.
    

    这段话的意思是说,如果我们把 ArrayList 用在了多线程的场景,需要在外部手动用 synchronized 等方式保证并发安全。

    所以 ArrayList 默认不适合并发读写,是我们错误地使用了它,导致了线程安全问题。所以,我们在使用其他类时如果会涉及并发场景,那么一定要首先确认清楚,对方是否支持并发操作,以上就是四种需要我们额外注意线程安全问题的场景,分别是访问共享变量或资源,依赖时序的操作,不同数据之间存在绑定关系,以及对方没有声明自己是线程安全的。

    总结

    当你考虑多线程的时候就要考虑线程安全问题,那怎么发现那些地方会有线程安全问题呢——有共享变量的地方就有线程安全问题

    我们认为引入多线程会带来两方面的问题

    1. 线程安全问题

    2. 性能问题

    展开全文
  • Java多线程

    万次阅读 多人点赞 2021-06-11 16:28:49
    Java多线程Java多线程线程的创建线程常见方法线程的状态线程的优先级守护线程线程组Java线程池线程池的创建线程池的参数线程池的使用线程不安全问题Java中的锁synchronized同步方法synchronized同步语句块...

    Java多线程

    线程的创建

    1.继承Thread
    2.实现Runnable
    3.实现Callable
    使用继承Thread类来开发多线程的应用程序在设计上是有局限性的,因为Java是单继承。
    继承Thread类

    public class ThreadDemo1 {
        // 继承Thread类 写法1
        static class MyThread extends Thread{
            @Override
            public void run() {
                //要实现的业务代码
            }
        }
    
        // 写法2
        Thread thread = new Thread(){
            @Override
            public void run() {
                //要实现的业务代码
            }
        };
    
    
    }
    

    实现Runnable接口

    //实现Runnable接口 写法1
    
    class MyRunnable implements Runnable{
        @Override
        public void run() {
            //要实现的业务代码
        }
    }
    
    //实现Runnable接口 写法2 匿名内部类
    class MyRunnable2 {
        public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    //要实现的业务代码
                }
            });
        }
    }
    

    实现Callable接口(Callable + FutureTask 创建带有返回值的线程)

    package ThreadDeom;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 17:34;
     */
    //创建有返回值的线程 Callable + Future
    public class ThreadDemo2 {
        static class MyCallable implements Callable<Integer>{
            @Override
            public Integer call() throws Exception {
                return 0;
            }
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            //创建Callable子对象
            MyCallable myCallable = new MyCallable();
            //使用FutureTask 接受 Callable
            FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
            //创建线程并设置任务
            Thread thread = new Thread(futureTask);
            //启动线程
            thread.start();
            //得到线程的执行结果
            int num = futureTask.get();
        }
    }
    
    

    也可以使用lambda表达式

    class ThreadDemo21{
        //lambda表达式
        Thread thread = new Thread(()-> {
            //要实现的业务代码
        });
    }
    
    

    Thread的构造方法
    在这里插入图片描述

    线程常用方法

    获取当前线程的引用、线程的休眠

    class Main{
        public static void main(String[] args) throws InterruptedException {
            Thread.sleep(1000);
            //休眠1000毫秒之后打印
            System.out.println(Thread.currentThread());
            System.out.println(Thread.currentThread().getName());
        }
    }
    

    在这里插入图片描述

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 18:38;
     */
    public class ThreadDemo6 {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
    
                    System.out.println("线程的ID:" + Thread.currentThread().getId());
                    System.out.println("线程的名称:" + Thread.currentThread().getName());
                    System.out.println("线程的状态:" + Thread.currentThread().getState());
    
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"线程一");
    
            thread.start();
            Thread.sleep(100);
            //打印线程的状态
            System.out.println("线程的状态:"+thread.getState());
            System.out.println("线程的优先级:"+thread.getPriority());
            System.out.println("线程是否存活:"+thread.isAlive());
            System.out.println("线程是否是守护线程:"+thread.isDaemon());
            System.out.println("线程是否被打断:"+thread.isInterrupted());
        }
    }
    
    

    在这里插入图片描述

    线程的等待
    假设有一个坑位,thread1 和 thread2 都要上厕所。一次只能一个人上,thread2只能等待thread1使用完才能使用厕所。就可以使用join()方法,等待线程1执行完,thread2在去执行。👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 10:48;
     */
    public class ThreadDemo13 {
        public static void main(String[] args) throws InterruptedException {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"🚾");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println(Thread.currentThread().getName()+"出来了");
                }
            };
    
            Thread t1 = new Thread(runnable,"thread1");
            t1.start();
    
            //t1.join();
            Thread t2 = new Thread(runnable,"thread2");
            t2.start();
        }
    }
    
    

    在这里插入图片描述

    没有join()显然是不行的。加上join()之后:
    在这里插入图片描述

    线程的终止

    1.自定义实现线程的终止

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 9:59;
     */
    public class ThreadDemo11 {
        private static boolean flag = false;
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!flag){
                        System.out.println("我是 : " + Thread.currentThread().getName() + ",我还没有被interrupted呢");
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("我是 "+Thread.currentThread().getName()+",我被interrupted了");
                }
    
            },"thread");
            thread.start();
    
            Thread.sleep(300);
            flag = true;
    
        }
    }
    
    

    在这里插入图片描述

    2.使用Thread的interrupted来中断

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 9:59;
     */
    public class ThreadDemo11 {
    //    private static boolean flag = false;
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!Thread.interrupted()){
                        System.out.println("我是 : " + Thread.currentThread().getName() + ",我还没有被interrupted呢");
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
    //                        e.printStackTrace();
                            break;
                        }
                    }
                    System.out.println("我是 "+Thread.currentThread().getName()+",我被interrupted了");
                }
    
            },"thread");
            thread.start();
    
            Thread.sleep(300);
            thread.interrupt();
    //        flag = true;
    
        }
    }
    
    

    在这里插入图片描述
    3.Thraed.interrupted()方法和Threaed.currentThread().interrupt()的区别
    Thread.interrupted()方法第一次接收到终止的状态后,之后会将状态复位,Thread.interrupted()是静态的,是全局的。Threaed.currentThread().interrupt()只是普通的方法。
    Thraed.interrupted()方法

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 10:32;
     */
    public class ThreadDemo12 {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(() ->{
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.interrupted());
                }
            });
    
            thread.start();
            thread.interrupt();
    
        }
    }
    
    

    在这里插入图片描述

    Threaed.currentThread().interrupt()

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 10:32;
     */
    public class ThreadDemo12 {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(() ->{
                for (int i = 0; i < 10; i++) {
    //                System.out.println(Thread.interrupted());
                    System.out.println(Thread.currentThread().isInterrupted());
                }
    
            });
    
            thread.start();
            thread.interrupt();
    
        }
    }
    
    

    在这里插入图片描述

    yield()方法
    让出CPU的执行权

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 11:47;
     */
    public class ThreadDemo15 {
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    Thread.yield();
                    System.out.println("thread1");
                }
            });
    
            thread1.start();
            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    System.out.println("thread2");
                }
            });
    
            thread2.start();
        }
    }
    
    

    在这里插入图片描述

    线程的状态

    在这里插入图片描述

    打印出线程的所有的状态,所有的线程的状态都在枚举中。👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 11:06;
     */
    public class ThreadDemo14 {
        public static void main(String[] args) {
            for (Thread.State state: Thread.State.values()) {
                System.out.println(state);
            }
        }
    }
    
    

    在这里插入图片描述

    NEW 创建了线程但是还没有开始工作
    RUNNABLE 正在Java虚拟机中执行的线程
    BLOCKED 受到阻塞并且正在等待某个监视器的锁的时候所处的状态
    WAITTING 无限期的等待另一个线程执行某个特定操作的线程处于这个状态
    TIME_WAITTING 有具体等待时间的等待
    TERMINATED 已经退出的线程处于这种状态

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 11:06;
     */
    
    class TestThreadDemo{
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            System.out.println(thread.getState());
            thread.start();
            System.out.println(thread.getState());
    
            Thread.sleep(100);
    
            System.out.println(thread.getState());
    
            thread.join();
    
            System.out.println(thread.getState());
        }
    }
    

    在这里插入图片描述

    线程的优先级

    在Java中线程 的优先级分为1 ~ 10 一共十个等级

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 21:22;
     */
    public class ThreadDemo9 {
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) {
                Thread t1 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("t1");
                    }
                });
                //最大优先级
                t1.setPriority(10);
                t1.start();
                Thread t2 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("t2");
                    }
                });
                //最小优先级
                t2.setPriority(1);
                t2.start();
                Thread t3 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("t3");
                    }
                });
                t3.setPriority(1);
                t3.start();
            }
        }
    }
    
    

    在这里插入图片描述
    线程的优先级不是绝对的,只是给程序的建议。
    线程之间的优先级具有继承的特性,如果A线程启动了B线程,那么B的线程的优先级与A是一样的。👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 20:46;
     */
    class ThreadA extends Thread{
        @Override
        public void run() {
            System.out.println("ThreadA优先级是:" + this.getPriority());
            ThreadB threadB = new ThreadB();
            threadB.start();
        }
    }
    
    class ThreadB extends ThreadA{
        @Override
        public void run() {
            System.out.println("ThreadB的优先级是:" + this.getPriority());
        }
    }
    public class ThreadDemo7 {
        public static void main(String[] args) {
    
            System.out.println("main线程开始的优先级是:" + Thread.currentThread().getPriority());
            
            System.out.println("main线程结束的优先级是:" + Thread.currentThread().getPriority());
    
            ThreadA threadA = new ThreadA();
            threadA.start();
        }
    }
    
    

    在这里插入图片描述

    再看👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 20:46;
     */
    class ThreadA extends Thread{
        @Override
        public void run() {
            System.out.println("ThreadA优先级是:" + this.getPriority());
            ThreadB threadB = new ThreadB();
            threadB.start();
        }
    }
    
    class ThreadB extends ThreadA{
        @Override
        public void run() {
            System.out.println("ThreadB的优先级是:" + this.getPriority());
        }
    }
    public class ThreadDemo7 {
        public static void main(String[] args) {
    
            System.out.println("main线程开始的优先级是:" + Thread.currentThread().getPriority());
            Thread.currentThread().setPriority(9);
            System.out.println("main线程结束的优先级是:" + Thread.currentThread().getPriority());
    
            ThreadA threadA = new ThreadA();
            threadA.start();
        }
    }
    
    

    结果为👇
    在这里插入图片描述

    守护线程

    Java中有两种线程:一种是用户线程,一种就是守护线程。
    什么是守护线程?守护线程是一种特殊的线程,当进程中不存在用户线程的时候,守护线程就会自动销毁。典型的守护线程就是垃圾回收线程,当进程中没有了非守护线程,则垃圾回收线程也就没有存在的必要了。
    Daemon线程的作用就是为其他线程的运行提供便利的。👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 21:06;
     */
    
    public class ThreadDemo8 {
        static private int i = 0;
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        i++;
                        System.out.println(i);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            //设置守护线程
            thread.setDaemon(true);
            thread.start();
            Thread.sleep(5000);
            System.out.println("我是守护线程thread 当用户线程执行完成后 我也就销毁了😭哭了");
        }
    }
    
    

    在这里插入图片描述
    注意:守护线程的设置必须放在start()之前,否则就会报错。
    在这里插入图片描述
    在守护线程中创建的线程默认也是守护线程。

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 9:35;
     */
    public class ThreadDemo10 {
        public static void main(String[] args) {
            Thread thread1 = new Thread(()->{
                Thread thread2 = new Thread(() -> {
                },"thread2");
                System.out.println("thread2是守护线程吗?:" + thread2.isDaemon());
    
            },"thread1");
    
            System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
    
            //thread1.setDaemon(true);
            thread1.start();
           // System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
    
        }
    }
    
    

    在这里插入图片描述
    再看👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 9:35;
     */
    public class ThreadDemo10 {
        public static void main(String[] args) {
            Thread thread1 = new Thread(()->{
                Thread thread2 = new Thread(() -> {
                },"thread2");
                System.out.println("thread2是守护线程吗?:" + thread2.isDaemon());
    
            },"thread1");
    
            System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
    
            thread1.setDaemon(true);
            thread1.start();
            System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
    
        }
    }
    

    在这里插入图片描述

    线程组

    为了便于对某些具有相同功能的线程进行管理,可以把这些线程归属到同一个线程组中,线程组中既可以有线程对象,也可以有线程组,组中也可以有线程。
    使用线程模拟赛跑

    public class ThreadDemo5 {
        //线程模拟赛跑(未使用线程分组)
        public static void main(String[] args) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
                }
            }, "选手一");
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
                }
            }, "选手二");
    
            t1.start();
            t2.start();
    
    
            System.out.println("所有选手到达了终点");
        }
    }
    
    

    运行结果:
    在这里插入图片描述
    不符合预期效果,就可以使用线程组来实现

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 18:24;
     */
    class ThreadGroup1 {
        //线程分组模拟赛跑
        public static void main(String[] args) {
            ThreadGroup threadGroup = new ThreadGroup("Group");
            Thread t1 = new Thread(threadGroup, new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("选手一到达了终点");
                }
            });
    
            Thread t2 = new Thread(threadGroup, new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("选手二到达了终点");
                }
            });
            t2.start();
            t1.start();
    
            while (threadGroup.activeCount() != 0) {
    
            }
            System.out.println("所有选手到达了终点");
        }
    }
    
    

    在这里插入图片描述
    线程组常用的方法
    在这里插入图片描述

    线程安全问题

    来看单线程情况下让count分别自增和自减10000次

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 12:03;
     */
    class Counter {
        private static int count = 0;
        public void increase(){
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    
        public void decrease(){
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        }
    
        public int getCount(){
            return count;
        }
    }
    public class ThreadDemo16 {
        public static void main(String[] args) {
            //单线程
            Counter counter = new Counter();
            counter.increase();
            counter.decrease();
            System.out.println(counter.getCount());
        }
    }
    
    

    结果符合预期
    在这里插入图片描述

    如果想使程序的执行速度快,就可以使用多线程的方式来执行。在来看多线程情况下的问题

    public class ThreadDemo16 {
        public static void main(String[] args) throws InterruptedException {
    
            //多线程情况下
            Counter counter = new Counter();
            Thread thread1 = new Thread(()->{
                counter.decrease();
            });
    
            Thread thread2 = new Thread(()->{
                counter.increase();
            });
    
            thread1.start();
            thread2.start();
    
            thread1.join();
            thread2.join();
    
            System.out.println(counter.getCount());
    
                    /*
            //单线程
            Counter counter = new Counter();
            counter.increase();
            counter.decrease();
            System.out.println(counter.getCount());
             */
        }
    }
    
    

    执行结果:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    每次的执行结果是不一样的。这就是多线程的不安全问题
    在这里插入图片描述
    预期的结果是0,但结果却不是。
    线程不安全问题的原因:
    1.CPU的抢占式执行
    2.多个线程共同操作一个变量
    3.内存可见性
    4.原子性问题
    5.编译器优化(指令重排)

    多个线程操作同一个变量
    如果多个线程操作的不是一个变量,就不会发生线程的不安全问题,可以将上面的代码修改如下:👇

    public class ThreadDemo16 {
        static int res1 = 0;
        static int res2 = 0;
    
        public static void main(String[] args) throws InterruptedException {
    
            Counter counter = new Counter();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    res1 = counter.getCount();
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    res2 = counter.getCount();
                }
            });
            System.out.println(res1 + res2);
    /*
            //多线程情况下
            Counter counter = new Counter();
            Thread thread1 = new Thread(()->{
                counter.decrease();
            });
    
            Thread thread2 = new Thread(()->{
                counter.increase();
            });
    
            thread1.start();
            thread2.start();
    
            thread1.join();
            thread2.join();
    
            System.out.println(counter.getCount());
            */
    
    
                    /*
            //单线程
            Counter counter = new Counter();
            counter.increase();
            counter.decrease();
            System.out.println(counter.getCount());
             */
        }
    }
    
    

    这样就可以了:
    在这里插入图片描述

    内存不可见问题:看下面的代码,是不是到thread2执行的时候,就会改变num的值,从而终止了thread1呢?

    package ThreadDeom;
    
    import java.util.Scanner;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 13:03;
     */
    public class ThreadDemo17 {
    
        private static int num = 0;
        public static void main(String[] args) {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (num == 0){}
                }
            });
    
            thread1.start();
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    Scanner scanner = new Scanner(System.in);
                    System.out.println("输入一个数字来终止线程thread1");
                    num = scanner.nextInt();
                }
            });
    
            thread2.start();
        }
    
    }
    
    

    结果是不能的:
    在这里插入图片描述
    输入一个数字后回车,并没有让thread1的循环结束。这就是内存不可见的问题。
    原子性的问题
    上面的++和–操作其实是分三步来执行的
    在这里插入图片描述
    假设在第二部的时候,有另外一个线程也来修改值,那么就会出现脏数据的问题了。
    所以就会发生线程的不安全问题

    编译器优化
    编译器的优化会打乱原本程序的执行顺序,就有可能导致线程的不安全问题发生。
    在单线程不会发生线程的不安全问题,在多线程就可能会不安全。

    volatile关键字

    可以使用volatile关键字,这个关键字可以解决指令重排和内存不可见的问题。
    在这里插入图片描述
    加上volatile关键字之后的运行结果
    在这里插入图片描述
    但是volatile关键字不能解决原子性的问题👇:

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 14:02;
     */
    
    class Counter1 {
        private static volatile int count = 0;
    
        public void increase() {
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    
        public void decrease() {
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        }
    
        public int getCount() {
            return count;
        }
    }
    
    
    public class ThreadDemo18 {
        public static void main(String[] args) throws InterruptedException {
            Counter1 counter1 = new Counter1();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    counter1.decrease();
                }
            });
    
            Thread thread2 = new Thread(() -> {
                counter1.increase();
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(counter1.getCount());
        }
    }
    
    

    在这里插入图片描述
    在这里插入图片描述
    那么Java中如何解决原子性的问题呢👇

    Java中的锁

    Java中的加锁操作有两种:
    1.synchronized锁(jvm层的解决方案,也叫监视器锁)
    在操作系统的层面使用的是互斥锁(mutex lock)
    在Java中放在了对象头中。
    2.手动锁Lock
    操作锁的流程
    1.尝试获取锁
    2.使用锁
    3.释放锁

    synchronized锁

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 14:12;
     */
    class Counter2 {
        private static volatile int count = 0;
    
        public void increase() {
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    
        public void decrease() {
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        }
    
        public int getCount() {
            return count;
        }
    }
    
    
    public class ThreadDemo19 {
        public static void main(String[] args) throws InterruptedException {
            //声明锁对象,任何的对象都可以作为锁
            Object lock = new Object();
            Counter2 counter2 = new Counter2();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //使用锁
                    synchronized (lock) {
                        counter2.decrease();
    
                    }
                }
            });
    
            Thread thread2 = new Thread(() -> {
                synchronized (lock) {
                    counter2.increase();
                }
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(counter2.getCount());
        }
    }
    
    

    结果是:
    在这里插入图片描述

    synchronized使用场景

    1.使用synchronized来修饰代码块(可以给任意的对象进行加锁操作)

    public class ThreadDemo19 {
        public static void main(String[] args) throws InterruptedException {
            //声明锁对象,任何的对象都可以作为锁
            Object lock = new Object();
            Counter2 counter2 = new Counter2();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //使用锁
                    synchronized (lock) {
                        counter2.decrease();
    
                    }
                }
            });
    
            Thread thread2 = new Thread(() -> {
                synchronized (lock) {
                    counter2.increase();
                }
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(counter2.getCount());
        }
    }
    
    

    在这里插入图片描述

    2.使用synchronized来修饰静态方法(对当前的类进行加锁的操作)

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 14:02;
     */
    
    class Counter1 {
        private static volatile int count = 0;
    
        public void increase() {
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    
        public void decrease() {
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        }
    
        public int getCount() {
            return count;
        }
    }
    
    
    public class ThreadDemo18 {
        public static void main(String[] args) throws InterruptedException {
            Counter1 counter1 = new Counter1();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    counter1.decrease();
                }
            });
    
            Thread thread2 = new Thread(() -> {
                counter1.increase();
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(counter1.getCount());
        }
    }
    
    

    在这里插入图片描述

    3.使用synchronized来修饰普通的方法(对当前类的实例来进行加锁)

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 14:12;
     */
    public class ThreadDemo20 {
        private static int num = 0;
        private static final int maxSize = 100000;
    
        public static void main(String[] args) throws InterruptedException {
    
            ThreadDemo20 threadDemo20 = new ThreadDemo20();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    threadDemo20.increase();
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                   threadDemo20. decrease();
                }
            });
    
            thread1.start();
            thread2.start();
    
            thread1.join();
            thread2.join();
    
            System.out.println(num);
        }
    
        //给静态的方法进行加锁,被加的锁是当前的对象。
    //    public synchronized static void increase(){
    
        //给普通的方法进行加锁的操作
        public synchronized void increase() {
    
            for (int i = 0; i < maxSize; i++) {
                num++;
            }
        }
    
        //    public synchronized static void decrease(){
        public synchronized void decrease() {
    
            for (int i = 0; i < maxSize; i++) {
                num--;
            }
        }
    }
    

    在这里插入图片描述

    synchronized注意事项

    1.加锁的时候一定要使用同一把锁对象

    Lock类的使用

    也叫手动锁

    package ThreadDeom;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 18:32;
     */
    public class ThreadDemo22 {
        private static int number = 0;
        private static final int maxSize = 100000;
    
        public static void main(String[] args) {
            //创建lock锁对象,lock是接口,不能实列化
            Lock lock = new ReentrantLock();
    
    
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < maxSize; i++) {
                    lock.lock();
                    try {
                        number++;
    
                    } finally {
                        lock.unlock();
                    }
                }
            });
    
    
            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < maxSize; i++) {
                    lock.lock();
                    try {
                        number--;
    
                    } finally {
                        lock.unlock();
                    }
    
                }
            });
    
            System.out.println(number);
        }
    }
    
    

    在这里插入图片描述

    Lock锁使用的注意事项

    1.lock()操作一定要放在try外面
    如果放在try的里面:
    1.try中抛出了异常,还没有加锁就释放了finally中的锁的操作了
    2.如果放在了try,没加锁就释放了锁,就会抛出异常,就会将业务代码中的异常吞噬掉👇
    如果一定要放的话,将lock()放在try的第一行。

    package ThreadDeom;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 18:49;
     */
    public class ThreadDemo23 {
        public static void main(String[] args) {
            Lock lock = new ReentrantLock();
    
            try{
    
                System.out.println(1/0);
                lock.lock();
    
            } finally {
                lock.unlock();
            }
        }
    }
    
    

    在这里插入图片描述

    公平锁、非公平锁

    公平锁的调度:
    一个线程释放锁。
    主动唤醒“需要得到锁”的队列来得到锁。
    非公平锁
    当一个线程释放锁之后,另一个线程刚好执行到获取锁的代码就可以直接获取锁。
    Java中的所有锁默认都是非公平锁。
    非公平锁的性能更高。
    ReentrantLock可以设置非公平锁。
    公平锁

    package ThreadDeom;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 19:22;
     */
    public class ThreadDemo24 {
        public static void main(String[] args) throws InterruptedException {
            ReentrantLock reentrantLock = new ReentrantLock();
    
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    reentrantLock.lock();
                    try {
                        System.out.println("thread1");
    
                    } finally {
                        reentrantLock.unlock();
                    }
                }
            });
    
            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    reentrantLock.lock();
                    try {
                        System.out.println("thread2");
                    } finally {
                        reentrantLock.unlock();
                    }
                }
            });
    
            Thread.sleep(100);
            thread1.start();
            thread2.start();
        }
    }
    
    

    打印的结果是无序的
    在这里插入图片描述
    如果设置为公平锁:👇
    在这里插入图片描述

    在这里插入图片描述
    thread1和thread2 交替输出

    synchronzied 和 Lock 的区别

    1.synchronzied可以自动的进行加锁和释放锁,而Lock需要手动的加锁、释放锁。
    2.Lock是Java层面的锁实现,而synchronzied 是JVM层面锁的实现
    3.synchronzed 即可以修饰代码块,又可以修饰普通方法和静态的方法,而Lock 只能修饰代码块
    4. synchronized 实现的是 非公平的锁,而Lock 可以实现公平锁。

    5.lock的灵活性更高

    死锁

    在两个或两个以上的线程运行中,因为资源的抢占而造成线程一直等待的问题。
    看👇:

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 19:48;
     */
    public class ThreadDemo25 {
        public static void main(String[] args) throws InterruptedException {
            Object lockA = new Object();
            Object lockB = new Object();
    
    
            Thread thread1 = new Thread(() -> {
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName() + "获取到lockA");
                    //让线程2获取lockB
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    synchronized (lockB) {
    
                        System.out.println(Thread.currentThread().getName() + "获取到lockB");
                    }
                }
    
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //线程2获取资源B
                    synchronized (lockB) {
                        System.out.println(Thread.currentThread().getName() + "获取到lockB");
                        //让线程1先获取到锁lockA
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (lockA) {
                            System.out.println(Thread.currentThread().getName() + "获取到lockA");
    
                        }
    
                    }
    
                }
            });
            
            thread1.start();
    
            thread2.start();
        }
    }
    
    

    这就造成了死锁
    在这里插入图片描述

    造成死锁的四个条件

    1.互斥条件:
    当资源被一个线程拥有之后,就不能被其它的线程拥有了
    2.拥有请求条件:
    当一个线程拥有了一个资源之后,又试图请求另一个资源。
    3.不可剥夺条件:
    当一个线程拥有了一个资源之后,如果不是这个线程主动的释放资源,其他线程就不能拥有这个线程。
    4.环路等待条件:
    两个或两个以上的线程拥有了资源之后,试图获取对方的资源的时候形成了一个环路。

    死锁的解决方案

    解决请求拥有和环路等待。
    最有效的解决方案就是控制加锁的顺序。

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 20:25;
     */
    public class ThreadDemo26 {
        public static void main(String[] args) throws InterruptedException {
            Object lockA = new Object();
            Object lockB = new Object();
    
    
            Thread thread1 = new Thread(() -> {
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName() + "获取到lockA");
                    //让线程2获取lockB
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    synchronized (lockB) {
    
                        System.out.println(Thread.currentThread().getName() + "获取到lockB");
                    }
                }
    
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (lockA) {
                        System.out.println(Thread.currentThread().getName() + "获取到lockA");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (lockB) {
                            System.out.println(Thread.currentThread().getName() + "获取到lockB");
    
                        }
    
                    }
    
                }
            });
    
            thread1.start();
    
            thread2.start();
        }
    }
    
    

    在这里插入图片描述

    线程间通信

    线程之间的通讯是指在一个线程中的操作可以影响另一个线程。

    wait/notify机制的原理

    拥有相同锁的线程之间才能使用wait/notify机制。
    wait()是Object()的方法,它的作用是是当前执行wait()方法的线程等待,在wati()所在的代码出停止执行,并释放锁,直到接到通知或者被中断为止。即在调用wait()的方法之前,线程必需先获取到对象级别的锁,也就是只能在同步方法或者同步块中使用wait()方法。
    如果在使用wait()方法之前线程没有获得相应的锁,那么程序在执行时就会抛出异常。
    notify()方法要在同步方法或者同步块中执行,即在调用notify()方法之前,线程必需要先获取到锁对象。如果线程没有持有锁对象的话,那么也会抛出异常。该方法用来通知可能在等待该锁的其它线程,如果有多个线程,那么则按照执行wait()方法的顺序来对处于wait()方法的线程发出通知,并使该线程重新获取锁。执行notify()方法之后,当前线程不会马上释放锁,处于wait()状态的线程也不会立马得到这个对象锁。而是要等notify的synchronized同步区域执行完成之后才会释放锁,处于wait()状态的线程才会得到锁对象。

    总结:wait()方法用于让线程停止运行,而notify()方法用于通知暂停的线程继续运行。
    在使用wait()或者notify()方法之前没有对象锁,就会报异常👇:

            lock.notify();
    
    

    在这里插入图片描述
    正确的使用之后

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 21:11;
     */
    public class ThreadDemo27 {
        //设置锁对象
    
        private static Object lock = new Object();
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
    
                @Override
    
                public void run() {
                    synchronized (lock) {
                        System.out.println("在wait()");
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("被notify()唤醒之后");
                    }
                }
            });
            thread.start();
    
            Thread.sleep(1000);
    
            synchronized (lock) {
                lock.notify();
    
            }
    
        }
    }
    
    

    在这里插入图片描述

    注意:使用wait()方法的时候一定要和线程的锁对象是一个锁。

    notifyAll

    在多线程的情况下使用notify()方法只可以唤醒一个线程👇
    在这里插入图片描述

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 8:06;
     */
    public class ThreadDemo28 {
        //设置锁对象
    
        private static Object lock = new Object();
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(new Runnable() {
    
                @Override
    
                public void run() {
                    synchronized (lock) {
                        System.out.println("thread1在wait()");
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("thread1被notify()唤醒之后");
                    }
                }
            });
    
    
            Thread thread2 = new Thread(() -> {
                synchronized (lock) {
                    System.out.println("thread2在wait()");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("thread2被notify()唤醒之后");
                }
            });
    
            Thread thread3 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (lock) {
                        System.out.println("thread3在wait()");
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("thread3被notify()唤醒之后");
                    }
    
                }
            });
    
    
            thread1.start();
            thread2.start();
            thread3.start();
            Thread.sleep(1000);
    
            synchronized (lock) {
    
                System.out.println("主线程调用notify()之后");
    
                lock.notify();
    
            }
    
        }
    }
    
    

    那么如果使用notifyAll()方法呢?
    在这里插入图片描述
    可以看到所有的线程都被唤醒了
    在这里插入图片描述

    那么使用notify()唤醒的线程有没有什么顺序呢?
    使用notify()唤醒线程的顺序是正序、倒序、还是随机的,这取决与JVM的具体实现,并不是所有的JVM在执行notify()时都是按照wait()的执行顺序进行唤醒的,也不是所有的notidyAll()都是按照wait()方法的倒序进行唤醒的,这取决于JVM的具体实现。
    wait()和notify()不能唤醒指定的线程。

    wait()和sleep()的区别

    也可以让wait()等待指定的时间,如果超过给定的时间,wait()不会无限期的等待下去.
    在这里插入图片描述
    没有被notify()唤醒,过了1000毫秒之后会自动停止。
    在这里插入图片描述

    wait()在不传入任何参数的时候,线程会进入waiting 的状态,而在wait()中加入一个大于0的参数的时候,线程会进入time_wating的状态。

    sleep()和wait()的区别 : 线程在sleep()的时候是不会释放锁的,而执行wait()的时候它就会释放锁。👇:

    package ThreadDeom;
    
    import jdk.nashorn.internal.ir.Block;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 8:45;
     */
    public class ThreadDemo29 {
        private static Object lock = new Object();
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (lock) {
                        try {
                            System.out.println("thread获取到了锁");
                            //如果sleep释放锁的话,会在thread获取到了锁和thread释放了锁之间打印
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
    
                    }
                    System.out.println("thread释放了锁");
    
                }
            });
            thread.start();
    
            //让thread 先获取到锁
            Thread.sleep(1000);
            synchronized (lock) {
                System.out.println("主线程获取到了锁");
            }
        }
    }
    
    

    在这里插入图片描述

    可以看到线程在sleep()的时候,线程是不会释放锁的。再来看看wait()方法👇:
    在这里插入图片描述

    在这里插入图片描述
    线程使用wait()的时候它就会释放掉锁。

    1.wait()和sleep()都是让线程进行休眠的
    2.wait()和sleep()方法都有可能在执行的过程接收到线程终止的通知
    3.wait()必须和synchronzied一起使用,而sleep()不用。
    4.wait()会释放锁,而sleep()不会释放锁。
    5.wait()时Object的方法,而sleep()时Thread的方法。
    6.默认情况下,wait()不传任何的参数的情况下,wait()会进入waiting的状态,如果传递了参数,wait()会进入time_waiting的状态。而sleep()进入的是time_waiting的状态。
    sleep(0) 和wait(0)的区别:
    1.sleep(0)表示0毫秒之后继续执行,而wait(0)表示线程会一直休眠下去wait(0)和wait()是一样的,wait()的源码就是调用了wait(0)方法。
    2.sleep(0)表示重新出发一次CPU的竞争。
    为什么wait()会释放锁,而sleep()不会释放锁?
    sleep()需要传递一个最大的等待时间,也就是说sleep()是可控的,而wait()是不可以传递参数的,从设计的层面来说,如果让wait()一直持有所得话,那么线程就可能一直阻塞。
    为什么wait()是Object的方法,而sleep()是线程的方法?
    wait()需要操作锁,而锁是属于对象级别的,所有的锁都是放在对象头中的,它不是线程级别的,一个线程可以有多把的锁,为了灵活,就将wait()放在Object中了。

    LockSupport park()/unpark()

    使用LockSupport可以解决wait()/notify()随机唤醒的问题。

    package ThreadDeom;
    
    import java.util.concurrent.locks.LockSupport;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 9:36;
     */
    public class ThreadDemo30 {
        public static void main(String[] args) {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //让线程休眠
                    LockSupport.park();
                    System.out.println("unPark()了thread1");
                }
            });
    
            Thread thread2 = new Thread(() -> {
                LockSupport.park();
                System.out.println("unPark()了thread2");
    
            });
    
    
            Thread thread3 = new Thread() {
                @Override
                public void run() {
                    LockSupport.park();
                    System.out.println("unPark()了thread3");
    
                }
            };
    
    
            thread1.start();
            thread2.start();
            thread3.start();
    
    
            LockSupport.unpark(thread1);
            LockSupport.unpark(thread2);
    
    
        }
    }
    
    

    在这里插入图片描述

    Java线程池

    线程的缺点:
    1.线程的创建它会开辟本地方法栈、JVM栈、程序计数器私有的内存,同时消耗的时候需要销毁以上三个区域,因此频繁的创建和销毁线程比较消耗系统的资源。
    2.在任务量远远大于线程可以处理的任务量的时候,不能很好的拒绝任务。
    所以就有了线程池:
    使用池化的而技术来管理和使用线程。

    线程池的优点

    1.可以避免频繁的创建和销毁线程
    2.可以更好的管理线程的个数和资源的个数。
    3.线程池拥有更多的功能,比如线程池可以进行定时任务的执行。
    4.线程池可以更友好的拒绝不能处理的任务。

    线程池的6种创建方式

    一共有7种创建方式
    创建方式一:
    创建固定个数的线程池:

    package ThreadPoolDemo;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 10:24;
     */
    public class ThreadPoolDemo1 {
        public static void main(String[] args) {
            //创建一个固定个数的线程池
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            //执行任务
            for (int i = 0; i < 10; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("线程名" + Thread.currentThread().getName());
                    }
                });
            }
    
        }
    }
    
    

    在这里插入图片描述
    那么如果执行次数大于10次呢?
    线程池不会创建新的线程,它会复用之前的线程。
    在这里插入图片描述

    在这里插入图片描述
    那么如果只执行两个任务呢?它创建了是10个线程还是两个线程呢?
    我们可以使用Jconsole来看一看:
    在这里插入图片描述

    在这里插入图片描述

    结果是只有2个线程被创建。

    创建方式二:
    创建带有缓存的线程池:
    适用于短期有大量的任务的时候使用

    public class ThreadPoolDemo2 {
        public static void main(String[] args) {
            //创建带缓存的线程池
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < 100; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName());
                    }
                });
            }
        }
    }
    
    

    在这里插入图片描述

    方式三:
    创建执行定时任务的线程池

    package ThreadPoolDemo;
    
    import java.util.Date;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 11:32;
     */
    public class ThreadPoolDemo3 {
        public static void main(String[] args) {
            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
    
            System.out.println("执行定时任务前的时间:" + new Date());
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务的时间:" + new Date());
                }
            },1,2, TimeUnit.SECONDS);
        }
    }
    
    

    在这里插入图片描述
    执行任务的四个参数的意义:
    参数1:延迟执行的任务
    参数2:延迟一段时间后执行
    参数3:定时任务执行的频率
    参数4:配合前两个参数使用,是2、3参数的时间单位

    还有两种执行的方法:
    只会执行一次的方法:
    在这里插入图片描述

    在这里插入图片描述
    第三种的执行方式:
    在这里插入图片描述

    在这里插入图片描述
    那么这种的执行方式和第一种的执行方式有什么区别呢?
    当在两种执行的方式中分别加上sleep()之后:
    在这里插入图片描述

    方式一:
    在这里插入图片描述

    方式三:
    在这里插入图片描述
    结论很明显了:
    第一种方式是以上一个任务的开始时间+定时的时间作为当前任务的开始时间

    第三种方式是以上一个任务的结束时间来作为当前任务的开始时间。

    创建方式四:

    package ThreadPoolDemo;
    
    import java.util.Date;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 12:38;
     */
    public class ThreadPoolDemo4 {
        public static void main(String[] args) {
            //创建单个执行任务的线程池
            ScheduledExecutorService scheduledExecutorService
                    = Executors.newSingleThreadScheduledExecutor();
            System.out.println("执行任务之前" + new Date());
            scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
                @Override
                public void run() {
                    System.out.println("我是SingleThreadSchedule"+ new Date());
                }
            },3,1, TimeUnit.SECONDS);
        }
    }
    
    

    在这里插入图片描述
    在这里插入图片描述
    创建方式五:
    创建单个线程的线程池

    package ThreadPoolDemo;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 12:55;
     */
    public class ThreadPoolDemo5 {
        public static void main(String[] args) {
            //创建单个线程的线程池
    
            ExecutorService executorService = Executors.newSingleThreadExecutor();
            for (int i = 0; i < 20; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("线程名 " +  Thread.currentThread().getName());
                    }
                });
            }
        }
    }
    
    

    在这里插入图片描述
    创建单个线程池的作用是什么?
    1.可以避免频繁创建和销毁线程所带来的性能的开销
    2.它有任务队列,可以存储多余的任务
    3.可以更好的管理任务
    4.当有大量的任务不能处理的时候,可以友好的执行拒绝策略
    创建方式六:
    创建异步线程池根据当前CPU来创建对应个数的线程池

    package ThreadPoolDemo;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 13:12;
     */
    public class ThreadPoolDemo6 {
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newWorkStealingPool();
    
            for (int i = 0; i < 10; i++) { 
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("线程名" + Thread.currentThread().getName());
                    }
                });
            }
        }
    }
    
    

    在这里插入图片描述
    运行结果为什么什么都没有呢?
    看下面的异步与同步的区别就知道了。
    加上这个
    在这里插入图片描述

    就可以输出结果了
    在这里插入图片描述

    线程池的第七种创建方式

    前六种的创建方式有什么问题呢?
    1.线程的数量不可控(比如带缓存的线程池)
    2.工作任务量不可控(默认的任务队列的大小时Integer.MAX_VALUE),任务比较大肯会导致内存的溢出。
    所以就可以使用下面的创建线程池的方式了:

    package ThreadPoolDemo;
    
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadFactory;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 15:05;
     */
    public class ThreadPoolDemo7 {
        private static int threadId = 0;
    
        public static void main(String[] args) {
            ThreadFactory threadFactory = new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setName("我是threadPool-" + ++threadId);
                    return thread;
                }
            };
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 100,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(12),
                    threadFactory, new ThreadPoolExecutor.AbortPolicy());
    
            for (int i = 0; i < 15; i++) {
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName());
                    }
                });
            }
        }
    }
    
    
    

    在这里插入图片描述

    参数说明:
    在这里插入图片描述

    参数一:核心线程数|线程池正常情况下的线程 数量
    参数二:最大线程数|当有大量的任务的时候可以创建的最多的线程数
    参数三:最大线程的存活时间
    参数四:配合参数三一起使用的表示参数三的时间单位
    参数五:任务队列
    参数六:线程工厂
    参数七:决绝策略

    注意事项:最大的线程数要大于等于核心的线程数
    在这里插入图片描述

    在这里插入图片描述

    五种拒绝策略

    在这里插入图片描述
    在这里插入图片描述

    为什么拒绝策略可以舍弃最新的任务或者最旧的任务呢?
    因为LinkedBlockingDeque时FIFO的。
    第五种:自定义的拒绝策略
    在这里插入图片描述

    在这里插入图片描述

    ThreadPoolExecutor的执行方式

    在这里插入图片描述

    package ThreadPoolDemo;
    
    import java.util.concurrent.*;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 16:58;
     */
    public class ThreadPoolDemo9 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 100,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.DiscardOldestPolicy());
    
    
            //线程池的执行方式一
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("使用了execute()执行了线程池");
                }
            });
    
            //线程池的执行方式二
    
            Future<String> futureTask =
                    threadPoolExecutor.submit(new Callable<String>() {
                        @Override
                        public String call() throws Exception {
                            return "使用submit(new Callable<>())执行了线程池";
                        }
                    });
    
            System.out.println(futureTask.get());
            
            
    
        }
    }
    
    

    无返回值的执行方式
    在这里插入图片描述

    有返回值的执行方式
    在这里插入图片描述

    ThreadPoolExecutor的执行流程

    当任务量小于核心线程数的时候,ThreadPoolExecutor会创建线程来执行任务
    当任务量大于核心的线程数的时候,并且没有空闲的线程时候,且当线程池的线程数小于最大线程数的时候,此时会将任务存放到任务队列中
    如果任务队列也被存满了,且最大线程数大于线程池的线程数的时候,会创建新的线程来执行任务。
    如果线程池的线程数等于最大的线程数,并且任务队列也已经满了,就会执行拒绝策略。👇
    在这里插入图片描述

    线程池的终止

    shutdown()
    线程池的任务会执行完
    shutdownNow()
    立即终止线程池,线程池的任务不会执行完

    线程池的状态

    在这里插入图片描述

    异步、同步

    1. Java 线程 同步与异步
      多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了A线程处理的数理。显然这是由于全局资源造成的,有时为了解决此问题,优先考虑使用局部变量,退而求其次使用同步代码块,出于这样的安全考虑就必须牺牲系统处理性能,加在多线程并发时资源挣夺最激烈的地方,这就实现了线程的同步机制

    同步
    A线程要请求某个资源,但是此资源正在被B线程使用中,因为同步机制存在,A线程请求不到,怎么办,A线程只能等待下去

    异步
    A线程要请求某个资源,但是此资源正在被B线程使用中,因为没有同步机制存在,A线程仍然请求的到,A线程无需等待
    同步的方式:
    1.发送请求
    2.等待执行完成
    3.有结果的返回

    异步的方式
    1.发请求
    2.执行完成
    3.另一个线程异步处理
    4.处理完成之后返回回调结果

    显然,同步最最安全,最保险的。而异步不安全,容易导致死锁,这样一个线程死掉就会导致整个进程崩溃,使用异步的机制,性能会有所提升

    线程工厂

    设想这样一种场景,我们需要一个线程池,并且对于线程池中的线程对象,赋予统一的线程优先级、统一的名称、甚至进行统一的业务处理或和业务方面的初始化工作,这时工厂方法就是最好用的方法了

    package ThreadPoolDemo;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadFactory;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 11:12;
     */
    public class ThreadFactoryDemo {
        public static void main(String[] args) {
            MyThreadFactory myThreadFactory = new MyThreadFactory();
            ExecutorService executorService =  Executors.newFixedThreadPool(10,myThreadFactory);
    
            for (int i = 0; i < 10; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("使用线程工厂设置的线程名:"+ Thread.currentThread().getName() +
                                " 使用线程工厂设置的线程的优先级" + Thread.currentThread().getPriority());
                    }
                });
            }
    
    
    
        }
    
        private static int count = 0;
         static class MyThreadFactory implements ThreadFactory{
             @Override
             public Thread newThread(Runnable r) {
                 Thread thread = new Thread(r);
                 thread.setPriority(8);
                 thread.setName("thread--" + count++);
                 return thread;
             }
         }
    
    }
    
    
    
    

    在这里插入图片描述

    SimpleDateFormat非线程安全问题

    实现1000个线程的时间格式化

    package SimpleDateFormat;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 17:30;
     */
    public class SimpleDateFormat1 {
        private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
    
        public static void main(String[] args) {
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,10,100,
                    TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(1000),new ThreadPoolExecutor.DiscardPolicy());
    
    
            for (int i = 0; i < 1001; i++) {
                int finalI = i;
                threadPoolExecutor.submit(new Runnable() {
                    @Override
                    public void run() {
                        Date date = new Date(finalI * 1000);
                        myFormatTime(date);
                    }
                });
            }
    
            threadPoolExecutor.shutdown();
        }
    
        private static void myFormatTime(Date date){
            System.out.println(simpleDateFormat.format(date));
        }
    
    }
    
    

    产生了线程不安全的问题👇:

    在这里插入图片描述
    这是因为:
    在这里插入图片描述
    多线程的情况下:
    在这里插入图片描述
    线程1在时间片用完之后,线程2来setTime()那么线程1的得到了线程2的时间。

    所以可以使用加锁的操作:

    在这里插入图片描述

    就不会有重复的时间了
    在这里插入图片描述
    但是虽然可以解决线程不安全的问题,但是排队等待锁,性能就会变得低

    所以可以使用局部变量:
    在这里插入图片描述
    也解决了线程不安全的问题:
    在这里插入图片描述

    但是每次也都会创建新的私有变量
    那么有没有一种方案既可以避免加锁排队执行,又不会每次创建任务的时候不会创建私有的变量呢?
    那就是ThreadLocal👇:

    ThreadLocal

    ThreadLocal的作用就是让每一个线程都拥有自己的变量。
    那么选择锁还是ThreadLocal?
    看创建实列对象的复用率,如果复用率比较高的话,就使用ThreadLocal。

    ThreadLocal的原理

    类ThreadLocal的主要作用就是将数据放到当前对象的Map中,这个Map时thread类的实列变量。类ThreadLocal自己不管理、不存储任何的数据,它只是数据和Map之间的桥梁。
    执行的流程:数据—>ThreadLocal—>currentThread()—>Map。
    执行后每个Map存有自己的数据,Map中的key中存储的就是ThreadLocal对象,value就是存储的值。每个Thread的Map值只对当前的线程可见,其它的线程不可以访问当前线程对象中Map的值。当前的线程被销毁,Map也随之被销毁,Map中的数据如果没有被引用、没有被使用,则随时GC回收。

    ThreadLocal常用方法

    在这里插入图片描述

    set(T):将内容存储到ThreadLocal
    get():从线程去私有的变量
    remove():从线程中移除私有变量

    package ThreadLocalDemo;
    
    import java.text.SimpleDateFormat;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 18:37;
     */
    public class ThreadLocalDemo1 {
        private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
    
        public static void main(String[] args) {
            //设置私有变量
            threadLocal.set(new SimpleDateFormat("mm:ss"));
    
            //得到ThreadLocal
            SimpleDateFormat simpleDateFormat = threadLocal.get();
            
            //移除
            threadLocal.remove();
        }
    }
    
    

    ThreadLocal的初始化

    ThreadLocal提供了两种初始化的方法
    initialValue()和
    initialValue()初始化:

    package ThreadLocalDemo;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 19:07;
     */
    public class ThreadLocalDemo2 {
        //创建并初始化ThreadLocal
    
        private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal() {
            @Override
            protected SimpleDateFormat initialValue() {
                System.out.println(Thread.currentThread().getName() + "执行了自己的threadLocal中的初始化方法initialValue()");
                return new SimpleDateFormat("mm:ss");
            }
        };
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                Date date = new Date(5000);
                System.out.println("thread0格式化时间之后得结果时:" + threadLocal.get().format(date));
            });
            thread1.setName("thread0");
            thread1.start();
    
    
            Thread thread2 = new Thread(() -> {
                Date date = new Date(6000);
                System.out.println("thread1格式化时间之后得结果时:" + threadLocal.get().format(date));
            });
            thread2.setName("thread1");
    
            thread2.start();
    
        }
    }
    
    

    在这里插入图片描述
    withInitial方法初始化:

    package ThreadLocalDemo;
    
    import java.util.function.Supplier;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 17:23;
     */
    public class ThreadLocalDemo3 {
        private static ThreadLocal<String> stringThreadLocal =
                ThreadLocal.withInitial(new Supplier<String>() {
                    @Override
                    public String get() {
                        System.out.println("执行了withInitial()方法");
                        return "我是" + Thread.currentThread().getName() + "的ThreadLocal";
                    }
                });
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                System.out.println(stringThreadLocal.get());
            });
    
            thread1.start();
    
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(stringThreadLocal.get());
                }
            });
    
            thread2.start();
        }
    }
    
    

    在这里插入图片描述
    注意:
    ThreadLocal如果使用了set()方法的话,那么它的初始化方法就不会起作用了。
    来看:👇

    package ThreadLocalDemo;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 18:43;
     */
    
    class Tools {
        public static ThreadLocal t1 = new ThreadLocal();
    }
    
    class ThreadA extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("在ThreadA中取值:" + Tools.t1.get());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public class ThreadLocalDemo4 {
        public static void main(String[] args) throws InterruptedException {
            //main是ThreadA 的 父线程 让main线程set,ThreadA,是get不到的
    
            if (Tools.t1.get() == null) {
                Tools.t1.set("main父线程的set");
            }
    
            System.out.println("main get 到了: " + Tools.t1.get());
    
    
            Thread.sleep(1000);
            ThreadA a = new ThreadA();
            a.start();
    
        }
    }
    
    

    在这里插入图片描述
    类ThreadLocal不能实现值的继承,那么就可以使用InheritableThreadLocal了👇

    InheritableThreadLocal的使用

    使用InheritableThreadLocal可以使子线程继承父线程的值

    在这里插入图片描述
    在来看运行的结果:
    在这里插入图片描述

    子线程有最新的值,父线程依旧是旧的值

    package ThreadLocalDemo;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 19:07;
     */
    class ThreadB extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("在ThreadB中取值:" + Tools.t1.get());
                if (i == 5){
                    Tools.t1.set("我是ThreadB中新set()");
                }
                try {
                    Thread.sleep(100);
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public class ThreadLocalDemo5 {
        public static void main(String[] args) throws InterruptedException {
            if (Tools.t1.get() == null) {
                Tools.t1.set("main父线程的set");
            }
    
            System.out.println("main get 到了: " + Tools.t1.get());
    
    
            Thread.sleep(1000);
            ThreadA a = new ThreadA();
            a.start();
            Thread.sleep(5000);
    
            for (int i = 0; i < 10; i++) {
                System.out.println("main的get是:" + Tools.t1.get());
                Thread.sleep(100);
            }
    
        }
    }
    
    

    在这里插入图片描述
    ThreadLocal的脏读问题
    来看👇

    package ThreadLocalDemo;
    
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 19:49;
     */
    
    public class ThreadLocalDemo6 {
        private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
        private static class MyThread extends Thread {
            private static boolean flag = false;
    
            @Override
            public void run() {
                String name = this.getName();
    
                if (!flag) {
                    threadLocal.set(name);
                    System.out.println(name + "设置了" + name);
                    flag = true;
                }
    
                System.out.println(name + "得到了" + threadLocal.get());
            }
        }
    
        public static void main(String[] args) {
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10));
    
    
            for (int i = 0; i < 2; i++) {
                threadPoolExecutor.execute(new MyThread());
            }
    
            threadPoolExecutor.shutdown();
        }
    }
    
    

    在这里插入图片描述

    发生了脏读:
    线程池复用了线程,也复用了这个线程相关的静态属性,就导致了脏读
    那么如何避免脏读呢?
    去掉static 之后:
    在这里插入图片描述
    在这里插入图片描述

    单例模式与多线程

    单例模式就是全局唯一但是所有程序都可以使用的对象
    写单例模式步骤:
    1.将构造函数设置为私有的
    2.创建一个静态的类变量
    3.提供获取单例的方法

    立即加载/饿汉模式

    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 21:02;
     */
    //饿汉方式实现单例模式
    public class Singleton {
        //1.将构造函数设置为私有的,不然外部可以创建
        private Singleton(){
        }
        
        //2.创建静态的类变量(让第三步的方法进行返回)
        private static Singleton singleton = new Singleton();
        
        //给外部接口提供的获取单例的方法
        public static Singleton getInstance(){
            return singleton;
        }
        
    }
    
    

    测试饿汉的单例模式

        //测试饿汉方式实现的单例模式,创建两个线程,看是不是得到了一个实列对象,如果为true就说明饿汉的单例模式没有问题
    
        static Singleton singleton1 = null;
        static Singleton singleton2 = null;
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(() -> {
                singleton1 = Singleton.getInstance();
            });
            Thread thread2 = new Thread(() -> {
                singleton2 = Singleton.getInstance();
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(singleton1 == singleton2);
        }
    
    

    在这里插入图片描述

    延时加载/懒汉模式

    不会随着程序的启动而启动,而是等到有人调用它的时候,它才会初始化

    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 21:22;
     */
    //懒汉方式实现单例模式
    public class Singleton2 {
    
        static class Singleton {
            //1.设置私有的构造函数
            private Singleton() {
            }
    
            //2.提供一个私有的静态变量
            private static Singleton singleton = null;
    
            //3.提供给外部调用,返回一个单例对象给外部
    
            public static Singleton getInstance() {
                if (singleton == null) {
                    singleton = new Singleton();
                }
    
                return singleton;
            }
        }
    }
    
    

    那么这样写有什么问题呢?
    我们来看看多线程情况下的懒汉方式实现单例模式:

    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 21:22;
     */
    //懒汉方式实现单例模式
    public class Singleton2 {
    
        static class Singleton {
            //1.设置私有的构造函数
            private Singleton() {
            }
    
            //2.提供一个私有的静态变量
            private static Singleton singleton = null;
    
            //3.提供给外部调用,返回一个单例对象给外部
    
            public static Singleton getInstance() throws InterruptedException {
                if (singleton == null) {
                    Thread.sleep(100);
                    singleton = new Singleton();
                }
    
                return singleton;
            }
        }
    
        static Singleton singleton1 = null;
        static Singleton singleton2 = null;
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(() -> {
                try {
                    singleton1 = Singleton.getInstance();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            Thread thread2 = new Thread(() -> {
                try {
                    singleton2 = Singleton.getInstance();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(singleton1 == singleton2);
        }
    
    }
    
    

    结果:
    在这里插入图片描述

    所以发生了线程不安全的问题
    那么要如何更改呢?
    加锁:👇
    在这里插入图片描述
    结果就是true了:
    在这里插入图片描述
    给方法加锁可以实现线程安全,但是所锁的粒度太大。
    使用双重校验锁优化后:

        static class Singleton {
            //1.设置私有的构造函数
            private Singleton() {
            }
    
            //2.提供一个私有的静态变量
            private static Singleton singleton = null;
    
            //3.提供给外部调用,返回一个单例对象给外部
    
            public static Singleton getInstance() {
                if (singleton == null) {
                    synchronized (Singleton.class) {
                        if (singleton == null) {
                            singleton = new Singleton();
                        }
                    }
                }
    
                return singleton;
            }
        }
    
    

    在这里插入图片描述
    那么这样写就没有问题了吗?

    不是的:有可能还会发生指令重排的问题
    当有线程在进行第一次初始化的时候,就有可能发生问题👇
    先来看初始化的过程
    1。先分配内存空间
    2.初始化
    3.将singleton指向内存

    有可能指令重排序之后:
    线程1执行的顺序变成了 1 --> 3 --> 2
    在线程1执行完1、3之后时间片使用完了
    线程2再来执行,线程2得到了未初始化的singleton,也就是的到了一个空的对象
    也就发生了线程不安全的问题

    那么要如何解决指令重排序的问题呢?
    那就是使用volatile关键字👇:

    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 21:22;
     */
    //懒汉方式实现单例模式
    public class Singleton2 {
    
        static class Singleton {
            //1.设置私有的构造函数
            private Singleton() {
            }
    
            //2.提供一个私有的静态变量
            private static volatile Singleton singleton = null;
    
            //3.提供给外部调用,返回一个单例对象给外部
    
            public static Singleton getInstance() {
                if (singleton == null) {
                    synchronized (Singleton.class) {
                        if (singleton == null) {
                            singleton = new Singleton();
                        }
                    }
                }
    
                return singleton;
            }
        }
    
    
    

    这样就没有问题了

    饿汉/懒汉对比

    饿汉方式:
    优点:实现简单,不存在线程安全的问题,因为饿汉的方式是随着程序的启动而初始化的,因为类加载是线程安全的,所以它是线程安全的。
    缺点:随着程序的启动而启动,有可能在整个程序的运行周期都没有用到,这样就带来了不必要的开销。

    阻塞队列的实现

    import java.util.Random;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 8:57;
     */
    public class MyBlockingQueue {
        private int[] values;
        private int first;
        private int last;
        private int size;
    
        MyBlockingQueue(int maxSize) {
            this.values = new int[maxSize];
            this.first = 0;
            this.last = 0;
            this.size = 0;
        }
    
        public void offer(int val) throws InterruptedException {
    
            synchronized (this) {
                if (this.size == values.length) {
                    this.wait();
                }
                this.values[last++] = val;
                size++;
    
                //变为循环队列
                if (this.last == values.length) {
                    this.last = 0;
                }
    
                //唤醒消费者
                this.notify();
            }
    
        }
    
        public int poll() throws InterruptedException {
            int result = 0;
            synchronized (this) {
                if (size == 0) {
                    this.wait();
                }
                result = this.values[first++];
                this.size--;
                if (first == this.values.length) {
                    this.first = 0;
                }
                //唤醒生产者开生产数据
                this.notify();
    
            }
            return result;
        }
    
        public static void main(String[] args) {
            MyBlockingQueue myBlockingQueue = new MyBlockingQueue(100);
            //生产者
            Thread thread1 = new Thread(() -> {
    
                while (true) {
                    try {
                        int num = new Random().nextInt(100);
                        myBlockingQueue.offer(num);
                        System.out.println("生产者生产数据:" + num);
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                }
            });
    
    
            //消费者
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true) {
                            int res = myBlockingQueue.poll();
    
                            System.out.println("消费者消费数据:" + res);
                        }
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            thread1.start();
            thread2.start();
        }
    }
    
    

    可以看到生产者每生产一个数据都会被取走:
    在这里插入图片描述

    常见的锁策略

    乐观锁

    它认为程序在一般的情况下不会发生问题,所以他在使用的时候不会加锁,只有在数据修改的时候才会判断有没有锁竞争,如果没有就会直接修改数据,如果有就会返回失败信息给用户自行处理。

    CAS

    乐观锁的经典实现
    Compare and Swap
    CAS 实现的三个重要的属性:
    (V,A,B)

    V:内存中的值
    A:预期的旧值
    B:新值
    V == A? V -> B : 修改失败

    修改失之后:
    自旋对比和替换
    CAS 的底层实现:
    CAS在Java中是通过unsafe来实现的,unsafe时本地类和本地方法,它是c/c++实现的原生方法,通过调用操作系统Atomic:: cmpxchg原子指令来实现的

    CAS在java中的应用

    i++、i–问题
    可以使用加锁、ThreadLocal 解决问题
    也可以使用atomic.AtomicInteger来解决问题,底层也使用了乐观锁。

    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 10:12;
     */
    public class ThreadDemo1 {
        private static AtomicInteger count  = new AtomicInteger(0);
        private static final int MaxSize = 100000;
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < MaxSize; i++) {
                        count.getAndIncrement();//i++
                    }
                }
            });
    
            thread1.start();
    
            Thread thread2 = new Thread(()->{
                for (int i = 0; i < MaxSize; i++) {
                 count.getAndDecrement();//i--
                }
            });
    
            thread2.start();
    
            thread1.join();
            thread2.join();
    
            System.out.println(count);
        }
    }
    
    

    在这里插入图片描述

    CAS 的ABA问题

    当有多个线程对一个原子类进行操作的时候,某个线程在短时间内将原子类的值A修改为B,又马上将其修改为A,此时其他线程不感知,还是会修改成功。
    来看:

    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 10:43;
     */
    public class ThreadDemo2 {
        //线程操作资源,原子类ai的初始值为4
        static AtomicInteger ai = new AtomicInteger(4);
        public static void main(String[] args) {
            new Thread(() -> {
                //利用CAS将ai的值改成5
                boolean b = ai.compareAndSet(4, 5);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为5:"+b);
                //休眠一秒
                try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
                //利用CAS将ai的值改回4
                b = ai.compareAndSet(5,4);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为4:"+b);
            },"A").start();
            new Thread(() -> {
                //模拟此线程执行较慢的情况
                try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
                //利用CAS将ai的值从4改为10
                boolean b = ai.compareAndSet(4, 10);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为10:"+b);
            },"B").start();
    
            //等待其他线程完成,为什么是2,因为一个是main线程,一个是后台的GC线程
            while (Thread.activeCount() > 2) {
                Thread.yield();
            }
    
            System.out.println("ai最终的值为:"+ai.get());
        }
    }
    
    

    上面例子模拟的是A、B两个线程操作一个资源ai,A的执行速度比B的快,在B执行前,A就已经将ai的值改为5之后马上又把ai的值改回为4,但是B不感知,所以最后B就修改成功了。

    那么会造成会有什么问题呢?
    假设A现在有100元,要给B转账100元,点击了两次转账按钮,第一次B只会得到100元,A现在剩余0元。第二次A是0元,预期的旧值是100,不相等,就不会执行转账操作。
    如果点击第二次按钮之前,A又得到了100元,B不能感知的到,此时A得到了转账100元,预期的旧值就是100,又会转给B100元。

    那么如何解决这个问题呢?👇

    ABA 问题的解决

    我们可以给操作加上版本号,每次修改的时候判断版本号和预期的旧值,如果不一样就不会执行操作了。
    即是预期的旧值和V值相等,但是版本号不一样,也不会执行操作。
    在Java中的实现:

    
    import java.util.concurrent.atomic.AtomicStampedReference;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 11:05;
     */
    public class ThreadDemo3 {
        static AtomicStampedReference<Integer> ai = new AtomicStampedReference<>(4,0);
        public static void main(String[] args) {
            new Thread(() -> {
                //四个参数分别是预估内存值,更新值,预估版本号,初始版本号
                //只有当预估内存值==实际内存值相等并且预估版本号==实际版本号,才会进行修改
                boolean b = ai.compareAndSet(4, 5,0,1);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为5:"+b);
                try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
                b = ai.compareAndSet(5,4,1,2);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为4:"+b);
            },"A").start();
            new Thread(() -> {
                try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
                boolean b = ai.compareAndSet(4, 10,0,1);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为10:"+b);
            },"B").start();
    
            while (Thread.activeCount() > 2) {
                Thread.yield();
            }
    
            System.out.println("ai最终的值为:"+ai.getReference());
        }
    }
    
    

    在这里插入图片描述

    注意:里面的旧值对比的是引用。
    如果范围在-128 - 127 里,会使用缓存的值,如果超过了这个范围,就会重新来new对象
    可以将Integer 的高速缓存的值的边界调整

    悲观锁

    悲观锁认为只要执行多线程的任务,就会发生线程不安全的问题,所以正在进入方法之后会直接加锁。
    直接使用synchronzied关键字给方法加锁就可以了

    独占锁、共享锁、自旋锁、可重入锁

    独占锁:指的是这一把锁只能被一个线程所拥有
    比如:synchronzied、Lock
    共享锁: 指的是一把锁可以被多个线程同时拥有
    ReadWriterLock读写锁就是共享锁
    读锁就是共享的,将锁的粒度更加的细化

    import java.util.Date;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 11:42;
     */
    public class ThreadDemo4 {
        //创建读写锁
        public static void main(String[] args) {
            ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    
            //读锁
    
            ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
            //写锁
            ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 1000,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100), new ThreadPoolExecutor.DiscardPolicy());
    
    
            //任务一:读锁演示
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    readLock.lock();
    
                    try {
                        System.out.println(Thread.currentThread().getName() + "进入了读锁,时间:" + new Date());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        readLock.unlock();
                    }
                }
            });
    
            //任务二:读锁演示
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    readLock.lock();
    
                    try {
                        System.out.println(Thread.currentThread().getName() + "进入了读锁,时间:" + new Date());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        readLock.unlock();
                    }
                }
            });
    
            //任务三:写锁
    
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    writeLock.lock();
    
                    try {
                        System.out.println(Thread.currentThread().getName() + "进入了写锁,时间:" + new Date());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        writeLock.unlock();
                    }
                }
            });
            //任务四:写锁
    
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    writeLock.lock();
    
                    try {
                        System.out.println(Thread.currentThread().getName() + "进入了写锁,时间:" + new Date());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        writeLock.unlock();
                    }
                }
            });
    
    
        }
    }
    
    

    在这里插入图片描述

    可重入锁:
    当一个线程拥有了锁之后,可以重复的进入,就叫可重入锁。
    synchronzied就是典型的可重入锁的代表
    读锁的时间在一秒内,所以两个线程读到的锁是一把锁,即读锁是共享锁
    而写锁的时间刚好是一秒,所以写锁是独占锁。

    在这里插入图片描述

    在这里插入图片描述

    自旋锁:相当于死循环,一直尝试获取锁

    详解synchronized锁的优化问题

    synchroized加锁的整个过程,都是依赖于Monitor(监视器锁)实现的,监视器锁在虚拟机中又是根据操作系统的Metux Lock(互斥量)来实现的,这就导致在加锁的过程中需要频繁的在操作系统的内核态和和JVM级别的用户态进行切换,并且涉及到线程上下文的切换,是比较消耗性能的。所以后来有一位大佬Doug Lea基于java实现了一个AQS的框架,提供了Lock锁,性能远远高于synchroized。这就导致Oracle公司很没有面子,因此他们在JDK1.6对synchroized做了优化,引入了偏向锁和轻量级锁。存在一个从无锁-》偏向锁–》轻量级锁–》重量级锁的升级过程,优化后性能就可以和Lock锁的方式持平了。
    对象头
    HotSpot虚拟机中,对象在内存中分为三块区域:对象头、实例数据和对齐填充。
    在这里插入图片描述

    对象头包括两部分:Mark Word 和 类型指针。类型指针是指向该对象所属类对象的指针,我们不关注。mark word用于存储对象的HashCode、GC分代年龄、锁状态等信息。在32位系统上mark word长度为32bit,64位系统上长度为64bit。他不是一个固定的数据结构,是和对象的状态紧密相关,有一个对应关系的,具体如下表所示:

    在这里插入图片描述

    当某一线程第一次获得锁的时候,虚拟机会把对象头中的锁标志位设置为“01”,把偏向模式设置为“1”,表示进入偏向锁模式。同时使用CAS操作将获取到这个锁的线程的ID记录在对象的Mark Word中。如果CAS操作成功,持有偏向锁的线程每次进入这个锁的相关的同步块的时候。虚拟机都可以不在进行任何的同步操作。

    当其他线程进入同步块时,发现已经有偏向的线程了,偏向模式马上结束。根据锁对象目前是否处于被锁定的状态决定是否撤销偏向,也就是将偏向模式设置为“0”,撤销后标志位恢复到“01”,也就是未锁定的状态或者轻量级锁定,标志位为“00”的状态,后续的同步操作就按照下面的轻量级锁那样去执行
    1、在线程进入同步块的时候,如果同步对象状态为无锁状态(锁标志为 01),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录的空间,用来存储锁对象目前的 Mark Word 的拷贝。拷贝成功后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针,并将 Lock Record 里的 owner 指针指向锁对象的 Mark Word。如果更新成功,则执行 2,否则执行 3。

    在这里插入图片描述
    2、如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且锁对象的 Mark Word 中的锁标志位设置为 “00”,即表示此对象处于轻量级锁定状态,这时候虚拟机线程栈与堆中锁对象的对象头的状态如图所示。
    在这里插入图片描述
    3、如果这个更新操作失败了,虚拟机首先会检查锁对象的 Mark Word 是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重要量级锁,锁标志的状态值变为 “10”,Mark Word 中存储的就是指向重量级锁的指针,后面等待锁的线程也要进入阻塞状态。而当前线程便尝试使用自旋来获取锁。自旋失败后膨胀为重量级锁,被阻塞。

    Semaphore

    Semaphore的作用:

    在java中,使用了synchronized关键字和Lock锁实现了资源的并发访问控制,在同一时间只允许唯一了线程进入临界区访问资源(读锁除外),这样子控制的主要目的是为了解决多个线程并发同一资源造成的数据不一致的问题。也就是做限流的作用

    Semaphore实现原理:

    Semaphore是用来保护一个或者多个共享资源的访问,Semaphore内部维护了一个计数器,其值为可以访问的共享资源的个数。一个线程要访问共享资源,先获得信号量,如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器值减去1,再访问共享资源。

    如果计数器值为0,线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量。

    就好比一个厕所管理员,站在门口,只有厕所有空位,就开门允许与空侧数量等量的人进入厕所。多个人进入厕所后,相当于N个人来分配使用N个空位。为避免多个人来同时竞争同一个侧卫,在内部仍然使用锁来控制资源的同步访问。

    Semaphore的使用:

    Semaphore使用时需要先构建一个参数来指定共享资源的数量,Semaphore构造完成后即是获取Semaphore、共享资源使用完毕后释放Semaphore。

    使用Semaphore 来模拟有四辆车同时到达了停车场的门口,但是停车位只有两个,也就是只能停两辆车,这就可以使用信号量来实现。👇:

    
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 14:00;
     */
    public class ThreadDemo6 {
        public static void main(String[] args) {
            Semaphore semaphore = new Semaphore(2);
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 200,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100), new ThreadPoolExecutor.DiscardPolicy());
    
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "到达了停车场");
    
                    try {
                        Thread.sleep(1000);
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "进入了停车场");
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
    
                    try {
                        Thread.sleep(1000);
    
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println(Thread.currentThread().getName() + "出了了停车场");
    
                    semaphore.release();
    
                }
            });
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "到达了停车场");
    
                    try {
                        Thread.sleep(1000);
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "进入了停车场");
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        Thread.sleep(2000);
    
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "出了了停车场");
    
                    semaphore.release();
    
    
                }
            });
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "到达了停车场");
    
                    try {
                        Thread.sleep(1000);
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "进入了停车场");
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
    
                    try {
                        Thread.sleep(500);
    
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "出了了停车场");
    
                    semaphore.release();
    
                }
            });
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "到达了停车场");
    
                    try {
                        Thread.sleep(1000);
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "进入了停车场");
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
    
                    try {
                        Thread.sleep(1500);
    
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println(Thread.currentThread().getName() + "出了了停车场");
    
                    semaphore.release();
    
                }
            });
    
            threadPoolExecutor.shutdown();
        }
    }
    
    

    在这里插入图片描述

    CountDownLatch\CyclicBarrier

    CountDownLatch
    一个可以用来协调多个线程之间的同步,或者说起到线程之间的通信作用的工具类。

    它能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成了任务,然后在CountDownLatch上等待的线程就可以恢复执行任务。

    CountDownLatch的用法

    某一线程在开始运行前等待n个线程执行完毕。
    将CountDownLatch的计数器初始化为n:new CountDownLatch(n) ,每当一个任务线程执行完毕,就将计数器减1, countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

    实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。做法是初始化一个共享的CountDownLatch(1),将其计数器初始化为1,多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。

    CountDownLatch的不足
    CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。

    在这里插入图片描述
    模拟赛跑:当三个运动员都到达终点的时候宣布比赛结束

    import java.util.Random;
    import java.util.concurrent.*;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 14:27;
     */
    public class ThreadDemo7 {
        public static void main(String[] args) throws InterruptedException {
            CountDownLatch countDownLatch = new CountDownLatch(3);
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 200,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100));
    
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "开跑");
    
                    int num = new Random().nextInt(4);
                    num += 1;
    
                    try {
                        Thread.sleep(1000*num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
    
                    countDownLatch.countDown();
                }
            });
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "开跑");
    
                    int num = new Random().nextInt(4);
                    num += 1;
    
                    try {
                        Thread.sleep(1000*num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
    
                    countDownLatch.countDown();
                }
            });
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "开跑");
    
                    int num = new Random().nextInt(4);
                    num += 1;
    
                    try {
                        Thread.sleep(1000*num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
    
                    countDownLatch.countDown();
                }
            });
            countDownLatch.await();
            System.out.println("所有的选手都到达了终点");
            threadPoolExecutor.shutdown();
        }
    }
    

    在这里插入图片描述
    CyclicBarrier

    CyclicBarrier 的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程进入屏障通过CyclicBarrier的await()方法。

    CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

    import java.util.concurrent.*;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 15:03;
     */
    public class ThreadDemo8 {
        public static void main(String[] args) {
            CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
                @Override
                public void run() {
                    System.out.println("到达了循环屏障");
                }
            });
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 200,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100));
    
            for (int i = 0; i < 10; i++) {
    
                int finalI = i;
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
    
                        try {
                            Thread.sleep(finalI * 1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "进入了任务");
    
                        try {
    
                            cyclicBarrier.await();
    
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } catch (BrokenBarrierException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "退出了任务");
    
                    }
                });
            }
    
    
            threadPoolExecutor.shutdown();
        }
    }
    
    

    在这里插入图片描述
    CyclicBarrier原理
    每当线程执行await,内部变量count减1,如果count!= 0,说明有线程还未到屏障处,则在锁条件变量trip上等待。
    当count == 0时,说明所有线程都已经到屏障处,执行条件变量的signalAll方法唤醒等待的线程。
    其中 nextGeneration方法可以实现屏障的循环使用:
    重新生成Generation对象
    恢复count值
    CyclicBarrier可以循环的使用。

    hashmap/ConcurrentHashMap

    hashmap在JDK1.7中头插死循环问题

    来看👇JDK1.7 hashMap transfer的源码

    void transfer(Entry[] newTable, boolean rehash) {
            int newCapacity = newTable.length;
            for (Entry<K,V> e : table) {
                while(null != e) {
                    Entry<K,V> next = e.next;
                    if (rehash) {
                        e.hash = null == e.key ? 0 : hash(e.key);
                    }
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                }
            }
        }
    

    来看多线程情况下的问题:
    在这里插入图片描述
    这样就会造成死循环。

    hashmap在JDK1.8中值覆盖问题

    在JDK1.8的时候使用的是尾插法
    来看👇:

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有hash碰撞则直接插入元素
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
    
    
    

    在多线程的情况下:
    在这里插入图片描述
    其中第六行代码是判断是否出现hash碰撞,假设两个线程1、2都在进行put操作,并且hash函数计算出的插入下标是相同的,当线程1执行完第六行代码后由于时间片耗尽导致被挂起,而线程2得到时间片后在该下标处插入了元素,完成了正常的插入,然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程2插入的数据被线程1覆盖了,从而线程不安全。

    除此之前,还有就是代码的第38行处有个++size,我们这样想,还是线程1、2,这两个线程同时进行put操作时,假设当前HashMap的zise大小为10,当线程1执行到第38行代码时,从主内存中获得size的值为10后准备进行+1操作,但是由于时间片耗尽只好让出CPU,线程2快乐的拿到CPU还是从主内存中拿到size的值10进行+1操作,完成了put操作并将size=11写回主内存,然后线程1再次拿到CPU并继续执行(此时size的值仍为10),当执行完put操作后,还是将size=11写回内存,此时,线程1、2都执行了一次put操作,但是size的值只增加了1,所有说还是由于数据覆盖又导致了线程不安全。

    ConcurrentHashMap & HashTable

    来看这个🤣

    展开全文
  • C#多线程和线程安全问题

    千次阅读 2013-08-13 16:12:46
    在很多情况下〔都会〕或者〔需要〕使用到多线程,这样会给用户带来更好的体验,不至于用户正在操作一个功能时突然卡死啦。在.net 的winform情况下,一般在不涉及到控件操作,数据量又很大的情况下可以考虑使用〔异步...

    在很多情况下〔都会〕或者〔需要〕使用到多线程,这样会给用户带来更好的体验,不至于用户正在操作一个功能时突然卡死啦。在.net 的winform情况下,一般在不涉及到控件操作,数据量又很大的情况下可以考虑使用〔异步/辅助线程〕。

    使用异步/多线程的方式一般都会考虑到创建委托,然后BeginInvoke,或者直接另辟Thread操作,实际上异步和多线程是有细微区分的,更严格的异步好像和什么硬盘的结构有关系,而多线程不是。呵呵不管那么多深奥的东东,以下以委托Thread为例几种创建方式和辅助线程安全的问题。

    异步

    多见的是①创建委托。②创建委托实例或者称为事件。③为事件绑定方法。④调用啦

            public delegate void ThreadHandler();
            public ThreadHandler ThreadEvent = null;
            public void ThreadInvoke()
            {
                //...
                return;
            }

    然后在具体的事件里调用,如下:

                ThreadEvent = new ThreadHandler(ThreadInvoke);
                ThreadEvent.BeginInvoke(result =>//回调函数,当ThreadInvoke执行完调用,然后结束异步。
                    {
                        (result.AsyncState as ThreadHandler).EndInvoke(result);
                    }, null);

    多线程

    借助于以上代码,常见的如:

               Thread t = new Thread(ThreadInvoke);//普通方式
                t.IsBackground = true;
                t.Start();

    或者使用在framework 2.0就出现的匿名方法:

                Thread t1 = new Thread(delegate()//匿名方法
                    {
                        this.Invoke(new Action(delegate() //这里的this就是主线程UI的form
                            {
                                //...调用主线程UI控件的操作
                            }));
                    });
                t1.IsBackground = true;
                t1.Start();

    或者更直接使用lambda表达式:

               Thread t2 = new Thread(() =>//lambda表达式
                {
                    //辅助线程执行...可以查看threadId和ui线程是不同的
                });
                t2.IsBackground = true;
                t2.Start();

    线程安全

    一般多线程都会涉及到线程安全,线程安全一般都是在非主线程调用了控件,因此一般当在辅助线程调用控件(或赋值之类的)时再委托主线程的方法去引用控件。代码一般可以在辅助线程如下写:

               {
                        this.Invoke(new Action(delegate() //这里的this就是主线程UI的form
                            {
                                //...调用主线程UI控件的操作
                            }));
                    });

    展开全文
  • 使用单线程还是多线程问题

    千次阅读 2016-06-01 22:05:26
    对于处理时间短的服务或者启动频率高的要用单线程,相反用...使用多线程编程可以给程序员带来很大的灵活性,同时也使原来需要复杂技巧才能解决  的问题变得容易起来。但是,不应该人为地将编写的程序分成一些碎片,让

    对于处理时间短的服务或者启动频率高的要用单线程,相反用多线程! 
    不论什么时候只要能用单线程就不用多线程,只有在需要响应时间要求比较高的情况下用多线程

    某次操作允许并发而且该操作有可能阻塞时, 用多线程. 例如SOCKET, 磁盘操作.

    使用多线程编程可以给程序员带来很大的灵活性,同时也使原来需要复杂技巧才能解决的问题变得容易起来。但是,不应该人为地将编写的程序分成一些碎片,让这些碎片按各自的线程执行,这不是开发应用程序的正确方法。 线程很有用,但当使用线程时,可能会在解决老问题的同时产生新问题。

    例如要开发一个字处理程序,并想让打印功能作为单独的线程自己执行。这听起来是很好的主意,因为在打印时,用户可立即返回,开始编辑文档。但这样一来,在该文档被打印时文档中的数据就有可能被修改,打印的结果就不再是所期望的内容。也许最好不要把打印功能放在单独的线程中,不过,如果一定要用多线程的话,也可以考虑用下面的方法解决:第 一种方法是锁定正在打印的文档,让用户编辑其他的文档,这样在结束打印之前,该文档不会作任何修改;另一个方法可能更有效一些,即可以把该文档拷贝到一个临时文件中,打印这个临时文件的内容,同时允许用户对原来的文档进行修改。当包含文档的临时文件打印完成时,再删去这个临时文件。 
    通过上面的分析可以看出,多线程在帮助解决问题的同时也可能带来新问题。因此有必要弄清楚,什么时候需要创建多线程,什么时候不需要多线程。总的来说,多线程往往用于在前台操作的同时还需要进行后台的计算或逻辑判断的情况,而对于GUI(图形用户 接口),除了开发MDI(多文档界面)应用程序外,应尽量不使用多线程。

    多线程

    1、就如多了几副碗筷,可以抢占更多的系统资源,加快可以分割成独立执行单元的程序段运行

    2、提供良好的操作感受:不在UI主线程执行费时的作业(这些作业以线程运行)

    3、响应多个并行的请求

    简单的顺序执行方式不能满足要求的时候需要考虑多线程实现,或者有些多任务管理时也需要用到多线程,否则无法并行执行不同功能等等。

    单线程好比所有工作都要你自己干,那样你只能一样一样来,多进程好比你把这些工作分给若干人,大家同步进行,同步进行的好处是大家各干各的,除了接口外,其他工作都独立完成,这样不管是逻辑还是时间上都更加合理,就好象你们单位的若干部门协调工作一样。如果所有部门的工作都要你们老总一个人干,那就是单线程了。你明白了多线程的道理,想学多线程就非常简单了,mfc给你提供了若干实现多线程的函数和机制,直接调用就好,当然你还要注意资源共享,数据独占,互斥量等一些问题。

    ----------------------------------------------------------------------------------------------------------

    多线程就是可以同时做N件事情而不用堵塞在那里,

    多线程主要处理好同步问题

    什么时候该使用多线程,以及更恰当的多线程编程方法之讨论

    大约在写了一年左右的应用程序以后开始发生了一些困惑,在我写的程序中很多使用了多线程,我们了解使用线程的必要性,但是什么时候使用,以及该如何更好使用和管理多线程方面,我觉得值得思考。

    先讨论一下CApp类的实质。App就是一个用户界面线程。

    众所周知,CApp类继承于CWinThread类,是一个线程类,它的实例就是应用程序的主线程(一个用户界面线程)。App 实例化一个WinThread以后,主要的工作就是维护一个消息循环,直到收到程序退出消息,退出循环并终止线程。

    可见App对象使一个线程具有了处理消息的能力,而线程本身并没有这个能力,(了解这一点的目的在于弄清楚线程消息的概念。)拥有这种能力的线程也就是用户界面线程。

    一个线程就是一个代码的执行路径,如果没有App那么线程将按照一个固定的路径执行代码,不会受到任何外界情况的干扰(除非是CPU掉电,或者发生了硬件中断)。显然这不是我们想要看到的,于是如何控制一个线程的运行便成为了一个需要解决的问题,操作系统大多采用了消息机制的解决方案。所谓的消息是一种抽象的概念,其实就是一个操作系统的数据结构,一个可以被App主线程消息循环函数接受的参数。有了消息还不够,还必须要有发送消息的对象。这个对象一般是窗口,窗口作为用户界面线程的一部分用以接受各种事件,事件发生以后,窗口线程就向(自己)线程的消息队列里发送事件所对应的消息。消息的发出者还可以是主线程的其它子线程,也可以是主线程自身(窗口),当然也可以是操作系统。了解了消息机制的原理以后可以对我们编程提供一些帮助,比如,App主线程中不应该执行耗时很长的代码(比如循环,或者I/O操作,或者运算量很大的代码),这样才不会阻塞消息循环,导致界面“死掉”。 解决这个问题的方案就是创建子(辅助)线程来完成这些任务。

    这里要指出的几点是:(注:以下所说的子线程皆指辅助线程,而非界面线程)

    1.主线程不可以向子线程发送消息(但是反之则可以),这种做法本身没有任何意义,是概念不清的问题;

    2.当窗口要向子线程发送消息时;这个问题也是本文讨论的重点。首先,子线程没有处理消息的能力,它只能按照原先设定好的路径运行。所以窗口只能将消息发送给主线程,再由主线程想办法改变子线程的行为。主线程如何跟子线程通讯呢?(显然不能用消息)我所能想到的就是主线程改变一些公共域的值,然后由子线程通过轮询的方式来实现通讯。这也是windows给我们提供的方法,当我们使用AfxBeginThread或者CreateThread创建一个子线程的时候,会传入一个对象的指针,很显然,这个对象就是控制子线程的关键。

    至此了解了什么时候使用多线程的问题。

    第2个问题:如何更好的使用多线程

    首先是代码结构的问题。我们可以将子线程所要完成的功能全部在一个线程函数里实现,这显然在大多数时候是不合理的,第二种方法是通过父线程传递来的对象指针,调用该对象类的成员函数来实现所需的功能。这里引发新的问题,需不需要单独创建一个类来包装这些函数,还是将这些函数写在父线程的类中(包括辅助线程处理函数自己)。这两种方法从本质上来说似乎没有什么差别。后者的话,当我们需要改变子线程的行为时,只需要改变自身类成员变量就可以了,但是结构显得有些混乱。

    转自http://blog.163.com/loveyingchun_1314/blog/static/238242512011112284128908/

    展开全文
  • Java:多线程:到底什么时候该用多线程

    万次阅读 多人点赞 2018-09-30 16:27:29
    系统接受实现多用户多请求的高并发时,通过多线程来实现。   二、线程后台处理大任务 一个程序是线性执行的。如果程序执行到要花大量时间处理的任务时,那主程序就得等待其执行完才能继续执行下面的...
  • Java多线程之sleep带来的困惑

    千次阅读 2018-06-06 11:43:43
    以前对多线程这一块了解的很少,经历过这几个月的春招备战,面试后,感觉多线程这一块是很多面试官经常问的,可以说对于我们来说这一块也是重中之重呀。其实当你接触了他以后,感觉多线程这个东西还是挺有意思的。...
  • 面试题:线程是什么?多线程

    万次阅读 多人点赞 2018-10-27 10:52:48
    什么使用多线程?多线程的示例以及解决方案?线程池是什么? 一.线程是什么? 在Thread类中有这样的明确定义:线程是程序中执行的线程,Java虚拟机允许程序同时运行多个执行线程。 怎么创建一个线程呢? Thread中...
  • 多线程性能及效率问题

    万次阅读 2015-11-09 22:52:38
    一、[多线程概要]  随着计算机技术的发展,编程模型也越来越复杂多样化。但多线程编程模型是目前计算机系统架构的最终模型。随着CPU主频的不断攀升,X86架构的硬件已经成为瓶,在这种架构的CPU主频最高为4G。事实...
  • 以前我认为多线程的作用就是提升性能。实际上,多线程并不一定能提升...这时候可以采用多线程,我感觉就等于是异步调用。这样的例子有很多:  ajax调用,就是浏览器会启一个新的线程,不阻塞当前页面的正常操作; ...
  • servlet 多线程问题

    万次阅读 2011-10-19 10:24:37
    介绍了Servlet多线程机制,通过一个实例并结合Java 的内存模型说明引起Servlet线程不安全的原因,给出了保证Servlet线程安全的三种解决方案,并说明三种方案在实际开发中的取舍。  关键字:Servlet 线程安全 ...
  • HttpClient多线程并发问题

    千次阅读 2015-03-27 22:24:47
     在HttpClient中使用多线程的一个主要原因是可以一次执行多个方法。在执行期间,每一个方法都使用一个HttpConnection实例。由于在同一时间多个连接只能安全地用于单一线程和方法和有限的资源,我们就必须确保连接...
  • 一般在LINUX服务器应用,在多线程编程中用recv非阻塞模式同时接受属于各自描述符的网络数据。因为大量的数据同时进来,recv可能会出现阻塞状态,并且极有可能完全阻塞从而导致线程阻塞。(不过有一种情况,就是...
  • 解决线程同步带来的访问冲突问题

    千次阅读 2016-05-30 16:41:57
    线程的同步也可以称为并发,由于进程是处理机分配资源的最小单位, 就会出现多个线程共同享用一个资源的...关键字synchronized,在多线程情况下,可以确保资源安全,即线程安全 以前提到的HashTable线程安全,HashMap
  • Java多线程引发的性能问题以及调优策略

    万次阅读 多人点赞 2017-11-21 21:06:06
    本文主要介绍了Java多线程引发的性能问题以及调优策略,包括对线程同步、线程池、以及伪共享所引发的问题,并提出了相应的解决方法...
  • Python的多线程性能问题和并发问题

    千次阅读 2015-08-21 16:37:57
    原文标题:一行 Python 实现并行化 -- 日常多线程操作的新思路 原文地址:http://www.zhangzhibo.net/2014/02/01/parallelism-in-one-line/ Python 在程序并行化方面多少有些声名狼藉。撇开技术上的问题,...
  • 系统接受实现多用户多请求的高并发时,通过多线程来实现。 二、线程后台处理大任务 一个程序是线性执行的。如果程序执行到要花大量时间处理的任务时,那主程序就得等待其执行完才能继续执行下面的。那用户就不得...
  • JavaWeb有关多线程问题

    千次阅读 2009-12-24 14:42:00
    JavaWeb有关多线程问题 关于javaWeb多线程看了一些资料,感觉这篇不错,转过来啦……Servlet是在多线程环境下的。即可能有多个请求发给一个servelt实例,每个请求是一个线程。struts下的action也类似,同样在多线程...
  • 多线程问题及处理

    万次阅读 2015-03-25 22:05:58
    多线程编程为程序开发带来了很多的方便,但是也带来了一些问题,这些问题是在程序开发过程中必须进行处理的问题。   这些问题的核心是,如果多个线程同时访问一个资源,例如变量、文件等,时如何保证访问安全的...
  • 如果你要去投资银行面试,例如巴克莱,花旗银行,摩根士丹利为股票前端Java开发人员的职位,可以期待很多多线程的面试问题多线程和并发是投资银行访谈的热门话题,尤其是电子交易开发工作,他们在许多棘手的Java...
  • 多线程面试题(值得收藏)

    万次阅读 多人点赞 2019-08-16 09:41:18
    史上最强多线程面试47题(含答案),建议收藏 金九银十快到了,即将进入找工作的高峰期,最新整理的最全多线程并发面试47题和答案总结,希望对想进BAT的同学有帮助,由于篇幅较长,建议收藏后细看~ 1、并发编程三要素?...
  • 多线程

    千次阅读 2012-06-13 08:59:10
    多线程 进程:是一个正在执行的程序 每个进程都有一个执行顺序,该顺序是一个执行路径或叫一个控制单元, 线程:就是进程中的一个独立的控制单元。 线程在控制着进程的执行 一个进程中可以有多个线程, 但是一个...
  • 多线程和单线程的执行效率问题

    千次阅读 2016-10-12 21:46:31
    一提到多线程一般大家的第一感觉就是可以提升程序性能,在实际的操作中往往遇到性能的问题,都尝试使用多线程来解决问题,但多线程程序并不是在任何情况下都能提升效率,在一些情况下恰恰相反,反而会降低程序的性能...
  • 多线程什么用?

    千次阅读 2018-04-15 15:11:32
    多线程问题是多个人同时吃一道菜的时候容易发生争抢,例如两个人同时夹一个菜,一个人刚伸出筷子,结果伸到的时候已经被夹走菜了。。。此时就必须等一个人夹一口之后,在还给另外一个人夹菜,也就是说资源共享就会...
  • 但不足的是,StopWatch是个线程不安全的工具类,不能用于多线程环境,因此基于StopWatch的不足,自定义组件`mbyx-component-concurrent-stopwatch`提供一个多线程版本的ConcurrentStopWatch便于在多线程环境监控代码
  • C++多线程安全类的问题

    千次阅读 2010-06-28 12:08:00
    尝试着一步一步对一个简单的c++类进行多线程安全设计,剖析其中遇到的问题
  • 在Python中,同样可以实现多线程,有两个标准模块thread和threading,不过我们主要使用更高级的threading模块 threading模块提供的类:   Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, ...
  • Python 多线程学习

    千次阅读 多人点赞 2019-05-10 22:32:10
    为此我们可以使用多线程或者多进程来处理。 多线程和多进程是不一样的!一个是 threading 库,一个是 multiprocessing 库。而多线程 threading 在 Python 里面被称作鸡肋的存在!关于 Python 多线程有这样一句名言...
  • 什么是进程? 当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。...多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务, 也...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 323,087
精华内容 129,234
关键字:

多线程可以带来什么问题