Java并发包中线程同步器之CountDownLatch的简单使用及原理简介

使用背景

在开发过程中经常会在主线程中去开多个线程并行执行任务,并且主线程需要等所有子线程执行完毕汇总的场景。

CountDownLatch

在CountDownLatch出现之前一般都采用join()方法来实现,但不够灵活。下面来一个使用案例:

package concurrentProgramming;

import java.util.concurrent.CountDownLatch;


public class CountDownLatchTest1 {
    
    
    //创建一个CountDownLatch实例,传入的参数为线程数
    private static volatile CountDownLatch countDownLatch = new CountDownLatch(2);

    public static void main(String[] args) throws InterruptedException {
    
    
        Thread threadOne = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    Thread.sleep(1000);
                    System.out.println("线程1执行完毕!");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    countDownLatch.countDown();
                }
            }
        });

        Thread threadTwo = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    Thread.sleep(1000);
                    System.out.println("线程2执行完毕!");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    countDownLatch.countDown();
                }
            }
        });

        threadOne.start();
        threadTwo.start();

        System.out.println("等待子线程执行完毕!");

        //等待子线程执行完毕后返回
        countDownLatch.await();

        System.out.println("所有线程执行完毕!");
    }
}

线程池管理线程

在实际开发过程中,由于避免直接操作线程,一般都是将线程交由线程池来管理。对以上代码改进为:

package concurrentProgramming;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatch2 {
    
    
    private static volatile CountDownLatch countDownLatch = new CountDownLatch(2);
    public static void main(String[] args) throws InterruptedException {
    
    
        //创建线程池管理线程
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        //将线程1添加到线程池中
        executorService.submit(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    Thread.sleep(1000);
                    System.out.println("线程1执行完毕!");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    countDownLatch.countDown();
                }
            }
        });

        //将线程二添加到线程池中
        executorService.submit(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    Thread.sleep(1000);
                    System.out.println("线程2执行完毕!");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    countDownLatch.countDown();
                }
            }
        });

        System.out.println("等待子线程执行完毕!");

        //等待子线程执行完毕后返回
        countDownLatch.await();

        System.out.println("所有线程执行完毕!");
        //关闭线程池
        executorService.shutdown();
    }
}

上述两段代码的运行结果:

等待子线程执行完毕!
线程2执行完毕!
线程1执行完毕!
所有线程执行完毕!

原理探究

查看源码可以得出CountDownLatch是使用AQS实现的。通过构造函数,你会发现,实际上是把计数器的值赋值给了AQS的state变量,AQS的state用来表示计数器的值。

public class CountDownLatch {
    
    

    private static final class Sync extends AbstractQueuedSynchronizer {
    
    
      
    }
 }

构造函数源代码:

    public CountDownLatch(int count) {
    
    
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

void await()方法

当线程调用CountDownLatch对象的await() 方法后,当前线程会被阻塞,直到一下两种情况才返回:CountDownLatch的计数器为0;其他线程调用了当前线程的中断方法。

    public void await() throws InterruptedException {
    
    
        sync.acquireSharedInterruptibly(1);
    }
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
    
    
        if (Thread.interrupted())//被中断
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)//计数器已经为0
            doAcquireSharedInterruptibly(arg);
    }

void countDown()

线程调用该方法后,计数器的值递减,递减后如果计数器值为0,则唤醒所有因调用await方法而被阻塞的线程。

    public void countDown() {
    
    
        sync.releaseShared(1);
    }

    public final boolean releaseShared(int arg) {
    
    
        if (tryReleaseShared(arg)) {
    
    
            doReleaseShared();
            return true;
        }
        return false;
    }

Sync类中的 tryReleaseShared方法:

 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;
            }
        }

从源代码可以看到,在进行值递减的时候其实用的是CAS来保证计数器的同步的。

小结

CountDownLatch相比join()方法使用起来更加灵活和方便。当线程调用await方法之后,会加入AQS的阻塞队列,只有当计数器为0 的时候,才会调用AQS的方法来激活因为调用await方法阻塞的线程。

猜你喜欢

转载自blog.csdn.net/weixin_42643321/article/details/108506935