前言
大家好,我是小郭,这一篇我们主要是对wait()、notify()、join()进行图解,可能有些粗糙,不足之处多多指出。
概要
- wait()方法
- notify()方法
- join()方法
我们先对Object.wait()进行一波分析。
接着上一篇留下的问题
- 为什么调用Object.wait必须持有对象锁?
- Object.wait()被挂起后,是否会释放当前锁,让出CPU?
我们先来回答第一个问题
通过锁的原理,知道javap生成的字节码包含"monitorenter" 和"monitorexit",这里先不对锁进行扩展,我们先知道有这么一个东西就行。
这也是为什么wait需要先获取锁,才能获得monitor对象。
1. wait()方法
HotSpot虚拟机中,monitor采用ObjectMonitor 实现
//ObjectMonitor的对象的结构体
ObjectMonitor::
ObjectMonitor() {
_header = NULL;
_count = 0;//用来记录该线程获取锁的次数
_waiters = 0,
_recursions = 0;//锁的重入次数
_object = NULL;
_owner = NULL; //指向持有ObjectMonitor对象的线程
_WaitSet = NULL;//存放处于wait状态的线程队列
_WaitSetLock = 0;
_Responsible = NULL;
_succ = NULL;
_cxq = NULL;
FreeNext = NULL;
_EntryList = NULL;//存放处于等待锁block状态的线程队列
_SpinFreq = 0;
_SpinClock = 0;
OwnerIsThread = 0;
}
继续往下看,通过wait接口的解释来回答第二个问题
This method causes the current thread (call it <var>T</var>) toplace itself in the wait set for this object and then to relinquish any and all synchronization claims on this object. 主要就是说,将对象放入waitSet中,然后放弃所有的同步声明,意思就是让出cpu。
在下才疏学浅,画了一个很粗糙的流程图😆,如果有不对的请求指出。
总结一下上面的流程
- 获取锁的monitor对象。
- 检测当前线程对象是否获取锁。
- 创建ObjectWaiter,将其状态设置为TS_WAIT。
- 操作_WaitSet链表,将当前的node节点尾部插入到队列中。
- 调用Exit()方法,退出monitor,同时释放该锁,让出CPU。
- 调用Park()方法,将线程挂起。
- 当ObjectWaiter状态为TS_WAIT,WaitSet移除当前node节点,修改状态为TS_RUN。
- 调用Enter(Self),重新抢占该锁。
- 退出当前等待monitor。
2. notify()方法
趁热打铁,我们再对Object.notify()进行一波分析。
总结一下上面的流程
- 线程A在wait() 后被加入了_WaitSet队列中。
- 线程C被线程B启动后竞争锁失败,被加入到_cxq队列的首位。
- 线程B在notify()时,从_WaitSet中取出第一个,根据Policy的不同,将这个线程放入_EntryList或者_cxq队列中的起始或末尾位置。
- 根据QMode的不同,将ObjectWaiter从_cxq或者_EntryList中取出后唤醒。
3. join()方法
可以看见join方法的核心还是wait,join方法利用了synchronized来修饰,就是因为wait方法必须获取锁。
Thread.join()
//等待该线程结束
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
//判断线程是否活着
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
我们来通过实例看一下join的使用
static class CreateRunable implements Runnable {
public CreateRunable(int i) {
this.i = i;
}
private int i;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
@Override
public void run() {
synchronized (this){
System.out.println("Runable接口,实现线程"+i);
}
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0;i< 20 ;i++){
Thread createThread = new Thread(new CreateRunable(i));
createThread.start();
createThread.join();
}
System.out.println("mian阻塞最后执行");
}
通过一个简单的流程图,就可以看到调用的过程
synchronized修饰在方法层,相当于synchronized(this),也就是createThread本身的实例。
主线程会持有这个对象的锁,然后去调用wait阻塞,谁调用谁阻塞,所以造成了主线程的阻塞,子线程在结束后会调用
nitifyAll()去唤醒主线程,主线程只要获取到了cpu和锁就可以继续执行。
总结
上面三个方法通过简单的流程图来描述源码的过程,我们可以看到主要是通过对锁的抢占来实现的线程等待和释放。
不足之处,希望大家能够多多指出,及时改正~