在多线程编程中,通常要求一个线程等待另一个线程采取某些措施。
语义上,根据基础线程模型不可能进行直接线程通信,相反,开发人员可以使用基于共享变量的某些条件来实现线程间通信。
让我们考虑以下示例,其中thread1打印一条消息,而thread2填充相同的消息字符串:
public class SimpleWaitNotifyDemo1 {
private static String message;
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println(message);
});
Thread thread2 = new Thread(() -> {
message = "A message from thread1";
});
thread1.start();
thread2.start();
}
}
由于线程调度程序的不可预测性,我们不能保证线程1将打印由线程2填充的消息。线程1必须有某种机制来向线程2发信号,通知已填充共享消息。以下是开发人员可以想到的最常见的解决方案:
public class SimpleWaitNotifyDemo2 {
private static String message;
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (message == null) {
}
System.out.println(message);
});
Thread thread2 = new Thread(() -> {
message = "A message from thread1";
});
thread1.start();
thread2.start();
}
}
上面的thread1的while循环被称为保护块“ Guarded Blocks”,但这并不是一种有效的解决方案,因为此循环浪费CPU周期,我们也无法避免线程干扰 。
Java扩展了它的“固有锁定”模型提供更好的解决方案,即使用java.lang.Object的
wait()和notify()/notifyAll()方法。
所有这些Object的方法调用都重定向到本机方法,这意味着该机制由底层操作系统固有地提供。
让我们修改上面的示例以查看其工作方式:
public class SimpleWaitNotifyDemo {
private static String message;
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
while (message == null) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(message);
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
message = "A message from thread1";
lock.notify();
}
});
thread1.start();
thread2.start();
}
}
Object.wait()和Object.notify()
wait()导致当前线程等待(阻止或挂起执行),直到另一个线程为此对象调用notify()方法或notifyAll()方法。
wait()方法抛出InterruptedException
-如果相应的线程调用interrupt(),则等待状态将被中断(就像sleep()方法一样)。引发此异常时,将清除当前线程的中断状态。
与不释放“内在锁”的sleep方法不同,此方法释放“内在锁”。这意味着在该线程处于等待状态时,其他线程有机会执行其他同步的块/方法,这对于总体性能是有利的。
IllegalMonitorStateException
如果当前线程不是对象监视器的所有者(内部锁),则也会引发此方法调用。这就是我们在上面的示例中使用同步块的原因。
在上面的示例中,我们也可以改用同步方法。让我们修改上面的示例,以将同步和线程通信整合到单个对象:
public class WaitNotifyDemo {
public static void main (String[] args) {
SharedObject obj = new SharedObject();
Thread thread1 = new Thread(() -> {
obj.printMessage();
});
Thread thread2 = new Thread(() -> {
obj.setMessage("A message from thread1");
});
thread1.start();
thread2.start();
}
private static class SharedObject {
private String message;
public synchronized void setMessage (String message) {
this.message = message;
notify();
}
public synchronized void printMessage () {
while (message == null) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(message);
}
}
}
notify()vs notifyAll()
notify()唤醒在该对象的监视器上等待的单个线程(固有锁定)。如果有多个线程在等待,那么只有一个线程会收到通知或将被唤醒;我们无法预测哪个线程将被通知,这完全取决于调度程序。
notifyAll()将导致所有线程在由于wait()调用而处于等待状态时唤醒。
由于notify()和notifyAll()也从同步的方法/块中调用:等待线程将不会开始执行,直到调用这些方法的线程释放了锁。