基本含义
如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回。
线程Thread除了提供join()方法之外,还提供了join(long millis)和join(long millis,int nanos)两个具备超时特性的方法。这两个超时方法表示,如果线程thread在给定的超时时间里没有终止,那么将会从该超时方法中返回。
实现原理
首先介绍下线程的状态
线程的状态
Java线程在运行的生命周期中可能处于6种不同的状态,在给定的一个时刻,线程只能处于其中的一个状态。如下内容截取JDK 1.8 Thread.java的源码:
- NEW: 初始转态,线程被构建,但是还没有调用start()方法。
- RUNNABLE: 正在执行的线程状态,JVM中runnable线程状态对应于操作系统中的就绪和运行两种状态。
- BLOCKED: 线程等待monitor互斥量的阻塞状态,在blocked状态的线程正在被执行Object.wait()后等着进入或者再次同步块或者同步方法。
- WAITING: 等待状态,下列方法会导致线程处于等待状态:
- Object.wait with no timeout
- Thread.join with on timeout
- LockSupport.park
- TIMED_WAITING: 超时等待,超过等待时间便会返回自动运行状态,下列方法会导致线程处于超时等待状态:
- Thread.sleep
- Object.wait(long) with timeout
- Thread.join(long) with timeout
- LockSupport.parkNanos
- LockSupport.parkUntil
- TERMINATED: 线程完成执行后结束的状态。
在介绍下Monitor
Monitor
Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象的锁。每一个对象都有,也仅有一个 monitor。
在HotSpot JVM中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的):
ObjectMonitor() {
_header = NULL;
_count = 0; //记录个数
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; //处于等block状态的线程,会被加入到该列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
复制代码
ObjectMonitor中主要有以下4个参数:
- _Owner: 用于指向ObjectMonito对象的线程
- _EntrySet:用来保存处于blocked状态的线程列表
- _WaitSet: 用来保存处于waiting状态的线程
- _count: 计数器
当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程。同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 _WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。如下图所示:
实现源码
上源码: Thread.java
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
...
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
Thread parent = currentThread();
...
if (g == null) {
g = parent.getThreadGroup();
}
...
}
...
public synchronized void start() {
...
group.add(this);
...
}
...
public final void join() throws InterruptedException {
join(0);
}
...
public final synchronized void join(long millis)
throws InterruptedException {
...
if (millis == 0) {
while (isAlive()) {
wait(0);
}
}
...
}
...
/**
* This method is called by the system to give a Thread
* a chance to clean up before it actually exits.
*/
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
...
}
...
}
复制代码
ThreadGroup.java
void threadTerminated(Thread t) {
synchronized (this) {
remove(t);
if (nthreads == 0) {
notifyAll();
}
...
}
}
复制代码
Object.java
/**
* The current thread must own this object's monitor. Causes the current thread to wait until either another thread invokes the method...
* This method causes the current thread call it to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object.
*/
public final native void wait(long timeout) throws InterruptedException;
复制代码
实现机制
个简单的例子。
public class ThreadA {
public static void main(String[] args) {
MyThread threadB = new MyThread();
//启动线程
threadB.start();
try {
//调用join()方法
threadB.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程执行完毕");
System.out.println("~~~~~~~~~~~~~~~");
}
}
复制代码
底层是如何实现join()语义的呢,以上面的例子举例。
- 由于join(long millis)方法加了对象锁,锁的是Thread类当前对象实例即threadB。同时,Thread.start()方法在启动后,threadB也持有自己线程对象实例的所有内容,包括对象实例threadB的对象锁。具体可参见start0()源码。
- 如果threadB线程在join()方法前执行完了,释放了对象锁,threadA获取锁进入同步方法join(long millis)时,调用threadB的方法isAlive()判断threadB线程已经不存活,那么执行完join()逻辑退出,继续执行threadA的逻辑。
- 如果threadB线程在join()方法前没执行完,并且由于某种原因释放了对象锁,当threadA获取锁进入同步方法join(long millis)时,调用threadB的方法isAlive()判断threadB线程还存活。于是,threadA就调用native方法wait()释放锁并进行等待(threadA进入threadB对应的monitor对象的Wait Set,此时threadA的线程状态为waiting)。以便这个对象锁能被threadB获取继续执行。直到threadB执行完成,释放锁并结束。
- threadB线程结束时会执行exit()方法,notifyAll()同一线程组的其他线程,从上面Thread.java精简版源码,可以发现。threadA线程在new threadB的时候,threadA和threadB共享一个线程组。同时线程初始化的时候,线程所在的线程组都包含线程本身,于是threadB的线程组会包含threadA。那么,threadB结束时threadA会被notify,从而threadB对应的monitor对象的Wait Set移动到该monitor对象的Entry Set,线程状态变为Blocked,等待调度获取monitor的控制权。
- 获取monitor的控制权,继续执行while (isAlive()) 循环,此时isAlive()为false。那么执行完join()逻辑退出,继续执行threadA的逻辑。
通过综上的设计,Thread.join()实现了当前线程A等待thread线程终止之后才从thread.join()返回的设计逻辑。
等待/通知的经典范式
可以发现Thread.join()方法与等待/通知的经典范式中的等待范式如出一辙。 而Thread.exit()方法则有点类似于其中的通知范式。
等待/通知的经典范式分为两个部分:等待方和通知方。 等待方遵循如下原则:
- 获取对象的锁。
- 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
- 条件满足则执行对应的逻辑。 对应的伪代码如下:
synchronized(对象) {
while(条件不满足) {
对象.wait();
}
对应的处理逻辑
}
复制代码
通知方遵循如下原则:
- 获得对象的锁。
- 改变条件。
- 通知所有等待在对象上的线程。 对应的伪代码如下:
synchronized(对象) {
改变条件
对象.notifyAll();
}
复制代码