java – How the join() method works in the Thread class

Question:

According to the idea of ​​the join() method, if I understand it correctly, it must transfer program control to the thread on the object of which it was called, while the thread that made this call must wait.

However, if we have several stream objects for which the join() method is called in a row, then the second one is also started.

That is, in the main method we have the following code:

    try {
        thread01.join();
        thread02.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

That, logically, immediately after the call thread01.join(); , the main method should have stopped and waited until thread01 returns control of the program to it, but the logging shows that instead, it also executes thread02.join(); and only this goes into standby mode.

Why is that?

Does this mean that the join() method works only in cases where it is not followed by another join() , or does the try block have some special meaning here? Please help me figure it out.

Full thread code:

public class CounterOfSpace {
    private int counterOfSpaces;
    private int counterOfWords;
    private String text;
    private Thread thread01;
    private Thread thread02;

    public CounterOfSpace(String text) {
        this.text = text;
    }

        void startAll() {
            System.out.println("Start program");

            calcWords();
            calcSpaces();

            thread01.start();
            thread02.start();
            try {
                thread01.join();
                thread02.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("Finish program");
        }

        private void calcSpaces() {
            thread01 = new Thread(new Runnable() {
                @Override
                public void run() {
                    if (text.length() == 0) return;
                    for (char c : text.toCharArray()) {
                        if (c == ' ') {
                            counterOfSpaces++;
                            System.out.println("counterOfSpaces "+counterOfSpaces);
                        }
                    }
                }
            });
        }

        private void calcWords() {
            thread02 = new Thread(new Runnable() {
                @Override
                public void run() {
                    if (text.length() == 0) return;
                    String[] words = text.split(" ");
                    for (String word : words) {
                        if (!word.equals(" ")) {
                            counterOfWords++;
                            System.out.println("counterOfWords "+counterOfWords);
                        }
                    }
                }
            });
        }
    }

Answer:

According to the idea of ​​the join() method, if I understand it correctly, it should transfer control of the program to that thread,

This is not entirely correct, the thread must go to sleep, but the scheduler decides who to give control to.

However, if we have several stream objects for which the join() method is called in a row, then the second one is also started.

That does not contradict the paradigm in any way. In this case, we have three histories with two synchronization points:

Main      Thread1             Thread2
<init>    <init>              <init>
running   running? finished?  running? finished?
join t1   finished            running? finished?
join t2   finished            finished

This code guarantees that thread t1 will terminate after t1.join completes, but does not guarantee that it will not terminate before t1.join is called, which is obviously what happens in this case. By the time t1.join is called, thread t1 can be in any state, even if it has to read heavy data for a long time – formally this is allowed, although in practice this will not happen often. Together with

logging shows

this suggests that, most likely, the thread just finishes very quickly, and you take this for a phenomenon that does not comply with the specification. Logging should not be relied upon for such problems, because its synchronization relationship with the main program is vague, and I would not be surprised by non-observance of the order of lines in the output or long (relative to the processes occurring in the program) delays in the output. It's also a good idea to rely moderately on timing things like this, because the standard time source might not be monotonic and return smaller values ​​on subsequent calls, making it look like the program is running in reverse order.

If you wish, you can look into the Thread source code and see that join is implemented via wait and protected by isAlive guards from accidental wakeups, so the join method will not return until the thread stops returning true on isAlive.

Scroll to Top