JUC共享模型下

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC共享模型下相关的知识,希望对你有一定的参考价值。


wait notify

小故事 - 为什么需要 wait

由于条件不满足,小南不能继续进行计算

但小南如果一直占用着锁,其它人就得一直阻塞,效率太低


于是老王单开了一间休息室(调用 wait 方法),让小南到休息室(WaitSet)等着去了,但这时锁释放开,其它人可以由老王随机安排进屋

直到小M将烟送来,大叫一声 [ 你的烟到了 ] (调用 notify 方法)


小南于是可以离开休息室,重新进入竞争锁的队列


wait notify 原理

  • Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
  • BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
  • BLOCKED 线程会在 Owner 线程释放锁时唤醒
  • WAITING 线程会在 Owner 线程调用 notify 或 notifyAll时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争
  • 如果同时有两个WAITING 的线程,当他们被唤醒时,先进入EntryList 重新竞争锁,如果竞争失败,没有获取到锁的那个线程,会再次进入等待状态

API 介绍

  • obj.wait() 让进入 object 监视器的线程到 waitSet 等待
  • obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒
  • obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒

它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法



notify和notifyall方法执行的前提也都是对象获得了锁


@Slf4j
public class Main

    final static Object obj = new Object();
    public static void main(String[] args) throws InterruptedException 
        new Thread(() -> 
            synchronized (obj) 
                log.debug("执行....");
                try 
                    obj.wait(); // 让线程在obj上一直等待下去
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                log.debug("其它代码....");
            
        ).start();
        new Thread(() -> 
            synchronized (obj) 
                log.debug("执行....");
                try 
                    obj.wait(); // 让线程在obj上一直等待下去
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                log.debug("其它代码....");
            
        ).start();
        // 主线程两秒后执行
        sleep(2);
        log.debug("唤醒 obj 上其它线程");
        synchronized (obj) 
            obj.notify(); // 唤醒obj上一个线程
            // obj.notifyAll(); // 唤醒obj上所有等待线程
        
    

notify 的一种结果

20:00:53.096 [Thread-0] c.TestWaitNotify - 执行.... 
20:00:53.099 [Thread-1] c.TestWaitNotify - 执行.... 
20:00:55.096 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
20:00:55.096 [Thread-0] c.TestWaitNotify - 其它代码....

notifyAll 的结果

19:58:15.457 [Thread-0] c.TestWaitNotify - 执行.... 
19:58:15.460 [Thread-1] c.TestWaitNotify - 执行.... 
19:58:17.456 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
19:58:17.456 [Thread-1] c.TestWaitNotify - 其它代码.... 
19:58:17.456 [Thread-0] c.TestWaitNotify - 其它代码....
  • wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到 notify为止
  • wait(long n) 有时限的等待, 到 n 毫秒后结束等待,或是被 notify



wait notify 的正确姿势

sleep(long n) 和 wait(long n) 的区别

  • 1) sleep 是 Thread 方法,而 wait 是 Object 的方法
  • 2) sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
  • 3) sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
  • 4) 它们都是状态 TIMED_WAITING

使用案例

思考下面的解决方案好不好,为什么?

import lombok.extern.slf4j.Slf4j;

import static java.lang.Thread.sleep;

@Slf4j
public class Main 
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args)
    
        new Thread(() -> 
            synchronized (room) 
                log.debug("有烟没?[]", hasCigarette);
                if (!hasCigarette) 
                    log.debug("没烟,先歇会!");
                    try 
                        sleep(2);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                
                log.debug("有烟没?[]", hasCigarette);
                if (hasCigarette) 
                    log.debug("可以开始干活了");
                
            
        , "小南").start();
        for (int i = 0; i < 5; i++) 
            new Thread(() -> 
                synchronized (room) 
                    log.debug("可以开始干活了");
                
            , "其它人").start();
        
        try 
            //抛出可能被其他线程打断的异常
            sleep(1);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        new Thread(() -> 
            // 这里能不能加 synchronized (room)?
            hasCigarette = true;
            log.debug("烟到了噢!");
        , "送烟的").start();
    


输出

20:49:49.883 [小南] c.TestCorrectPosture - 有烟没?[false] 
20:49:49.887 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:49:50.882 [送烟的] c.TestCorrectPosture - 烟到了噢!
20:49:51.887 [小南] c.TestCorrectPosture - 有烟没?[true] 
20:49:51.887 [小南] c.TestCorrectPosture - 可以开始干活了
20:49:51.887 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.887 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
  • 其它干活的线程,都要一直阻塞,效率太低
  • 小南线程必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来
  • 加了 synchronized (room) 后,就好比小南在里面反锁了门睡觉,烟根本没法送进门,main 没加
    synchronized 就好像 main 线程是翻窗户进来的
  • 解决方法,使用 wait - notify 机制


案例优化1

import lombok.extern.slf4j.Slf4j;

import static java.lang.Thread.sleep;

@Slf4j
public class Main 
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args)
    
        new Thread(() -> 
            synchronized (room) 
                log.debug("有烟没?[]", hasCigarette);
                if (!hasCigarette) 
                    log.debug("没烟,先歇会!");
                    try 
                        room.wait(2000);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                
                log.debug("有烟没?[]", hasCigarette);
                if (hasCigarette) 
                    log.debug("可以开始干活了");
                
            
        , "小南").start();

        for (int i = 0; i < 5; i++) 
            new Thread(() -> 
                synchronized (room) 
                    log.debug("可以开始干活了");
                
            , "其它人").start();
        

        try 
            sleep(1);
         catch (InterruptedException e) 
            e.printStackTrace();
        

        new Thread(() -> 
            synchronized (room) 
                hasCigarette = true;
                log.debug("烟到了噢!");
                room.notify();
            
        , "送烟的").start();
        
    



输出

20:51:42.489 [小南] c.TestCorrectPosture - 有烟没?[false] 
20:51:42.493 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:51:42.493 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.493 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:43.490 [送烟的] c.TestCorrectPosture - 烟到了噢!
20:51:43.490 [小南] c.TestCorrectPosture - 有烟没?[true] 
20:51:43.490 [小南] c.TestCorrectPosture - 可以开始干活了

问题:

  • 解决了其它干活的线程阻塞的问题,但如果有其它线程也在等待条件呢?
  • 因为notify会随机唤醒一个沉睡线程,如果出现唤醒不希望唤醒的线程情况怎么办

虚假唤醒问题演示

import lombok.extern.slf4j.Slf4j;

import static java.lang.Thread.sleep;

@Slf4j
public class Main 
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args)
    
        new Thread(() -> 
            synchronized (room) 
                log.debug("有烟没?[]", hasCigarette);
                if (!hasCigarette) 
                    log.debug("没烟,先歇会!");
                    try 
                        room.wait();
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                
                log.debug("有烟没?[]", hasCigarette);
                if (hasCigarette) 
                    log.debug("可以开始干活了");
                 else 
                    log.debug("没干成活...");
                
            
        , "小南").start();
        
        new Thread(() -> 
            synchronized (room) 
                Thread thread = Thread.currentThread();
                log.debug("外卖送到没?[]", hasTakeout);
                if (!hasTakeout) 
                    log.debug("没外卖,先歇会!");
                    try 
                        room.wait();
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                
                log.debug("外卖送到没?[]", hasTakeout);
                if (hasTakeout) 
                    log.debug("可以开始干活了");
                <

以上是关于JUC共享模型下的主要内容,如果未能解决你的问题,请参考以下文章

JUC学习之共享模型工具之JUC并发工具包上

JUC学习之共享模型上

JUC学习之共享模型之内存

JUC学习之共享模型之内存

JUC - 共享模型之工具 - 第六篇

JUC - 共享模型之无锁 - 第四篇