Java多线程之wait、notify/notifyAll 详解,用wait 和notifyAll 以及synchronized实现阻塞队列,多线程拓展之ReentrantLock与Condition
前言:这几天看了很多关于多线程的知识,分享一波。(但是目前接触的项目还未用到过,最多用过线程池,想看线程池 请看我之前的博客)
关于基本的理论等 参考如下:
https://www.cnblogs.com/LipeiNet/p/6475851.html
https://www.cnblogs.com/moongeek/p/7631447.html
一:synchronized
synchronized中文解释是同步,那么什么是同步呢,解释就是程序中用于控制不同线程间操作发生相对顺序的机制,通俗来讲就是2点,第一要有多线程,第二当多个线程同时竞争某个资源的时候会有先后顺序。在java中有三种写synchronized的方式
- 第一种:
- 写在普通方法的前面,这种表示对实例对象加锁。
- 第二种:
- 写在静态方法前面,这种表示对类对象加锁
- 第三种:
- 写在代码块中,锁是Synchonized括号里配置的对象(可能是实例对象,也可能是类对象)
总体说来就2种,一种就是锁实例对象,一种锁类对象。
锁实例对象就是当多个线程同时操作这个实例对象的时候必须先获取锁,如果无法获取锁,则必须处于等待状态,而和锁类对象区别是,当多个线程同时操作的时候,任何以这个类对象实例化的对象都要获取锁才能操作
二:wait、notify已经notifyAll
wait、notify、notifyAll是Object对象的属性,并不属于线程。我们先解释这三个的一个很重要的概念
wait:使持有该对象的线程把该对象的控制权交出去,然后处于等待状态(这句话很重要,也就是说当调用wait的时候会释放锁并处于等待的状态)
notify:通知某个正在等待这个对象的控制权的线程可以继续运行(这个就是获取锁,使自己的程序开始执行,最后通过notify同样去释放锁,并唤醒正在等待的线程)
notifyAll:会通知所有等待这个对象控制权的线程继续运行(和上面一样,只不过是唤醒所有等待的线程继续执行)
三:这里插入我写出的阻塞队列代码
import java.util.ArrayList; import java.util.List; public class BlockQueue { private List<String> list=new ArrayList<>(); private int listSize; private Object lock=new Object(); //通过构造初始化list 大小 public BlockQueue(int size){ this.listSize=size; System.out.println(Thread.currentThread().getName()+"初始化 完成 list 大小为:"+listSize); } public void put(String name){ synchronized (lock){ if(list.size()==listSize){ //队列已经满了 System.out.println(Thread.currentThread().getName()+":当前队列已经满了 需要等待..."); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // try { // Thread.sleep(500); // } catch (InterruptedException e) { // e.printStackTrace(); // } list.add(name); System.out.println(Thread.currentThread().getName()+": put 放入队列中的元素 "+name); //唤醒其他所有 队列存或者取数据 lock.notifyAll(); } } public String get(){ synchronized (lock){ if(list.size()==0){ //说明当前队列已经空了 需要等待 System.out.println(Thread.currentThread().getName()+":当前队列已空,需要等待放入元素..."); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // try { // Thread.sleep(500); // } catch (InterruptedException e) { // e.printStackTrace(); // } String name= list.get(0); list.remove(0); //唤醒其他所有 队列存或者取数据 lock.notifyAll(); System.out.println(Thread.currentThread().getName()+": 获取到队列数据 "+name); return name; } } public static void main(String[] args) { BlockQueue queue=new BlockQueue(3); new Thread(() -> { queue.put("name1"); queue.put("name2"); queue.put("name3"); queue.put("name4"); queue.put("name5"); queue.put("name6"); queue.put("name7"); },"T1").start(); new Thread(() -> { System.out.println(queue.get()); System.out.println(queue.get()); System.out.println(queue.get()); },"T2").start(); } }
这里可以查看更多知识点:
四:notifyAll唤醒所有阻塞线程。试想,我们是否能够显示的在代码中只唤醒“对方”处于阻塞状态的线程呢?
换句话说,在持有同一个锁的情况下,生产者只唤醒消费者,消费者只唤醒生产者,这样的需求是否能实现?
答案是可以的。使用Condition配合Lock,就能解决这样的需求了。
让我们来看看Condition,如何在多生产者消费者模型中实现交替唤醒功能。
拓展:入门AQS锁 – ReentrantLock与Condition
这里的拓展是出自简书:链接地址:https://www.jianshu.com/p/f207e85d0238