CountDownLatch、CyclicBarrier和 Semaphore都属于线程控制器。其有不同的使用场景
CountDownLatch像是一个计数器,适用于一个线程要等待其它线程执行完之后才继续执行的场景,计数器为0后只能重新构造,不可重用:
有以下方法:
CountDownLatch(int count) : 指定线程控制量,即要等待几个线程完毕(准确的说是要执行几次countDown使得count为0)之后等待线程才能继续执行
void countDown():每次执行count减1,可以作为线程的最后一个步表示当前线程执行完毕
void await():等待其它线程执行完毕(count为0)。如果count不为0则一直等待下去直到count为0或线程被中断。
boolean await(long timeout, TimeUnit unit):等待其它线程执行完毕(count为0),参数为超时时间,即超过这个时间等待线程不在等待,任由还没执行完毕的线程继续执行。
其具有返回值。如果是count为0导致的等待结束返回true。超时导致的await结束返回false。
long getCount():当前还在执行的线程数(count)
值得注意的是所谓的线程执行完毕是需要通过countDown知会CountDownLatch。也就是说await是否继续等待的标准是count是否为0而不是其它线程执行状态,如果不在其它线程执行完毕时间执行countDown,那么等待线程就会一直等待下去或超时或等待线程被中断。
通过一个小例子来简单表示CountDownLatch的简单使用:
package sync;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
public class CountDownLatchTest {
public static void main(String[] args) {
final CountDownLatch cd= new CountDownLatch(2); // 要控制的线程数,即需要countDown的次数
Callable<String> c1 = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("This Is Callable1");
Thread.sleep(5000);
System.out.println("c.countDown");
cd.countDown(); // 初始count数减1
return "The ruturn of Callable1";
}
};
FutureTask<String> task1 = new FutureTask<>(c1);
final Thread t1 = new Thread(task1);
Callable<String> c2 = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("This Is Callable2");
cd.countDown();
System.out.println("cd count : " + cd.getCount()); // task1在等待,这里已经countDown,因此这里是1
Thread.sleep(1000);
System.out.println("中断task1");
t1.interrupt(); // 另一个线程中断await依然会等待下去
return "The return of Exception Callable2";
}
};
FutureTask<String> task2 = new FutureTask<>(c2);
try {
t1.start();
Thread.sleep(500); // 睡眠500保证task1进入sleep状态
new Thread(task2).start();
System.out.println("开始await");
long ts = System.currentTimeMillis();
System.out.println(cd.await(3, TimeUnit.SECONDS)); // 只有3个条件能使await停止等待:1.所控制线程执行完毕,即getCount等于0时
// 2.超时 3.主线程(await所在的线程)被结束,他所控制的线程如果在执行countDown之前被中断await也不会管,
// 他依然会等待下去(await只认countDown与超时,你是否被中断他不管),直到超时。
// count为0导致的等待结束返回true否则返回false
long te = System.currentTimeMillis();
System.out.println("等待时间:" + (te - ts));
System.out.println("END");
} catch (InterruptedException e) {
System.out.println("Exception");
}
}
}
运行结果:
This Is Callable1
开始await
This Is Callable2
cd count : 1
中断task1
false
等待时间:3003
END
CyclicBarrier:可重复使用的线程控制器、适用于多个线程需到达某个状态之后在同时开始执行的场景,就好似有道屏障,只有在屏障处等待的线程到指定数量这个屏障才会打开:
含有以下方法:
CyclicBarrier(int parties, Runnable barrierAction):构造方法,parties标识要控制的线程数,即当多少个线程在屏障处等待时就放行。barrierAction为需要在放行是同步执行的操作。
CyclicBarrier(int parties):构造方法,parties标识要控制的线程数,即当多少个线程在屏障处等待时就放行。
int getParties():获得要控制的线程数,即当多少个线程在屏障处等待时就放行。
int await():等待屏障处线程达到数量
int await(long timeout, TimeUnit unit):等待屏障处线程达到数量 带超时时间
boolean isBroken():屏障是否已经被破坏 true为已被破坏
void reset():CyclicBarrier可重复使用并不代表屏障每开启一次就要调这个方法一次。每当屏障处到达指定数量的等待线程,屏障会开启并自动关闭等待下一波线程。无需每开启一次就重置一次。这个方法使屏障恢复初始状态,不过如果再重置时有线程在屏障处等待,则抛出BrokenBarrierException.
int getNumberWaiting():获得当前在屏障处等待的线程数
通过一个小例子来简单表示CyclicBarrier的简单使用:
package sync;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class CyclicBarrierTest {
public static void main(String[] args) {
Runnable allAwaitRun = new Runnable() {
@Override
public void run() {
System.out.println("大家都执行完毕了,准备下一步工作喽");
}
};
final CyclicBarrier c = new CyclicBarrier(3, allAwaitRun); // 参数二为屏障放行之后要启的线程
// 如果你实际有3个线程但是屏障设置为2,那么先到的2个会被屏障放行,
//最后一个屏障并不知道也不去管,导致最后一个到的一直等待或者超时报TimeoutException
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("线程1开始进行第一步工作...");
try {
Thread.sleep(500); // 等待模式耗时操作
System.out.println("线程1第一步工作完毕,进入等待状态...当前等待线程量 - " + c.getNumberWaiting());
try {
System.out.println("r1.await - " + c.await(2, TimeUnit.SECONDS)); // await返回该线程到达屏障的顺序,最快的为n-1,最慢的为0
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
System.out.println("线程1通过屏障");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println("线程2开始进行第一步工作...");
try {
Thread.sleep(1000);// 等待模式耗时操作
System.out.println("c2 - " + c.isBroken());
System.out.println("线程2第一步工作完毕,进入等待状态...当前等待线程量 - " + c.getNumberWaiting());
try {
System.out.println("r2.await - " + c.await(2, TimeUnit.SECONDS));
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
System.out.println("线程2通过屏障");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable r3 = new Runnable() {
@Override
public void run() {
System.out.println("线程3开始进行第一步工作...");
try {
Thread.sleep(1500);// 等待模式耗时操作
// Thread.sleep(5000);// 等待模式耗时操作 5s的话显而易见的会报超时,由先到屏障处即await返回值为n-1的那个线程抛出TimeoutException
//超时异常一经抛出,屏障被破坏,因此会导致其他已在屏障处的等待线程await抛出 BrokenBarrierException.
// (依据对这个异常的处理(catch还是throws(Callable可抛出异常))来判定线程是继续执行还是抛出停止)
//但是对线程3因为此线程延迟5s,所以此时还没到屏障处
// 所以会继续执行,直至到达屏障处发现屏障已被破坏,抛出BrokenBarrierException
System.out.println("线程3第一步工作完毕,进入等待状态...当前等待线程量 - " + c.getNumberWaiting());
try {
System.out.println("r3.await - " + c.await(2, TimeUnit.SECONDS));
} catch (BrokenBarrierException e) {
System.out.println("e - " + c.isBroken()); // 设置sleep(5000)时屏障被击穿 这里为true
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
System.out.println("线程3通过屏障");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
new Thread(r1).start();
new Thread(r2).start();
new Thread(r3).start();
System.out.println("c.getParties() - " + c.getParties()); // CyclicBarrier掌控的线程数
c.reset(); // 重置,如果此时有线程在屏障处等待,那么抛出BrokenBarrierException
}
}
运行结果:
线程1开始进行第一步工作...
线程3开始进行第一步工作...
c.getParties() - 3
线程2开始进行第一步工作...
线程1第一步工作完毕,进入等待状态...当前等待线程量 - 0
c2 - false
线程2第一步工作完毕,进入等待状态...当前等待线程量 - 1
线程3第一步工作完毕,进入等待状态...当前等待线程量 - 2
大家都执行完毕了,准备下一步工作喽
r3.await - 0
线程3通过屏障
r1.await - 2
线程1通过屏障
r2.await - 1
线程2通过屏障
Semaphore:线程多于资源的线程控制器。很像锁同步,和锁同步不同的是锁锁定一个资源,同时只能有一个线程操作这个资源。而Semaphore则是锁定一批资源。同时只允许指定数目的线程执行操作。
也与线程池有一定相似。不同的是线程池等待的线程未运行,而这个线程已经在运行并且在争夺。
适用于资源量小于线程量的情况。比如一个连接只允许同时有10个线程连接,现在有100个线程要执行这个连接操作,那么就可以用Semaphore进行控制。
其内含有以下方法:
Semaphore(int permits):构造方法,入参为许可量,即同时允许几个线程动作。默认为非公平的
Semaphore(int permits, boolean fair):构造方法入参一为许可量,入参二为是否公平竞争 true为公平
void acquire() throws InterruptedException:去获得一个许可,获得不到就一直等。等待过程中被中断报InterruptedException
void acquireUninterruptibly():去获得一个许可,获得不到就一直等,无视中断命令
boolean tryAcquire(): 尝试获得一个许可,如果获得就返回true,没获得就返回false
boolean tryAcquire(long timeout, TimeUnit unit): 在指定时间里尝试获得一个许可,如果获得就返回true,没获得就返回false
void release(): 释放一个许可,无需一定持有许可。即没有持有许可依然可以发出许可释放指令
void acquire(int permits) throws InterruptedException:去获得指定数目的许可,获得不到就一直等。等待过程中被中断报InterruptedException
void acquireUninterruptibly(int permits):去获得指定数目的许可,获得不到就一直等,无视中断命令
boolean tryAcquire(int permits):去获得指定数目的许可,如果获得就返回true,没获得就返回false
boolean tryAcquire(int permits, long timeout, TimeUnit unit):在指定时间里尝试获得指定数目的许可,如果获得就返回true,没获得就返回false
void release(int permits): 释放指定数目的许可,无需一定持有许可。
int availablePermits(): 当前可用许可量
int drainPermits():获得所有的许可并返回获得的许可量
boolean isFair():是否为公平竞争
final boolean hasQueuedThreads():当前是否有线程在竞争
final int getQueueLength():当前竞争队列的长度
通过一个小例子来简单表示Semaphore的简单使用:
package sync;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
public static void main(String[] args) {
final Semaphore s = new Semaphore(2); // 入参为2则为正常的3任务调2个许可流程,为1可验证无需获得许可便可释放许可
Runnable r1 = new Runnable() {
@Override
public void run() {
try {
System.out.println("1号请求资源 - " + showNow());
s.acquire();
System.out.println("1号获得资源 - " + showNow());
System.out.println("1号开始工作 - " + showNow());
Thread.sleep(5000);
System.out.println("1号工作完毕,开始释放资源 - " + showNow());
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
try {
// Thread.sleep(200); // 保证执行顺序是1.2.3
System.out.println("2号请求资源 - " + showNow());
s.acquire(); // 如果把这个地方注释掉,并打开保证释放顺序的代码并设置许可为一个,运行会发现1号先持有资源工作,然后2无需获得许可就可释放许可,使得3在1未执行完毕释放许可时便获得许可开始执行
System.out.println("2号获得资源 - " + showNow());
System.out.println("2号开始工作 - " + showNow());
Thread.sleep(1000);
System.out.println("2号工作完毕,开始释放资源 - " + showNow());
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable r3 = new Runnable() {
@Override
public void run() {
try {
// Thread.sleep(500);// 保证执行顺序是1.2.3
System.out.println("3号请求资源 - " + showNow());
s.acquire();
System.out.println("3号获得资源 - " + showNow());
System.out.println("3号开始工作 - " + showNow());
Thread.sleep(1000);
System.out.println("3号工作完毕,开始释放资源 - " + showNow());
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
new Thread(r1).start();
new Thread(r2).start();
new Thread(r3).start();
}
private static String showNow(){
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
return sdf.format(new Date());
}
}
运行结果:
2号请求资源 - 17:23:48.107
1号请求资源 - 17:23:48.107
2号获得资源 - 17:23:48.107
3号请求资源 - 17:23:48.107
2号开始工作 - 17:23:48.107
1号获得资源 - 17:23:48.107
1号开始工作 - 17:23:48.107
2号工作完毕,开始释放资源 - 17:23:49.107
3号获得资源 - 17:23:49.108
3号开始工作 - 17:23:49.108
3号工作完毕,开始释放资源 - 17:23:50.108
1号工作完毕,开始释放资源 - 17:23:53.107