一、使用用例
public class ThreadTest {
static final Object obj = new Object();
private static boolean flag = false;
public static void main(String[] args) throws Exception {
Thread consume = new Thread(new Consume(), "Consume");
Thread produce = new Thread(new Produce(), "Produce");
consume.start();
Thread.sleep(1000);
produce.start();
try {
produce.join();//强制生产者先退出
consume.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 生产者线程
static class Produce implements Runnable {
@Override
public void run() {
synchronized (obj) {
System.out.println("进入生产者线程");
System.out.println("生产");
try {
TimeUnit.MILLISECONDS.sleep(2000); //模拟生产过程
flag = true;
obj.notify(); //通知消费者
System.out.println("通知消费者?");
TimeUnit.MILLISECONDS.sleep(1000); //模拟其他耗时操作
System.out.println("退出生产者线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者线程
static class Consume implements Runnable {
@Override
public void run() {
System.out.println("进入消费者线程");
System.out.println("wait flag 1:" + flag);
synchronized (obj) {
while (!flag) { //判断条件是否满足,若不满足则等待
try {
System.out.println("还没生产,进入等待");
obj.wait();
System.out.println("结束等待");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("wait flag 2:" + flag);
System.out.println("消费");
System.out.println("退出消费者线程");
}
}
}
运行结果
进入消费者线程
wait flag 1:false
还没生产,进入等待
进入生产者线程
生产
通知消费者?
退出生产者线程
结束等待
wait flag 2:true
消费
退出消费者线程
二、原理
问题1:为什么wait/nofity需要配合synchronized使用
问题2:明明消费者线程获得了锁,并没走完synchronized方法,生产者是如何进入到synchronized的?
问题3:生产者是如何通知消费者的?
问题4:生产者在调用notify的时候,消费者为何并没有被唤醒?
synchronized:代码块通过javap生成的字节码中包含 monitorenter 和 monitorexit 指令,执行monitorenter指令可以获取对象的monitor
查看Object.wait源码,上面有句注释
This method should only be called by a thread that is the owner of this object's monitor
@问题1
Object.wait实际调用的是wait(0);wait(0)上面的注释写到
This method causes the current thread (call it <var>T</var>) to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object
意思就是wait方法会导致把当前线程放到wait set队列,并释放所有monitor对象,等待被唤醒
@问题2
ObjectMonitor:
每个线程都有ObjectMonitor对象,ObjectMonitor对象维护了free和used的objectMonitor对象列表,如果当前free列表为空,将向全局global list请求分配ObjectMonitor
WaitSet :处于wait状态的线程,会被加入到wait set;
EntryList:处于等待锁block状态的线程,会被加入到entry set;
ObjectWaiter:
ObjectWaiter对象是双向链表结构,保存了_thread(当前线程)以及当前的状态TState等数据, 每个等待锁的线程都会被封装成ObjectWaiter对象。
wait方法实现
lock.wait()方法最终通过ObjectMonitor的void wait(jlong millis, bool interruptable, TRAPS);实现:
1、将当前线程封装成ObjectWaiter对象;
2、通过ObjectMonitor::AddWaiter方法将ObjectWaiter添加到_WaitSet列表中;
3、通过ObjectMonitor::exit方法释放当前的ObjectMonitor对象,这样其它竞争线程就可以获取该ObjectMonitor对象。
4、最终底层的park方法会挂起线程;
@问题3
notify:
lock.notify()方法最终通过ObjectMonitor的void notify(TRAPS)实现:
1、如果当前_WaitSet为空,即没有正在等待的线程,则直接返回;
2、通过ObjectMonitor::DequeueWaiter方法,获取_WaitSet列表中的第一个ObjectWaiter节点,实现也很简单。
这里需要注意的是,在jdk的notify方法注释是随机唤醒一个线程,其实是第一个ObjectWaiter节点
3、根据不同的策略,将取出来的ObjectWaiter节点,加入到_EntryList或则通过Atomic::cmpxchg_ptr指令进行自旋操作cxq,具体代码实现有点长,这里就不贴了,有兴趣的同学可以看objectMonitor::notify方法;
@问题4
notifyAll:
lock.notifyAll()方法最终通过ObjectMonitor的void notifyAll(TRAPS)实现:
通过for循环取出_WaitSet的ObjectWaiter节点,并根据不同策略,加入到_EntryList或则进行自旋操作。
从JVM的方法实现中,可以发现:notify和notifyAll并不会释放所占有的ObjectMonitor对象,其实真正释放ObjectMonitor对象的时间点是在执行monitorexit指令,一旦释放ObjectMonitor对象了,entry set中ObjectWaiter节点所保存的线程就可以开始竞争ObjectMonitor对象进行加锁操作了。
网友评论