Scan the qr code below or search the wechat official account, cainiao Feiyafei, you can follow the wechat official account, read more Spring source code analysis and Java concurrent programming articles.

The problem

  • The join() method eventually calls the object’s wait() method, which is usually paired with notify() or notifyAll(). When we use join(), we don’t write notify() or notifyAll() at all. Why?
  • If you know the answer, this article is not going to help you, so you can just skip it.

preface

  • The previous two articles examined the use and principles of CyclicBarrier and CountDownLatch, two utility classes provided under JUC and developed by concurrency guru Doug Lea, which control the execution order of threads. In fact, there is an API in the Java language that controls the order in which threads are executed: the Join () method in the Thread class, which was developed by the developers at Sun (now owned by Oracle).

How to use

  • The join() method is used to block thread A after calling the join() method of thread B. Thread A will unblock until thread B completes its logic and continues to execute its business logic. You can use the following Demo to get a sense of its usage.
  • In the Demo example,” Because the main thread was hungry and wanted to eat, it asked the nanny to cook the meal and wait until the meal was ready to eat. Call join() on the main thread and let the nanny finish the meal before telling her to eat.
public class JoinDemo {

    public static void main(String[] args) {
        System.out.println("I'm hungry. I want to eat.");

        Thread thread = new Thread(() -> {
            System.out.println("Start cooking.");
            try {
                // Let the thread sleep for 10 seconds to simulate the cooking process
                Thread.sleep(10000l);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("The rice is ready.");
        });
        thread.start();

        try {
            // Wait for the meal to be ready
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // Don't start eating until the meal is ready
        System.out.println("Start eating."); }}Copy the code

The principle of

  • The join() method is simple to use, and here’s how it works.
  • The essence of what the join() method does is essentiallyCommunication between threads. The CyclicBarrier and CountDownLatch classes are essentially threads that communicate, and in our source code analysis, we can see that the bottom layer of these two classes is ultimately implemented through AQS, which in turn is implemented through LockSupportpark()andunpark()Method to achieve thread communication. The Join () of Thread passes through the Object classwaitandNotify () and notifyAll ()Method to implement.
  • Why is it possible to block threads when thread.join() is called? How does it work? The answer lies in the source code for the join() method. The join() method has three overloaded methods, and the other two support timeout waiting, where the main thread wakes up if the child thread has not finished executing after the specified time. When the join() method is called, it is called directlyjoin(0)Method,If 0 is passed, the wait time is unlimited. The source code for the JOIN (Long Millis) method is shown below.
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");
    }

    // When millis is 0, the wait is unlimited
    if (millis == 0) {
        // With a while() loop, wait as long as the thread is alive
        while (isAlive()) {
            wait(0); }}else {
        // When millis is not 0, the timeout is calculated and the thread waits for the specified time
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break; } wait(delay); now = System.currentTimeMillis() - base; }}}Copy the code
  • The signature of the join(long millis) method has been addedsynchronizedKeyword to ensure thread-safety, join(Long millis) eventually calls Object’s wait() method, making the main thread wait. The synchronized keyword implements implicit lockingthis(Synchronized join(long millis) is a member method and locks the instance object). In the Demo, we call the join() method of thread from the main thread. When thread’s wait() method is called, thread’s wait() method is called, and the main thread enters the wait state. (The wait() method for Thread is called. This is important because the notify() or notifyAll() methods of Thread are used to wake up the main thread).
  • Now that wait() is called, where is notify() or notifyAll() called? However, we found no notify() or notifyAll() methods in the source code of join() or our own Demo code. We all know wait() and notify() or notifyAll()It must have come in pairsIn the case of join(), where is notify() or notifyAll() called? The answer isjvm.
  • The JVM equivalent of the Thread class in Java is thread.cpp. The path to the thread. CPP file isjdk-jdk8-b120/hotspot/src/share/vm/runtime/thread.cppI downloaded the openJDK8 source code locally. The thread. CPP file defines many thread-specific methods. Native methods in the Thread. Java class are implemented in Thread. CPP.
  • According to the description of the join() method, its purpose is to let the Thread business logic complete before the main thread starts executing. So we can guess that notify() or notifyAll() is called when the JVM is doing some finishing work on the thread after it has executed run(). In the JVM, thread.cpp files are called when each thread completes executionJavaThread::exit(bool destroy_vm, ExitType exit_type)Method (the code for this method is around line 1730). The source code for the exit() method is quite long, almost 200 lines, removing useless code and preserving only what is relevant to today’s topic. The source code is as follows.
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
	assert(this == JavaThread::current(),  "thread consistency check");
	/ /... A lot of code is omitted

	// 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).
    
    // The key code is in this line, which, as can be inferred from the name of the method, is used by the thread to ensure the logic of the join() method upon exit. Where this refers to the current thread.
    // It is used to handle logic related to join()
	ensure_join(this);

	/ /... A lot of code is omitted

}
Copy the code
  • As you can see, the main logic is inensure_join()Method, and then findensure_join()Method, the source code is as follows. (ensure_join()The source code for the method is also in the thread. CPP file. If you are interested, you can use Clion, a smart tool provided by JetBrains, to view the SOURCE code of the JVM. IDEA is not available, and you can also track the source code by holding down option (or Alt) + the left mouse button.
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); // Core code is in the following line // isn't that a surprise? There is no doubt that notify_all() is used to wake up. Notify_all (thread); // Ignore pending exception (ThreadDeath), since we are exiting anyway thread->clear_pending_exception(); }Copy the code

conclusion

  • This paper mainly introduces the function of join() method is used to control the execution order of threads, and demonstrates its usage with Demo, and then analyzes the implementation principle of Join () method with source code. The Join (Long Millis) method is a synchronized method because it uses the synchronized keyword modifier and locks this, the instance object itself. The wait() method of the instance object is called in the Join (Long millis) method, and the notifyAll() method is called in the JVM. In real development, join() is used sparingly, and we usually use the utility classes provided under the JUC packageCountDownLatchorCyclicBarrierBecause the latter two are more powerful.
  • There is a classic programming paradigm hidden in the source code for the Join (Long Millis) method. As follows.
// This is the classic wait/notification paradigm.
while{wait(); }Copy the code

recommended

  • @ Import and @ EnableXXX
  • How the @autowired annotation works
  • How does Spring address loop dependencies
  • Pipe program: the cornerstone of concurrent programming
  • Design principle of queue synchronizer (AQS)
  • ReentrantLock source code analysis
  • The implementation principle of ReadWriteLock
  • Semaphore source code analysis and use scenarios
  • Concurrency tool class CountDownLatch source analysis and usage scenarios
  • CyclicBarrier concurrency tool class source analysis and usage scenarios