前言
Github:https://github.com/yihonglei/jdk-source-code-reading(java-concurrent)
一 等待/通知机制
等待/通知机制是线程间通信的一种方式,首先需要了解wait、notify、notifyAll方法含义。
1、 wait()
wait()方法为Object对象的方法,使线程等待,进入阻塞队列。
2、notify()/notifyAll()
notify()随机通知一个线程唤醒,如果通知线程少于等待线程时,将不能及时唤醒多个线程。
notifyAll() 唤醒所有等待执行线程使其继续运行。
3、对象锁的就绪队列和阻塞队列
每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,
阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待cpu调度;
否则,一个线程被wait后,就会进入阻塞队列,等待下次被唤醒。
二 wait/notify
1、非wait/notify实例
在实例分析wait/notify前,先通过另外一种方式进行线程间通信。
实例类:
package com.jpeony.concurrent.waitnotify.no;
import java.util.ArrayList;
import java.util.List;
/**
* @author yihonglei
*/
public class MyList {
private List<String> list = new ArrayList<String>();
public void addUserName(String username) {
list.add(username);
}
public int size() {
return list.size();
}
}
创建线程A:
package com.jpeony.concurrent.waitnotify.no;
/**
* @author yihonglei
*/
public class ThreadA extends Thread {
private MyList myList;
public ThreadA(MyList myList) {
super();
this.myList = myList;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
// 往集合添加元素
myList.addUserName("element" + (i + 1));
System.out.println(Thread.currentThread().getName() + "添加了" + myList.size() + "个元素!");
Thread.sleep(1000);
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
创建线程B:
package com.jpeony.concurrent.waitnotify.no;
/**
* @author yihonglei
*/
public class ThreadB extends Thread {
private MyList myList;
public ThreadB(MyList myList) {
super();
this.myList = myList;
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "还活着");
if (myList.size() >= 5) {
System.out.println("A线程已经将list添加了5个元素,B线程要退出了");
// 异常法退出线程
throw new InterruptedException();
}
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
测试类:
package com.jpeony.concurrent.waitnotify.no;
/**
* 不使用等待/通知模式,通过sleep和while配合实现线程间的通信;
* A线程不断添加元素,B线程while循环一直检查共享的list,
* B线程可以根据list做各种判断和处理,从而实现A线程与B线程之间的通信!
* 即两个线程通过读写一个共享变量来完成线程通信。
* <p>
* 这种处理方式的缺点:
* (1) B线程一直通过某个条件轮询,浪费cpu资源;
* (2) B线程轮询时间大小,对轮询条件也有影响,渠道的轮询条件有可能出现"错过精准条件"
* 比如,如果B线程判断改为myList.size() == 5,则可能出现A线程添加到5的时候,
* B线程没有轮询到,当B线程执行到myList.size()的时候,可能取到的值为6,
* 哪么就会出现,B线程错过了与A线程精准对接的条件,导致B线程与A线程错过了通信的机会
*
* @author yihonglei
*/
public class RunTest {
public static void main(String[] args) {
MyList myList = new MyList();
ThreadA threadA = new ThreadA(myList);
threadA.setName("A");
threadA.start();
ThreadB threadB = new ThreadB(myList);
threadB.setName("B");
threadB.start();
}
}
运行结果:
A和B两个线程共享同一个对象,A的修改对于B可见,B线程通过条件判断退出程序。
使用这种方式处理线程通信,有以下缺点:
1)B线程一直通过某个条件轮询,浪费cpu资源;
2)B线程轮询时间大小,对轮询条件也有影响,渠道的轮询条件有可能出现"错过精准条件"
比如,如果B线程判断改为myList.size() == 5,则可能出现A线程添加到5的时候,
B线程没有轮询到,当B线程执行到myList.size()的时候,可能取到的值为6,
哪么就会出现,B线程错过了与A线程精准对接的条件,导致B线程与A线程错过了通信的机会
所以,为了解决上面这些问题,采用Object对象的wait,notify方法,通过等待/通知机制完成线程间通信。
2、wait/notify实例
创建用于发起等待的A线程:
package com.jpeony.concurrent.waitnotify.yes;
/**
* 用于处理等待
*
* @author yihonglei
*/
public class ThreadA extends Thread {
private final Object lock;
public ThreadA(Object lock) {
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
System.out.println("wait-ThreadA begin time = " + System.currentTimeMillis());
lock.wait();
System.out.println("wait-ThreadA end time = " + System.currentTimeMillis());
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
创建用于发起通知的B线程:
package com.jpeony.concurrent.waitnotify.yes;
/**
* 用于处理通知
*
* @author yihonglei
*/
public class ThreadB extends Thread {
private final Object lock;
public ThreadB(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println("notify-ThreadB begin time = " + System.currentTimeMillis());
lock.notify();
System.out.println("notify-ThreadB end time = " + System.currentTimeMillis());
}
}
}
创建测试类:
package com.jpeony.concurrent.waitnotify.yes;
/**
* 测试代码
*
* @author yihonglei
*/
public class RunTest {
public static void main(String[] args) {
try {
Object lock = new Object();
ThreadA threadA = new ThreadA(lock);
threadA.start();
// 5秒后调用唤醒线程,调用notify方法,唤醒等待线程
Thread.sleep(5000);
ThreadB threadB = new ThreadB(lock);
threadB.start();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
运行结果:
线程A调用了wait方法,线程处于等待状态,5秒后线程B执行调用notify唤醒等待线程,
A继续执行完成。
三 wait/notifyAll
关于唤醒一个线程或唤醒多个线程的实例:
1. 一个用于控制线程等待的服务类
package com.jpeony.concurrent.waitnotify.notifyall;
/**
* @author yihonglei
*/
public class MyService {
public void serviceMethod(Object lock) {
try {
synchronized (lock) {
System.out.println("begin wait ThreadName = " + Thread.currentThread().getName()
+ " begin time = " + System.currentTimeMillis());
lock.wait();
System.out.println("end wait ThreadName = " + Thread.currentThread().getName()
+ " end time = " + System.currentTimeMillis());
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
2. 创建线程A
package com.jpeony.concurrent.waitnotify.notifyall;
/**
* @author yihonglei
*/
public class ThreadA extends Thread {
private Object lock;
public ThreadA(Object lock) {
this.lock = lock;
}
@Override
public void run() {
MyService service = new MyService();
service.serviceMethod(lock);
}
}
3. 创建线程B
package com.jpeony.concurrent.waitnotify.notifyall;
/**
* @author yihonglei
*/
public class ThreadB extends Thread {
private Object lock;
public ThreadB(Object lock) {
this.lock = lock;
}
@Override
public void run() {
MyService service = new MyService();
service.serviceMethod(lock);
}
}
4. 创建线程C
package com.jpeony.concurrent.waitnotify.notifyall;
/**
* @author yihonglei
*/
public class ThreadC extends Thread {
private Object lock;
public ThreadC(Object lock) {
this.lock = lock;
}
@Override
public void run() {
MyService service = new MyService();
service.serviceMethod(lock);
}
}
5. 创建通知线程
package com.jpeony.concurrent.waitnotify.notifyall;
/**
* @author yihonglei
*/
public class NotifyThread extends Thread {
private Object lock;
public NotifyThread(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println("begin general ThreadName = " + Thread.currentThread().getName()
+ " begin time = " + System.currentTimeMillis());
// 随机通知一个线程进行唤醒
lock.notify();
// 通知全部线程进行唤醒
// lock.notifyAll();
System.out.println("end general ThreadName = " + Thread.currentThread().getName()
+ " end time = " + System.currentTimeMillis());
}
}
}
6. 测试类
package com.jpeony.concurrent.waitnotify.notifyall;
/**
* wait(): 使线程等待,进入阻塞队列。
* wait(long): 表示一个线程等待指定时间内是否有线程对锁进行唤醒,如果没有,将自动唤醒。
* general(): 随机通知一个线程唤醒,如果通知线程少于等待线程时,将不能及时唤醒多个线程。
* notifyAll(): 唤醒所有线程。
*
* @author yihonglei
*/
public class RunTest {
public static void main(String[] args) {
try {
// 创建一个锁对象
Object lock = new Object();
ThreadA threadA = new ThreadA(lock);
threadA.setName("A");
threadA.start();
ThreadB threadB = new ThreadB(lock);
threadB.setName("B");
threadB.start();
ThreadC threadC = new ThreadC(lock);
threadC.setName("C");
threadC.start();
// 休眠5秒后调用唤醒线程
Thread.sleep(5000);
NotifyThread notifyThread = new NotifyThread(lock);
notifyThread.setName("NotifyThread");
notifyThread.start();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
7. 运行结果
从运行结果可以知道,线程A,B,C处于等待,过5秒后执行通知线程,唤醒线程。
但是只是显示了A线程被唤醒,B,C线程一直处于等待状态,控制台运行按钮一直显示红色。
这是因为,通知线程调用notify只是随机唤醒一个线程,其余线程未被唤醒,所以处于等待状态。
解决这个问题的办法就是将NotifyThread中的notify方法改成notifyAll,然后再运行程序,运行结果:
从运行结果可以看出,所有线程均被唤醒。
四 总结
1、wait让线程等待执行。
2、notify只能唤醒一个线程,notifyAll唤醒多个线程。
3、等待/通知模型除了用wait/notify,还可以用ReentrantLock的Condition的await/signal实现。
ReentrantLock和Condition参考:https://blog.csdn.net/yhl_jxy/article/details/87088314