java多线程学习之【CyclicBarrier】

目前卷文化盛行,为了增强面试能力,开始了无边无际的学习,无边界不是重点,重点是要深入1万米。言归正传,本文打算做一个多线程学习的系列文章,沉淀自我。


前言

本文主要是讲解CyclicBarrier的概念,基本用法,使用场景,底层代码原理剖析。

一、CyclicBarrier是什么?

按照源码注释,翻译成中文,就是以下内容:

  • 让一组线程都等待对方达到一个共同的障碍点。
  • 循环障碍在程序涉及到固定大小的线程偶尔必须互相等待是有用的。
  • 障碍叫做循环,因为在等待线程被释放后它可以重复使用。
  • 循环障碍支持一个可选的Runnable命令,在最后一个线程后到达,但是在任何线程被释放前,每运行一次障碍点。 在任何线程继续运行之前,这个动作Runnable更新共享状态是有用的。
  • CyclicBarrier对于失败同步尝试使用all-or-none breakage model:如果一个线程离开障碍点过早中断,失败,或超时,所有其他等待障碍点线程也会异常通过离开。

二、使用方法

CyclicBarrier 提供了一些方法:

方法说明
await()使当前线程进入同步队列进行等待,并且值减1
await(long timeout, TimeUnit unit)带超时时间的await()。
getParties()返回需要跳过栅栏的线程数量,它就是初始化构造函数的parties值。
getNumberWaiting()返回目前在栅栏边等待的线程数量,它是一个变化的值。
reset()重置初始状态的障碍。如果有线程在栅栏边等待会抛出异常BrokenBarrierException

三、应用场景

主要用于多线程等待,Runnable命令执行后,多线程执行直到释放。

3.1并发分解模式

代码如下


package com.valley.juc.tools.cyclicbarrier;

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

/**
 * @author valley
 * @date 2022/6/16
 * @Description TODO
 */
public class Solver {

    final int N;
    final float[][] data;
    final CyclicBarrier barrier;

    public static void main(String[] args) {
        float[][] matrix={{1.2f,1.3f,1.4f,1.5f},{1.2f,1.7f,1.4f,1.5f},{3.2f,1.3f,1.4f,9.5f},{2.2f,2.3f,2.4f,2.5f}};
//        float[][] matrix2=new float[][]{{1.2f,1.3f,1.4f,1.5f},{2.2f,2.3f,2.4f,2.5f}};

        Solver solver =new Solver(matrix);

    }

    class Worker implements Runnable {
        int myRow;

        Worker(int row) {
            myRow = row;
        }

        public void run() {
            while (!done()) {
                processRow(myRow);
                try {
                    barrier.await();
                } catch (InterruptedException ex) {
                    return;
                } catch (BrokenBarrierException ex) {
                    return;
                }finally {
                    System.out.println("我是"+myRow+"号工人,开始干活。。。");
                    return;
                }
            }
        }

        private void processRow(int myRow) {
            System.out.println("我是"+myRow+"号工人,进场了。。。");
        }

        private boolean done() {
            return false;
        }
    }

    public Solver(float[][] matrix) {
        data = matrix;
        N = matrix.length;
        Runnable barrierAction =
                new Runnable() {
                    public void run() {
                        mergeRows(data);
                    }
                };
        barrier = new CyclicBarrier(N, barrierAction);

        List<Thread> threads = new ArrayList<Thread>(N);
        for (int i = 0; i < N; i++) {
            Thread thread = new Thread(new Worker(i));
            threads.add(thread);
            thread.start();
        }

        // wait until done
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    private void mergeRows(float[][] data) {
        System.out.println("工人们准备就绪开始干活了");
    }
}


四、源码剖析

CyclicBarrier 的源码在JUC并发工具中,相对复杂点:

  • 使用ReentrantLock构建lock,也就是构建了栅栏,ReentrantLock底层基于AbstractQueuedSynchronizer 来实现的。
  • CyclicBarrier是通过Lock的Condition实现的,每个CyclicBarrier对应个Lock锁和该锁的condition条件。创建CyclicBarrier时设置一个count计数,当调用await()时做两件事:将count-1 ;将线程阻塞并构造成结点加入condition条件队列。当count变为0时,达到等待线程数量要求,condition将条件队列中的线程全部唤醒。
    private final ReentrantLock lock = new ReentrantLock();

 abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
        abstract void lock();

        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        // Methods relayed from outer class

        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked() {
            return getState() != 0;
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }
  • CyclicBarrier 构造函数中指定的count和可选的Runnable命令;
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }
    public CyclicBarrier(int parties) {
        this(parties, null);
    }
  • await(),等待直到其他线程都到达栅栏,并且每执行一次会将全局变量count减去1直到等于0。
        public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }
/**
     * Main barrier code, covering the various policies.
     */
    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();
        }
    }
  • reset(),加锁操作构建新一代。

    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            breakBarrier();   // break the current generation
            nextGeneration(); // start a new generation
        } finally {
            lock.unlock();
        }
    }
 

五、和其他工具比较

  • CountDownLatch与CyclicBarrier
  • CountDownLatch和CyclicBarrier都能够实现线程之间的等待,区别如下:
  • CountDownLatch一般用于一个或多个线程,等待其他线程执行完任务后再执行,而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
  • CountDownLatch计数减为0后不能重用;而CyclicBarrier可置0后复用。

总结

主要讲解CyclicBarrier,后面重点讲ReentrantLock和AQS。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

blackoon88

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值