一起学并发编程
来源:互联网 发布:java方法名命名规范 编辑:程序博客网 时间:2024/05/01 15:45
wait
,notify
和notifyAll
,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视,而本文则是对这些关键字的使用进行描述。
存在即合理
在java中,每个对象都有两个池,锁池(monitor)和等待池(waitset),每个对象又都有
wait
、notify
、notifyAll
方法,使用它们可以实现线程之间的通信,只是平时用的较少。
- wait(): 使当前线程处于等待状态,直到另外的线程调用
notify
或notifyAll
将它唤醒 - notify(): 唤醒该对象监听的其中一个线程(规则取决于JVM厂商,FILO,FIFO,随机…)
- notifyAll(): 唤醒该对象监听的所有线程
锁池: 假设T1线程
已经拥有了某个对象(注意:不是类)的锁
,而其它的线程想要调用该对象的synchronized方法(或者synchronized块)
,由于这些线程在进入对象的synchronized方法
之前都需要先获得该对象的锁的拥有权,但是该对象的锁目前正被T1线程
拥有,所以这些线程就进入了该对象的锁池中。
等待池: 假设T1线程
调用了某个对象的wait()
方法,T1线程
就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前T1线程就已经拥有了该对象的锁)
,同时T1线程
进入到了该对象的等待池中。如果有其它线程调用了相同对象的notifyAll()
方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,从新争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.
注意事项
- 在调用wait(), notify()或notifyAll()的时候,都必须获得某个
对象(注意:不是类)的锁
。 - 永远在循环(loop)里调用
wait
和notify
,而不是在 If 语句中 - 永远在
synchronized
的函数或对象里使用wait、notify和notifyAll
,不然Java虚拟机会生成IllegalMonitorStateException
。
使用案例 - 生产消费
private static int i = 0;static void product() {//生产者 System.out.println("P->" + (++i));}static void consumer() {//消费者 System.out.println("C->" + i);}public static void main(String[] args) { new Thread(() -> { while (true) { product(); } }).start(); new Thread(() -> { while (true) { consumer(); } }).start();}////////////////////////日志//////////////////////////P->1//P->2//P->3//P->4//C->1 ////////////////////////日志////////////////////////
分析: 从日志中可以看到数据会出现多次生产或多次消费的问题,因为在线程执行过程中,两个线程缺少协作关系,都是各干各的,T1线程
只管生产数据,不管T2线程
是否处理了。
改进方案 - 变量消息传递
private static int i = 0;private static boolean isProduction = true;static void product() {//生产者 if (isProduction) { System.out.println("P->" + (++i)); isProduction = false; }}static void consumer() {//消费者 if (!isProduction) { System.out.println("C->" + i); isProduction = true; }}
分析: 这种情况下我们通过维护一个变量的方式,通知对方,但是效率及其差,线程频繁请求与判断大大的浪费了系统资源,虽然满足了当前要求,但并非是可选方案…
改进方案 - wait/notify
上文已经介绍了使用wait
和notify
的前提了,接下来看案例
private final static byte[] LOCK = new byte[0];//定义一个锁对象private static boolean isProduction = true;//消息投递private static int i = 0;//生产的消息static void product() { synchronized (LOCK) {// 必须是在 synchronized中 使用 wait/notify/notifyAll try { if (isProduction) {//如果标示位为生产状态,则继续生产 System.out.println("P->" + (++i)); isProduction = false; LOCK.notify();//消费者可以消费了 } else { LOCK.wait();//说明生产出来的数据还未被消费掉 } } catch (InterruptedException e) { e.printStackTrace(); } }}static void consumer() { try { synchronized (LOCK) { if (isProduction) {//如果当前还在生产,那么就暂停消费者线程 LOCK.wait(); } else { System.out.println("C->" + i); isProduction = true; LOCK.notify();//通知我已经消费完毕了 } } } catch (InterruptedException e) { e.printStackTrace(); }}public static void main(String[] args) { new Thread(() -> { while (true) { product(); } }).start(); new Thread(() -> { while (true) { consumer(); } }).start();}////////////////////////日志//////////////////////////P->1//C->1//P->2//C->2//.....//P->354217//C->354217////////////////////////日志////////////////////////
分析: 一切都是那么完美,在T1线程
中,调用LOCK.wait()
将当前线程移入等待池中,并交出执行权,锁池中的其他线程去竞争并取得锁的使用权(T2线程获取
),当T2线程
消费完毕后,调用LOCK.notify()
方法通知当前对象锁等待池中的其中一个线程(因为这里notify是基于JVM算法而定
,因为我们只有两个线程,所以T1线程
会接收到T2线程
发出的通知,从而继续生产数据。
问题: 虽然一对一没有问题,但假设多个生产者多个消费者的情况下怎么办呢?
BUG - 埋点
public static void main(String[] args) { Stream.of("P1", "P2", "P3", "P4").forEach(name -> new Thread(() -> { while (true) { product(); } }, name).start()); Stream.of("C1", "C2").forEach(name -> new Thread(() -> { while (true) { consumer(); } }, name).start());}////////////////////////日志//////////////////////////P2 -> 1//C2 -> 1//P2 -> 2//C1 -> 2//P3 -> 3////////////////////////日志////////////////////////
分析: 居然不执行了,借助前面说过的 死锁分析知识,我们看看是不是发生死锁了
结果表明,虽然没有Found one deadlock...
字眼,但是可以看到有个线程都被wait
住了,没有被释放,所以导致我们当前无法继续生产消费
解决方案 - notifyAll
LOCK.notifyAll();//通知所有线程,我已经消费完毕了,你们继续生产
分析: 这里只修改了一句代码,就是将consumer方法
中的notify -> notifyAll
,由通知单个线程变成通知所有在等待池
中的线程
P1 -> 1C1 -> 1P2 -> 2C2 -> 2...P3 -> 42894C1 -> 42894...P1 -> 42898C1 -> 42898
- 说点什么
全文代码:https://gitee.com/battcn/battcn-concurent/tree/master/Chapter1-1/battcn-thread/src/main/java/com/battcn/chapter7
- 个人QQ:1837307557
- battcn开源群(适合新手):391619659
微信公众号:battcn
(欢迎调戏)
喜大普奔,迎来了十一国庆节….
- 一起学并发编程
- 一起学并发编程
- 一起学并发编程
- 一起学并发编程
- 一起学并发编程
- 一起学并发编程
- 一起学并发编程
- 一起学并发编程
- 一起学并发编程
- 一起学并发编程
- 一起学并发编程
- 一起学编程(1)
- 看动画学并发编程
- 看动画学并发编程
- 面向方面编程,有兴趣的一起学
- 《与孩子一起学编程》译者序
- 《与孩子一起学编程》书评
- 与孩子一起学编程1
- TAG
- ubuntu17.04下安装LNMP
- 用递归函数和栈操作逆序一个栈(每日一道算法题)
- 【无人零售】易观:2017中国无人便利店发展专题分析
- Python3网络爬虫快速入门实战解析(一小时入门 Python 3 网络爬虫)
- 一起学并发编程
- 【人工智能】终于把“人工”+“智能”讲清楚了!!!
- 【区块链】安永:区块链平台调研与分析
- php 一个简单的符合PSR-0规范的框架
- JPA persistence.xml 配置
- 【新零售】CBNData:2017线下零售新生态报告
- Accord.NET Framework
- 【财富空间】傅盛:自我进化是一切
- 解决 /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/universal-darwi