线程间通信
线程与线程之间不是独立的个体,它们彼此之间可以相互通信与协作。
线程间通信后,系统之间的交互性会更强大,在大大提交CPU利用率的同时,还会使程序要对各线程任务在处理的过程中进行有效的把控和监督。
学习目录:
一、使用wait/notify实现线程间的通信
二、生产者/消费者模式的实现
三、方法join的使用
四、ThreadLocal类的使用
一、使用wait/notify实现线程间的通信
1.1 不使用等待/通知机制实现线程间通信:
线程A做i++的操作,线程B利用while(true){if(i==5){to do ...}}轮询去检测 i 的值,这样会非常浪费CPU资源。
如果轮询时间间隔较短,更浪费CPU资源;如果轮询时间间隔较长,有可能会取不奥想要得到的数据。
所以,就需要一种机制来减少CPU的资源浪费,而且还可以实现在多个线程间通信,它就是“wait/notify”机制。
1.2 等待/通知机制的实现】
方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法是用来将当前线程置入“预执行队列”中,并且在wait()所在的代码处停止执行,知道接到通知或被中断为止。
wait()方法只能在同步方法中或同步块中调用。如果调用wait()时,没有持有适当的锁,会抛出异常。
wait()方法执行后,当前线程释放锁,线程与其他线程竞争重新获取锁。
方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其他线程,对其发出通知notify,并使它重新获取该对象的对象锁。如果有多个线程等待,则有线程规划器随机挑选出一个呈wait状态的线程。
在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块中。
小结一下:wait()是使线程停止运行,而notify使停止的线程继续运行。
简单案例
代码清单:
<span style="font-size:18px;">package org.jksoft.thread.Volatile;
/**
* 测试三:使用wait/notify机制的案例
* 注意:wait/notify都必须在同步代码块中调用。
* @author mcl
*
* 2016-2-20-下午12:01:25
*/
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
MyThread1 t1 = new MyThread1(lock);
t1.start();
Thread.sleep(3000);
MyThread2 t2 = new MyThread2(lock);
t2.start();
}
}
class MyThread1 extends Thread{
private Object lock;
public MyThread1(Object lock){
this.lock = lock;
}
@Override
public void run(){
try {
synchronized (lock) {
System.out.println("begin wait "+System.currentTimeMillis());
lock.wait();
System.out.println("end wait "+System.currentTimeMillis());
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class MyThread2 extends Thread{
private Object lock;
public MyThread2(Object lock){
this.lock = lock;
}
@Override
public void run(){
synchronized (lock) {
System.out.println("begin notify "+System.currentTimeMillis());
lock.notify();
System.out.println("end notify "+System.currentTimeMillis());
}
}
}</span>
案例结果:3秒后,唤醒处于wait等待的线程。
Java为每一个Object都实现了wait()和notify()方法,他们必须用于同步代码块内。
notify()方法可以随机唤醒等待队列中等待同一共享资源的“一个线程”,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知“一个“线程。
notifyAll()方法可以使所有正在等待队列中等待同一资源的"全部"线程从等待状态中退出,进入可运行状态。
1.3 方法wait()锁释放与notify()锁不释放。
1.4 当interrupt方法遇到wait方法
当线程呈wait状态时,调用线程对象的interrupt()方法会出现异常。
1.5 方法wait(long)的使用
带一个参数的wait(long)方法的功能是等待某一个时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。
1.6 等待wait的条件发生变化
案例:线程r1,r2负责向list集合中移除一个元素,如果list的size为0,则进入堵塞队列
线程a负责向list集合中添加一个元素,然后唤醒所有等待在堵塞队列的所有线程。
代码清单:
package org.jksoft.thread.Volatile;
import java.util.ArrayList;
import java.util.List;
/**
* 测试四:使用wait/notify机制的案例,当wait的等待条件发生改变时 注意:wait/notify都必须在同步代码块中调用。
*
* @author mcl
*
* 2016-2-20-下午12:01:25
*/
public class Test4 {
private static List<String> list = new ArrayList<String>();
public static void main(String[] args) throws InterruptedException {
String lock = "";
removeThread r1 = new removeThread(lock);
r1.start();
removeThread r2 = new removeThread(lock);
r2.start();
Thread.sleep(3000);
addThread a = new addThread(lock);
a.start();
}
static class addThread extends Thread {
private String lock;
public addThread(String lock) {
this.lock = lock;
}
public void run() {
synchronized (lock) {
list.add("a");
lock.notifyAll();
System.out.println("唤醒所有等待该锁的线程");
}
}
}
static class removeThread extends Thread {
private String lock;
public removeThread(String lock) {
this.lock = lock;
}
public void run() {
try {
synchronized (lock) {
if (list.size() == 0) {
System.out.println("进入等待唤醒的队列..."
+ Thread.currentThread().getName());
lock.wait();
System.out.println("等待队列的线程被唤醒..."
+ Thread.currentThread().getName());
}
list.remove(0);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
运行结果:
运行结果分析:
刚开始的时候,list的size为0,所以两个r线程进入堵塞队列。等到a线程向list集合中添加一个元素后,唤醒了r1,r2两个线程。两个线程都去继续向下执行,都进行list.remove(0)的运算,那么这样的话,只有一个元素的list是不能移出两个的,所以只能抛出异常。
问题出现原因:是由于r1,r2两个线程没有重新去验证list的size的条件,导致后续结果的异常。
解决方案:
将上诉代码中,
if (list.size() == 0) {
更改为
while (list.size() == 0) {
我们再看一下运行结果:这就与我们所期待的结果一致了。