• 线程屏障CyclicBarrier实现原理


      生产环境中,存在需要等待多个线程都达到某种状态后,才继续运行的情景。并发工具CyclicBarrier就能够完成这种功能。本篇从源码方面,简要分析CyclicBarrier的实现原理。

    使用示例

    public class CyclicBarrierTest {
        public static void main(String[] args) {
            //屏障,阻拦3个线程
            CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
            
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程1正在执行");
                    try {
                        // 等待
                        cyclicBarrier.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程1运行结束,时间: " + System.currentTimeMillis());
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程2正在执行");
                    try {
                        // 等待
                        cyclicBarrier.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程2运行结束,时间: " + System.currentTimeMillis());
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程3正在执行");
                    try {
                        //线程3阻塞2秒,测试效果
                        Thread.sleep(2000);
                        // 等待
                        cyclicBarrier.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程3运行结束,时间: " + System.currentTimeMillis());
                }
            }).start();
            
        }
    }

      执行结果如下:

    线程1正在执行
    线程2正在执行
    线程3正在执行
    线程1运行结束,时间: 1550324116837
    线程3运行结束,时间: 1550324116837
    线程2运行结束,时间: 1550324116837

      可以看到线程1,2,3在同一个时间结束。

    源码分析

      主要成员:

    private final ReentrantLock lock = new ReentrantLock();
        
    private final Condition trip = lock.newCondition();
    
    private int count;

      CyclicBarrier主要借助重入锁ReentrantLock和Condition实现。count初始值等于CyclicBarrier实例化指明的等待线程数量,用于等待线程计数。

      

      主要方法await()

    public int await() throws InterruptedException, BrokenBarrierException {
            try {
                return dowait(false, 0L);
            } catch (TimeoutException toe) {
                throw new Error(toe); // cannot happen
            }
        }
    private int dowait(boolean timed, long nanos)
            throws InterruptedException, BrokenBarrierException,
                   TimeoutException {
            final ReentrantLock lock = this.lock;
            lock.lock();   // 1
            try {
                final Generation g = generation;
    
                if (g.broken)
                    throw new BrokenBarrierException();
    
                if (Thread.interrupted()) {
                    breakBarrier();
                    throw new InterruptedException();
                }
    
                int index = --count;   // 2
                if (index == 0) {    // 3
                    boolean ranAction = false;
                    try {
                        final Runnable command = barrierCommand;
                        if (command != null)
                            command.run();
                        ranAction = true;
                        nextGeneration();   // 4
                        return 0;
                    } finally {
                        if (!ranAction)
                            breakBarrier();   // 5
                    }
                }
    
                // loop until tripped, broken, interrupted, or timed out
                for (;;) {
                    try {
                        if (!timed)
                            trip.await();      // 6
                        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();     // 7
            }
        }
    1. 对当前对象加锁
    2. 每个线程获得锁,执行这部分代码时,都把count - 1,记做index
    3. 如果index为0,执行第4步,代表CyclicBarrier屏障已经拦截了足够数量(count)的线程,线程可以接着往下执行了。不为0,说明当前线程还没有达到屏障CyclicBarrier拦截的数量,执行第6步
    4. 调用nextGeneration()方法,唤醒所有等待线程
    5. breakBarrier()确保一定能执行唤醒动作
    6. 调用Condition的await()方法,将当前线程放入等待队列,释放锁
    7. 一定执行的释放锁动作。

      nextGeneration()的代码如下:

    private void nextGeneration() {
            // signal completion of last generation
            trip.signalAll();
            // set up next generation
            count = parties;
            generation = new Generation();
        }

      使用Condition的signalAll()方法,唤醒全部等待线程

      说完CyclicBarrier的原理之后,再对本篇的使用示例做一下描述:

    1. 线程1开始执行,调用await()方法,获得锁。此时count为3,count--,故count为2,index为2,调用Condition.await()方法,线程1进入等待队列,释放锁
    2. 线程2开始执行,过程与第一步相同,只是count减为1
    3. 线程3开始执行,获得锁,count减为0,达到拦截数量,调用nextGeneration()方法唤醒全部线程,释放自己持有的锁
    4. 线程1,2都被唤醒,根据锁竞争结果,依次执行完await()方法,最后释放锁
    5. 3个线程再往下执行自己的run()方法

    异常分析:

      假设调用cyclicBarrier.await()进行等待的线程数大于屏障CyclicBarrier实例化时声明的拦截数,会发生什么情况呢?

      例如如下代码:

    public static void main(String[] args) {
            //屏障,阻拦3个线程
            CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
            
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程1正在执行");
                    try {
                        // 等待
                        cyclicBarrier.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程1运行结束,时间: " + System.currentTimeMillis());
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程2正在执行");
                    try {
                        // 等待
                        cyclicBarrier.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程2运行结束,时间: " + System.currentTimeMillis());
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程3正在执行");
                    try {
                        //线程3阻塞2秒,测试效果
    //                    Thread.sleep(2000);
                        // 等待
                        cyclicBarrier.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程3运行结束,时间: " + System.currentTimeMillis());
                }
            }).start();
            
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程4正在执行");
                    try {
                        // 等待
                        cyclicBarrier.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程4运行结束,时间: " + System.currentTimeMillis());
                }
            }).start();
        }

      调用cyclicBarrier.await()方法等待的线程一共4个,CyclicBarrier声明只拦截3个。

      上述用例将导致一个线程得不到执行,处于等待状态。

      分析一下原因:

        在CyclicBarrier的dowait()方法215行(JDK1.8)中,只有在index == 0,也就是CyclicBarrier拦截到了实例化时指明的线程数量时,才会调用Condition.signalAll()唤醒等待线程。所以在第4个线程进入此方法时,index减为-1,会调用Condition.await()开始等待。这样就没有线程能执行唤醒逻辑了,它将一直处于等待状态。

  • 相关阅读:
    SQL Server CTE 递归查询全解(转载)
    ASP.NET Core MVC中Controller的Action,默认既支持HttpGet,又支持HttpPost
    ASP.NET Core 使用外部登陆提供程序登陆的流程,以及身份认证的流程 (转载)
    SQL Server中比较末尾带有空格的字符串遇到的坑 (转载)
    ASP.NET Core如何设置请求超时时间
    ADO.NET的Connection Timeout和Command Timeout (转载)
    风尘浪子 只要肯努力,梦想总有一天会实现 WF工作流与Web服务的相互调用 —— 通过Web服务调用Workflow工作流(开发持久化工作流) _转
    WPF学习资源整理
    WCF 学习笔记
    WorkFlow 工作流 学习笔记
  • 原文地址:https://www.cnblogs.com/sunshine-ground-poems/p/10388355.html
Copyright © 2020-2023  润新知