CyclicBarrier
该类是一个同步的辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程(就是线程数的确定)的程序中,这些线程必须不时地互相等待。
我们设置这个平衡点的作用,就是保持所有线程同时到达。对于失败的同步尝试,CyclicBarrier 使用了一种要么全部要么全不 (all-or-none) 的破坏模式:如果因为中断、失败或者超时等原因,导致线程过早地离开了屏障点,那么在该屏障点等待的其他所有线程也将通过 BrokenBarrierException(如果它们几乎同时被中断,则用 InterruptedException)以反常的方式离开。
简单示例
private static final int threadnumber = 4;
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(threadnumber);
//创建一个新的 CyclicBarrier,它将在给定数量的参与者
//(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。
for(int i = 0;i<4;i++) {
Thread t = new Thread(new Test105(). new Mythread(cb));
t.setName("线程"+i);
t.start();
}
}
class Mythread implements Runnable{
CyclicBarrier bc;
public Mythread(CyclicBarrier bc) {
super();
this.bc = bc;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("我是"+Thread.currentThread().getName()+"开始工作啦");
try {
Thread.sleep(5000);//在这里模拟业务
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("我是"+Thread.currentThread().getName()+"工作结束啦");
try {
bc.await();//当之前的线程如果不是所有的读执行完成,那么已执行完毕的会在这等待。全部执行结束才会执行下面操作。
// 在所有参与者都已经在此 barrier上,调用 await 方法之前,将一直等待。
} catch (InterruptedException | BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("啦啦啦,所有线程都操作好啦");
}
}
运行结果
await方法
每次调用 await() 都将返回能到达屏障处的线程的索引。然后,您可以选择哪个线程应该执行屏障操作
该方法的返回值:- 1 指示将到达的第一个线程,零指示最后一个到达的线程
int index = bc.await();
if(index==0) {
System.out.println("我是最后达到的"+Thread.currentThread().getName()+"index:"+index);
}
我们还可以通过该类的另一个构造函数来实现。
基于上面的代码,我们做下面修改即可。
CyclicBarrier cb = new CyclicBarrier(threadnumber,new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("我是"+Thread.currentThread().getName()+"最后到达,做的屏障操作");
}
});
await指定超时时间
在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。如果指定时间到达,没有到达,那么会抛出异常,但是还是会继续下面的任务,不会终止程序。
为了模拟线程执行慢,我们让线程睡一会。
private static final int threadnumber = 4;
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(threadnumber,new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("我是"+Thread.currentThread().getName()+"最后到达,做的屏障操作");
}
});
//创建一个新的 CyclicBarrier,它将在给定数量的参与者
//(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。
for(int i = 0;i<4;i++) {
if(i==3) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Thread t = new Thread(new Test106(). new Mythread(cb));
t.setName("线程"+i);
t.start();
}
}
class Mythread implements Runnable{
CyclicBarrier bc;
public Mythread(CyclicBarrier bc) {
super();
this.bc = bc;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("我是"+Thread.currentThread().getName()+"开始工作啦");
try {
Thread.sleep(5000);//在这里模拟业务
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("我是"+Thread.currentThread().getName()+"工作结束啦");
try {
bc.await(2000,TimeUnit.MILLISECONDS);//在这里指定了时间,如果超出时间,
//没有到达barrier,会报超时异常,并且程序会执行完线程体的内容
//TimeUnit是个枚举类,TimeUnit 不维护时间信息,
//但是有助于组织和使用可能跨各种上下文单独维护的时间表示形式。
//毫微秒定义为千分之一微秒,微秒为千分之一毫秒,毫秒为千分之一秒,
//一分钟为六十秒,一小时为六十分钟,一天为二十四小时。
/* DAYS
HOURS
MICROSECONDS 微秒
MILLISECONDS 毫秒
MINUTES
NANOSECONDS 毫微
SECONDS*/
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("超时了");
}
System.out.println("啦啦啦,所有线程都操作好啦");
}
}
效果图:
CyclicBarrier的重用
因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier,但是这个有个前提,就是你之前几个线程使用的barrier,必须全部释放后(所有到达后),这是barrier在可以新的一次使用。
代码很简单,和单次使用barrier差不多,这里就不放了
CountDownLatch
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
重要方法
await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier,CyclicBarrier是不可以重用的。
await后面指定时间的话,那么超过时间,是不会抛出异常,还是一样的执行后面的任务。
下面是我测试的效果图:
这样根本达不到我们使用的要求,那么你这样使用有啥子意思。
简单使用
CountDownLatch cdl = new CountDownLatch(2);//指加计数的线程数有2个
for(int i = 0;i<2;i++) {
if(i==1) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Thread m = new Thread(new Test108().new MyThread(cdl));
m.setName("线程"+i);
m.start();
}
}
class MyThread implements Runnable{
CountDownLatch sdl = null;
public MyThread(CountDownLatch sdl) {
super();
this.sdl = sdl;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName()+"开始工作");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"工作完毕");
sdl.countDown();//执行任务好了之后,计数器就减1
try {
sdl.await();//如果计数器中的不为1,那么会阻塞在这边。
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("所有的都执行好了");
}
Semaphore
一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。
将信号量初始化为 1,使得它在使用时最多只有一个可用的许可,从而可用作一个相互排斥的锁。
此类的构造方法可选地接受一个公平参数。当设置为 false 时,此类不对线程获取许可的顺序做任何保证。特别地,闯入 是允许的,也就是说可以在已经等待的线程前为调用 acquire() 的线程分配一个许可,从逻辑上说,就是新线程将自己置于等待线程队列的头部。当公平设置为 true 时,信号量保证对于任何调用获取方法的线程而言,都按照处理它们调用这些方法的顺序(即先进先出;FIFO)来选择线程、获得许可。注意,FIFO 排序必然应用到这些方法内的指定内部执行点。所以,可能某个线程先于另一个线程调用了 acquire,但是却在该线程之后到达排序点,并且从方法返回时也类似。还要注意,非同步的 tryAcquire 方法不使用公平设置,而是使用任意可用的许可。
常用方法
acquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。
release()用来释放许可。注意,在释放许可之前,必须先获获得许可。
tryAcquire() 尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false。
简单例子
若一个工厂有5台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才能继续使用。那么我们就可以通过Semaphore来实现:
private int wokernumber = 8;
static Semaphore s = new Semaphore(5);
public static void main(String[] args) {
for(int i = 0;i<8;i++) {
Thread t = new Thread(new Test109().new Mythread(s));
t.setName("工人"+i);
t.start();
}
}
class Mythread implements Runnable{
Semaphore s = null;
public Mythread(Semaphore s) {
super();
this.s = s;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
s.acquire();
System.out.println("我是"+Thread.currentThread().getName()+"开始使用机器");
Thread.sleep(2000);
s.release();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println("我是"+Thread.currentThread().getName()+"使用完毕机器");
}
}
效果图:
这个类是可以用来设置连接数的,比如数据库的最大连接数。它主要保证了规定量的线程对资源操作。
总结
终于完成了这个三个类的学习,心情还是蛮激动,这只是一个开头,对他们稍微有了点认识,脑袋里对他们有了个概念,但是要说真正用好,还是不容易,结合业务逻辑,期待以后的提高。
CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;如果没执行,这个线程将一直等待,如果设置超时。
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。但是锁只可以一个对象操作,而Semaphore可以是几个。