【线程 锁】栅栏 CyclicBarrier的构造函数入参parties详解

1. 目的

本文是解答之前的文章《Java并发编程实战】5.5.4 栅栏 CyclicBarrier》“4.1 构造函数”章节中遗留的问题:

CyclicBarrier barrier = new CyclicBarrier(int parties);

parties参数:当parties值大于线程总数或小于线程总数时,是什么情况呢?

2. parties值等于线程数的场景

我们改造《Java并发编程实战】5.5.4 栅栏 CyclicBarrier》中的CyclicBarrierDemo.java,这次不涉及重入的代码,我们重新演示parties值等于线程数的场景:

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

public class CyclicBarrierDemo2 {
    static class TaskThread extends Thread {

        CyclicBarrier barrier;

        public TaskThread(CyclicBarrier barrier) {
            this.barrier = barrier;
        }

        @Override
        public void run() {
            
            try {
                // 模拟线程在不同时刻到执行点(栅栏)
                Thread.sleep(new Random().nextInt(5000));
                System.out.println(Thread.currentThread().getName() + "开始写入数据...");
                barrier.await(); // 通过调用await方法在此处设置栅栏,使得线程执行到此进入阻塞状态等待其他线程执行完成。
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "写入数据完毕!");
        }

        public static void main(String[] args) {
            // 创建线程数
            int threadNum = 5;
            // 线程数和满足冲破栅栏的线程相同,方便理解
            CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {

                @Override
                public void run() {
                    // 当最后一个线程进入栅栏时,触发冲破栅栏,会额外执行run方法体
                    System.out.println(Thread.currentThread().getName() + " 完成最后任务");
                }
            });

            for (int i = 0; i < threadNum; i++) {
                new TaskThread(barrier).start();
            }
        }

    }
}

执行结果,比较简单,不再分析了:

Thread-2开始写入数据...
Thread-3开始写入数据...
Thread-1开始写入数据...
Thread-0开始写入数据...
Thread-4开始写入数据...
Thread-4 完成最后任务
Thread-4写入数据完毕!
Thread-2写入数据完毕!
Thread-3写入数据完毕!
Thread-1写入数据完毕!
Thread-0写入数据完毕!

3. parties值≠线程数

我将通过一个小故事来说明这个问题。

故事是这样的,有这样一场骑马比赛,有5名骑手,他们各自拥有自己的赛道,并且每条赛道上在开赛之前就已经预设好了一个栅栏,注意只有一个栅栏,栅栏很长,覆盖所有的赛道。

另外还有一名栅栏管理员和一名指挥官,指挥官只负责向管理员发号施令,他只会告诉管理员一个数字n。这个数字的意思是,管理员要打开的栅栏的数量,前提是他必须看到有n个骑手都到达栅栏前,才会将这n个骑手面前的栅栏打开。当这n个骑手通过栅栏后,会立即重新竖起栅栏,然后重复着检查的职责。

于是管理会记着这个数字n在栅栏旁守着,当有1名、2名…骑手到达栅栏时,管理员不予理会,直到有第n名骑手到达栅栏处时,管理员统一打开栅栏放行,骑手们得以同时继续骑行,而管理员则完成了任务。

如果指挥官告诉管理员的数字是5,那么一切和平的进行,不会发生任何问题。但是如果指挥官,告诉管理员的数字不是5,比如说4或者6。这样问题就产生了,对于这两种情况,我们分别来分析一下:

3.1 parties值大于线程数的场景

指挥官给出的数字n大于骑手数量,这里的n对应parties的值,我们假设为6(parties=6),结论是会一直阻塞等待。

我前边说过,每条赛道上在开赛之前都预设好了栅栏,那么此时管理从指挥官那里得到的数字是6,因此管理员要执行的任务就是看着栅栏,直到第1名骑手到达,第2名骑手到达,第3名…直到管理员看见第6名骑手到达时才进行统一放行,但是问题是,总共的参赛选手只有5名啊!这下好了,管理员是个死心眼,他不看见第6名骑手他是绝对不会进行放行的,那么大家可以想象一下,5名骑手被卡在栅栏前不能继续后面的比赛,而管理员在那里痴痴的等着第6名骑手的到来,就这样一直到天荒地老…

修改parties=6,执行结果:

Thread-0开始写入数据...
Thread-1开始写入数据...
Thread-2开始写入数据...
Thread-3开始写入数据...
Thread-4开始写入数据...

从结果来看,5个线程一直在等待,永远不会冲破栅栏

3.2 parties值小于线程数的场景

指挥官给出的数字小于骑手数量,这里我们假设是4(parties=4),结论是前四个线程会先等待,然后冲破栅栏,多余的那个线程A最后永远等待,原因是第二重循环,线程A实际上重入栅栏,此时等同于仅存在一个线程,而栅栏的parties值为4,这不就是3.1节的场景吗?

还像刚才一样,管理拿到数字4后便在栅栏旁等着,直到看见骑手的相继到来,第1名,第2名,第3名,第4名!管理员看见第4名骑手到来后,立即打开他们各自赛道前的栅栏,于是这4名骑手继续后面的比赛,管理员也完成了自己的任务,重新竖起栅栏…等等!还有第5名骑手呢!第5名骑手到达栅栏前惊奇的发现,其他骑手都被放行了,但是他的赛道上还有栅栏,他不得不等下等着管理员放行,但是郁闷的是,他并不知道管理员再也不会打开栅栏了,因为管理员要等到集齐4个骑手才会放行,于是他就这样孤独的一直等待着…

试想,如果骑手有100的话,parties=4, 那么就会每四组,产生一次循环,一共25次

修改parties=4,执行结果:

Thread-2开始写入数据...   
Thread-1开始写入数据...
Thread-4开始写入数据...
Thread-0开始写入数据...  //前四个线程依次进入等待状态
Thread-0 完成最后任务    //前四个中的最后一个线程触发barrierAction
Thread-0写入数据完毕!
Thread-2写入数据完毕!
Thread-1写入数据完毕!
Thread-4写入数据完毕!     //前四个线程依次冲破栅栏
Thread-3开始写入数据...   //最后的线程A进入阻塞,并一直阻塞下去





参考:
戏说java多线程之CyclicBarrier(循环栅栏)的CyclicBarrier(int parties)构造方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值