1.需求(为什么需要线程通信)
当我们需要多个线程完成同一任务时,并且希望他们有规律的执行,那么多线程之间需要一些通信机制,并且可以协调他们的工作,以此实现多个线程共同操作共享数据.
例 : A做包子,B吃包子,包子相当于共享操作的数据,B必须等到A做好才能吃,那么线程AB间就需要通信.即等待唤醒机制.
2.等待唤醒机制
这是多线程的一种协同的机制.谈到线程我们常常想到是多个线程竞争一把锁.但不完全是这样.线程之间也可以实现协同.
在一个线程满足某个条件时,就进入等待状态(wait),等待其他线程进行到某处指定代码时,将等待状态的线程唤醒(notify).可以指定wait的时间,过了这个时间可以自动唤醒@也可以使用notifyAll来唤醒所有等待的线程.
- wait : 线程不在参与活动,将释放CPU调度.进入wait set中,不会浪费CPU资源.也不会去竞争锁.此时线程状态为WAITING或者是TIME_WAITING.他等待其他线程执行某个操作(notify/wait(time)执行了time的时间),才能够将其从wait set中释放出来,重新参与CPU调度.
- notify : 选取待在wait set中的线程将其释放重新参与CPU调度.
- notifyAll : 将全部待在wait set中的线程全部释放参与CPU调度.
3.例 : 交替打印出输出的一百以内的数.
解释 : 调用start方法启动线程.假设是线程1(当然也可能是线程2)进入while语句并握住锁,进行输出语句后,调用wait()释放锁,线程2握住锁,进行到notify()唤醒线程1重新参与CPU调度......
public class WaitNotifyTest {
public static void main(String[] args) {
WaitNotify w = new WaitNotify();
Thread t1 = new Thread(w, "线程-1");
Thread t2 = new Thread(w, "线程-2");
t1.start();
t2.start();
}
}
class WaitNotify implements Runnable {
int change = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized(obj) {
obj.notify();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (change > 0) {
System.out.println(Thread.currentThread().getName() + "\t" + change);
change--;
}
try{
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
控制台
线程-1 100
线程-2 99
线程-1 98
线程-2 97
线程-1 96
线程-2 95
线程-1 94
注 :
- 这三个方法的使用必须都要在同步代码块或者同步方法中.
- 此三个方法的调用者必须是同步监视器,否则会触发异常.
- 按住Ctrl键,可以查看该三个方法的源码源码.
public final native void notify();
public final void wait() throws InterruptedException { wait(0L); }
- 此三个方法都声明在Object类中,Ctrl+F12,查看Object类.
- 因为同步监视器可以是任何对象(保证线程安全性需要满足唯一),由于该三个方法是Object类中的方法,所以同步监视器可以调用.
4.wait()与sleep()的区别.
(1). 查看源码 :
public static native void sleep(long millis) throws InterruptedException;
public final void wait() throws InterruptedException {
wait(0L);
}
(2) 相同点 :
一旦执行,都可以阻塞线程.
(3) 不同点 :
- 声明的位置不同 : sleep()方法声明在Thread类中,而且是静态的方法.wait()方法声明在Object类中.
- 使用的场景不同 : sleep()可以声明在任何所需要的场景.wait()必须声明在同步代码块或同步方法中(因为该写方法必须通过同步监视器调用).
- CPU调度 : 使用sleep()并不会释放CPU调度,只是线程阻塞.而wait()阻塞线程,并且释放该线程的CPU调度.
- 结束阻塞方式 : sleep(time)方法在时间time之后结束阻塞.wait(time)方法在时间time之后或者notify/notifyAll结束阻塞.