说说 Java 线程间通信
序言







正文
一、Java线程间如何通信?
线程间通信的目标是使线程间能够互相发送信号,包括如下几种方式:
1、通过共享对象通信
线程间发送信号的一个简单方式是在共享对象的变量里设置信号值;线程A在一个同步块里设置boolean型成员变量hasDataToProcess为true,线程B也在同步块里读取hasDataToProcess这个成员变量;线程A和B必须获得指向一个MySignal共享实例的引用,以便进行通信;如果它们持有的引用指向不同的MySingal实例,那么彼此将不能检测到对方的信号;需要处理的数据可以存放在一个共享缓存区里,它和MySignal实例是分开存放的。示例如下:
public class MySignal{
protected boolean hasDataToProcess = false;
public synchronized boolean getHasDataToProcess(){
return this.hasDataToProcess;
}
public synchronized void setHasDataToProcess(boolean hasData){
this.hasDataToProcess = hasData;
}
}
【场景展现】:
B同学去了图书馆,发现这本书被借走了(执行了例子中的hasDataToProcess),他回到宿舍,等了几天,再去图书馆找这本书,发现这本书已经被还回,他顺利借走了书。
2、忙等待
准备处理数据的线程B正在等待数据变为可用;换句话说,它在等待线程A的一个信号,这个信号使hasDataToProcess()返回true,线程B运行在一个循环里,以等待这个信号。示例如下:
protected MySignal sharedSignal = ...
...
while(!sharedSignal.hasDataToProcess()){
//do nothing... busy waiting
}
【场景展现】:
假如A同学在B同学走后一会就把书还回去了,B同学却是在几天后再次去图书馆找的书,为了早点借到书(减少延迟),B同学可以就在图书馆等着,比如,每隔几分钟(while循环)他就去检查这本书有没有被还回,这样只要A同学一还回书,B同学很快就会知道。
3、wait(),notify()和notifyAll()
忙等待没有对运行等待线程的CPU进行有效的利用,除非平均等待时间非常短,否则,让等待线程进入睡眠或者非运行状态更为明智,直到它接收到它等待的信号。
一个线程一旦调用了任意对象的wait()方法,就会变为非运行状态,直到另一个线程调用了同一个对象的notify()方法;为了调用wait()或者notify(),线程必须先获得那个对象的锁;也就是说,线程必须在同步块里调用wait()或者notify()。示例如下:
public class MonitorObject{
}
public class MyWaitNotify{
MonitorObject myMonitorObject = new MonitorObject();
public void doWait(){
synchronized(myMonitorObject){
try{
myMonitorObject.wait();
} catch(InterruptedException e){...}
}
}
public void doNotify(){
synchronized(myMonitorObject){
myMonitorObject.notify();
}
}
}
等待线程调用doWait(),而唤醒线程调用doNotify();当一个线程调用一个对象的notify()方法,正在等待该对象的所有线程中将有一个线程被唤醒并允许执行(这个将被唤醒的线程是随机的,不可以指定唤醒哪个线程),可以使用notifyAll()方法来唤醒正在等待一个指定对象的所有线程。
【场景展现】:
检查很多次后,B同学发现这样做自己太累了,身体有点吃不消,不过很快,学校图书馆系统改进,加入了短信通知功能(notify()),只要A同学一还回书,立马会短信通知B同学,这样B同学就可以在家睡觉等短信了。
4、丢失的信号
notify()和notifyAll()方法不会保存调用它们的方法,因为当这两个方法被调用时,有可能没有线程处于等待状态,通知信号过后便丢弃了;因此,如果一个线程先于被通知线程调用wait()前调用了notify(),等待的线程将错过这个信号,在某些情况下,这可能使等待线程永远在等待,不再醒来,因为线程错过了唤醒信号。
为了避免丢失信号,必须把它们保存在信号类里。示例如下:
public class MyWaitNotify2{
MonitorObject myMonitorObject = new MonitorObject();
boolean wasSignalled = false;
public void doWait(){
synchronized(myMonitorObject){
if(!wasSignalled){
try{
myMonitorObject.wait();
} catch(InterruptedException e){...}
}
//clear signal and continue running.
wasSignalled = false;
}
}
public void doNotify(){
synchronized(myMonitorObject){
wasSignalled = true;
myMonitorObject.notify();
}
}
}
【场景展现】:
学校图书馆系统是这么设计的:当一本书被还回来的时候,会给等待者发送短信,并且只会发一次,如果没有等待者,他也会发(只不过没有接收者),这样问题就出现了,因为短信只会发一次,当书被还回来的时候,没有人等待借书,他会发一条空短信,但是之后有等待借此本书的同学永远也不会再收到短信,导致这些同学会无休止的等待;为了避免这个问题,我们在等待的时候先打个电话问问图书馆管理员是否继续等待(if(!wasSignalled))。
5、假唤醒
由于某种原因,线程有可能在没有调用过notify()和notifyAll()的情况下醒来,这就是所谓的假唤醒(spurious wakeups)。
如果在MyWaitNotify2的doWait()方法里发生了假唤醒,等待线程即使没有收到正确的信号,也能够执行后续的操作,这可能出现严重问题。
为了防止假唤醒,保存信号的成员变量将在一个while循环里接受检查,而不是在if表达式里,这样的一个while循环叫做自旋锁(这种做法会消耗CPU,如果长时间不调用doNotify方法,doWait方法会一直自旋,CPU会有很大消耗),被唤醒的线程会自旋直到自旋锁(while循环)里的条件变为false。示例如下:
public class MyWaitNotify3{
MonitorObject myMonitorObject = new MonitorObject();
boolean wasSignalled = false;
public void doWait(){
synchronized(myMonitorObject){
while(!wasSignalled){
try{
myMonitorObject.wait();
} catch(InterruptedException e){...}
}
//clear signal and continue running.
wasSignalled = false;
}
}
public void doNotify(){
synchronized(myMonitorObject){
wasSignalled = true;
myMonitorObject.notify();
}
}
}
【场景展现】:
图书馆系统还有一个bug:系统会偶尔给你发条错误短信,说书可以借了(其实书不可以借),我们之前已经给图书馆管理员打过电话了,他说让我们等短信,我们很听话,一等到短信(其实是bug引起的错误短信),就去借书了,到了图书馆后发现这书根本就没还回来!我们很郁闷,但也没办法啊,学校不修复bug,我们得聪明点:每次在收到短信后,再打电话问问书到底能不能借(while(!wasSignalled))。
二、多个线程如何按顺序执行?
多个线程如何保证执行顺序,是一个很高频的面试题,实现方式很多,这里介绍四种实现方式:
1、使用Thread的join方法
Thread类中的join方法的主要作用就是同步,调用线程需等待join线程执行完或指定时间后执行,如:join(10),表示等待某线程执行10秒后再执行。示例如下:
public class ThreadChildJoin {
public static void main(String[] args) {
final Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("需求分析...");
}
});
final Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
t1.join();
System.out.println("功能开发...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
t2.join();
System.out.println("功能测试...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t3.start();
t1.start();
t2.start();
}
}
2、使用Condition(条件变量)
Condition是一个多线程间协调通信的工具类,使得某个或者某些线程一起等待某个条件(Condition),只有当该条件具备( signal 或者 signalAll方法被带调用)时 ,这些等待线程才会被唤醒,从而重新争夺锁。
Condition类主要方法包括:await方法(类似于Object类中的wait()方法)、signal方法(类似于Object类中的notify()方法)、signalAll方法(类似于Object类中的notifyAll()方法)。示例如下:
public class ThreadCondition {
private static Lock lock = new ReentrantLock();
private static Condition condition1 = lock.newCondition();
private static Condition condition2 = lock.newCondition();
/**
* 为什么要加这两个标识状态?
* 如果没有状态标识,当t1已经运行完了t2才运行,t2在等待t1唤醒导致t2永远处于等待状态
*/
private static Boolean t1Run = false;
private static Boolean t2Run = false;
public static void main(String[] args) {
final Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("需求分析...");
t1Run = true;
condition1.signal();
lock.unlock();
}
});
final Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
if(!t1Run){
condition1.await();
}
System.out.println("功能开发...");
t2Run = true;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
if(!t2Run){
condition2.await();
}
System.out.println("功能测试...");
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t3.start();
t1.start();
t2.start();
}
}
3、使用CountDownLatch(倒计数)
顾名思义,使用CountDownLatch可以实现类似计数器的功能。示例如下:
public class ThreadCountDownLatch {
private static CountDownLatch c1 = new CountDownLatch(1);
/**
* 用于判断线程二是否执行,倒计时设置为1,执行后减1
*/
private static CountDownLatch c2 = new CountDownLatch(1);
public static void main(String[] args) {
final Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("需求分析...");
//对c1倒计时-1
c1.countDown();
}
});
final Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
//等待c1倒计时,计时为0则往下运行
c1.await();
System.out.println("功能开发...");
//对c2倒计时-1
c2.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
//等待c2倒计时,计时为0则往下运行
c2.await();
System.out.println("功能测试...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t3.start();
t1.start();
t2.start();
}
}
4、使用CyclicBarrier(回环栅栏)
CyclicBarrier可以实现让一组线程等待至某个状态之后再全部同时执行,“回环”是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用,可以把这个状态当做barrier,当调用await()方法之后,线程就处于barrier了。示例如下:
public class ThreadCyclicBarrier {
static CyclicBarrier barrier1 = new CyclicBarrier(2);
static CyclicBarrier barrier2 = new CyclicBarrier(2);
public static void main(String[] args) {
final Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("需求分析...");
//放开栅栏1
barrier1.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
final Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
//放开栅栏1
barrier1.await();
System.out.println("功能开发...");
//放开栅栏2
barrier2.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
final Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
//放开栅栏2
barrier2.await();
System.out.println("功能测试...");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
t3.start();
t1.start();
t2.start();
}
}
参考:
[1] http://ifeve.com/thread-signaling/
说说 Java 线程间通信的更多相关文章
- Java线程间通信-回调的实现方式
Java线程间通信-回调的实现方式 Java线程间通信是非常复杂的问题的.线程间通信问题本质上是如何将与线程相关的变量或者对象传递给别的线程,从而实现交互. 比如举一个简单例子,有一个多线程的 ...
- Java线程间通信之wait/notify
Java中的wait/notify/notifyAll可用来实现线程间通信,是Object类的方法,这三个方法都是native方法,是平台相关的,常用来实现生产者/消费者模式.我们来看下相关定义: w ...
- java线程间通信:一个小Demo完全搞懂
版权声明:本文出自汪磊的博客,转载请务必注明出处. Java线程系列文章只是自己知识的总结梳理,都是最基础的玩意,已经掌握熟练的可以绕过. 一.从一个小Demo说起 上篇我们聊到了Java多线程的同步 ...
- Java——线程间通信
body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...
- 说说Java线程间通信
序言 正文 [一] Java线程间如何通信? 线程间通信的目标是使线程间能够互相发送信号,包括如下几种方式: 1.通过共享对象通信 线程间发送信号的一个简单方式是在共享对象的变量里设置信号值:线程A在 ...
- java线程间通信1--简单实例
线程通信 一.线程间通信的条件 1.两个以上的线程访问同一块内存 2.线程同步,关键字 synchronized 二.线程间通信主要涉及的方法 wait(); ----> 用于阻塞进程 noti ...
- java线程间通信之通过管道进行通信
管道流PipeStream是一种特殊的流,用于在不同线程间直接传送数据,而不需要借助临时文件之类的东西. jdk中提供了四个类来使线程间可以通信: 1)PipedInputStream和PipedOu ...
- Java——线程间通信问题
wait和sleep区别: 1.wait可以指定时间可以不指定. sleep必须指定时间. 2.在同步时,对cpu的执行权和锁的处理不同. wait:释放执行权,释放锁. ...
- Java线程间通信
1.由来 当需要实现有顺序的执行多个线程的时候,就需要进行线程通信来保证 2.实现线程通信的方法 wait()方法: wait()方法:挂起当前线程,并释放共享资源的锁 notify()方法: not ...
随机推荐
- Anaconda简单使用手册
安装部分 准备工作 下载各平台对应的安装包,各平台安装包下载链接如下: Windows macOs Linux 安装过程 安装过程在此不给出具体过程,可参照官方给出教程,各平台对应教程如下: Wind ...
- Delphi - 获取文件大小
GetFileSize获取文件大小 封装成如下函数,可以直接使用: ///函数功能:获取文件大小,单位取KB,小数自动进位 ///参数:sFilePath文件全路径 ///Result: 成功是返回文 ...
- Python MySQL 数据库
python DB API python访问数据库的统一接口规范,完成不同数据库的访问 包含的内容: connection cursor exceptions 访问数据库流程: 1.创建connect ...
- [Environment setup]python+selenium+Eclipse+pydev环境搭建
转载自: http://www.cnblogs.com/Bonker/p/3584707.html http://www.cnblogs.com/fnng/archive/2013/05/29/310 ...
- 牛客 136G-指纹锁 set容器重载
136G-指纹锁 题意: 设计一个容器,支持插入x,若与容器中的值最小相差为k,则自动忽略.删除操作,把与x相差为k的值都从容器中删除.查询操作,问容器中有没有和x相差为k的数值. 思路: 一个stl ...
- 微信小程序 select 下拉框组件
一.源码地址 https://github.com/imxiaoer/WeChatMiniSelect 二.效果图 录屏图片质量较差,所以大家会看到残影(捂脸) 三.组件源码 1. select.wx ...
- WordCount2.2
说明: 合作者:201631062507 201631062526(学号) 代码地址:https://gitee.com/WordCountMC/WordCountTeam 本次作业链接:https ...
- Python字符串中删除特定字符
分析 在Python中,字符串是不可变的.所以无法直接删除字符串之间的特定字符. 所以想对字符串中字符进行操作的时候,需要将字符串转变为列表,列表是可变的,这样就可以实现对字符串中特定字符的操作. 1 ...
- Servlet+jsp用户登录加上验证码
最近公司有个项目被客户拿去进行漏洞扫描,发现用户登录太简单,容易被暴力破解.当然发现的问题很多,什么反射型XSS,存储型XSS,敏感信息泄露等等.但是我们今天不讲这么多,就说说如何修复暴力破解的问题. ...
- tomcat启动抛出异常
2018-5-26 15:55:47 org.apache.catalina.startup.VersionLoggerListener log信息: Server version: Apache T ...