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)构造方法