当前位置:网站首页>第五章:多线程通信—wait和notify
第五章:多线程通信—wait和notify
2022-08-05 10:05:00 【全栈程序员站长】
大家好,又见面了,我是你们的朋友全栈君。
线程通信概念:线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体,线程间的通信就成为整体的必用方式之一。当线程存在通信指挥,系统间的交互性会更大,在提高CPU利用率的同时还会使开发人员对线程任务在处理的过程中进行有效的把控与监督。
使用wait/notify方法实现线程间的通信。(注意这两个方法都是object的类的方法,换句话说java为所有的对象都提供了这两个方法)
1.wait和notify必须配合synchronized关键字使用
2.wait方法释放锁,notify方法不释放锁。
下面我们来看一道阿里巴巴的面试题,我们看如下所示类代码,代码的意思是在ListAdd1类中添加了一个add方法,该方法向list中添加字符串,size方法返回list的大小。线程”t1″调用10次add方法,每调用一次休眠0.5秒(这样线程”t2″便有时间来判断list的大小),线程”t2″有个死循环不停的去判断list的大小是否到5了,如果到5了,那么就记录日志并抛出异常结束死循环。
package com.xiaoyexinxin.ThreadLearn;
import java.util.ArrayList;
import java.util.List;
public class ListAdd1 {
private volatile static List list = new ArrayList();
public void add(){
list.add("winner");
}
public int size(){
return list.size();
}
public static void main(String[] args){
final ListAdd1 list1 = new ListAdd1();
Thread t1 = new Thread(new Runnable() {
public void run() {
try {
for(int i=0;i<10;i++){
list1.add();
System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素..");
Thread.sleep(500);
}
} catch (Exception e) {
e.printStackTrace();
}
}
},"t1");
Thread t2 = new Thread(new Runnable() {
public void run() {
while(true){
if(list.size() == 5){
System.out.println("当前线程收到通知:"+Thread.currentThread().getName()+"list size=5线程停止..");
throw new RuntimeException();
}
}
}
},"t2");
t1.start();
t2.start();
}
}
我们来看执行结果,可以看到结果与我们所设计的一致,但是这种设计很不好,因为它需要线程”t2″不停的去判断list的大小,这是很耗性能的。
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程收到通知:t2list size=5线程停止..
Exception in thread "t2" java.lang.RuntimeException
at com.xiaoyexinxin.ThreadLearn.ListAdd1$2.run(ListAdd1.java:39)
at java.lang.Thread.run(Unknown Source)
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
那么第一问便是:请使用wait和notify来改善上面的代码
使用wait和notify第一版代码如下,可以看到加上了synchronized关键字并且使用了wait和notify,特别需要注意的是,线程的启动顺序是先启动t2然后启动t1。
package com.xiaoyexinxin.ThreadLearn;
import java.util.ArrayList;
import java.util.List;
public class ListAdd1 {
private volatile static List list = new ArrayList();
public void add(){
list.add("winner");
}
public int size(){
return list.size();
}
public static void main(String[] args){
final ListAdd1 list2 = new ListAdd1();
//1.实例化出来一个lock
//当使用wait和notify的时候,一定要配合着synchronized关键字去使用
final Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
public void run() {
try {
//线程t1和t2一定要用同一把锁,就是都使用lock
synchronized (lock) {
for(int i=0;i<10;i++){
list2.add();
System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素..");
Thread.sleep(500);
if(list2.size() == 5){
System.out.println("已经发出通知");
lock.notify(); //唤醒等待的锁,但是也要先执行当前的锁内容
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
},"t1");
Thread t2 = new Thread(new Runnable() {
public void run() {
synchronized (lock) {
if(list2.size() != 5){
try {
lock.wait(); //线程等待,资源交给其他的线程
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("当前线程"+Thread.currentThread().getName()+"收到通知,线程停止..");
throw new RuntimeException();
}
}
},"t2");
t2.start(); //让t2线程先执行
t1.start();
}
}
执行结果:
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
已经发出通知
eee
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程t2收到通知,线程停止..
Exception in thread "t2" #######3333333333333333
java.lang.RuntimeException
at com.xiaoyexinxin.ThreadLearn.ListAdd1$2.run(ListAdd1.java:58)
at java.lang.Thread.run(Unknown Source)
出现这样的结果是由于wait是释放锁的,而notify是不释放锁的,线程”t2″先执行,一判断发现list2.size不等于5,于是乎线程”t2″进入wait状态,释放了锁,这时线程”t1″便获得锁开始执行,当线程”t1″添加5个元素后判断发现list2.size是5了,于是乎打出了”已发出通知”的日志,lock.notify去唤醒”t2″线程,但是由于notify并不释放锁,因此线程”t1″依然拿着锁执行后面的代码,直到线程”t1″执行完后,线程”t2″才获得锁开始执行,于是乎打印出”收到通知,线程停止”并抛出异常。
就是如果让线程”t1″先执行,线程”t2″后执行,会是什么结果呢?我们把两个线程的启动顺序调换,如下所示。
t1.start();
t2.start(); //让t2线程先执行
运行结果如下,发现线程”t1″执行完了,但是线程”t2″一直在等待,无法结束。
当前线程:t1添加了一个元素.. 当前线程:t1添加了一个元素.. 当前线程:t1添加了一个元素.. 当前线程:t1添加了一个元素.. 当前线程:t1添加了一个元素.. 已经发出通知 eee 当前线程:t1添加了一个元素.. 当前线程:t1添加了一个元素.. 当前线程:t1添加了一个元素.. 当前线程:t1添加了一个元素.. 当前线程:t1添加了一个元素..
#######3333333333333333
为什么会出现上面的情况呢?这是由于先执行线程”t1″的话,线程”t1″便先获得锁开始执行,当list2中元素的个数达到5时虽然线程”t1″调用了lock.notify();但是由于notify并不释放锁,因此线程”t1″继续向下执行,list2继续添加元素直到元素的个数达到10,线程”t1″结束,这时线程”t2″才获得锁开始执行,但由于list2.size这时已经是10了,再也不会是5了,因此线程”t2″判断list2的元素个数不等于5,于是线程”t2″进入wait状态,线程”t1″已经结束了,没有线程去唤醒线程”t2″了,因此线程”t2″便一直处于等待状态了。
我们可以看到,当前这种处理方式(线程t2先执行,t1后执行)不好,因为线程”t2″要等到线程”t1″执行完毕才能接收到通知,这显然不符合实时性的要求。 这时请看该题的第二问:既然上面那种方式不合理,请用java.util.concurrent包下的一个工具来实现实时的接收通知。答案如下:我们使用的工具类是CountDownLatch,该类还有个好处就是不用我们写synchronized关键字修饰了,我们在线程”t2″调用等待方法(countDownLatch.await();),在线程”t1″调用唤醒方法(countDownLatch.countDown();)。而且我们也不必纠结于线程”t1″和”t2″谁先启动谁后启动的问题,谁先启动都可以了。
package com.xiaoyexinxin.ThreadLearn;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class ListAdd3 {
private volatile static List list = new ArrayList();
public void add(){
list.add("winner");
}
public int size(){
return list.size();
}
public static void main(String[] args){
final ListAdd3 list2 = new ListAdd3();
//这是并发包下的一个非常好用的工具类,实例化时的参数1代表需要调用几次
//countDownLatch.countDown();才能叫醒,1就是调用1次即可,2就要调2次才行,
//我们一般都用1就行了
final CountDownLatch countDownLatch = new CountDownLatch(1);
Thread t1 = new Thread(new Runnable() {
public void run() {
try {
for(int i=0;i<10;i++){
list2.add();
System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素..");
Thread.sleep(500);
if(list2.size() == 5){
System.out.println("已经发出通知");
countDownLatch.countDown();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
},"t1");
Thread t2 = new Thread(new Runnable() {
public void run() {
if(list2.size() != 5){
try {
countDownLatch.await();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("当前线程"+Thread.currentThread().getName()+"收到通知,线程停止..");
throw new RuntimeException();
}
},"t2");
t1.start();
t2.start();
}
}
可以看到线程”t2″实时的接收到了通知(我们不必纠结于”t2″线程停止前打印了6条”t1″添加元素的信息,这是打印的顺序的问题,我们看到后面有四条添加元素的信息就对了)。可见这个工具类还是非常好用的。
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
已经发出通知
Exception in thread "t2" java.lang.RuntimeException
at com.xiaoyexinxin.ThreadLearn.ListAdd3$2.run(ListAdd3.java:54)
at java.lang.Thread.run(Unknown Source)
当前线程:t1添加了一个元素..
当前线程t2收到通知,线程停止..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/106147.html原文链接:https://javaforall.cn
边栏推荐
- 偏向锁/轻量锁/重级锁锁锁更健康,上锁解锁到底是怎么完成实现的
- STM32+ULN2003驱动28BYJ4步进电机(根据圈数正转、反转)
- Four years of weight loss record
- High-quality DeFi application building guide to help developers enjoy DeFi Summer
- 高质量 DeFi 应用构建指南,助力开发者玩转 DeFi Summer
- 技术干货 | 基于 MindSpore 实现图像分割之豪斯多夫距离
- DFINITY 基金会创始人谈熊市沉浮,DeFi 项目该何去何从
- Imitation SBUS fixed with serial data conversion
- three.js debugging tool dat.gui use
- What is CRM Decision Analysis Management?
猜你喜欢
2022华数杯数学建模A题环形振荡器的优化设计思路思路代码分享
hcip BGP 增强实验
偏向锁/轻量锁/重级锁锁锁更健康,上锁解锁到底是怎么完成实现的
语音社交软件开发——充分发挥其价值
还在找网盘资源吗?快点收藏如下几个值得收藏的网盘资源搜索神器吧!
SD NAND Flash简介!
Our Web3 Entrepreneurship Project, Yellow
Meteorological data processing example - matlab string cutting matching and R language date matching (data splicing)
我们的Web3创业项目,黄了
基于MindSpore高效完成图像分割,实现Dice!
随机推荐
正则表达式replaceAll()方法具有什么功能呢?
七夕浪漫约会不加班,RPA机器人帮你搞定工作
Brief Analysis of WSGI Protocol
static linking and dynamic linking
歌词整理
[Office] Collection of Microsoft Office download addresses (offline installation and download of Microsoft's official original version)
What is SPL?
Four years of weight loss record
韦东山 数码相框 项目学习(六)tslib的移植
无题十一
高质量 DeFi 应用构建指南,助力开发者玩转 DeFi Summer
Still looking for a network backup resources?Hurry up to collect the following network backup resource search artifact it is worth collecting!
The century-old Nordic luxury home appliance brand ASKO smart wine cabinet in the three-temperature area presents the Chinese Valentine’s Day, and tastes the love of the delicacy
【zeno】为zeno增加子模块/新节点的最小化的例子
C语言的高级用法
我们的Web3创业项目,黄了
Egg framework usage (1)
shell脚本实例
气象数据数据处理实例——matlab字符串切割匹配与R语言日期匹配(数据拼接)
Is digital transformation a business buy-in?