J.U.C.中AQS管理线程队列, LockSupport用来block/unblock线程. 通过HotSpot(Java 9)的源码来粗略的看看JVM这层的实现:
HotSpot的Thread/JavaThread
HotSpot里的Thread类对应着一个OS的Thread, JavaThread类继承字Thread, JavaThread实例对应着一个Java层的Thread.
所以, 在Java层的Thread, 在操作系统上对应一个thread, linux上就是一个轻量级task.
|
|
Thread类里有两个ParkEvent和一个Parker, 其实ParkEvent和Parker实现和功能都类似,只是源码没有重构而已. 一个是给实现synchronized关键字用的, 一个是给Thread.sleep/wait用的. parker是用来实现J.U.C的park/unpark(阻塞/唤醒).
JavaThread和Java的线程一一对应, 成员变量oop _threadObj指向Java层的thread对象.
OSThread是通过os::create_thread()创建, 最后还是调用POSIX phtread, glibc在linux平台上就是fork一个轻量级task.
HotSpot的Parker
LockSupport是调用Unsafe_Park/Unsafe_unpark, unsafe只是调用parker.park()(另,还收集trace/profile信息). 看看Parker:
|
|
常见用pthread写多线程程序, 一般来说是多个线程竞争mutex/cond变量, 把线程队列的管理的逻辑扔给mutex/cond去做, 底层的glibc或linux kernel做线程队列的管理. 而在HotSpot里, 每个JavaThread实例里都自己的pthread mutex/cond变量.
那么问题是:
- 为什么这么整?
- 每个线程有自己的mutex, 那哪来的有竞争呢?
1, JDK中的LockSupport只是用来block(park,阻塞)/unblock(unpark,唤醒)线程, 线程队列的管理是JDK的AQS处理的. 从Java层来看, 只需要mutex/cond提供个操作系统的block/unblock API即可.
2, 但是, 实现时, 竞争还是有的, 比如当前线程某thread_a.park()时, 而另外一个线程thread_b.unpark(thread_a), 这时候, 对于thread_a的mutex来说有竞争的.
只不过, 对于JDK而言, 可以透明,只是需要关注block/unblock是阻塞/唤醒了线程操作.
不同系统实现不一样, 从Parker::park的代码可以看到, 这里的data racing主要是处理: 如果别的线程在unpark它的情况.
只看Linux的(已经删掉一些不是核心逻辑的代码, 我加了自己的理解,写在注释里):
再看unpark, B线程唤醒A线程, 是B调用了A的parker::unpark()(搜Unsafe_Unpark的代码).
Parker::unpark()比较简单, 设置_counter=1, 并unlock mutex和cond_signal.
_counter不会增加, 设死为1. 这就是Doug Lea的AQS论文里说’Calls to unpark are not “counted”.