join() 在多线程中的使用
在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束,这时如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,这个时候就要用到 join() 方法了。方法 join() 的作用是等待线程对象销毁。
目录
5 方法join(long) 与 sleep(long)的区别
7 方法 join(long millis, int nanos) 的使用
方法 join() 的使用
在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束,这时如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,这个时候就要用到 join() 方法了。方法 join() 的作用是等待线程对象销毁。
1 学习方法 join() 前的铺垫
在进入 join() 方法学习之前,先看一个实验。
测试代码如下:
public class joinTest {
static 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) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
//Thread.sleep(?)
System.out.println("main 线程想实现当 thread 对象执行完毕后再继续向下执行 ,");
System.out.println("但上面代码中的 sleep() 中的值应该写多少呢?");
System.out.println("答案是:根据不能确定 :)");
}
}
程序运行结果如图所示:
2 用方法 join() 解决问题
方法 join() 可以解决这个问题。新的测试代码如下:
public class joinTest2 {
public static 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) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try{
MyThread thread = new MyThread();
thread.start();
thread.join();
System.out.println("等待thread对象执行后再执行.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
方法join()的作用是使所属的线程对象X正常的执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程X销毁后再继续执行线程z后面的代码。也就是说,join()方法具有串联执行的作用:你不销毁,我不继续往下走!
结果如图:
方法join()也支持多个线程的等待。
public class joinTest3 {
static int number1 = 0;
static int number2 = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(){
@Override
public void run() {
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
number1 = 1000;
}
};
Thread t2 = new Thread(){
@Override
public void run() {
try{
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
long beginTime = System.currentTimeMillis();
t1.start();
t2.start();
System.out.println("A "+System.currentTimeMillis());
t1.join();
System.out.println("B "+System.currentTimeMillis());
t2.join();
System.out.println("C "+System.currentTimeMillis());
long endTime = System.currentTimeMillis();
System.out.println("number1值为:"+number1+",number2值为:"+number2+",耗时:"+(endTime-beginTime));
}
}
运行结果如下:
如果改变代码 顺序为 :
t2.join();
t1.join();
程序运行结果如下:
方法join()具有使线程排队运行的效果,有些类似synchronized同步的运行效果,但是他们的区别在于,join()在内部使用 wait() 方法进行等待,会释放锁,而synchronized关键字一致持有锁。
走millis = 0 的逻辑代码:
3 方法join() 和 interrupt() 出现异常
在join()方法运行过程中,如果当前线程对象被中断,则当前线程出现异常。
public class joinTest4 {
static class ThreadA extends Thread{
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
String newString = new String();
Math.random();
}
}
}
static class ThreadB extends Thread{
@Override
public void run() {
try{
ThreadA threadA = new ThreadA();
threadA.start();
threadA.join();
System.out.println("线程B在run end 处打印了");
} catch (InterruptedException e) {
System.out.println("线程B在catch处打印了");
e.printStackTrace();
}
}
}
static class ThreadC extends Thread{
private ThreadB threadB;
public ThreadC(ThreadB threadB) {
this.threadB = threadB;
}
@Override
public void run() {
threadB.interrupt();
}
}
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();
}
}
}
运行结果如图:
可见,如果join()与interrupt()彼此遇到,程序会出现异常,无论它们的执行顺序是怎么样的。进程按钮还是红色,原因是ThreadA还在继续运行,它并未出现异常。
4 方法join(long)的使用
方法 x.join(long) 中的参数用于设定最长的等待时间,当线程小于long时间销毁或long时间到达并且 重新获得了锁时,当前程序会继续向后运行。如果没有重新获得锁,线程会一直尝试,直到获得锁为止。
public class joinLong {
static class MyThread extends Thread{
@Override
public void run() {
try{
System.out.println("run begin Timer ="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("run end Timer="+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try{
MyThread thread = new MyThread();
thread.start();
System.out.println(" main begin time="+System.currentTimeMillis());
thread.join(2000);//只等2s 小于5s
System.out.println(" main end time="+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行结果如图:
从运行结果可以看出,run()方法执行了5s,main暂停了2s。
如果更改代码,如下所示:
public static void main(String[] args) {
try{
MyThread thread = new MyThread();
thread.start();
System.out.println(" main begin time="+System.currentTimeMillis());
thread.join(8000);//等8s 大于5s
System.out.println(" main end time="+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:
从运行结果上来看,run方法执行了5s ,main方法暂停了5s。
从以上案例可以分析出,join(long)方法和sleep(long)方法具有类似的功能,就是是当前的线程暂停指定的时间。两者的区别是:join(long)暂停的时间是可变的,取决于线程是否销毁,而sleep(long )暂停的时间是不确定的。另外两者还有一个关键的区别:锁是否被释放。
5 方法join(long) 与 sleep(long)的区别
方法join(long)是在内部使用wait(long)方法实现的,所以join(long)方法具有释放锁的特点。
方法join(long)的源代码如下,是被同步 synchronized 修饰的:
public final synchronized void join(final long millis)
throws InterruptedException {
if (millis > 0) {
if (isAlive()) {
final long startTime = System.nanoTime();
long delay = millis;
do {
wait(delay);
} while (isAlive() && (delay = millis -
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
}
} else if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
throw new IllegalArgumentException("timeout value is negative");
}
}
从源代码中可以了解到,当执行wait(long)方法后锁被释放,那么其他线程就可以调用此线程中的同步方法了。当start()后的线程销毁时会在本地使用C语言代表通知所有的等待者,等待者被唤醒后继续执行后面的代码。
而Thread. sleep(long)方法却不释放锁,我们来看看下面的示例。
public class join_sleep {
static class ThreadA extends Thread{
private ThreadB threadB;
public ThreadA(ThreadB threadB) {
this.threadB = threadB;
}
@Override
public void run() {
try{
synchronized (threadB){
threadB.start();
Thread.sleep(6000);
//不释放锁
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class ThreadB extends Thread{
@Override
public void run() {
try{
System.out.println(" b run begin timer="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(" b run end timer="+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void bService(){
System.out.println("打印了bService timer="+System.currentTimeMillis());
}
}
static class ThreadC extends Thread{
private ThreadB threadB;
public ThreadC(ThreadB threadB) {
this.threadB = threadB;
}
@Override
public void run() {
threadB.bService();
}
}
public static void main(String[] args) {
try{
ThreadB b = new ThreadB();
ThreadA a = new ThreadA(b);
a.start();
Thread.sleep(1000);
ThreadC c = new ThreadC(b);
c.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
由于线程 ThreadA 在使用Thread.sleep(6000)方法时一致持有ThreadB对象的锁,时间达到6s,所以线程ThreadC只有在ThreadA释放ThreadB的锁时,才可以调用ThreadB中的同步方法synchronized public void bService()。
下面将继续验证 join() 方法释放锁的特点。
创建实验用的项目 join_sleep_2,将join_sleep_1中的所有代码复制到join_sleep_2项目中,更改ThreadA类中的代码,如下:
static class ThreadA extends Thread{
private ThreadB threadB;
public ThreadA(ThreadB threadB) {
this.threadB = threadB;
}
@Override
public void run() {
try{
synchronized (threadB){
threadB.start();
threadB.join();//执行join()方法的一瞬间,b立即释放锁。
for (int i = 0; i < Integer.MAX_VALUE; i++) {
String newString = new String();
Math.random();
}
//不释放锁
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行结果如图:
由于线程ThreadA释放了ThreadB的锁,所以线程ThreadC可以调用 ThreadB 中的同步方法 synchronized public void bService()。
另外还需要注意的一点,join()无参方法或join(time)有参方法一旦执行,说明源代码中的wait(time)已经被执行,也就证明锁立即被释放,仅仅在指定的 join(time) 时间后当前线程才会继续运行。
6 方法join()后的代码提前运行
测试前文的代码可能遇到陷阱,稍有不注意就有可能掉进坑里。
public class joinMore {
static class ThreadA extends Thread {
private ThreadB threadB;
public ThreadA(ThreadB threadB) {
this.threadB = threadB;
}
@Override
public void run() {
try {
synchronized (threadB) {
System.out.println("begin A ThreadName="
+ Thread.currentThread().getName() + " "
+ System.currentTimeMillis());
Thread.sleep(500);
System.out.println(" end A threadName="
+ Thread.currentThread().getName() + " "
+ System.currentTimeMillis());
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
static class ThreadB extends Thread {
@Override
synchronized public void run() {
try {
System.out.println("begin B ThreadName="
+ Thread.currentThread().getName() + " "
+ System.currentTimeMillis());
Thread.sleep(500);
System.out.println(" end B ThreadName="
+ Thread.currentThread().getName() + " "
+ System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
ThreadB b = new ThreadB();
ThreadA a = new ThreadA(b);
a.start();
b.start();
b.join(200);
System.out.println(" main end " + System.currentTimeMillis());
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行后,在控制台中打印结果可能如图1:
也可能图2:
为什么会出现不同的运行结果呢?
修改main()函数:
public static void main(String[] args) {
ThreadB b = new ThreadB();
ThreadA a = new ThreadA(b);
a.start();
b.start();
System.out.println(" main end=" + System.currentTimeMillis());
}
结果如图:
由此可见:多数时候 main end 是第一个打印的。所以可以得出如下结论:如果将System.out.println()方法当作join(200),join(200)大部分是可以先运行的,也就是先抢到ThreadB的锁,然后快速释放。但由于线程执行run()方法的随机性,ThreadA 和 B以及main都有可能先获得锁。
所以,图一:
join(200)先抢到b锁,释放执行内部wait(200)后立即释放, A抢到执行 打印begin sleep (500) 打印 end 后释放锁,执行完后wait(200)时间已到还剩300ms时,main和B争抢锁,main抢到后释放,B抢到,同时main和B又争抢PrintStream的锁main抢到打印后释放,B抢到打印。
同理,图二就是最后B先抢到PinrtStream的锁打印begin后释放,main抢到打印后,B再抢到打印end。
7 方法 join(long millis, int nanos) 的使用
源代码:
public final synchronized void join(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
join(millis);
}
方法等待的时间最长为millis毫秒+nanos纳秒,如果参数nanos<0或>999999就会出现异常。同时,这个等待时间也并非完全精确的,是在原先的基础上+1。
测试代码:
public class Test {
public static void main(String[] args) throws InterruptedException {
long begin = System.currentTimeMillis();
Thread.currentThread().join(2000,999999);
long endTime = System.currentTimeMillis();
System.out.println(endTime-begin);
}
}
程序显示方法join()耗时2001毫秒。
更多推荐
所有评论(0)