• [Java] 多线程


    🔥一个人走得远了,就会忘记自己为了什么而出发,希望你可以不忘初心,不要随波逐流,一直走下去🎶
    🦋 欢迎关注🖱点赞👍收藏🌟留言🐾
    🦄 本文由 程序喵正在路上 原创,CSDN首发!
    💖 系列专栏:Java入门
    🌠 首发时间:2022年7月14日
    ✅ 如果觉得博主的文章还不错的话,希望小伙伴们三连支持一下哦

    🐟实现多线程

    1. 什么是进程?

    进程:是正在运行的程序
    ● 是系统进行资源分配和调用的独立单位
    ● 每一个进程都有它自己的内存空间和系统资源

    我们打开电脑的任务管理器,就可以看到进程

    在这里插入图片描述

    2. 什么是线程?

    线程:是进程中的单个顺序控制流,是一条执行路径
    ● 单线程:一个进程如果只有一条执行路径,则称为单线程程序
    ● 多线程:一个进程如果有多条执行路径,则称为多线程程序

    举例
    ● 记事本程序
    我们可以在记事本文件上打字,也可以设置这个记事本的属性,但是我们却没办法在设置记事本的属性的同时在上面打字,所以记事本是单线程程序

    在这里插入图片描述

    ● 扫雷程序
    我们经常玩的扫雷游戏,当我们开始游戏的时候,游戏会自动计时,这时候我们不进行操作游戏依然会计时,它不会等我们下一次操作再计时,所以扫雷是多线程程序

    在这里插入图片描述

    3. 多线程的实现方式

    方式一:继承 Thread 类

    步骤

    • 定义一个类 MyThread 继承 Thread
    • MyThread 类中重写 run() 方法
    • 创建 MyThread 类的对象
    • 启动线程
    //创建MyThread类
    public class MyThread extends Thread {
        //重写run方法
        @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
    //创建MyThreadDemo类
    public class MyThreadDemo {
        public static void main(String[] args) {
            MyThread my1 = new MyThread();
            MyThread my2 = new MyThread();
    
            //run方法的调用并没有启动线程
    //        my1.run();
    //        my2.run();
    
            //void start() 使线程开始执行,Java虚拟机会调用线程的run方法
            my1.start();
            my2.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    调用run方法

    run 方法的调用并不能真正启动线程
    
    • 1

    在这里插入图片描述

    调用start方法

    start 方法使线程开始执行,Java 虚拟机会调用线程的 run 方法
    
    • 1

    在这里插入图片描述

    为什么要重写 run() 方法?

    因为 run() 方法是用来封装被线程执行的代码
    
    • 1

    run()方法和 start() 方法的区别?

    run() : 封装线程执行的代码,直接调用,相当于普通方法的调用
    start() : 启动线程,然后由 JVM 调用此线程的 run() 方法
    
    • 1
    • 2

    4. 设置和获取线程名称

    Thread 类中设置和获取线程名称的方法

    • void setName(String name) : 将此线程的名称更改为等于参数 name
    • String getName() : 返回此线程的名称
    • 通过构造方法也可以设置线程名称

    如何获取 main() 方法所在的线程名称?

    • public static Thread currentThread(): 返回对当前正在执行的线程对象的引用
    //创建MyThread类
    //鼠标选中Thread,按Ctrl+B可以查看这个类
    public class MyThread extends Thread {
        //构造方法
        public MyThread() {
        }
    
        public MyThread(String name) {
            super(name);
        }
    
        //重写run方法
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                System.out.println(getName() + ":" + i);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    //创建MyThreadDemo类
    public class MyThreadDemo {
        public static void main(String[] args) {
            MyThread my1 = new MyThread("高铁");
            MyThread my2 = new MyThread("飞机");
    
            //void setName(String name): 将此线程的名称更改为等于参数name
    //        my1.setName("高铁");
    //        my2.setName("飞机");
    
            //void start() 使线程开始执行,Java虚拟机会调用线程的run方法
            my1.start();
            my2.start();
    
            //static Thread currentThread() 返回对当前正在执行的线程对象的引用
            System.out.println(Thread.currentThread().getName());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    5. 线程调度

    线程有两种调度模型

    • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
    • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些

    Java 使用的是抢占式调度模型

    假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU 时间片,也就是使用权,才可以执行指令,所以说多线程程序的执行是有随机性的,因为谁抢到 CPU 的使用权是不一定的

    Thread 类中设置和获取线程优先级的方法

    • public final int getPriority() : 返回此线程的优先级
    • public final void setPriority(int newPriority) : 更改此线程的优先级

    接下来我们到程序中测试一下,创建一个 ThreadPriority 类和 ThreadPriorityDemo

    public class ThreadPriority extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName() + ":" + i);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    public class ThreadPriorityDemo {
        public static void main(String[] args) {
            ThreadPriority tp1 = new ThreadPriority();
            ThreadPriority tp2 = new ThreadPriority();
            ThreadPriority tp3 = new ThreadPriority();
    
            tp1.setName("高铁");
            tp2.setName("飞机");
            tp3.setName("汽车");
    
            //public final int getPriority() : 返回此线程的优先级
    //        System.out.println(tp1.getPriority());  //5
    //        System.out.println(tp2.getPriority());  //5
    //        System.out.println(tp3.getPriority());  //5
    
            //public final void setPriority(int newPriority) : 更改此线程的优先级
    //        tp1.setPriority(10000); //IllegalArgumentException
            System.out.println(Thread.MAX_PRIORITY);    //10
            System.out.println(Thread.MIN_PRIORITY);    //1
            System.out.println(Thread.NORM_PRIORITY);   //5
    
            //设置正确的优先级
            tp1.setPriority(5);
            tp1.setPriority(10);
            tp1.setPriority(1);
    
            tp1.start();
            tp2.start();
            tp3.start();
        }
    }
    
    • 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
    • 线程默认优先级是 5;线程优先级的范围是:1 - 10
    • 线程优先级高仅仅表示线程获取的 CPU 时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果

    6. 线程控制

    方法名说明
    static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数
    void join()等待这个线程死亡
    void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java 虚拟机将退出
    • sleep() 方法测试
    public class ThreadSleep extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName() + ":" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    public class ThreadSleepDemo {
        public static void main(String[] args) {
            ThreadSleep ts1 = new ThreadSleep();
            ThreadSleep ts2 = new ThreadSleep();
            ThreadSleep ts3 = new ThreadSleep();
    
            ts1.setName("曹操");
            ts2.setName("刘备");
            ts3.setName("孙权");
    
            ts1.start();
            ts2.start();
            ts3.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述
    ps:每执行一次会停一秒

    • join() 方法测试
    public class ThreadJoin extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName() + ":" + i);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    public class ThreadJoinDemo {
        public static void main(String[] args) throws InterruptedException {
            ThreadJoin tj1 = new ThreadJoin();
            ThreadJoin tj2 = new ThreadJoin();
            ThreadJoin tj3 = new ThreadJoin();
    
            tj1.setName("康熙");
            tj2.setName("四阿哥");
            tj3.setName("八阿哥");
    
            tj1.start();
            tj1.join();		//调用join()方法
            tj2.start();
            tj3.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述
    ps:等康熙驾崩,四阿哥和八阿哥才开始争夺皇位

    • daemon() 方法测试
    public class ThreadDaemon extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName() + ":" + i);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    public class ThreadDaemonDemo {
        public static void main(String[] args) {
            ThreadDaemon td1 = new ThreadDaemon();
            ThreadDaemon td2 = new ThreadDaemon();
    
            td1.setName("关羽");
            td2.setName("张飞");
    
            //设置主线程为刘备
            Thread.currentThread().setName("刘备");
    
            //设置守护线程,不求同年同月同日生,但求同年同月同日死
            td1.setDaemon(true);
            td2.setDaemon(true);
    
            td1.start();
            td2.start();
    
            for (int i = 0; i < 10; 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

    在这里插入图片描述
    ps:主公一死,游戏结束

    7. 线程的生命周期

    在这里插入图片描述

    8. 多线程的实现方式

    方式二:实现 Runnable 接口

    • 定义一个类 MyRunnable 实现 Runnable 接口
    • MyRunnable 类中重写 run() 方法
    • 创建 MyRunnable 类的对象
    • 创建 Thread 类的对象,把 MyRunnable 对象作为构造方法的参数
    • 启动线程
    public class MyRunnable 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
    public class MyRunnableDemo {
        public static void main(String[] args) {
            //创建对象
            MyRunnable my = new MyRunnable();
    
            //创建Thread类的对象,把MyRunnable对象作为构造方法的参数
    //        Thread t1 = new Thread(my);
    //        Thread t2 = new Thread(my);
    
            Thread t1 = new Thread(my, "高铁");
            Thread t2 = new Thread(my, "飞机");
    
            t1.start();
            t2.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    多线程的实现方案有两种

    • 继承 Thread
    • 实现 Runnable 接口

    相比继承 Thread 类,实现 Runnable 接口的好处

    • 避免了 Java 单继承的局限性
    • 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好地体现了面向对象的设计思想

    🐬线程同步

    1. 案例:卖票

    需求:某电影院目前正在上映国产大片,共有 100 张票,而它有 3 个窗口卖票,请设计一个程序模拟该电影院卖票

    思路:

    • 定义一个类 SellTicket 实现 Runnable 接口,里面定义一个成员变量:private int tickets = 100;
    • SellTicket 类中重写 run() 方法实现卖票,代码步骤如下
      A:判断票数大于 0,就卖票,并告知是哪个窗口卖的
      B:卖了票之后,总票数要减 1
      C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
    • 定义一个测试类 SellTicketDemo,里面有 main 方法,代码步骤如下
      A:创建 SellTicket 类的对象
      B:创建三个 Thread 类的对象,把 SellTicket 对象作为构造方法的参数,并给出对应的窗口名称
      C:启动线程
    public class SellTicket implements Runnable {
        private int tickets = 100;
    
        @Override
        public void run() {
            while (true) {
                //判断票数
                if (tickets > 0) {
                    //通过sleep()方法来模拟出票时间
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    public class SellTicketDemo {
        public static void main(String[] args) {
            //创建SellTicket类的对象
            SellTicket st = new SellTicket();
    
            //创建三个Thread类的对象
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
    
            //启动线程
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    思考

    卖票出现了问题

    • 相同的票出现了多次
    • 出现了负数的票

    问题原因

    • 线程执行的随机性导致的

    2. 数据安全问题的解决

    为什么会出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准)

    • 是否是多线程环境
    • 是否有共享数据
    • 是否有多条语句操作共享数据

    如何解决多线程安全问题呢?

    • 基本思想:让程序没有安全问题的环境

    怎么实现呢?

    • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
    • Java 提供了同步代码块的方式来解决

    3. 同步代码块

    锁多条语句操作共享数据,可以使用同步代码块来实现

    • 格式:
    synchronized(任意对象) {
    	多条语句操作共享数据的代码
    }
    
    • 1
    • 2
    • 3
    • synchronized(任意对象):相当于给代码加锁了,任意对象就可以看成是一把锁
    public class SellTicket implements Runnable {
        private int tickets = 100;
        private final Object obj = new Object();
    
        @Override
        public void run() {
            while (true) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj) {
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                        tickets--;
                    }
                }
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    public class SellTicketDemo {
        public static void main(String[] args) {
            //创建SellTicket类的对象
            SellTicket st = new SellTicket();
    
            //创建三个Thread类的对象
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
    
            //启动线程
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    同步的好处和弊端

    • 好处:解决了多线程的数据安全问题
    • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

    4. 同步方法

    同步方法:就是把 synchronized 关键字加到方法上

    • 格式:
      修饰符 synchronized 返回值类型 方法名(方法参数) { }

    同步方法的锁对象是什么呢?

    • this
    public class SellTicket implements Runnable {
        private int tickets = 100;
        private final Object obj = new Object();
        private int x = 0;
    
        @Override
        public void run() {
            while (true) {
                if (x % 2 == 0) {
                    synchronized(this) {	//锁对象要换成this
                        if (tickets > 0) {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
    
                            System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                            tickets--;
                        }
                    }
                } else {
                    sellTicket();
                }
                x++;
            }
    
        }
    
    	//内部操作相同,只不过封装成一个方法
        private synchronized void sellTicket() {
            if (tickets > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                tickets--;
            }
        }
    }
    
    • 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

    同步静态方法:就是把 synchronized 关键字添加到静态方法上

    • 格式:
      修饰符 static synchronized 返回值类型 方法名(方法参数) { }

    同步静态方法的锁对象是什么呢?

    • 类名.class
    public class SellTicket implements Runnable {
        private static int tickets = 100;
        private final Object obj = new Object();
        private int x = 0;
    
        @Override
        public void run() {
            while (true) {
                if (x % 2 == 0) {
                    synchronized (SellTicket.class) {
                        if (tickets > 0) {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
    
                            System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                            tickets--;
                        }
                    }
                } else {
                    sellTicket();
                }
                x++;
            }
    
        }
    
        private static synchronized void sellTicket() {
            if (tickets > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                tickets--;
            }
        }
    }
    
    • 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

    5. 线程安全的类

    StringBuffer

    • 线程安全,可变的字符系列
    • 从版本 JDK5 开始,被 StringBuilder 替代。通常应该使用 StringBuilder 类,因为它支持所有相同的操作,但它更快,因为它不执行同步

    Vector

    • Java2 平台 1.2 开始,该类改进了 List 接口,使其成为 Java Collections Framework 的成员。与新的集合实现不同,Vector 被同步。如果不需要线程安全的实现,建议使用 ArrayList 代替 Vector

    Hashtable

    • 该类实现了一个哈希表,它将键映射到值,任何非 null 对象都可以用作键或者值
    • Java2 平台 1.2 开始,该类进行了改进,实现了 Map 接口,使其成为 Java Collections Framework 的成员。与新的集合实现不同,Hashtable 被同步。如果不需要线程安全的实现,建议使用 HashMap 代替 Hashtable

    6. Lock锁

    虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清楚地表达如何加锁和释放锁,JDK5 以后提供了一个新的锁对象 Lock

    Lock 实现提供比使用 synchronized 方法和语句可以获得更广泛的锁定操作

    Lock 中提供了获得锁和释放锁的方法

    • void lock(): 获得锁
    • void unlock(): 释放锁

    Lock 是接口不能直接实例化,这里采用它的实现类 ReentrantLock 来实例化

    ReentrantLock 的构造方法

    • ReentrantLock() : 创建一个 ReentrantLock 的实例

    Lock 锁给之前的卖票案例加锁

    import java.util.concurrent.locks.*;
    
    public class SellTicket implements Runnable {
        private int tickets = 100;
        //创建锁对象
        private Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                try {
                    lock.lock();
    
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                        tickets--;
                    }
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    
    • 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

    🐋生产者消费者

    1. 生产者消费者模式概述

    生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。

    所谓生产者消费者问题,实际上主要是包含了两类线程:

    • 一类是生产者线程用于生产数据
    • 一类是消费者线程用于消费数据

    为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

    • 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
    • 消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为

    为了体现生产和消费过程中的等待和唤醒,Java 就提供了几个方法供我们使用,这几个方法在 Object 类中

    方法名说明
    void wait()导致当前线程等待,直到另一个线程调用该对象的 notify() 方法或 notifyAll() 方法
    void notify()唤醒正在等待对象监视器的单个线程
    void notifyAll()唤醒正在等待对象监视器的所有线程

    2. 生产者消费者案例

    生产者消费者案例中包含的类:

    • 奶箱类(Box):定义一个成员变量,表示第 x 瓶奶,提供存储牛奶和获取牛奶的操作
    • 生产者类(Producer):实现 Runnable 接口,重写 run() 方法,调用存储牛奶的操作
    • 消费者类(Customer):实现 Runnable 接口,重写 run() 方法,调用获取牛奶的操作
    • 测试类(BoxDemo):里面有 main 方法,main 方法中的代码步骤如下
      ① 创建奶箱对象,这是共享数据区域
      ② 创建生产者对象,把奶香对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
      ③ 创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
      ④ 创建 2 个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
      ⑤ 启动线程

    代码实现

    //奶箱类
    public class Box {
        //定义一个成员变量,表示第x瓶奶
        private int milk;
        //定义一个成员变量,表示奶箱的状态
        private boolean state = false;
    
        //提供存储牛奶和获取牛奶的操作
        public synchronized void put(int milk) throws InterruptedException {
            //如果有牛奶,等待消费
            if (state) {
                wait();
            }
    
            //如果没有牛奶,就生产牛奶
            this.milk = milk;
            System.out.println("送奶工将第" + this.milk + "瓶奶放入奶箱");
    
            //生产完毕之后,修改奶箱状态
            state = true;
    
            //唤醒其他等待线程
            notifyAll();
        }
    
        public synchronized void get() throws InterruptedException {
            //如果没有牛奶,等待生产
            if (!state) {
                wait();
            }
    
            //如果有牛奶,就消费牛奶
            System.out.println("用户拿到第" + this.milk + "瓶奶");
    
            //消费完毕之后,修改奶箱状态
            state = false;
    
            //唤醒其他等待线程
            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
    //生产者类
    public class Producer implements Runnable {
        private Box b;
    
        public Producer(Box b) {
            this.b = b;
        }
    
        @Override
        public void run() {
            for (int i = 1; i <= 5; i++) {
                try {
                    b.put(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    //消费者类
    public class Customer implements Runnable {
        private Box b;
    
        public Customer(Box b) {
            this.b = b;
        }
    
        @Override
        public void run() {
            while (true) {
                try {
                    b.get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    //测试类
    public class BoxDemo {
        public static void main(String[] args) {
            //创建奶箱对象
            Box b = new Box();
            //创建生产者对象
            Producer p = new Producer(b);
            //创建消费者对象
            Customer c = new Customer(b);
    
            //创建2个线程对象
            Thread t1 = new Thread(p);
            Thread t2 = new Thread(c);
    
            //启动线程
            t1.start();
            t2.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    运行结果

    在这里插入图片描述

    在这里插入图片描述
    🧸这次的分享就到这里啦,继续加油哦🍬
    ✨下期预告:网络编程
    🍭有出错的地方欢迎在评论区指出来,共同进步,谢谢啦

  • 相关阅读:
    2022年0902Maven的继承和利用Idea创建Maven工程的内容<第五课>
    客服快捷短语
    【youcans动手学模型】目标检测之 SPPNet 模型
    手机拍照转机器人末端坐标(九点标定法)
    NO.1 | 704. 二分查找,27. 移除元素
    TiUP 简介
    120年奥运历史数据分析
    Shell命令笔记2
    微信如何实现自动转发朋友圈(跟圈)?
    VALSE2022天津线下参会个人总结8月24日-3
  • 原文地址:https://blog.csdn.net/weixin_62511863/article/details/125671412