• Java多线程详解


    目录

    Java多线程详解

    1. 进程和线程的关系
    1.1 什么是进程?什么是线程?

    进程是一个应用程序(1个进程是一个软件)

    线程是一个进程中的执行场景/执行单元

    一个 进程可以启动多个线程

    1.2 对于java程序,在DOS命令窗口中输入:

    java Helloworld回车后。

    会先启动JVM,而JVM就是一个进程。

    JVM会再启动一个主线程调用Main方法,同时再启动一个垃圾回收器线程负责看护,回收垃圾。最起码,现在的java程序至少有两个线程并发。

    一个是垃圾回收器线程,一个是执行main方法的主线程。

    note:

    • 进程A和进程B的内存独立不共享
    • 线程A和线程B: 堆内存和方法区共享,但是栈内存不共享,一个线程一个栈
    • 假设启动10个线程,会有10个栈空间,每个栈和每一个栈之间,互不干扰,各自执行各自的,这就是多线程开发
    • java之所以有多线程机制,目的就是为了提高程序的处理效率
    1.3 思考:

    使用多线程之后,main方法结束,是不是有可能程序也不会结束。main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈

    1.4 堆和方法区共享栈独立

    一个线程一个栈
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MLKiBX5c-1667385647997)(C:\Users\86152\AppData\Roaming\Typora\typora-user-images\image-20221019212239908.png)]

    2. 线程分析
    2.1 分析以下代码有几个线程?
    /**
     * @Auther liu xiang zheng
     * @Date 2022/10/19
     * @Description 分析一下除了垃圾回收器以外,以下代码有几个线程?
     *              1个线程,因为程序只有一个栈!
     **/
    public class ThreadTest01 {
        public static void main(String[] args) {
    
            System.out.println("main begin");
            m1();
            System.out.println("main over");
    
        }
    
        private static void m1() {
    
            System.out.println("m1 begin");
            m2();
            System.out.println("m1 over");
        }
    
        private static void m2() {
    
            System.out.println("m2 begin");
            m3();
            System.out.println("m2 over");
        }
    
        private static void m3() {
    
            System.out.println("m3 execute");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    1个线程,因为程序只有一个栈!一个栈中,自上而下的顺序依次逐行执行!
    在这里插入图片描述

    3.java语言中,实现线程的两种方式
    3.1 第一种方式: 编写一个类,直接继承java.lang.Thread,重写run方法。
    /**
     * @Auther liu xiang zheng
     * @Date 2022/10/22
     * @Description 实现线程的第一种方式
     *      编写一个类,直接继承java.lang.Thread,重写run方法
     *      怎么创建线程对象? new就行了
     *      怎么启动线程呢? 调用线程的start()方法
     *
     *      note:
     *          亘古不变的道理:方法体当中的方法都是自上而下的执行。
     *
     **/
    public class ThreadTest02 {
        public static void main(String[] args) {
            // 这里是main方法,这里的代码属于主线程,在主栈中运行
            // 新建一个分支线程对象
            MyThread myThread = new MyThread();
    
            // 启动线程
            // start()方法的作用是:启动一个分支线程,在Jvm中开辟一个新的栈空间,这段代码瞬间就结束了.
            // 这段代码的任务只为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就借宿了。线程就启动成功了。
            // 启动成功的线程会自动调用run方法,并且run方法在分支栈的底部(压栈)。
            // run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main都是平级的。
            // run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
            // 调线程中的run方法和线程中的start方法的区别是什么:
            // MyThread.start(); 会启动线程,会分配新的分支栈。
            // MyThread.run(); 不会启动线程,不会分配新的分支栈。(这种方式就是单线程)
            myThread.start(); // java代码的执行顺序一定是从上到下的,这一行代码会瞬间结束,因为他只是启动分支栈。
            // 这里的代码还是运行在主线程中。
            for (int i = 0; i < 50; i++){
                System.out.println("主线程--->" + i);
            }
    
        }
    }
    class MyThread extends Thread{
        @Override
        public void run() {
            // 编写程序,这段程序运行在分支线程中(分支栈)
            for (int i = 0; i < 50; i++){
                System.out.println("分支线程--->" + i);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    3.2 run和start的区别

    MyThread.start(); 会启动线程,会分配新的分支栈。
    在这里插入图片描述

    MyThread.run(); 不会启动线程,不会分配新的分支栈。(这种方式就是单线程)
    在这里插入图片描述

    3.3 第二种方式: 编写一个类,实现java.lang.Runnable接口,实现run方法
    /**
     * @Auther liu xiang zheng
     * @Date 2022/10/23
     * @Description 实现线程的第二种方式, 编写一个类实现java.lang.Runnable接口
     **/
    public class ThreadTest03 {
        public static void main(String[] args) {
    
            // 创建一个可运行的对象
            MyRunnable r = new MyRunnable();
            // 将可运行的对象封装成一个线程对象
            Thread t = new Thread(r);
            // 启动线程
            t.start();
            for (int i = 0; i < 1000; i++) {
                System.out.println("主线程--->");
            }
        }
    }
    
    // 这并不是一个线程类,是一个可运行的类。它还不是一个线程。
    class MyRunnable implements Runnable{
    
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("分支线程---->");
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    note

    采用第二种方式更好,即实现Runnable接口的方式。因为一个类实现了接口,它还可以去继承其它的类,更灵活。

    4.采用匿名内部类的方式
    public class ThreadTest04 {
    
        public static void main(String[] args) {
    
            Thread t = new Thread(new Runnable(){
                @Override
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        System.out.println("分支线程t ---->" + i);
                    }
                }
            });
            // 启动线程
            t.start();
            for (int i = 0; i < 100; i++) {
    
                System.out.println("主线程 ----> " + i);
    
            }
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    5.线程生命周期

    新建状态 就绪状态 运行状态 阻塞状态 死亡状态
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8fe4gBMh-1667385648000)(C:\Users\86152\AppData\Roaming\Typora\typora-user-images\image-20221023194359783.png)]

    6.获取线程的名字
    /**
     * @Auther liu xiang zheng
     * @Date 2022/10/23
     * @Description
     * 1.怎么获取当前线程对象?
     * 2.获取线程对象的名字   String name = 线程对象.getName();
     * 3.修改线程对象的名字   线程对象.setName("线程名字");
     * 4.当线程没有设置名字的时候,默认的名字 Thread-0 Thread-1 Thread-2
     **/
    public class ThreadTest05 {
    
        public static void main(String[] args) {
    
            // 创建分支线程
            MyThread1 t1 = new MyThread1();
            // 设置线程名字
            t.setName("ttttttt");
            // 获取线程的名字
            String tName = t.getName(); //  线程默认名字为Thread-0
            System.out.println(tName);
            MyThread1 t2 = new MyThread1();
            System.out.println(t2.getName());
    
        }
    
    }
    
    class MyThread1 extends Thread{
        @Override
        public void run() {
            System.out.println("分支线程--->");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    7.获取当前线程对象

    Thread t = Thread.currentThread();

    返回值t就是当前线程。

    // currentThread就是当前线程对象
    // 这个代码出现在main方法当中,所以当前线程就是主线程
    Thread currentThread = Thread.currentThread();
    
    • 1
    • 2
    • 3
    7.1 currentThread就是当前线程对象,当前线程是谁呢?

    当上述代码出现在main方法当中,所以线程就是主线程

    当t1线程执行run方法, 那么这个当前线程就是t1

    当t2线程执行run方法, 那么这个当前线程就是t2

    8.线程的sleep方法
    /**
     * @Auther liu xiang zheng
     * @Date 2022/10/23
     * @Description 线程的sleep方法:
     *      static void sleep(long millis)
     *      1. 静态方法:  Thread.sleep(1000);
     *      2. 参数是毫秒
     *      3. 作用: 让当前线程进入休眠,进入"阻塞状态",放弃占有CPU时间片,让给其它线程使用
     *          这行代码出现在A线程中,A线程就会进入休眠
     *          这行代码出现在B线程中,B线程就会进入休眠
     **/
    public class ThreadTest06 {
    
        public static void main(String[] args) throws InterruptedException {
    
            // 让当前线程进入休眠,睡眠五秒
            try {
                sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 5秒之后执行此处的代码
            System.out.println("hello world!");
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    8.1 关于sleep方法的面试题

    以下代码会让t线程进入休眠吗?

    /**
     * @Auther liu xiang zheng
     * @Date 2022/10/23
     * @Description 关于sleep.方法的一个面试题
     **/
    public class ThreadTest07 {
    
        public static void main(String[] args) {
            // 创建线程对象
            MyThread3 t = new MyThread3();
            t.setName("t");
            t.start();
            // 调用sleep方法
            try {
                t.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hello world!");
        }
    
    }
    class MyThread3 extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "-->" + i);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    不会!!! 因为sleep方法跟对象没关系,它是一个静态方法!
    在这里插入图片描述

    在执行的时候还是会转换成:Thread.sleep ( 1000* 5);

    这行代码的作用:是让当前线程进入休眠,也就是说main线程进入休眠。

    这样的代码出现在main方法中,main线程休眠

    8.2 终止线程的睡眠

    调用线程的interrupt()方法:

    t.interrupt(); // 会出现线程睡眠中断的异常信息; 此处的t是一个线程
    
    • 1

    该方法执行的时候,会出现线程睡眠被中断的异常:

    java.lang.InterruptedException: sleep interrupted
    	at java.lang.Thread.sleep(Native Method)
    	at com.thread.MyRunnable2.run(ThreadTest08.java:46)
    	at java.lang.Thread.run(Thread.java:748)
    
    • 1
    • 2
    • 3
    • 4
    8.3 强行终止线程的执行

    调用线程的stop()方法:(已过时,不建议使用)

    /**
     * @Auther liu xiang zheng
     * @Date 2022/10/25
     * @Description 在java中强行终止一个线程的执行
     *              这种方式存在很大的缺点: 容易丢失数据。因为这种方式直接将线程杀死了。
     *              线程没有保持的数据将会丢失,不建议使用。
     **/
    public class ThreadTest09 {
    
        public static void main(String[] args) {
    
            Thread t = new Thread(new MyRunnable3());
            t.setName("t");
            t.start();
            try {
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 5 秒之后强行终止t线程
            t.stop();// 已过时(不建议使用)
        }
    
    }
    
    class MyRunnable3 implements Runnable{
    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    8.4 合理终止一个线程的执行

    利用中间值 boolean run = true 来进行线程的终止。

    /**
     * @Auther liu xiang zheng
     * @Date 2022/10/25
     * @Description
     **/
    public class ThreadTest10 {
    
        public static void main(String[] args) {
    
            MyRunnable4 r = new MyRunnable4();
    
            Thread t = new Thread(r);
            t.setName("t");
            t.start();
            try {
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 5 秒之后强行终止t线程
            r.run = false;
        }
    
    }
    class MyRunnable4 implements Runnable{
    
        // 打一个布尔值
        boolean run = true;
    
        @Override
        public void run() {
                for (int i = 0; i < 10; i++) {
                    if(run) {
                        System.out.println(Thread.currentThread().getName() + "--->" + i);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
    
                        }
                    }else{
                        // 终止线程
                        return;
    
                    }
                }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    9.线程调度概述(了解)

    强占式调度模型:那个线程的优先级比较高,抢到的cpu时间片的概率就高一些/多一些。java采用的就是抢占是调度模型。

    均分式调度模型:平均分配,一切平等。

    10.线程调度的方法

    实例方法:

    void setPriority(int newPriority)  设置线程的优先级
    int getPriority()                  获取线程优先级
    
    • 1
    • 2

    最低优先级是1

    默认优先级是5

    最高优先级是10

    优先级比较高的获取cpu时间片可能会多一些。(只是概率上)

    静态方法:

    static void yield() 让位方法
    暂停当前正在执行的线程对象,并执行其它线程
    yield() 方法不是阻塞方法。让当前线程让位,让给其它线程使用。
    yield() 方法的执行会让当前线程从"运行状态"回到"就绪状态"
    
    • 1
    • 2
    • 3
    • 4

    note: 在回到就绪之后,有可能还会再次抢到。

    10.1 线程的让位

    让位,让当前线程暂停,回到就绪状态,让给其它线程。

    静态方法: Thread.yield();

    /**
     * @Auther liu xiang zheng
     * @Date 2022/10/25
     * @Description 线程的让位
     **/
    public class ThreadTest12 {
    
        public static void main(String[] args) {
    
            Thread t = new Thread(new MyRunnable6());
    
            t.setName("t");
            t.start();
    
            for (int i = 0; i < 10000; i++) {
    
                System.out.println(Thread.currentThread().getName() + "--->" + i);
    
            }
    
        }
    
    }
    class MyRunnable6 implements Runnable {
    
        @Override
        public void run() {
    
            for (int i = 0; i < 10000; i++) {
    
                // 每100位个让位一次
                if(i % 100 == 0){
                    Thread.yield(); // 当前线程暂停一下,让给主线程
                }
                System.out.println(Thread.currentThread().getName() + "--->" + i);
    
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    10.2 线程的合并

    让t线程合并到当前线程中,当前线程受阻,t线程执行直到结束。

    /**
     * @Auther liu xiang zheng
     * @Date 2022/10/25
     * @Description 线程合并
     **/
    public class ThreadTest13 {
    
        public static void main(String[] args) {
    
            System.out.println("main begin");
            Thread t = new Thread(new MyRunnable7());
            t.start();
            // 合并线程
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main over");
        }
    
    }
    class MyRunnable7 implements Runnable{
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在内存上并不是栈的合并,而是栈之间的协调。

    11.线程安全(重点)
    11.1 为什么这个是重点?

    以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。

    最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据
    在多线程并发的环境下是否是安全的。(重点)

    11.2 什么时候数据在多线程并发的环境下会存在线程安全问题呢?

    三个条件:

    • 条件1:多线程并发
    • 条件2:有共享数据
    • 条件3:共享数据有修改行为

    满足以上三个条件之后,就会存在线程安全问题。

    11.3 怎么解决线程安全问题?

    线程排队执行。(不能并发)。用排队执行解决线程安全问题,这种机制被称为:线程同步机制。

    线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。

    11.4 同步和异步

    异步编程模型:线程t1和t2,各自执行各自的,t1不管t2,t2不管t1.谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高)

    同步编程模型:线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低。线程排队执行-

    异步就是并发,同步就是排队

    11.5 模拟两个线程对同一个账户取款

    在这里插入图片描述

    Account类:

    public class Account {
    
        // 账号
        private String actno;
        // 余额
        private double balance;
    
        public Account(String actno, double balance) {
            this.actno = actno;
            this.balance = balance;
        }
    
        public Account() {
        }
    
        public String getActno() {
            return actno;
        }
    
        public void setActno(String actno) {
            this.actno = actno;
        }
    
        public double getBalance() {
            return balance;
        }
    
        public void setBalance(double balance) {
            this.balance = balance;
        }
    
        // 取款方法
        public void withdraw(double money){
            // t1和t2并发这个方法。。。(t1和t2是连个栈。两个栈操作堆中同一个对象)
            // 取款之前的余额
            double before = this.getBalance();
            // 取款之后的余额
            double after = before - money;
            // 更新余额
            // 思考: t1执行到这里了,但是还没有来得及执行这行代码,t2线程进来withdraw方法了。此时一定出问题。
            this.setBalance(after);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    AccountThread类:

    public class AccountThread extends Thread{
        // 保证两个线程共享同一个账户
        // 有一个实例变量
        private Account act;
        // 通过构造方法传递账户对象
        public AccountThread(Account act){
            this.act = act;
        }
        @Override
        public void run() {
            // run方法的执行表示取款操作
            // 假设取款 5000
            double money = 5000;
            act.withdraw(money);
            System.out.println("对账户"+act.getActno()+"取款成功,余额: "+act.getBalance() );
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Test类:

    public class Test {
        public static void main(String[] args) {
            // 创建账户对象 (只创建一个)
            Account act = new Account("act-001", 10000);
            // 创建两个线程
            Thread t1 = new AccountThread(act);
            Thread t2 = new AccountThread(act);
            // 设置name
            t1.setName("t1");
            t2.setName("t2");
            // 启动线程取款
            t1.start();
            t2.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    以上代码可能会出现线程安全的问题,因为在取款方法withdraw()中,如果t1线程执行到:

    // 取款之后的余额
    double after = before - money;
    
    • 1
    • 2

    这句代码以后就完成了取款,但没有执行更新余额的代码:

    // 取款之后的余额
    double after = before - money;
    
    • 1
    • 2

    而就在该更新余额代码执行之前,t2线程也在执行该方法并且执行结束了,就会导致余额的不一致,从而导致多线程并发出现问题!如何解决多线程并发的问题呢?就要用到同步代码块!

    11.6 同步代码块synchronized

    线程同步吧机制的语法是:

          synchronized(){
              // 线程同步代码块
           }
          synchronized后面小括号中传的这个"数据"是相当关键的。
          这个数据必须是多线程共享的数据。才能达到多线程排队。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ()中写什么?取决于你想让哪些线程同步。

    假设t1、t2、t3、t4、t5有5个线程,你只希望t1、t2、t3排队,t4、t5不需要排队,怎么办?

    你一定要在()中写一个t1、t2、t3共享的对象,而这个对象对于t4、t5来说不是共享的。

    将取款方法改为:

    public void withdraw(double money){
    			synchronized(this){
                // 取款之前的余额
                double before = this.getBalance();
                // 取款之后的余额
                double after = before - money;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 更新余额
                this.setBalance(after);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    就可以避免多线程并发问题!

    在java语言中,任何一个对象都有一把锁”,其实这把锁就是标记。(只是把它叫做锁。)100个对象,100把锁。1个对象1把锁。

    以上代码的执行原理:

    • 假设t1和t2线程并发,开始执行以上代码的时候,肯定有一个先一个后。
    • 假设t1先执行了,遇到了synchronized,这个时候自动找后面共享对象的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
    • 假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,查到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后t2占有这把锁之后,进入同步代码块执行程序。

    这样就达到了线程排队执行。这里需要注意的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的。对于synchronized的理解,可以理解为一种阻塞!

    原理:synchronized() 括号里面写的对象,是你要进行排队的对象!最简便的方法就是在括号里写this: synchronized(this)

    11.6 Java哪些变量有 线程安全问题?
    • 实例变量: 在堆中
    • 静态变量: 在方法去
    • 局部变量: 在栈中

    堆和方法区都是多线程共享,所以可能存在线程安全问题。

    11.7 synchronized出现在实例方法上
    public synchronized void withdraw(double money){
            // 取款之前的余额
            double before = this.getBalance();
            // 取款之后的余额
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    synchronized出现在实例方法上的优点:代码写得少了,节俭了。

    如果共享的对象就说this,并且需要同步的代码块就是整个方法体,建议使用这种方式。

    11.8 如果使用局部变量的话

    建议使用: StringBuilder. 因为局部变量不存在线程安全问题。选择StringBuilder。因为StringBuffer效率比较低。

    11.9 总结 : synchronized的三种写法
    • 第一种: 同步代码块(灵活)
    synchronized(线程共享对象){
        同步代码块;
    }
    
    • 1
    • 2
    • 3
    • 第二种: 在实例方法上使用synchronized

      表示共享对象一定是this,并且同步代码块是整个方法体。

    public synchronized void withdraw(double money){
    }
    
    • 1
    • 2
    • 第三种: 在静态方法上使用synchronized表示找类锁。

      表示找类锁。
      类锁永远只有1把。
      就算创建了100个对象,那类锁也只有一把。

      对象锁:1个对象1把锁,100个对象100把锁;类锁:100个对象,也可能只是1把类锁、

    12 synchronized面试题
    12.1 面试题1
    public class Exam01 {
        public static void main(String[] args) {
    
            MyClass mc = new MyClass();
            MyThread t1 = new MyThread(mc);
            MyThread t2 = new MyThread(mc);
    
            t1.setName("t1");
            t2.setName("t2");
    
            t1.start();
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
    
        }
    }
    class MyThread extends Thread{
    
        private MyClass mc;
        public MyThread(MyClass mc){
            this.mc = mc;
        };
    
        @Override
        public void run() {
            if(Thread.currentThread().getName().equals("t1")){
                mc.doSome();
            }
    
            if(Thread.currentThread().getName().equals("t2")){
                mc.doOther();
            }
        }
    }
    class MyClass{
    	// synchronized出现在实例方法上,表示锁this
        public synchronized void doSome(){
            System.out.println("doSome begin");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("doSome over");
        }
    
        public void doOther(){
            System.out.println("doOther begin");
            System.out.println("doOther over");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    以上代码中doOther()方法的执行是否需要等待doSome()方法的结束?

    不需要!因为doOther()方法上并没有synchronized()关键字修饰,故本身是没有锁的!

    12.2 面试题2
    public class Exam01 {
        public static void main(String[] args) {
    
            MyClass mc = new MyClass();
            MyThread t1 = new MyThread(mc);
            MyThread t2 = new MyThread(mc);
    
            t1.setName("t1");
            t2.setName("t2");
    
            t1.start();
            try {
                sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
    
        }
    }
    class MyThread extends Thread{
    
        private MyClass mc;
        public MyThread(MyClass mc){
            this.mc = mc;
        };
    
        @Override
        public void run() {
            if(Thread.currentThread().getName().equals("t1")){
                mc.doSome();
            }
    
            if(Thread.currentThread().getName().equals("t2")){
                mc.doOther();
            }
        }
    }
    class MyClass{
    
        public synchronized void doSome(){
            System.out.println("doSome begin");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("doSome over");
        }
    
        public synchronized void doOther(){
            System.out.println("doOther begin");
            System.out.println("doOther over");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    以上代码中doOther()方法的执行是否需要等待doSome()方法的结束?

    需要,因为doOther()方法上存在synchronized()关键字修饰,故本身是有锁的!

    12.3 面试题3
    public class Exam01 {
        public static void main(String[] args) {
    
            MyClass mc1 = new MyClass();
            MyClass mc2 = new MyClass();
            MyThread t1 = new MyThread(mc1);
            MyThread t2 = new MyThread(mc2);
    
            t1.setName("t1");
            t2.setName("t2");
    
            t1.start();
            try {
                sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
    
        }
    }
    class MyThread extends Thread{
    
        private MyClass mc;
        public MyThread(MyClass mc){
            this.mc = mc;
        };
    
        @Override
        public void run() {
            if(Thread.currentThread().getName().equals("t1")){
                mc.doSome();
            }
    
            if(Thread.currentThread().getName().equals("t2")){
                mc.doOther();
            }
        }
    }
    class MyClass{
    
        public synchronized void doSome(){
            System.out.println("doSome begin");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("doSome over");
        }
    
        public synchronized void doOther(){
            System.out.println("doOther begin");
            System.out.println("doOther over");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    以上代码中doOther()方法的执行是否需要等待doSome()方法的结束?

    不需要,因为MyClass对象是两把锁!

    12.4 面试题4
    public class Exam01 {
        public static void main(String[] args) {
    
            MyClass mc1 = new MyClass();
            MyClass mc2 = new MyClass();
            MyThread t1 = new MyThread(mc1);
            MyThread t2 = new MyThread(mc2);
    
            t1.setName("t1");
            t2.setName("t2");
    
            t1.start();
            try {
                sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
    
        }
    }
    class MyThread extends Thread{
    
        private MyClass mc;
        public MyThread(MyClass mc){
            this.mc = mc;
        };
    
        @Override
        public void run() {
            if(Thread.currentThread().getName().equals("t1")){
                mc.doSome();
            }
    
            if(Thread.currentThread().getName().equals("t2")){
                mc.doOther();
            }
        }
    }
    class MyClass{
    
        // synchronized出现在静态方法上是种类锁
        public synchronized static void doSome(){
            System.out.println("doSome begin");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("doSome over");
        }
    
        public synchronized static void doOther(){
            System.out.println("doOther begin");
            System.out.println("doOther over");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    以上代码中doOther()方法的执行是否需要等待doSome()方法的结束?

    需要,因为静态方法是类锁,不管创建了几个对象,类锁只有1把。

    13.死锁概述

    所谓死锁,是指多个进程因为竞争某些资源而造成的一种僵局,而这种僵局就是他们之间相互等待,此时如果外力的干预,会导致这些进程都将无法进行推进。
    举例子来说明一下:在一条道路较窄的单行道上,如果两方都有来车(此处不考虑逆行),车辆在会车的时候,彼此发现只有对方让出他们所占的那些车道的面积,就可以通过。但是他们又只能向前行驶,从而导致他们进行了无限等待(两辆车都无法经过这条单行道)

    死锁演示图:

    在这里插入图片描述

    public class DeadLock {
    
        public static void main(String[] args) {
    
            Object o1 = new Object();
            Object o2 = new Object();
            // t1和t2两个线程共享o1,o2
            Thread t1 = new MyThread1(o1, o2);
            Thread t2 = new MyThread1(o1, o2);
            t1.start();
            t2.start();
        }
    
    }
    class MyThread1 extends Thread{
            Object o1;
            Object o2;
    
            public MyThread1(Object o1,Object o2){
                this.o1 = o1;
                this.o2 = o2;
            }
            // 下面这个大括号执行结束才能释放o1的锁
            public void run(){
                synchronized(o1){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized(o2){ // 想锁o2的时候,o2已经被MyThread2锁了
                    }
                }
        }
    }
    class MyThread2 extends Thread{
        Object o1;
        Object o2;
    
        public MyThread2(Object o1,Object o2){
            this.o1 = o1;
            this.o2 = o2;
        }
        public void run(){
            synchronized(o2){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized(o1){
                }
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    以上的代码不会有任何执行结果!因为已经产生了死锁:
    运行结果:
    在这里插入图片描述

    14 开发中怎么解决线程安全问题
    14.1 直接选择synchronized的吗?

    不是。synchronized会让程序的执行效率降低,用户体验不好。

    系统的用户吞吐量降低。用户体验差。不得已的情况下再选择线程同步机制。

    • 第一种方案:尽量使用局部变量代替"实例变量和静态变量”。
    • 第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享就没有线程安全问题了。)
    • 第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。
    15 线程部分的其它内容
    15.1 守护线程

    java语言中线程分为两大类:

    • 一类是: 用户线程
    • 一类是: 守护线程(后台线程)(代表性: 垃圾回收器线程)

    守护线程的特点:一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。

    note:主线程main方法是一个用户线程

    守护线程用在什么地方呢?

    每天00 : 00的时候系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程.一直在那里看着p没到00:00的时候就备份一次。所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。|

    public class ThreadTest14 {
        public static void main(String[] args) {
            Thread t = new BakDataThread();
            t.setName("备份数据的线程");
            // 启动线程之前,将线程设置为守护线程
            t.setDaemon(true);
            t.start();
            // 主线程: 主线程是用户线程
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "--->"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    class BakDataThread extends Thread{
    
            public void run(){
                int i = 0;
                while(true){
                    // 即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止。
                    System.out.println(Thread.currentThread().getName() + "--->" + (++i));
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    15.2 定时器

    作用: 间隔特定的时间,执行特定的程序。

    每周要进行银行账户的总账操作。每天要进行数据的备份操作。
    在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:
    可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)
    在java的类库中已经写好了一个定时器: java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
    在实际的开发中,目前使用较多的是spring框架中提供的springTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。

    public class TimerTest {
        public static void main(String[] args) throws ParseException {
    
            // 创建定时器对象
            Timer timer = new Timer();
            // 指定定时任务
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
            Date firstTime = sdf.parse("2022-11-01 10:11:05");
            // 间隔10s执行一次
            timer.schedule(new LogTimerTask(),firstTime,1000 * 10);
        }
    }
    
    // 编写一个定时任务类
    // 假设是一个记录日志的定时任务
    class LogTimerTask extends TimerTask {
    
        @Override
        public void run() {
            // 编写你需要执行的任务
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String strTime = sdf.format(new Date());
            System.out.println(strTime + ":成功完成了一次数据备份!");
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    15.3 实现线程的第三种方式: FutureTask方式,实现Callable接口(JDK8)

    这种方式实现的线程可以获取线程的返回值。
    之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void。

    思考:
    系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么能拿到这个执行结果呢?
    使用第三种方式:实现callable接口方式。

    • 优点:可以获取到线程的执行结果。
    • 缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
    public class ThreadTest15 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    
            // 第一步: 创建一个"未来任务类"对象。
            FutureTask task = new FutureTask(new Callable() {
                @Override
                public Object call() throws Exception { // call方法相当于run方法,只不过这个有放回值
                    // 线程执行一个任务,执行智慧可能会有一个执行结果
                    // 模拟执行
                    System.out.println("call method begin");
                    Thread.sleep(1000 * 10);
                    System.out.println("call method end!");
                    int a = 100;
                    int b = 200;
                    return a+b; // 自动装箱(300结果变成Integer)
                }
            });
    
            // 创建线程对象
            Thread t = new Thread(task);
    
            // 启动线程
            t.start();
    
            //  main方法,主线程中,如何获取t线程的返回结果?
            // get( )方法的执行会导致当前线程阻赛
            Object o = task.get(); // 是否会使main方法受阻? 会
            System.out.println("线程执行结果:"+o);
            // main方法这里的程序要想执行必须等待get()方法的结束
            // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
            // 另一个线程执行是需要时间的。
    
            System.out.println("hello world");
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    15.4 关于Object类中的wait和notify方法(生产者和消费者模式)
    • 第一: wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是object类中自带的,wait方法和notify方法不是通过线程对象调用,不是这样的: t.wait(),也不是这样的: t.notify() …不对。
    • 第二: wait()方法作用?
    Object o = new Object();
    o.wait();
    表示: 让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。
    o.wait();方法的调用,会让"当前线程(正在o对象上活动的线程)"进入等待状态。
    
    • 1
    • 2
    • 3
    • 4

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yDYN3ITh-1667385648002)(C:\Users\86152\AppData\Roaming\Typora\typora-user-images\image-20221102154843532.png)]

    • 第三: notify()方法作用?
    Object o = new Object();
    o.notify();
    
    • 1
    • 2

    表示: 唤醒正在o对象上等待的线程。

    还有一个notifyAll()方法: 这个方法是唤醒o对象上处于等待的所有线程。

    (1)…生产者和消费者模式

    什么是"生产者和消费者模式”?
    生产线程负责生产,消费线程负责消费。生产线程和消费线程要达到均衡。
    这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。

    在这里插入图片描述

    • wait方法作用: o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。
    • notify方法作用: o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。

    代码实现: 一个对象想运行一个线程,只有拿到该线程的锁才可以运行!!!

    /**
     * @Auther liu xiang zheng
     * @Date 2022/11/2
     * @Description
     * 7、模拟这样一个需求:
     * 仓库我们采用List集合。
     * List集合中假设只能存储1个元素。1个元素就表示仓库满了。
     * 如果List集合中元素个数是0,就表示仓库空了。保证List集合中永远都是最多存储1个元素。
     * 必须做到这种效果:生产1个消费1个。
     **/
    public class ThreadTest16 {
    
        public static void main(String[] args) {
            List list = new ArrayList();
    
            // 创建两个线程:
            // 生产者线程
            Thread t1 = new Thread(new Producer(list));
            // 消费者线程
            Thread t2 = new Thread(new Consumer(list));
    
            t1.setName("生产者线程");
            t2.setName("消费者线程");
    
            t1.start();
            t2.start();
    
        }
    
    }
    // 生产线程
    class Producer implements Runnable{
        // 仓库
        private List list;
    
        public Producer(List list){
            this.list = list;
        }
    
        @Override
        public void run() {
            // 一直生产
            while(true){
                synchronized(list){
                    // 给仓库对象list加锁
                    if(list.size()>0){ // 说明当前仓库中已经有一个元素了
                        try {
                            // 当前线程进入等待状态,并且释放list集合的锁
                            list.wait(); // 释放锁以后,该线程就直接结束,即该方法直接结束
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 程序能到这  说明仓库是空的,可以生产
                    Object o = new Object();
                    list.add(o);
                    System.out.println(Thread.currentThread().getName()+"------->" + "obj");
                    // 唤醒消费者
                    list.notifyAll();
                }
            }
        }
    }
    
    // 消费线程
    class Consumer implements Runnable{
        // 仓库
        private List list;
    
        public Consumer(List list){
            this.list = list;
        }
    
        @Override
        public void run() {
            // 一直消费
            while(true){
                synchronized (list) {
                    // 给仓库对象list加锁
                    if (list.size() == 0) { // 说明当前仓库中已经有一个元素了
                        try {
                            // 当前线程进入等待状态,并且释放list集合的锁
                            list.wait(); // 释放锁以后,该线程就直接结束,即该方法直接结束
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    // 程序执行到这,说明仓库中有数据,可以进行进行消费
                    Object remove = list.remove(0);
                    System.out.println(Thread.currentThread().getName() + "--->" + remove);
                    // 唤醒生产者生产
                    list.notifyAll();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
  • 相关阅读:
    【C++从0到王者】第三十三站:AVL树
    【每日一题】2. 两数相加
    GO 在linux 平台编译android 交叉编译
    Alien Skin Exposure2024胶片滤镜中文免费版插件
    CDC和DSG等复制软件的原理,表存在主键才能复制的原因?而mysql binlog主从复制却不需要?
    Python 图片处理
    零代码编程:用ChatGPT批量合并文件夹
    图片拼接:如何将一堆杂乱无章的图片变成一个有意义的、协调的整体
    GBase 8s共享内存中的常驻内存段
    伦敦银行情中短线的支撑和阻力位
  • 原文地址:https://blog.csdn.net/ailaohuyou211/article/details/127656722