前言
大家好,我是小郭,这一篇我们主要是对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 T) 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和锁就可以继续执行。
总结
上面三个方法通过简单的流程图来描述源码的过程,我们可以看到主要是通过对锁的抢占来实现的线程等待和释放。
不足之处,希望大家能够多多指出,及时改正~