什么是join()方法
在Java中,join()方法是Thread类中的一个方法,用于将一个线程等待另一个线程的结束。也就是说,如果在一个线程中调用了另一个线程的join()方法,那么这个线程将会等待另一个线程执行完毕之后才会继续执行。
public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
public final synchronized void join(long millis, int nanos) throws InterruptedException
join()方法是Thread类方法的原生方法,不需要通过类的对象来调用。它有三个重载版本,第一个版本表示不带任何参数,表示如果线程A执行了B线程的join()方法,则线程A会等待B线程执行完毕之后才会继续执行。第二个版本表示线程A等待B线程的时间为milli秒,第三个版本表示线程A等待B线程的时间为milli毫秒+nanos纳秒。
为什么join()方法很重要
join()方法的重要性在于它可以保证线程之间的执行顺序和数据一致性。往往在一个程序中,多个线程是相互关联的。如果不对这些线程进行协调,那么就会产生问题,如数据竞争、死锁、性能下降等。
保证线程之间的执行顺序
在Java中,线程是一种异步执行的机制,也就是说,多个线程之间的执行顺序是不确定的。如果我们需要让某个线程在另一个线程执行完毕之后再执行,那么就需要使用join()方法。
public class JoinExample {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1 finished");
});
Thread thread2 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2 finished");
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("All threads finished");
}
}
在上面的例子中,我们创建了两个线程thread1和thread2。线程thread1休眠1秒钟后输出“Thread 1 finished”,线程thread2休眠2秒钟后输出“Thread 2 finished”。我们希望在这两个线程都执行完毕之后输出“All threads finished”。
如果我们不使用join()方法,程序输出的结果可能是这样的:
All threads finished
Thread 1 finished
Thread 2 finished
这是因为线程并不是按照我们期望的顺序执行的。而如果我们使用join()方法,程序输出的结果就一定是这样的:
Thread 1 finished
Thread 2 finished
All threads finished
保证数据的一致性
在Java中,共享的数据可能会被多个线程同时访问,如果不加以限制,就会产生数据竞争问题。这时候,我们需要通过同步机制来保证数据的一致性。如果我们需要等待一个线程执行完成之后才能访问这个线程所更新的数据,就需要使用join()方法。
public class JoinExample2 {
private static volatile boolean ready;
private static int result;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
if (ready) {
result = 1;
break;
}
Thread.yield();
}
});
thread.start();
Thread.sleep(1000);
ready = true;
thread.join();
System.out.println(result);
}
}
在上面的例子中,我们创建了一个线程,该线程不断地检测变量ready是否为真。如果ready为真,则将result的值设为1,并结束线程。
我们希望在main线程中等待该线程执行完毕之后输出result的值。但是,如果我们不使用join()方法,输出的结果可能是0(即使在我们看来,该线程已经执行完毕了)。
这是因为没有对该变量进行同步控制,导致result的值没有被正确地刷新到主存中。而如果我们在main线程中使用join()方法,就可以保证该线程执行完毕之后再访问result的值,从而得到正确的结果。
join()方法的注意事项
虽然join()方法很有用,但是需要注意一些使用细节,以避免产生一些问题:
避免死锁
如果线程A等待线程B,而线程B又在等待线程A,则会产生死锁问题。
public class JoinDeadlock {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("Thread 1 waiting for thread 2 to finish");
Thread2.getInstance().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1 finished");
});
Thread thread2 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("Thread 2 waiting for thread 1 to finish");
Thread1.getInstance().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2 finished");
});
thread1.start();
thread2.start();
}
private static class Thread1 {
private static Thread1 instance = new Thread1();
private Thread1() {}
public static Thread1 getInstance() {
return instance;
}
}
private static class Thread2 {
private static Thread2 instance = new Thread2();
private Thread2() {}
public static Thread2 getInstance() {
return instance;
}
}
}
在上面的例子中,我们创建了两个线程thread1和thread2。线程thread1等待线程thread2执行完毕之后才会执行,线程thread2等待线程thread1执行完毕之后才会执行。
如果我们运行该程序,就会发现它陷入了死锁状态。这是因为线程thread1等待线程thread2执行完毕之后,线程thread2又等待线程thread1执行完毕之后才能执行。
为避免死锁问题,通常建议在线程之间等待的时间不宜过长,并且尽量避免出现循环等待的情况。
避免竞争
如果多个线程同时等待一个线程的结束,就可能产生竞争问题。
public class JoinConcurrent {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread finished");
});
thread.start();
for (int i = 0; i < 5; i++) {
Thread t = new Thread(() -> {
try {
thread.join();
System.out.println("Thread " + Thread.currentThread().getId() + " finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
}
}
在上面的例子中,我们创建了一个线程thread,该线程休眠1秒钟后输出“Thread finished”。我们又创建了5个线程,这些线程都会等待thread执行完毕后输出自己的ID。
如果我们运行该程序,就会发现程序执行过程中出现了竞争问题。这是因为多个线程同时等待thread的结束,当thread执行完毕之后,会随机地唤醒一个线程并让其继续执行。
为避免竞争问题,通常建议在线程之间等待时,尽可能让等待的线程数量最小,并且尽量避免线程之间的竞争。
当然,还有一些其他的注意事项,如:
join()方法不能被中断,如果需要中断线程,可以使用Thread.interrupt()方法。
线程等待超时时间不能为负数。
join()方法不能对start()方法还未被调用的线程使用。
线程在等待另一个线程执行完毕的过程中,不会占用CPU时间。
总结
在Java中,join()方法是Thread类的一个重要方法,它可以保证线程之间的执行顺序和数据一致性。使用join()方法要注意避免死锁和竞争等问题。join()方法是编写多线程程序中必不可少的重要工具之一。