这个线程加入代码是什么意思?

170
在这段代码中,两个join和break的含义是什么?t1.join()会导致t2停止执行,直到t1终止。

在这段代码中,两个join和break的含义是什么?t1.join()会导致t2停止执行,直到t1终止。

Thread t1 = new Thread(new EventThread("e1"));
t1.start();
Thread t2 = new Thread(new EventThread("e2"));
t2.start();
while (true) {
   try {
      t1.join();
      t2.join();
      break;
   } catch (InterruptedException e) {
      e.printStackTrace();
   }
}

3
http://docs.oracle.com/javase/tutorial/essential/concurrency/join.html 和 http://docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html - m0skit0
3
既然它会阻塞并等待线程终止,为什么要使用while循环? - Mehdi
@MahdiElMasaoudi 我想,即使线程被中断,仍然继续等待可能不是一个好的做法。 - forresthopkinsa
请记得在这里接受有帮助的答案。 - Gray
如前所述,您不需要使用 while(true) 来调用 join 方法。 - Arefe
11个回答

342
这个线程的 join 代码是什么意思?
引用自 Thread.join() 方法 javadocsjoin() 等待该线程死亡。
你的示例代码中有一个线程运行,可能是 主线程
1. 主线程创建并启动 t1t2 线程。这两个线程开始并行运行。 2. 主线程调用 t1.join() 等待 t1 线程完成。 3. t1 线程完成,t1.join() 方法在主线程中返回。请注意,t1 可能已经在 join() 调用之前完成,此时 join() 调用将立即返回。 4. 主线程调用 t2.join() 等待 t2 线程完成。 5. t2 线程完成(或可能在 t1 线程之前完成),t2.join() 方法在主线程中返回。
重要的是要理解,t1t2 线程一直在并行运行,但启动它们的主线程需要等待它们完成才能继续。这是一个常见的模式。t1 和/或 t2 可能在主线程调用 join() 之前已经完成。如果是这样,join() 将不会等待,而是立即返回。 t1.join() 的意思不是使 t2 停止直到 t1 终止。正在调用 t1.join()线程将停止运行并等待 t1 线程完成。t2 线程在并行运行,不受 t1t1.join() 调用的影响。
至于 try/catch,在 join() 中抛出 InterruptedException,这意味着调用 join() 的主线程本身可能会被另一个线程中断。
while (true) {
在while循环中进行连结是一种奇怪的模式。通常情况下,您会先执行第一个连接,然后执行第二个连接,在每种情况下适当处理InterruptedException。没有必要将它们放在循环中。

28
+1 这是一个非常奇怪的模式,可能可以被移除。 - m0skit0
4
如果t1先完成,那么t2就完成。这似乎是一个顺序过程。一个线程先完成,然后另一个线程。多线程的意义何在? - user697911
10
因为t1t2可以并行运行。只是main需要等它们都完成后才能继续执行。这是一个典型的模式@user697911。 - Gray
3
while循环在这里是因为(我猜)它希望在一个join()调用被中断时重试。 @user697911,我肯定不会以那种方式编写它。 - Gray
5
循环的目的是确保t1t2都能够完成。也就是说,如果t1抛出了InterruptedException异常,循环会回到起点并等待t2。另一种选择是在每个try-catch块中分别等待两个线程,这样就可以避免使用循环。此外,根据"EventThread"的情况,使用这种方式有意义,因为我们运行了两个线程而不是一个。 - Michael Bisbjerg
显示剩余8条评论

76

这是一个Java面试中最受欢迎的问题。

Thread t1 = new Thread(new EventThread("e1"));
t1.start();
Thread e2 = new Thread(new EventThread("e2"));
t2.start();

while (true) {
    try {
        t1.join(); // 1
        t2.join(); // 2  These lines (1,2) are in in public static void main
        break;
    }
}

t1.join() 的意思是,t1会说出类似于 "我想要先完成" 的话。t2 也是一样。无论是谁启动了 t1 或者 t2 线程(在这个例子中是 main 方法),主线程都会等待直到 t1t2 完成它们的任务。

然而,需要注意的一个重要点是,t1t2 本身可以并行运行,与 join 调用顺序无关。是 main/daemon 线程必须等待


3
好的例子。关于“可以并行运行”:那又怎样呢?重要的是主线程将首先等待t1,然后再等待t2。从主线程的角度来看,t1或t2在做什么并不重要。 - Alex
1
你提到了“主/守护”线程。我理解主线程,但守护线程与之无关。主线程是非守护线程。 - Gray
1
t1.join(); t2.join();会阻止执行这些join的线程继续,直到两个线程都终止。在其他代码非常不寻常的情况下,join的顺序并不重要。 - David Schwartz
当t1完成时,t2.join()会被调用吗? - Leo DroidCoder
换句话说,如果我们想要“序列化”执行t1和t2线程,我们需要在t1.start()之后立即放置t1.join(),因为主线程启动了t1(然后是t2),当然也要从try/catch中删除它。 显然,这样做的结果将是失去并行性。 - dobrivoje

53

join()是等待一个线程完成的方法,它是一个阻塞方法。你的主线程(调用join()的那个线程)会在t1.join()这一行等待t1完成工作,然后会对t2.join()做同样的操作。


47

一张图片胜过千言万语。

    Main thread-->----->--->-->--block##########continue--->---->
                 \                 |               |
sub thread start()\                | join()        |
                   \               |               |
                    ---sub thread----->--->--->--finish    

希望对您有所帮助,如需了解更多,请点击这里


3
清晰而准确。 - dobrivoje
调用 join() 的线程是处于 BLOCKED 状态还是 WAITING 状态? - questioner
当一个线程被阻塞等待监视器时,它的状态是“BLOCKED”。当一个线程正在等待另一个线程时,它的状态是“WAITING”。欲了解更多详细信息,请点击我的其他博客。Java线程状态 - xxy
没有回答问题。 - questioner

12
当线程tA调用tB.join()时,它不仅会等待tB终止或tA自身被中断,还会在tA线程中的tB.join()之后与tB中的最后语句和下一条语句之间建立happens-before关系。

此外,这里有一个引用:当某个线程成功从该线程上的join()返回时,该线程中的所有操作发生在任何其他线程之前。

这意味着程序...
class App {
    // shared, not synchronized variable = bad practice
    static int sharedVar = 0;
    public static void main(String[] args) throws Exception {
        Thread threadB = new Thread(() -> {sharedVar = 1;});
        threadB.start();
        threadB.join();

        while (true) 
            System.out.print(sharedVar);
    }
}

始终打印

>> 1111111111111111111111111 ...

但是程序

class App {
    // shared, not synchronized variable = bad practice
    static int sharedVar = 0;
    public static void main(String[] args) throws Exception {
        Thread threadB = new Thread(() -> {sharedVar = 1;});
        threadB.start();
        // threadB.join();  COMMENT JOIN

        while (true)
            System.out.print(sharedVar);
    }
}

不仅可以打印

>> 0000000000 ... 000000111111111111111111111111 ...

但是
>> 00000000000000000000000000000000000000000000 ... 

始终只为'0'。

因为Java内存模型不要求在没有先发生happens-before关系(线程启动,线程加入,使用'synchronized'关键字,使用AtomicXXX变量等)的情况下将'threadB'对'sharedVar'的新值传递到主线程中。


6
简单来说:
t1.join()t1 完成后返回。
它不会对线程 t1 做任何事情,只是等待它完成。
当然,在 t1.join() 后面的代码只有在 t1.join() 返回后才会执行。

2
只有在 t1.join() 返回 +1 后才会执行。 - mohsen.nour

4

从Oracle文档关于加入的页面

join方法允许一个线程等待另一个线程完成。

如果t1是一个正在执行的线程,则它是一个Thread对象。

t1.join() : causes the current thread to pause execution until t1's thread terminates.

如果t2是一个正在执行的线程的Thread对象,
t2.join(); causes the current thread to pause execution until t2's thread terminates.
join API是低级别的API,早期版本的java中引入了这个API。随着时间的推移(特别是jdk 1.5发布后),并发方面发生了很多变化。
您可以使用java.util.concurrent API来实现相同的功能。以下是一些示例:
  1. ExecutorService上使用invokeAll
  2. 使用CountDownLatch
  3. 使用ExecutorsForkJoinPoolnewWorkStealingPool(自java 8以来)
有关相关的SE问题,请参阅: 等待所有线程完成其工作

3
对我来说,Join()的行为一直令人困惑,因为我试图记住谁会等谁。不要试图以那种方式记住它。
在底层,它是纯wait()和notify()机制。
我们都知道,当我们在任何对象(t1)上调用wait()时,调用对象(main)将被发送到等待室(阻塞状态)。
这里,主线程正在调用join(),它在幕后是wait()。因此,主线程将等待直到被通知。通知是由t1在完成其运行(线程完成)时给出的。
收到通知后,主线程离开等待室并继续执行。

这是不是意味着我可以把它看作是JavaScript的await函数?哈哈 - Max

0

join()方法用于暂停当前正在运行的线程,直到指定的线程死亡(执行完成)。

为什么要使用join()方法?

在正常情况下,我们通常有多个线程,线程调度程序安排线程,但不能保证线程执行的顺序。

让我们看一个例子,创建一个新项目并复制以下代码:

这是activity_main.xml代码:

<?xml version="1.0" encoding="utf-8"?>

<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/btn_without_join"
    app:layout_constraintTop_toTopOf="parent"
    android:text="Start Threads Without Join"/>
 <Button
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:id="@+id/btn_with_join"
     app:layout_constraintTop_toBottomOf="@id/btn_without_join"
     android:text="Start Threads With Join"/>
 <TextView
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:id="@+id/tv"
     app:layout_constraintTop_toBottomOf="@id/btn_with_join"
     />

 </androidx.constraintlayout.widget.ConstraintLayout>

这是MainActivity.java的代码:

public class MainActivity extends AppCompatActivity {

TextView tv;
volatile  String threadName = "";
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    tv = findViewById(R.id.tv);
    Button btn_without_join = findViewById(R.id.btn_without_join);
    btn_without_join.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            withoutJoin();
        }
    });
    Button btn_with_join = findViewById(R.id.btn_with_join);
    btn_with_join.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            withJoin();
        }
    });
}
private void withoutJoin()
{
    tv.setText("");
    Thread th1 = new Thread(new MyClass2(), "th1");
    Thread th2 = new Thread(new MyClass2(), "th2");
    Thread th3 = new Thread(new MyClass2(), "th3");
    th1.start();
    th2.start();
    th3.start();
}
private void withJoin()
{
    tv.setText("");
    Thread th1 = new Thread(new MyClass2(), "th1");
    Thread th2 = new Thread(new MyClass2(), "th2");
    Thread th3 = new Thread(new MyClass2(), "th3");
    th1.start();
    try {
        th1.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    th2.start();
    try {
        th2.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    th3.start();
}
class MyClass2 implements Runnable{

    @Override
    public void run() {
        Thread t = Thread.currentThread();
        runOnUiThread(new Runnable() {
            public void run() {
                tv.setText(tv.getText().toString()+"\n"+"Thread started: "+t.getName());
            }
        });
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        }
        runOnUiThread(new Runnable() {
            public void run() {
                tv.setText(tv.getText().toString()+"\n"+"Thread ended: "+t.getName());
            }
        });

    }
}
}

如果您按下第一个按钮(启动未加入的线程),将会得到以下结果: 输入图像描述

如果您按下第二个按钮(启动已加入的线程),将会得到以下结果: 输入图像描述


0

希望能有所帮助!

package join;

public class ThreadJoinApp {

    Thread th = new Thread("Thread 1") {
        public void run() {
            System.out.println("Current thread execution - " + Thread.currentThread().getName());
            for (int i = 0; i < 10; i++) {
                System.out.println("Current thread execution - " + Thread.currentThread().getName() + " at index - " + i);
            }
        }
    };

    Thread th2 = new Thread("Thread 2") {
        public void run() {
            System.out.println("Current thread execution - " + Thread.currentThread().getName());

            //Thread 2 waits until the thread 1 successfully completes.
            try {
            th.join();
            } catch( InterruptedException ex) {
                System.out.println("Exception has been caught");
            }

            for (int i = 0; i < 10; i++) {
                System.out.println("Current thread execution - " + Thread.currentThread().getName() + " at index - " + i);
            }
        }
    };

    public static void main(String[] args) {
        ThreadJoinApp threadJoinApp = new ThreadJoinApp();
        threadJoinApp.th.start();
        threadJoinApp.th2.start();
    }

    //Happy coding -- Parthasarathy S
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接