一、简单介绍
以下对join进行简单的介绍,相关示例的代码就不多加引用了。
大多情况下,主线程创建并启动子线程,如果子线程进行大量的耗时计算,则主线程往往早于子线程结束。这时如果主线程想等待子线程执行完成后再结束,就要用到join的方法。join作用是等待线程对象销毁。
在当前线程使用join方法时,若该线程被中断,则当前线程会出现异常
join与synchronized的区别:join再内部使用wait等待,synchronized使用的是对象监视器原理做同步。
join与sleep的区别:
Thread.sleep(long) 方法的作用是让当前执行的线程休眠据,且该线程不丢失任何监视器的所属权,即 sleep 方法并不释放锁,但是会让出 CPU 资源
而join方法是会释放锁的
二、join代码提前运行问题
public class TestThread {
public static void main(String[] args) {
try {
ThreadB b = new ThreadB();
ThreadA a = new ThreadA(b);
a.start();
b.start();
b.join(2000);
System.out.println("main end " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class RunFirst {
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());
}
}
class ThreadA extends Thread {
private ThreadB b;
public ThreadA(ThreadB b) {
this.b = b;
}
@Override
public void run() {
super.run();
try {
synchronized (b) {
System.out.println("begin A thread name = " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(" end A thread name = " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadB extends Thread {
@Override
synchronized public void run() {
super.run();
try {
System.out.println("begin B thread name = " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(" end B thread name = " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
分析完代码,我以为运行TestThread的主方法时,控制台打印的顺序应该是,先执行线程A的run方法,然后执行B方法,最后再执行主方法的打印。
而多次运行结果是
begin A thread name = Thread-1 1587651053517
end A thread name = Thread-1 1587651058517
main end 1587651058517
begin B thread name = Thread-0 1587651058517
end B thread name = Thread-0 1587651063517
接着多次执行RunFirst的主方法,结果为
main end = 1587651105291
begin A thread name = Thread-1 1587651105291
end A thread name = Thread-1 1587651110291
begin B thread name = Thread-0 1587651110291
end B thread name = Thread-0 1587651115292
主方法中的打印结果,往往都是先执行的。书本便给出了结论:方法join大部分是先运行的,即先抢到ThreadB的锁,然后快速进行释放。(说不出什么道理,但好像是这么一回事??)
这时再看join的源码。
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;
}
}
}
结合书本对TestThread主方法第二轮分析:
1、b.join先抢到了B锁,此时join中的while(isAlive)判断的是B线程是否允许,isAlive的结果为true,运行到了wait(delay)。这时释放B锁。
2、ThreadA抢到了锁,并打印,再次释放B锁。
3、这时join和threadB争抢锁,而join再次抢到锁,运行
now = System.currentTimeMillis() - base;
long delay = millis - now; if (delay <= 0) { break; }由于delay<=0故停止循环。
4、ThreadB抢到锁,打印结束。
那么如何才能让结果顺着我的第一个思路进行下去呢?
结合源码,这里假设t1为第一次运行到join的时间,则t1=base,假设t2为第二次运行到join的时间,则
t2=now(now = System.currentTimeMillis() - t1;),
要想先执行ThreadB中的内容,则需要delay的值大于0.故dealy=miles-now=miles-System.currentTimeMillis() + t1
可以通过改变ThreadA运行的时间改变System.currentTimeMillis()的值,也可以增大miles的值。
之后将ThreadA run方法中的sleep改为1000,结果如料想的一样。
以上均是个人理解,如果有误,或有描述不贴切的地方,希望能有大神指出。
参考资料:《Java多线程编程核心技术》