深刻理解Java并发编程之经过JDK C++源码以及Debug源码死扣Thread.join()

基本含义

若是一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止以后才从thread.join()返回。html

线程Thread除了提供join()方法以外,还提供了join(long millis)和join(long millis,int nanos)两个具有超时特性的方法。这两个超时方法表示,若是线程thread在给定的超时时间里没有终止,那么将会从该超时方法中返回。java

实现原理

首先介绍下线程的状态linux

线程的状态

Java线程在运行的生命周期中可能处于6种不一样的状态,在给定的一个时刻,线程只能处于其中的一个状态。以下内容截取JDK 1.8 Thread.java的源码:c++

  1. NEW: 初始转态,线程被构建,可是尚未调用start()方法。
  2. RUNNABLE: 正在执行的线程状态,JVM中runnable线程状态对应于操做系统中的就绪和运行两种状态。
  3. BLOCKED: 线程等待monitor互斥量的阻塞状态,在blocked状态的线程一般是因为执行Object.wait()后等待着进入或者再次进入同步块或者同步方法。
  4. WAITING: 等待状态,下列方法会致使线程处于等待状态:
    • Object.wait with no timeout
    • Thread.join with on timeout
    • LockSupport.park
  5. TIMED_WAITING: 超时等待,超过等待时间便会自动返回运行状态,下列方法会致使线程处于超时等待状态:
    • Thread.sleep
    • Object.wait(long) with timeout
    • Thread.join(long) with timeout
    • LockSupport.parkNanos
    • LockSupport.parkUntil
  6. TERMINATED: 线程完成执行后结束的状态。

再介绍下Monitor编程

Monitor

Monitor是 Java中用以实现线程之间的互斥与协做的主要手段,它能够当作是对象的锁。每个对象都有,也仅有一个 monitor。bash

在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个参数:并发

  1. _Owner: 用于指向ObjectMonito对象的线程
  2. _EntrySet:用来保存处于blocked状态的线程列表
  3. _WaitSet: 用来保存处于waiting状态的线程
  4. _count: 计数器

当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程。同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 _WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其余线程进入获取monitor(锁)。以下图所示:ide

实现机制

一个简单的例子。ui

public class ThreadA {
    public static void main(String[] args) {
        Runnable r = () -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("子线程执行完毕");
        };
        Thread threadB = new Thread(r, "Son-Thread");
        //启动线程
        threadB.start();
        try {
            //调用join()方法
            threadB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程执行完毕");
        System.out.println("~~~~~~~~~~~~~~~");
    }
}
复制代码

底层是如何实现join()语义的呢,以上面的例子举例。

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);
        ...
    }
复制代码
  1. 因为join(long millis)方法加了对象锁,锁的是Thread类当前对象实例即threadB。同时,Thread.start()方法在启动后,threadB也持有本身线程对象实例的全部内容,包括对象实例threadB的对应的monitor。具体可参见start0()源码
public final void join() throws InterruptedException {
        join(0);
    }
	...
    public final synchronized void join(long millis)
    throws InterruptedException {
	...
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        }
     ...
    }
复制代码
  1. 若是threadB线程在join()方法前执行完了,释放了对象锁,threadA获取锁进入同步方法join(long millis)时,调用threadB的方法isAlive()判断threadB线程已经不存活,那么执行完join()逻辑退出,继续执行threadA的逻辑。

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; 复制代码
  1. 若是threadB线程在join()方法前没执行完,而且因为某种缘由释放了对象锁,当threadA获取锁进入同步方法join(long millis)时,调用threadB的方法isAlive()判断threadB线程还存活。因而,threadA就调用native方法wait()释放锁并进行等待(threadA进入threadB对象实例对应的monitor对象的Wait Set,此时threadA的线程状态为waiting)。以便这个对象锁能被threadB获取继续执行。直到threadB执行完成,释放锁并结束。
/**
     * 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;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }
复制代码

ThreadGroup.java

void threadTerminated(Thread t) {
        synchronized (this) {
            remove(t);
            if (nthreads == 0) {
                notifyAll();
            }
			...
        }
    }
复制代码
  1. threadB线程结束时会执行exit()方法,进行一些资源的清理。从源码的注释能够发现,这个时候实际上线程是事实上存在的。那么是谁唤醒waiting状态的threadA呢?

错误解释:有不少博文的大体解释以下:threadB线程结束时会执行exit()方法,notifyAll()同一线程组的其余线程。threadA线程在new threadB的时候,threadA和threadB共享一个线程组。同时线程初始化的时候,线程所在的线程组都包含线程自己,因而threadB的线程组会包含threadA。那么,threadB结束时threadA会被notify。

这个解释是错误的,为何呢?因为if (nthreads == 0)的触发条件不知足,threadA和threadB共享一个线程组,当threadB被移除了,threadA还在线程组中,nthreads = 1。

/jdk7/hotspot/src/os/linux/vm/os_linux.cpp

int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);

static void *java_start(Thread *thread) {
  ...
  thread->run();
  return 0;
}
复制代码

/jdk7/hotspot/src/share/vm/runtime/thread.cpp

void JavaThread::run() {
  ...
  thread_main_inner();
}

void JavaThread::thread_main_inner() {
  ...
  this->exit(false);
  delete this;
}

void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
  ...
  // Notify waiters on thread object. This has to be done after exit() is called
  // on the thread (if the thread is the last thread in a daemon ThreadGroup the
  // group should have the destroyed bit set before waiters are notified).
  ensure_join(this);
  ...
}

static void ensure_join(JavaThread* thread) {
  // We do not need to grap the Threads_lock, since we are operating on ourself.
  Handle threadObj(thread, thread->threadObj());
  assert(threadObj.not_null(), "java thread object must exist");
  ObjectLocker lock(threadObj, thread);
  // Ignore pending exception (ThreadDeath), since we are exiting anyway
  thread->clear_pending_exception();
  // Thread is exiting. So set thread_status field in  java.lang.Thread class to TERMINATED.
  java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
  // Clear the native thread instance - this makes isAlive return false and allows the join()
  // to complete once we've done the notify_all below java_lang_Thread::set_thread(threadObj(), NULL); lock.notify_all(thread); // Ignore pending exception (ThreadDeath), since we are exiting anyway thread->clear_pending_exception(); } 复制代码

正确解释:在线程native代码的run()方法的结束,native代码会将线程的alive状态置为false,同时会notifyAll等待在这个线程实例上的全部其余线程。根据上面的c++源码,是lock.notify_all(thread) 这个动做会notify全部等待在当前线程实例上的其余线程。

除了看C++的源码验证,咱们也写了一个demo来验证这点,waitThread执行完结束后后,wait()在waitThread对象实例的其余线程才会被唤醒继续执行。

/**
     * Wait Thread wait thread.
     * Run Thread1 run thread outer.
     * Run Thread2 run thread outer.
     * Run Thread1before wait run thread inner.
     * Run Thread2before wait run thread inner.
     * exit: Wait Thread wait thread.
     * Run Thread2after wait run thread inner.
     * Run Thread1after wait run thread inner.
     */
    public static void main(String[] args) throws Exception {
        WaitThread waitRunner = new WaitThread();
        Thread waitThread = new Thread(waitRunner, "Wait Thread");

        waitThread.start();

        RunThread runRunner1 = new RunThread(waitThread);
        RunThread runRunner2 = new RunThread(waitThread);

        Thread runThread1 = new Thread(runRunner1, "Run Thread1");
        Thread runThread2 = new Thread(runRunner2, "Run Thread2");

        runThread1.start();
        runThread2.start();
    }

    static class WaitThread implements Runnable {
        @Override
        public void run() {
            long t1 = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + " wait thread.");
            while (true) {
                long t2 = System.currentTimeMillis();
                if (t2 - t1 > 10 * 1000) {
                    break;
                }
            }
            System.out.println("exit: " + Thread.currentThread().getName() + " wait thread.");
        }
    }

    static class RunThread implements Runnable {
        private final Thread thread;

        public RunThread(Thread thread) {
            this.thread = thread;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " run thread outer.");
            synchronized (thread) {
                System.out.println(Thread.currentThread().getName() + "before wait run thread inner.");
                try {
                    thread.wait(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "after wait run thread inner.");
            }
        }
    }
}
复制代码
  1. 那么,threadB结束时threadA会被notify,从而threadB对应的monitor对象的Wait Set移动到该monitor对象的Entry Set,线程状态变为Blocked,等待调度获取monitor的控制权。

  2. threadA获取monitor的控制权后,继续执行while (isAlive()) 循环,此时isAlive()为false。那么执行完join()逻辑退出,继续执行threadA的逻辑。

经过综上的设计,Thread.join()实现了当前线程A等待thread线程终止以后才从thread.join()返回的设计逻辑。

Debug分析

咱们经过上面的那个简单的例子来Debug逐点分析:

  1. 当主线程执行到join()逻辑中时,是RUNNING的状态

  1. 当子线程执行到exit()逻辑时,threadB依旧是存活,状态为RUNNING

threadA的状态为WAIT

  1. threadB执行到threadTerminated()逻辑,这时候发现nthreads:1,根本不会执行notifyAll()操做。就算执行了notifyAll()操做,也不会唤醒threadA,由于锁的对象都不同。一个是threadB的实例,一个是线程组的实例。

等待/通知的经典范式

能够发现Thread.join()方法与等待/通知的经典范式中的等待范式一模一样。 而Thread.exit()方法则有点相似于其中的通知范式。

等待/通知的经典范式分为两个部分:等待方和通知方。 等待方遵循以下原则:

  1. 获取对象的锁。
  2. 若是条件不知足,那么调用对象的wait()方法,被通知后仍要检查条件。
  3. 条件知足则执行对应的逻辑。 对应的伪代码以下:
synchronized(对象) {
	while(条件不知足) {
		对象.wait();
	}
	对应的处理逻辑
}
复制代码

通知方遵循以下原则:

  1. 得到对象的锁。
  2. 改变条件。
  3. 通知全部等待在对象上的线程。 对应的伪代码以下:
synchronized(对象) {
	改变条件
	对象.notifyAll();
}
复制代码

最后,以为写的不错的同窗麻烦点个赞,支持一下呗^_^~

参考与感谢

  1. 《Java并发编程的艺术》
  2. stackoverflow.com/questions/9…
  3. www.jianshu.com/p/81a56497e…
  4. www.cnblogs.com/zhengyun_us…
相关文章
相关标签/搜索