• 并发编程 | 对比Object类、ReentrantLock.newCondition、LockSupport类提供的等待唤醒机制


    本文为视频笔记:P50-P55
    链接:尚硅谷并发编程

    1. 三种等待唤醒机制

    已知Object类、ReentrantLock.newCondition、LockSupport类都有提供等待唤醒机制:

    • Object类的wait、notify方法
    • ReentrantLock.newCondition.await和signal方法
    • LockSupport类的park和unpark方法

    2. 区别

    Object类和ReentrantLock.newCondition提供的阻塞唤醒机制十分相似,体现在:

    • 要求阻塞和唤醒是有序的:它们都只能先阻塞再唤醒。否则会抛IllegalMonitorStateException异常,且被阻塞的线程不能被唤醒
    • 依赖加锁或同步代码块:调Object的等待唤醒方法时要搭配synchronized一起用。即Object#wait和Object#notify要写在sync同步代码块内;调ReentrantLock.newCondition的await和signal方法时要搭配lock,unlock一起用。否则也会抛IllegalMonitorStateException异常,且被阻塞的线程不能被唤醒,

    LockSupport类与前两者不同:

    • LockSupport类支持先唤醒后阻塞,不会抛异常(底层是通过permit通行证实现的)
    • LockSupport类不依赖加锁代码块,直接LockSupport.park, LockSupport.park调用即可,较方便

    3. demo演示

    3.1 synchronized搭配Object的wait、notify

    package com.example.juc.LockCondition;
    
    public class TestObj {
    
        public static void main(String[] args) {
    
            TestObj obj = new TestObj(); // 1. 待会借此生成一个sync锁
    
            Thread t1 = new Thread(() -> {
                synchronized (obj) {   // 2.1 记得锁住同个对象
                    System.out.println("start。。。。");
                    try {
                        obj.wait(10000); // 阻塞
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("end。。。。");
                }
            });
            t1.setPriority(9);
            t1.start();
    
            new Thread(() -> {
                synchronized (obj) { // 2.2 记得锁住同个对象
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("唤醒t1.....");
                    obj.notify(); // 通知唤醒t1
                }
            }).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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    3.2 ReentrantLock的await和signal方法

    先看个翻车日常,使用await和signal时并没有使用同个condition调用,结果被阻塞的线程没有真正被通知唤醒:
    在这里插入图片描述
    此外,以上的try catch写得也不太规范。加锁代码应放在try之外,释放锁应该写在finally内。
    正确代码演示:

    package com.example.juc.LockCondition;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class TestCondition {
    
        private static ReentrantLock lock = new ReentrantLock();
        private static Condition condition = lock.newCondition(); // await和signal都要使用同一个condition!
    
        public static void main(String[] args) {
    
            Thread t1 = new Thread(() -> {
                lock.lock(); // 代码规范1
                try {
                    System.out.println("开始阻塞");
                    condition.await(10000, TimeUnit.MILLISECONDS); // 阻塞
                    System.out.println("结束阻塞");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock(); // 代码规范2
                }
            });
            t1.setPriority(9);
            t1.start();
    
            new Thread(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(1000); // 此处设置休眠是想让t1先跑一会
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.lock();
                System.out.println("唤醒t1...");
                condition.signal();
                lock.unlock();
            }).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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    3.3 LockSupport的park和unpark

    package com.example.juc.LockCondition;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.LockSupport;
    
    public class TestPark {
    
        public static void main(String[] args) {
    
            Thread t1 = new Thread(() -> {
                System.out.println("开始阻塞");
                LockSupport.park(10000); // 阻塞
                System.out.println("结束阻塞");
            });
            t1.setPriority(9);
            t1.start();
    
            new Thread(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(1000); // 此处设置休眠是想让t1先跑一会
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println("唤醒t1...");
                LockSupport.unpark(t1); // 唤醒t1,给t1一个凭证
            }).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

    4. 总结

    使用这几种阻塞唤醒机制时,要注意一些细节,不要抛异常就OK
    在这里插入图片描述

  • 相关阅读:
    Java面试题之接口和抽象类的区别
    new动态创建一维数组、qsort函数、折半查找
    【JAVA并发】二、JAVA是如何解决并发问题的
    线性dp求解 最长子序列 —— 小题三则
    【软件测试】--功能测试4-html介绍
    微型神经网络库MicroGrad-基于标量自动微分的类pytorch接口的深度学习框架
    PHP JSON的解析和创建
    kettle数据迁移从oracle到mysql
    Echarts-实现3D柱状图
    OpenStack创建云主机并连接CRT
  • 原文地址:https://blog.csdn.net/xianyu_x/article/details/127419036