Java并发包之CountDownLatch,Semaphore,CyclicBarrier用法

本文深入解析了CountDownLatch、Semaphore及CyclicBarrier三种并发控制工具的使用场景、实战案例及其内部工作原理,帮助读者掌握线程间同步的具体实现。

写在前面:

1,CountDownLatch

CountDownLatch计数器闭锁是一个能阻塞主线程,让其他线程满足特定条件下主线程再继续执行的线程同步工具。

Latch闭锁的意思,是一种同步的工具类。类似于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭着的,不允许任何线程通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。且当门打开了,就永远保持打开状态。

CountDowmLatch是一种灵活的闭锁实现,包含一个计数器,该计算器初始化为一个正数,表示需要等待事件的数量。countDown方法递减计数器,表示有一个事件发生,而await方法等待计数器到达0,表示所有需要等待的事情都已经完成。

用法:

用给定的计数初始化CountDownLatch,其含义是要被等待执行完的线程个数。

每次调用CountDown(),计数减1

主程序执行到await()函数会阻塞等待线程的执行,直到计数为0

常见案例:

1:多线程读取批量文件, 并且读取完成之后汇总处理

2:多线程读取Excel多个sheet,读取完成之后获取汇总获取的结果

3:多个人一起一起来吃饭,主人等待客人到来,客人一个个从不同地方来到饭店,主人需要等到所有人都到来之后,才能开饭

4:汽车站,所有乘客都从不同的地方赶到汽车站,必须等到所有乘客都到了,汽车才会出发,如果设置了超时等待,那么当某个时间点到了,汽车也出发

2,Semaphore

Semaphore是一个计数信号量,常用于限制可以访问某些资源(物理或逻辑的)线程数目。

jdk解释中的含义大概下面五点:

  • Semaphore是一个计数信号量。
  • 从概念上将,Semaphore包含一组许可证
  • 如果有需要的话,每个acquire()方法都会阻塞,直到获取一个可用的许可证
  • 每个release()方法都会释放持有许可证的线程,并且归还Semaphore一个可用的许可证
  • 然而,实际上并没有真实的许可证对象供线程使用,Semaphore只是对可用的数量进行管理维护。

 

3,CyclicBarrier

CyclicBarrier是java 5中引入的线程安全的组件叫循环屏障。它有一个barrier的概念,主要用来等待所有的线程都执行完毕,然后再去执行特定的操作。

它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程进入屏障通过CyclicBarrier的await()方法。

假如我们有很多个线程,每个线程都计算出了一些数据,然后我们需要等待所有的线程都执行完毕,再把各个线程计算出来的数据加起来,的到最终的结果,那么我们就可以使用CyclicBarrier。

 

 

一,CountDownLatch

1.1,实战使用

项目需求:

三人跑步,有的人快,有的人慢,最后一个人跑完才算结束。

看代码:

import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch latch = new CountDownLatch(3);
        Thread runner1 = new Mythread("豪子", latch);
        Thread runner2 = new Mythread("李子", latch);
        Thread runner3 = new Mythread("刚子", latch);
        System.out.println("开始比赛。。。");
        runner1.start();
        runner2.start();
        runner3.start();
        latch.await();
        System.out.println("比赛结束。。。");
    }

}

class Mythread extends Thread{

    private String runner;
    private CountDownLatch latch;
    public Mythread(String runner, CountDownLatch latch){
        this.runner = runner;
        this.latch = latch;
    }


    @Override
    public void run() {
        System.out.println(runner + "begin run");

        try {
            Thread.sleep(new Random().nextInt(4)*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(runner + "end run");
        latch.countDown();
    }
}

运行结果:

开始比赛。。。
豪子begin run
李子begin run
刚子begin run
刚子end run
李子end run
豪子end run
比赛结束。。。

到这里,不知道你理解CountDownLatch的用法没。

1.2,原理

AQS模型,共享模式锁。CAS操作state变量,等于0 表示无锁,即等待所有的锁结束。

这里,我们直接看三个api就好了。

①new CountDownLatch(3)

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

可以看到,这里面设置volatile 类型的state变量为3。表示目前有桑格锁占用该对象。

②latch.countDown();

public void countDown() {
        sync.releaseShared(1);
}
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
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;
            }
}

对state变量进行减一操作。表示共享模式锁少一个持有者。

③,latch.await();

private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

循环等待释放所有锁。

 

二,Semaphore

2.1,实战

项目需求:

五个人三条跑道,一条跑道每次只能跑一个人,一个人跑完,下个人才可以入场。

代码如下:

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;

public class SemaphoreTest {

    public static void main(String[] args) throws InterruptedException {

        Semaphore semaphore = new Semaphore(3);//非公平模式,即不是按照先来后到的顺序假如跑道
        CountDownLatch latch = new CountDownLatch(5);
        Thread runner1 = new MySemaPhorethread("李子", semaphore,latch);
        Thread runner2 = new MySemaPhorethread("刚子", semaphore,latch);
        Thread runner3 = new MySemaPhorethread("豪子", semaphore,latch);
        Thread runner4 = new MySemaPhorethread("明子", semaphore,latch);
        Thread runner5 = new MySemaPhorethread("丸子", semaphore,latch);
        System.out.println("开始比赛。。。");
        runner1.start();
        runner2.start();
        runner3.start();
        runner4.start();
        runner5.start();
        latch.await();
        System.out.println("比赛结束。。。");
    }


}


class MySemaPhorethread extends Thread{

    private String runner;
    private Semaphore semaphore;
    private CountDownLatch latch;
    public MySemaPhorethread(String runner, Semaphore semaphore,CountDownLatch latch){
        this.runner = runner;
        this.semaphore = semaphore;
        this.latch = latch;
    }


    @Override
    public void run() {

        try {
            semaphore.acquire(); // 等待获取跑到,准备跑步
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(runner + "begin run");
        try {
            Thread.sleep(new Random().nextInt(4)*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(runner + "end run");
        semaphore.release();// 跑完了,释放跑到
        latch.countDown();
    }
}

运行结果如下:

李子begin run
明子begin run
豪子begin run
李子end run
丸子begin run
豪子end run
刚子begin run
明子end run
刚子end run
丸子end run
比赛结束。。。

可以看到由于跑道资源只有三个,最多只有三个人 begin run.。当有一个人 end run的时候,才会有新的人加入跑到。这里面使用了 CountDownLatch来限制,5个人跑完才比赛结束。

2.2,原理

我们也是看三个api.

①,new Semaphore(3); //限制了三个资源,三个信号变量

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

两个构造函数,可以声明公平模式/非公平模式的信号变量。其底层还是AQS模型的共享节点state。

protected final void setState(int newState) {
     state = newState;
}

 

②,semaphore.acquire();  //等待获取跑道,准备跑步

当前线程尝试去阻塞的获取1个许可证。

此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:

  • 当前线程获取了1个可用的许可证,则会停止等待,继续执行。
  • 当前线程被中断,则会抛出InterruptedException异常,并停止等待,继续执行。

看看java底层代码:

final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
}

等资源充足,使用cas操作成功后,才停止阻塞,表示当前线程获得某种资源。

③,semaphore.release();// 跑完了,释放跑道

当前线程释放1个可用的许可证。

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

 

三,CyclicBarrier

3.1,实战

项目需求:

3个人跑步,最后一个人跑完,计算总用时。

代码:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;


public class CyclicBarrierTest {



    public static void main(String[] args) {

        List<Integer> times = new ArrayList<>();
        CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {

                Integer count = 0;
                for(Integer time:times){
                    count += time;
                }

                System.out.println("一共用时:" + count);
            }
        });

        Thread runner1 = new MyCyclicBarrierthread("豪子", barrier, times);
        Thread runner2 = new MyCyclicBarrierthread("李子", barrier, times);
        Thread runner3 = new MyCyclicBarrierthread("刚子", barrier, times);
        System.out.println("开始比赛。。。");
        runner1.start();
        runner2.start();
        runner3.start();

    }

}
class MyCyclicBarrierthread extends Thread{

    private String runner;
    private CyclicBarrier cyclicBarrier;
    private List<Integer> times;


    public MyCyclicBarrierthread(String runner, CyclicBarrier cyclicBarrier, List<Integer> times){
        this.runner = runner;
        this.cyclicBarrier = cyclicBarrier;
        this.times = times;
    }


    @Override
    public void run() {

        long s1 = System.currentTimeMillis();
        System.out.println(runner + "begin run");
        try {
            Thread.sleep(new Random().nextInt(4)*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(runner + "end run");
        long s2 = System.currentTimeMillis();
        times.add((int) (s2 - s1));
        try {
            cyclicBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

开始比赛。。。
豪子begin run
刚子begin run
李子begin run
豪子end run
刚子end run
李子end run
一共用时:9003

可以看到,在主线程内并没有使用类似countdownlatch的结束,等所有线程结束。而是使用循环屏障到达一定条件后自动触发。这点和countdownlatch有点类似。

 

3.2,原理

在CyclicBarrier的内部定义了一个Lock对象,每当一个线程调用CyclicBarrier的await方法时,将剩余拦截的线程数减1,然后判断剩余拦截数是否为0,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁,接着先从await方法返回,再从CyclicBarrier的await方法中返回。

①,先看看构造器:

    /** The lock for guarding barrier entry */
    private final ReentrantLock lock = new ReentrantLock();
public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

可以看到,类实例变量lock。

②,cyclicBarrier.await();

private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            final Generation g = generation;

            if (g.broken)
                throw new BrokenBarrierException();

            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

            int index = --count;
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        Thread.currentThread().interrupt();
                    }
                }

                if (g.broken)
                    throw new BrokenBarrierException();

                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

每当一个线程调用CyclicBarrier的await方法时,将剩余拦截的线程数减1,然后判断剩余拦截数是否为0,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁,接着先从await方法返回,再从CyclicBarrier的await方法中返回。

CyclicBarrier主要用于一组线程之间的相互等待,而CountDownLatch一般用于一组线程等待另一组些线程。实际上可以通过CountDownLatch的countDown()和await()来实现CyclicBarrier的功能。即 CountDownLatch中的countDown()+await() = CyclicBarrier中的await()。注意:在一个线程中先调用countDown(),然后调用await()。

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值