线程通信
当我们需要多个线程共同完成一个任务时,并且希望线程之间能够有规律的执行,那么线程之间就需要进行通信,来协调它们工作。
例如:线程A负责生产包子,线程B负责吃包子,包子可以理解为共同操作的资源,线程B只能等线程A制作好包子之后才能吃,线程A只能等线程B要吃包子了才能生产。
这就是 线程的等待唤醒机制。
等待唤醒机制
在线程满足某个条件时,就进入 等待状态 wait() / wait(time) ,等待其他线程执行完代码释放同步监视器之后再将其 唤醒 notify()。
在有多个线程进行等待时,如果需要,可以使用 notifyAll()
来唤醒所有的等待线程。
方法 | 名称 | 描述 |
---|---|---|
wait | 等待 | 线程执行该方法,就进入等待状态,同时释放锁 |
notify | 唤醒 | 线程执行该方法,就会唤醒被 wait 的线程(当有多个线程被 wait 时,唤醒优先级最高的线程,优先级相同则随机唤醒) |
notifyAll | 唤醒所有等待 | 线程执行该方法,会唤醒所有被 wait 的线程 |
java
public static void main(String[] args) {
PrintfNumber p1 = new PrintfNumber();
Thread t1 = new Thread(p1, "线程一");
Thread t2 = new Thread(p1, "线程二");
t1.start();
t2.start();
}
class PrintfNumber extends Thread {
private static int number = 0;
@Override
public void run() {
while (true) {
synchronized (this) { //使用 this 作为同步监视器,因为当前类只被创建了一次
notify();
//this.notify();
if (number <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
} else {
break;
}
try {
wait();
//this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
上述代码中,线程一进入同步代码块打印后,到达 wait() 方法处等待,然后线程二进入同步代码块,首先把 wait 的线程一唤醒,然后线程二又到达 wait() 处等待,二者相互交替打印数值。
注意
wait()、notify()、notifyAll() 三个方法只能在同步代码块或同步方法中使用,不能在 ReentrantLock锁 中使用;
在同步代码块或同步方法中使用时,一定要 注意同步监视器是谁,因为这三个方法只能由同步监视器来调用,例如上面的示例,wait 和 notify 其实省略了 this 同步监视器来调用;
javaclass PrintfNumber extends Thread { Object obj = new Object(); @Override public void run() { synchronized (obj) { //使用 obj 作为同步监视器,wait和notify就必须由 obj 来调用 obj.notify(); //... try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
面试题:sleep() 方法和 wait() 方法相同点和不同点?
相同点:
- 二者都可以导致线程阻塞;
不同点:
- 声明的对象不同:wait() 声明在 Object 类中,因此可以通过 obj.wait() 调用,而 sleep() 声明在 Thread 类中;
- 使用场景不同:wait() 只能使用在同步代码块或同步方法中,而 sleep() 可以声明在任何地方;
- 能否释放同步监视器:wait() 会释放同步监视器,而 sleep() 不会释放;
- 结束阻塞的方式:wait() 阻塞之后,只能通过 notify() / notifyAll() 唤醒,而 sleep() 是计时结束后结束阻塞;