您的位置:首页 > 其它

Thread join方法细探

2017-01-20 00:00 155 查看

join方法的实现

首先join是Thread的成员函数,假设thread是代表一个线程实例,那么thread.join()的意思就是让当前线程(调用thread.join的线程)等待thread的线程死亡之后才能再次获得执行的机会。thread.join(timeout)的意思是让当前线程(调用thread.join(timeout)的线程)执行timeout毫秒之后才能再一次获得执行机会。
因为join()调用的是join(0),所以我们来看一下Thread#join(long timeout)代码:

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");
}
//Flag
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

注意看一下Flag标注的代码,isAlive()也是Thread的成员函数,通过isAlive()判断thread线程是否还活在。当前线程还活在就调用wait(0),wait(0)是Object的成员函数,就是让当前线程等待notify为止。wait(0)的原理是通过锁的获取与释放来实现的。要调用wait(0)一定持有锁。我们看join方法是synchronized修饰的,所以wait(0)获取的锁是Thread线程实例thread的this锁。
现在问题就变为调用thread.join()的线程获取了thread的this锁,然后通过wait释放锁等待重新获取锁。

现在问题来了,我们在join方法中没有看到任何notify,notifyAll操作,那么调用thread.join()的线程什么时候重新获取锁呢?答案是JVM在线程结束的时候会调用void JavaThread::exit(bool destroy_vm, ExitType exit_type)(在虚拟机中实现的)方法,这个方法中会执行notifyAll操作。所以调用thread.join()的线程会等到thread线程死了之后才能执行。

join的应用

那么join的应用场景呢?考虑下面的代码:

public class JoinTest {

static int sum = 0;

static class ThreadA extends Thread
{
public ThreadA(){}
public ThreadA(String name)
{
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
for(int i=1;i<=100;i++)
sum+=i;
}
}

public static void main(String[] args) {
ThreadA threadA = new ThreadA("threadA");
threadA.start();
System.out.println(sum);
}

}

代码本意是想执行threadA线程执行从1到100的叠加操作,然后输出结果。但是输出的结果一般情况下是被正确的,多数情况下是0。这是什么原因呢?是因为threadA.start()只是告诉jvm线程可以执行,但是并不一定马上执行,threadA.start()是在main线程中执行的,然后main线程接着执行 System.out.println(sum),因为大多数时候还没有开始执行threadA线程,所以打印的sum为0。

当然你可以通过Thread.sleep(timeout)来让main线程等待ThreadA线程执行,但是main线程应该等待多久呢?显然这样的实现是不够优雅的。

当然你可以通过CountDownLatch方式来实现:

import java.util.concurrent.CountDownLatch;

public class JoinTest {

private static CountDownLatch latch = new CountDownLatch(1);

static int sum = 0;

static class ThreadA extends Thread
{
public ThreadA(){}
public ThreadA(String name)
{
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
for(int i=1;i<=100;i++)
sum+=i;
latch.countDown();
}
}

public static void main(String[] args) {
ThreadA threadA = new ThreadA("threadA");
threadA.start();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sum);
}

}

当然对于这种只是简单的一个线程等另一个线程结束,那么前面结束的join方法就是一个不错的选择:

public class JoinTest {

static int sum = 0;

static class ThreadA extends Thread
{
@Override
public void run() {
for(int i=1;i<=100;i++)
sum+=i;
}
}

public static void main(String[] args) {
ThreadA threadA = new ThreadA();
threadA.start();
try {
threadA.join(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sum);
}

}

总结

join方法是Thread类的synchronized实例方法,所以拿到Thread实例的线程调用Thread实例的锁,然后释放Thread实例的锁,然后等待其他线程通知可以重新获取Thread实例的锁,但是没有线程会notify通过join的wait操作,所以只有等Thread实例线程本身死亡的时候jvm调用notifyAll来唤醒在Thread线程上执行join操作的线程。注意把Thread实例和线程区分清楚。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息