1 wait/notify/notifyAll
这三个方法是在java.lang.Object类提供的,使用的时候需要注意:
(1)这三个方法需要在synchronized方法或者同步快中调用;
(2)调用wait/notify/notifyAll的对象必须和synchronized对应的对象一致。也即如果synchronized(variableA),但是却使用variableB.wait()是不行的。
如果不遵循上面两个前提条件,会报错:
java.lang.IllegalMonitorStateException
wait:会导致当前线程进行等待,交出监视器的控制权,直到其他线程调用监视器的notify/notifyAll
notify:唤醒在监视器上等待的单个线程
notifyAll:唤醒在监视器上等待的所有线程
2 实现生产者/消费者模型
以下是使用wait/notify来实现的经典的生产者/消费者的代码示例:
public class test {
public static class Product {
private int maxSize;
private List<String> productList = new ArrayList<>();
public Product() {
this.maxSize = 10;
}
public void produceProduct() {
synchronized (this) {
while (productList.size() == maxSize) {
try {
wait();
} catch (InterruptedException e) {
}
}
System.out.println("Produce product, Num = " + productList.size());
productList.add(productList.size() + "");
notify();
}
}
public void consumeProduct() {
synchronized (this) {
while (productList.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
}
}
System.out.println("Consume product, Num = " + productList.get(productList.size() - 1));
productList.remove(productList.size() -1);
notify();
}
}
}
public static class ConsumerThread implements Runnable {
private Product product;
public ConsumerThread(Product product) {
this.product = product;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
product.consumeProduct();
}
}
}
public static class ProduceThread implements Runnable {
private Product product;
public ProduceThread(Product product) {
this.product = product;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
product.produceProduct();
}
}
}
public static void main(String[] args) {
Product product = new Product();
Thread consumer = new Thread(new ConsumerThread(product));
Thread producer = new Thread(new ProduceThread(product));
consumer.start();
producer.start();
try {
consumer.join();
producer.join();
} catch (InterruptedException e) {
}
System.out.println("main therad exit");
}
}
总结:
1)以上代码中使用Product的实例作为监视器,实际使用中也可以使用productList,或者使用新的变量Object lock = new Object(),都可以;
2)调用wait的对象,必须和监视器一样;
3)实现生产者/消费者模型时,执行wait/notify前提条件,一般都会写在 while循环中,这是为了防止,出现 InteruptedException时,条件还没有满足,就执行下面的任务了。
以上是实现生产者/消费者模型时,需要注意的问题。
3 继续理解wait/notify/notifyAll
在实际使用中,如果线程A调用wait进入Blocked状态,线程B在synchronized块(或者方法)中调用notify后,必须等到整个synchronized执行完毕以后,才会释放synchronized对象锁,这样后续线程A才能获得synchronized对象锁由Blocked状态变为Runnable,然后在wait点之后继续执行。
继续思考如下场景,能够更加透彻的理解
1)wait/notify:
基于同一个对象作为synchronized对象,如果有A/B/C三个线程调用了wait,进入阻塞状态。然后线程D调用notify,根据系统的调度,A/B/C中只会有一个线程会随机收到通知,收到通知的线程会被添加到synchronized锁对象的锁池中,在这个锁池中的线程会竞争synchronized锁,竞争到锁的线程会被移出锁池,继续执行wait之后的代码。
2)wait/notifyAll
和上面一样,唯一的差别是,当线程D调用notifyAll时,A/B/C三个线程都会收到通知,都会被添加到synchronized锁对象的锁池中,这个锁池中的线程会竞争synchronized锁,竞争到的锁的线程会被移出锁池,继续执行wait之后的代码。假设A竞争到锁,此时会执行A线程中wait之后的代码,执行完毕以后,释放锁以后,B/C继续继续竞锁,直到锁池中的线程都获得锁,执行完毕代码。
可见,当notify/notifyAll调用以后,wait不一定会立即执行,必须在调用notify/notifyAll的线程释放锁以后,wait的线程才有机会得到执行。
设计下面的线程便可看到效果:
public class test {
private static Object lock = new Object();
private static long time = System.currentTimeMillis();
public static class Task extends Thread {
public Task(String name) {
super(name);
}
@Override
public void run() {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
}
System.out.println("Task name:" + getName() + "; exexute time interval = " + (System.currentTimeMillis() - time));
}
}
}
public static void main(String[] args) {
Thread a = new Task("Thread-A");
Thread b = new Task("Thread-B");
Thread c = new Task("Thread-C");
a.start();
b.start();
c.start();
//main线程sleep 1s,等待县城A/B/C都调用wait方法等待
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
time = System.currentTimeMillis();
synchronized (lock) {
lock.notify();
//main线程执行notify之后,延迟10s才退出同步块,释放lock
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
}
System.out.println("main therad exit");
}
}
执行结果:
main therad exit
Task name:Thread-A; exexute time interval = 10000
可以看到main线程执行notify以后,延迟10s才释放锁,A/B/C中A在10s以后才执行wait,这也证明了我们的分析。