**
通信的方式
**
要想实现多个线程之间的协同,如:线程执行先后顺序、获取某个线程执行的结果等等。
涉及到线程之间相互通信,分为下面四类:
1) 文件共享
2) 网络共享
3) 变量共享
4) jdk提供的线程协调API
细分为:suspend/resume、wait/notify、park/unpark
线程协作 - JDK API
JDK中对于需要多线程协作完成某一任务的场景,提供了对应API支持。
对线程协作的典型场景是:生产者-消费者模型。(线程阻塞、线程唤醒)
API -
被弃用的suspend和resume (不会对锁进行释放锁 顺序问题)
作用:调用suspend挂起目标线程,通过resume可以恢复线程执行
被弃用的主要原因,容易写出死锁的代码。
所以用wait/notify、park/unpark机制对它进行替代
1、suspend挂起之后并不会释放锁,故此容易写出死锁
2、先执行suspend,再执行resume。反之则死锁
public class Test01 {
/**
* 线程协作通信的方式:
* suspend/resume
*/
/**包子店*/
public static Object baozidian =null;
/*正常的supend/resume */
public void suspendReumeTest() throws InterruptedException {
//启动线程
Thread consumerThread =new Thread(
new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for(;;) {
if (baozidian == null) {//如果没有包子则进入等待
System.out.println("1、进入等待");
Thread.currentThread().suspend();
} else {
System.out.println("2、买到包子回家");
return;
}
}
}
});
consumerThread.start();
//3秒种之后生产一个包子
Thread.sleep(3000L);
baozidian=new Object();
consumerThread.resume();
System.out.println("3、通知消费者");
}
/** 死锁的suspend/resume。 suspend并不会像wait一样释放锁,故此容易写出死锁代码 */
public void suspendResumeDeadLockTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
for(;;) {
if (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
Thread.currentThread().suspend();
}
} else {
System.out.println("2、买到包子,回家");
}
}
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
consumerThread.resume();
}
System.out.println("3、通知消费者");
}
//导致程序永久挂起的suspend/resume
public void suspendResumeDeadLockTest2() throws InterruptedException {
//启动线程
Thread consumerThread =new Thread(() -> {
// TODO Auto-generated method stub
for(;;) {
if (baozidian == null) {
System.out.println("1、没包子,进入等待");
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//这里挂起执行在resume后面
Thread.currentThread().suspend();
} else {
System.out.println("2、买到包子,回家");
}
}
});
consumerThread.start();
//3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian =new Object();
consumerThread.resume();
System.out.println("3、通知消费者");
consumerThread.join();
}
public static void main(String[] args) throws Exception {
//对调用顺序有要求,也要开发自己注意锁的释放,这个被启用的API,容易死锁,也容易导致永久挂起
new Test01().suspendReumeTest();
//new Test01().suspendResumeDeadLockTest();
//new Test01().suspendResumeDeadLockTest2();
}
}
wait/notify机制 (顺序问题)
这些方法只能由同一对象锁的持有者线程调用,也就是卸载同步块里面,否则会抛出
IllegalMonitorStateException异常。
wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。。
notify/notifyAll 方法唤醒一个或所有正在等待这个对象锁的线程。
注意:虽然wait会自动解锁,但是对 顺序有要求,如果在notify被调用之后,才开始wait
方法的调用,线程会永远处于waiting状态。
public class Test01 {
/**
* 线程协作通信的方式:
* suspend/resume
*/
/**
* 包子店
*/
public static Object baozidian = null;
/**
* 正常的wait/notify
*/
public void waitNotifyTest() throws Exception {
// 启动线程
new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
synchronized (this) {
try {
System.out.println("1、进入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、买到包子,回家");
}).start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消费者");
}
}
/**
* 会导致程序永久等待的wait/notify
*/
public void waitNotifyDeadLockTest() throws Exception {
//启动线程
new Thread(() -> {
if (baozidian == null) {//如果没有包子,则进入等待
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (this) {
try {
System.out.println("1、进入等待");
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("2、买到包子,回家");
}
}).start();
//3秒种之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消费者");
}
}
public static void main(String[] args) throws Exception {
//对调用顺序有要求,也要开发自己注意锁的释放,这个被启用的API,容易死锁,也容易导致永久挂起
new Test01().waitNotifyTest();
//new Test01().waitNotifyDeadLockTest();
}
}
park/unpark机制 令牌模式 (不会对锁进行释放锁 同步锁)
线程调用park则等待 ‘许可’,unpark方法为指定线程提供 ‘许可(permit)’。
不要求park和unpark方法的调用顺序。
import java.util.concurrent.locks.LockSupport;
public class Test01 {
/**
* 线程协作通信的方式:
* suspend/resume
*/
/**
* 包子店
*/
public static Object baozidian = null;
/**
* 正常的wait/notify
*/
/**
* 正常的park/unpark
*
* @throws InterruptedException
*/
public void parkUnparkTest() throws InterruptedException {
//启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) {
System.out.println("1|进入等待");
LockSupport.park();
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
//3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
LockSupport.unpark(consumerThread);
System.out.println("3、通知消费者");
}
/**
* 死锁的park/unpark
*/
public void parkUnparkDeadLockTest() throws Exception {
//启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) {//如果没有包子 进入等待
System.out.println("1、进入等待");
//当前线程拿到锁,然后挂起
synchronized (this) {
LockSupport.park();
}
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
//3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
//争取到锁之后,再回复consumerThread
synchronized (this) {
LockSupport.unpark(consumerThread);
}
System.out.println("3、通知消费者");
}
public static void main(String[] args) throws Exception {
//对调用顺序有要求,也要开发自己注意锁的释放,这个被启用的API,容易死锁,也容易导致永久挂起
new Test01().parkUnparkTest();
//new Test01().parkUnparkDeadLockTest();
}
}
–伪唤醒
官方建议应该在循环中检查等待条件,原因是处于等待状态的线程坑你
会收到 错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足
结束条件的情况下退出。
伪唤醒是指线程并非因为notify、notifyall、unpark等API调用而唤醒,是更底层原因导致的。