单核CPU就是"一根筋",只是简单的一条一条的串行执行机器指令,且每个时钟周期内只执行一条指令,如果一个进程不间断的运行,那么就只能运行一个任务;
将CPU的运行时间划分成一个个的时间段,这就是时间片(time slice),进程通过抢占时间片,以达到交替执行的效果,宏观上,多个任务同时运行,JVM正是采用抢占式调度的方式根据优先级来"有限"的控制线程的运行。

为了提高代码的执行效率,编译器会在编译时做指令重排等优化,这就造成了在多线程运行时,出现与单线程运行不一致的情况。再加上,为了优化多核执行效率,造成各个线程访问的变量值与内存主存的值不一致的情况,就出现内存可见性问题。
JMM通过内存屏障、CAS和mutex lock指令等手段,实现各种各样的锁,究竟Java提供了哪些方法可以实现线程的等待呢,接下来以一个样例场景打开"Java多线程等待的N种方式"。
题目与约定
假设有A、B、C三个线程,C必须等待A和B都结束时才能运行,约定每个线程的耗时操作为方法
doWork(String threadName)
接下来将以此样例分析实现方法和Java实现的原理:
第一篇: monitor
Thread.join等待线程完成
Object.wait-notify等待唤醒
LockSupport
第二篇: AQS
CountDownLatch
ReentrantLock
Semaphore
CyclicBarrier循环屏障
第三篇: 队列
Future
BlockingDeque
ListenableFuture
Thread.Join等待线程完成
等待最多millis
毫秒+nanos
纳秒等待完成完成,通过循环调用this.wait
等待this.isAlive
条件完成,当调用this.notifyAll
方法时,此线程终止。建议应用程序不要在线程实例上使用wait
、notify
或notifyAll
。
解题
运行结果
实现原理
join
方法本质是调用的Object
中的wait
方法实现线程的阻塞,调用wait方法必须要获取锁,所以join
方法是被synchronized
修饰的,synchronized
修饰在方法层面相当于synchronized(this)
,this
就是previousThread
本身的实例,两次调用join的previousThread
分别是A和B两个线程,此时previousThread
线程对象的监视器也就只有一个C线程在等待,因此当previousThread
执行完的时候,通过notify
和notifyAll
会立即唤醒(只有一个线程嘛)。
此例子中,有两把锁存在,分别等待threadA
和threadB
:
注意:建议调用
Thread.join
的时候,不要在线程实例上同时调用wait
、nofity
和notifyAll
方法
Object.wait-notify等待唤醒
wait-nofity
是Object中的等待唤醒组合,实现等待两个线程完成,可以使用一把锁,也可以使用两把锁。
C在对象finishedCount
上等待,A和B完成时,首先把finishedCount
加1,然后唤醒C
C被唤醒时,判断A和B已经完成(finishedCount
是否为2),完成则开始执行C的任务
Object.wait-notify实现原理

在HotSpot虚拟机中,monitor
采用ObjectMonitor
实现。
ObjectMonitor
对象中有两个队列,都用来保存ObjectWaiter
对象,分别是WaitSet
和 EntrySet
。_owner
用来指向获得ObjectMonitor对象的线程
ObjectWaiter
对象是双向链表结构,保存了_thread
(当前线程)以及当前的状态TState
等数据, 每个等待锁的线程都会被封装成ObjectWaiter
对象。 _WaitSet
:处于wait状态的线程,会被加入到wait set;
_EntrySet
:处于等待锁block状态的线程,会被加入到entry set;
wait方法实现
lock.wait()
方法最终通过ObjectMonitor
的 wait(jlong millis, bool interruptable, TRAPS)
实现
将当前线程封装成
ObjectWaiter
对象node通过
ObjectMonitor::AddWaiter
方法将node添加到_WaitSet
列表中通过
ObjectMonitor::exit
方法释放当前的ObjectMonitor
对象,这样其它竞争线程就可以获取该ObjectMonitor
对象最终底层的
park
方法会挂起线程
ObjectSynchorizer::wait
方法通过Object
对象找到ObjectMonitor
对象来调用方法 ObjectMonitor::wait()
,通过调用ObjectMonitor::AddWaiter()
可以把新建的ObjectWaiter
对象,放入到_WaitSet
队列的末尾,然后在ObjectMonitor::exit
释放锁,接着通过执行thread_ParkEvent->park
来挂起线程,也就是进行wait
。
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方法;
notifyAll方法实现
lock.notifyAll()
方法最终通过ObjectMonitor
的void notifyAll(TRAPS)
实现: 通过for
循环取出_WaitSet
的ObjectWaiter
节点,并根据不同策略,加入到_EntryList
或则进行自旋操作。
从JVM的方法实现中,可以发现:notify
和notifyAll
并不会释放所占有的ObjectMonitor
对象,其实真正释放ObjectMonitor
对象的时间点是在执行monitorexit
指令,一旦释放ObjectMonitor
对象了,entry set中ObjectWaiter
节点所保存的线程就可以开始竞争ObjectMonitor对象进行加锁操作了。
参考文献
https://docs.oracle.com/javase/8/docs/




