• 【JUC】线程通信与等待唤醒机制


    1. 线程通信

    多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同,于是这些线程之间就存在通信问题,称为线程间通信

    比如:生产者消费者问题

    当多个线程间存在通信问题时,我们希望它们能有规律地执行,因此就需要一些协调手段,其中,等待唤醒机制就是协调线程间通信的一种有效手段。

    2. Object类中的wait和notify方法实现等待和唤醒

    • wait和notify方法必须在同步块或者方法里面使用,且成对出现
    • 必须先wait后notify才OK
    public static void main(String[] args) {
        Object monitor = new Object();
        new Thread(() -> {
            synchronized (monitor) {
                System.out.println("线程1执行");
                try {
                    monitor.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    
        new Thread(() -> {
            System.out.println("线程2执行");
            synchronized (monitor) {
                monitor.notify();
            }
        }).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

    3. Condition接口中的await和signal方法实现等待和唤醒

    • Condition中的线程等待和唤醒方法,需要先获取锁
    • 一定要先await后signal
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        new Thread(() -> {
            System.out.println("线程1执行");
            lock.lock();
            try{
                condition.await();
                System.out.println("线程1被唤醒");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    
        new Thread(() -> {
            System.out.println("线程2执行");
            lock.lock();
            try{
                condition.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                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

    4. LockSupport实现等待和唤醒

    LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个permit。但与Semaphore不同的是,许可的累加上限是1

    • park():permit许可证默认没有,所以一开始调用park()方法当前线程就会阻塞,直到别的线程给该线程发放permit,该线程才会被唤醒
    • unpark(thread):发放permit许可证给对应线程thread
    • 满足 正常阻塞唤醒要求,无锁块要求;且支持先唤醒后等待
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("线程1执行");
            LockSupport.park();
            System.out.println("线程1被唤醒");
        });
        t1.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    
        new Thread(() -> {
            System.out.println("线程2执行");
            LockSupport.unpark(t1);
        }).start();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4.1 优点

    为什么推荐使用LockSupport来做线程的阻塞与唤醒(线程间协同工作),因为它具备如下优点

    • 以线程为操作对象更符合阻塞线程的直观语义
    • 操作更精准,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程)
    • 无需竞争锁对象(以线程作为操作对象),不会因竞争锁对象产生死锁问题
    • unparkpark没有严格的执行顺序,不会因执行顺序引起死锁问题,比如「Thread.suspendThread.resume」没按照严格顺序执行,就会产生死锁

    另外LockSupport还提供了park的重载函数,提升灵活性

    • void parkNanos(long nanos):增加了超时机制
    • void parkUntil(long deadline):加入超时机制(指定到某个时间点,1970年到指定时间点的毫秒数)
    • void park(Object blocker):设置blocker对象,当线程没有许可证被阻塞时,该对象会被记录到该线程的内部,方便后续使用诊断工具进行问题排查
    • void parkNanos(Object blocker, long nanos):设置blocker对象,加入超时机制
    • void parkUntil(Object blocker, long deadline):设置blocker对象,加入超时机制(指定到某个时间点,1970年到指定时间点的毫秒数)

    建议使用时,传入blocker对象,至于超时根据业务场景选择

  • 相关阅读:
    进程管理(二)
    计算机毕业设计(附源码)python职业信息服务平台
    Android Termux安装MySQL,内网穿透实现公网远程访问
    【芯片前端】四年经验|芯片前端|IP设计岗|面试问题|总结分享
    注意,2022年CCF会士评选结果揭晓
    【uni-app】condition 启动模式配置,生产环境无效,仅开发期间生效
    crontab的配置参数和基础使用教程
    WEEX上线高防系统,防御效果拔群,开启20万U盛大回馈庆典
    【Linux】腾讯云服务器Linux环境搭载
    Axios 中的文件上传(Upload File)方法
  • 原文地址:https://blog.csdn.net/xxx1276063856/article/details/133840346