Java技术之join的使用

在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。

1. 学习方法join前的铺垫

在介绍join方法之前,先来看一个实验。
创建测试用的java项目,名称为joinTest1,类MyThread.java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package extthread;
public class MyThread extends Thread {
@Override
public void run() {
try {
int secondValue = (int) (Math.random() * 10000);
System.out.println(secondValue);
Thread.sleep(secondValue);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

类Test.java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
package test;
import extthread.MyThread;
public class Test {
public static void main(String[] args) {
MyThread threadTest = new MyThread();
threadTest.start();
// Thread.sleep(?)
System.out.println("我想当threadTest对象执行完毕后我再执行");
System.out.println("但上面代码中的sleep()中的值应该写多少呢?");
System.out.println("答案是:根据不能确定:)");
}
}

程序运行结果如图3-44所示。

2. 20190221150830.png

方法join可以解决这个问题。新建java项目joinTest2,类MyThread.java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package extthread;
public class MyThread extends Thread {
@Override
public void run() {
try {
int secondValue = (int) (Math.random() * 10000);
System.out.println(secondValue);
Thread.sleep(secondValue);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

类Test.java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package test;
import extthread.MyThread;
public class Test {
public static void main(String[] args) {
try {
MyThread threadTest = new MyThread();
threadTest.start();
threadTest.join();
System.out.println("我想当threadTest对象执行完毕后我再执行,我做到了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

程序运行后的结果如图3-45所示。

方法join的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x销毁后再继续执行线程z后面的代码。

方法join具有使线程排队运行的作用,有些类似同步的运行效果。join与synchronized的区别是:join在内部使用wait()方法进行等待,而sychronized关键字使用的是“对象监视器”原理做为同步

3. 方法join与异常

在join过程中,如果当前线程对象被中断,则当前线程出现异常。
创建测试用的项目joinException,类ThreadA.java代码如下:

1
2
3
4
5
6
7
8
9
10
package extthread;
public class ThreadA extends Thread {
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
String newString = new String();
Math.random();
}
}
}

类ThreadB.java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package extthread;
public class ThreadB extends Thread {
@Override
public void run() {
try {
ThreadA a = new ThreadA();
a.start();
a.join();
System.out.println("线程B在run end处打印了");
} catch (InterruptedException e) {
System.out.println("线程B在catch处打印了");
e.printStackTrace();
}
}
}

类ThreadC.java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
package extthread;
public class ThreadC extends Thread {
private ThreadB threadB;
public ThreadC(ThreadB threadB) {
super();
this.threadB = threadB;
}
@Override
public void run() {
threadB.interrupt();
}
}

类Run.java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package test.run;
import extthread.ThreadB;
import extthread.ThreadC;
public class Run {
public static void main(String[] args) {
try {
ThreadB b = new ThreadB();
b.start();
Thread.sleep(500);
ThreadC c = new ThreadC(b);
c.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

程序运行后的效果如图3-46所示。

说明方法join()与interrupt()方法如果彼此遇到,则会出现异常。但进程按钮还呈“红色”,原因是线程ThreadA还在继续运行,线程ThreadA并未出现异常,是正常执行的状态。

方法join(long)中的参数是设定等待的时间。

4. 方法join(long)与sleep(long)的区别

方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。
方法join(long)源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

从源代码中可以了解到,当执行wait(long)方法后,当前线程的锁被释放,那么其他线程就可以调用此线程中的同步方法了。
Thread.sleep(long)方法不释放锁。