Understanding of thread join() method

Understanding of the join() method of java thread

thread.join() adds the specified thread to the current thread, and can combine two alternately executed threads into a sequentially executed thread. To put it simply, it is synchronization.

  • Example 1: For example, if the join method of thread A is called in thread B, thread B will not continue to execute until thread A completes execution.
  • Example 2: Another example is when we do a query operation. The total task needs to return three query lists. Then the main thread needs to wait for all three threads to finish executing and return the results before it can end. In this case, the join method needs to be used. (For specific examples, please see the blog “How to use thread pool (example)”)
  • Example 3: In some cases, sub-threads are enabled in the main thread. If the sub-thread requires a large number of algorithms and takes a long time to calculate, the main thread may end before the sub-thread ends. At this time, if you want to wait for the sub-thread to end Then end the main thread, you can use the join() method.
/**Example 1**/
t.join(); //Call the join method and wait for thread t to complete execution
t.join(1000); //Waiting for t thread, the waiting time is 1000 milliseconds. 
/**Example 3**/
public class JoinTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1= new Thread(()->{
            try {
                Thread.sleep(9000);//Sub-thread processing
                System.out.println("Sub-thread processing completed");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread1.start();
        thread1.join();
        System.out.println("Main thread ends");
    }
}

wait(0)The function of this method is to make the current thread wait for a period of time. A time of 0 means that the thread immediately enters the waiting state. Normally, we pass a time parameter to the wait() method, indicating how many milliseconds to let the thread wait. But if we pass 0 or a negative number, the thread will immediately enter the waiting state until it is awakened by other threads. It should be noted that the wait() method must be called in a synchronized block.

join source code analysis

Look at a piece of source code provided by JDK

0

  • Waits at most millis milliseconds for this thread to die. A timeout of 0 means to wait forever. It means waiting for a period of time until this thread dies. Timeout 0 means wait forever.
  • From the source code, we can see that the join method is implemented through wait, which is a method provided by Object. When the main thread calls t.join, the main thread will obtain the lock of the thread object (wait means getting the lock of the object), and call the object’s wait(time) until the object wakes up the main thread. Such as exiting. This means thatwhen the main thread calls t.join, it must be able to obtain the lock of the thread t object. If you can’t get it, you can’t wait.

Note here that waiting for thread death means that if the main thread calls t.join(100), then the main thread will wait for t thread, and the waiting time is 100ms.

for example:

public class JoinTest {
    public static void main(String[] args) {
        Thread t=new Thread(new RunnableImpl());
        t.start();
        try {
            t.join(1000);
            System.out.println("join ended");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class RunnableImpl implements Runnable{

    @Override
    public void run() {
        try {
            System.out.println("Start sleep");
            Thread.sleep(1000);
            System.out.println("End sleep");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

The result is:

Start sleeping

end sleep

join ended

=====>Explanation: When the main thread calls t.joni(1000), the main thread waits for the t thread for 1 second.

But if you run it several times, you will find different results:

Start sleeping

join ended

end sleep

=====>This is because the t thread is not immediately allocated a time slice by the CPU schedule after entering the ready state, because the CPU time slice allocation uses an unfair strategy and will not be executed first just because it enters the ready queue first. Therefore, the main thread continues execution after waiting for the t thread for 1 second, resulting in this phenomenon.

If the main thread is allowed to wait for 1 second and the t thread sleeps for 2 seconds, this phenomenon will be more obvious.

@Override
public void run() {
    try {
        System.out.println("Start sleep");
        //Thread.sleep(1000);
        Thread.sleep(2000);
        System.out.println("End sleep");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

Results:

Start sleeping

join ended

end sleep

=====>Obviously the main thread will only wait for 1 second and will not care when the t thread ends. The main thread will continue to execute after 1 second.

We remember that there is also a sentence in the source code comments, timeout 0 means waiting forever. In other words, calling t.join(0) or t.join() by the main thread means that the main thread must wait for the t thread to finish executing before it can execute.

join() has the same meaning as join(0). We can know this in the source code.

Next, explain the second point under the join source code explanation: “When the main thread calls t.join, it must be able to obtain the lock of the thread t object.”

Create a new thread to grab the lock of object t:

public class JoinTest {
    public static void main(String[] args) {
        long start=System.currentTimeMillis();
        Thread t=new Thread(new RunnableImpl());
        new MythreadTest(t).start();
        t.start();
        try {
            t.join(1000);
            System.out.println("join ended");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Time: " + (System.currentTimeMillis()-start));
    }
}

class RunnableImpl implements Runnable{

    @Override
    public void run() {
        try {
            System.out.println("Start sleep");
            //Thread.sleep(1000);
            Thread.sleep(2000);
            System.out.println("End sleep");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class MythreadTest extends Thread{

    Thread thread;

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

    @Override
    public void run() {
        holdThreadLock();
    }

    public void holdThreadLock(){
        synchronized (thread){
            System.out.println("Get object lock");

        try {
            Thread.sleep(9000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
            System.out.println("Release object lock");
        }
    }
}

Results:

Get object lock

Start sleeping

end sleep

(wait nine seconds)

Release object lock

join ended

Time: 9013

===============================

Also possible results:

Start sleeping

Get object lock

end sleep

(wait nine seconds)

Release object lock

join ended

Time: 9016

==========Same reason as above================

In the main method, instantiate the ThreadTest thread object through new ThreadTest(t).start();. In the holdThreadLock() method, it acquires the lock of the thread object t through synchronized (thread), and releases it after Sleep (9000). This means that even if the main method t.join(1000) waits for one second, it must wait for the ThreadTest thread to release the t lock before entering the wait method. Its actual waiting time is 9000ms~10000ms.