对比Thread.join()和CountDownLatch及原理解析

本文意在对比Thread.join()和CountDownLatch

一、引入

在main线程中启动100个线程,100个线程完成后,主线程打印“完成”,如何实现?

二、思考

关键点在于如何让主线程在其他100个线程执行完成之后再执行。

三、测试实验(JDK1.8)

1.使用join方式实现

 1 public static void main(String[] args) throws InterruptedException {
 2     for (int i = 0; i < 100; i++) {
 3         Thread t = new Thread(()-> {
 4             System.out.println("current thread name is " + Thread.currentThread().getName());
 5             System.out.println(Thread.currentThread().getId());
 6             //try {
 7                 //TimeUnit.SECONDS.sleep(2);
 8             //} catch (InterruptedException e) {
 9                 //e.printStackTrace();
10             //}
11         });
12         t.start();
13         //主线程中调用其他线程的join
14         t.join();
15     }
16     System.out.println("main completed");
17 }

输出结果(部分解图):可以看出等待100个线程都输出完成之后main线程才输出"main completed"。

2.使用CountDownLatch方式实现

 1 public static void main(String[] args) throws InterruptedException {
 2     CountDownLatch c = new CountDownLatch(100);
 3     for (int i = 0; i < 100; i++) {
 4         new Thread(() -> {
 5             System.out.println("current thread name is " + Thread.currentThread().getName());
 6             c.countDown();
 7         System.out.println("i:" + c.getCount());
 8         }).start();
 9     }
10     c.await();
11     System.out.println("main completed");
12 }

输出结果(部分截图):同样地,在100个线程输出完成之后main线程才输出。

四、结论分析

1.join()和CountDownLatch都能实现让当前线程阻塞等待直到其他线程执行完毕。

2.从结果图来看,上述实验使用join()每个线程都是顺序执行的,而CountDownLatch并没有顺序执行;

实际上,如果将CountDownLatch中//System.out.println("i:" + c.getCount());这行代码取消注释之后,可以看到输出的count值完全是乱序的,并且和System.out.println("current thread name is " + Thread.currentThread().getName());输出的结果也不保证原子性,更有就是甚至可能在main线程输出之后才输出。

而join测试中,将//System.out.println("i:" + c.getCount());取消注释之后,完全是按照线程顺序依次执行完成且main线程最后执行。

3.对于第2条按顺序执行,是因为在for循环中调用了t.join()方法,也就是每一次循环体,线程启动了之后不管有没有执行完成主线程都需要阻塞等待,并且上一个线程都执行完毕了之后才会执行下一个线程,其实这也就印证了执行下一次循环时主线程需要等待线程执行完毕。

五、源码分析

1.Thread.join() -> join(0)

源码:

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");
    }
    //如果传入的参数是0,一直阻塞直到isAlive==false,也就是线程正常执行完或者发生异常
    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

在当前线程调用了另外一个线程的join()时,实际上是调用了wait(),当线程正常执行完run方法中的代码时,JVM会调用exit()方法退出线程,而在exit()方法中调用了notifyAll(),唤醒处于等待状态的main线程。

2.CountDownLatch

源码:

//内部类,AQS框架
private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;
    
    //把传入的count作为状态值,Uses AQS state to represent count.
    Sync(int count) { 
        setState(count);
    }
    //获取状态值
    int getCount() {
        return getState();
    }
    //尝试获取同步状态,当count(state)已经减为0时返回1
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }
    //CAS自旋,尝试修改state的值,如果在尝试的过程中state为0,则直接返回false,不能再被修改;否则通过CAS的方式将state自减1
    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}
private final Sync sync;
//构造方法,用传入的count初始化Sync的同步状态值
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}
//中断等待
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
//调用这个方法尝试使count(state同步状态)减1
public void countDown() {
    sync.releaseShared(1);
}

使用CountDownLatch,每当线程调用countDown()时count减一(同步状态state),而主线程中调用await()时去判断state是否为0,如果为0表示所有的线程都已经完成,主线程可以继续运行,否则主线程只能继续阻塞等待。

六、结论

1.join()是Thread提供的方法,而CountDownLatch JUC包提供的并发类;

2.join()和CountDownLatch都能实现让当前线程阻塞等待直到其他线程执行完毕;

3.join()使用简单但是却很粗暴,而CountDownLatch粒度更具体,可以通过在run()中控制调用countDown()的位置控制粒度,而不需要等待线程run()中全部逻辑执行完之后才释放锁。所以在实际业务场景中,为了提高并发性能,通常会选用更加灵活的CountDownLatch实现;

4.虽然CountDownLatch更加灵活,但是也需要在使用时明确等待条件,否则main线程还是会await,造成不必要的性能开销。

posted @ 2020-08-28 14:23  沐先生forever  阅读(306)  评论(1)    收藏  举报