一、Semaphore (信号量)
说简单点,Semaphore维护了一个许可集合,在创建Semaphore的时候,设置上许可数,每条线程在只有在获得一个许可的时候才可以继续往下执行逻辑(申请一个许可,则Semaphore的许可池中减少一个许可),没有获得许可的线程会进入阻塞状态。
举个栗子:
public static void main(String[] args) {
//创建一个Semaphore 有5条许可
final Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 10; i++) {
final int finalI = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
//申请一个许可 许可池-1 若许可池为0则申请许可失败,阻塞线程
semaphore.acquire();
System.out.print(finalI);
//模仿耗时操作
Thread.sleep(1000);
//释放一个许可 许可池+1
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
然后看log的打印:
12-19 11:06:52.720 13842-13978/lbx.myapplication E/lbxlog: 0
12-19 11:06:52.720 13842-13981/lbx.myapplication E/lbxlog: 3
12-19 11:06:52.720 13842-13979/lbx.myapplication E/lbxlog: 1
12-19 11:06:52.720 13842-13980/lbx.myapplication E/lbxlog: 2
12-19 11:06:52.720 13842-13982/lbx.myapplication E/lbxlog: 4
//这里注意一下 仔细对比时间
12-19 11:06:53.720 13842-13983/lbx.myapplication E/lbxlog: 5
12-19 11:06:53.720 13842-13985/lbx.myapplication E/lbxlog: 7
12-19 11:06:53.720 13842-13987/lbx.myapplication E/lbxlog: 9
12-19 11:06:53.720 13842-13986/lbx.myapplication E/lbxlog: 8
12-19 11:06:53.720 13842-13984/lbx.myapplication E/lbxlog: 6
看了上面的log,发现前五个是52秒左右打印出来的,而后五个是53秒左右打印出来的,因为许可池里只有5个许可,前五条线程把5个许可占用了,后五条线程需要等到有许可后才会继续执行逻辑。
接下来介绍几个常用的方法:
//释放1个许可
semaphore.release();
//使acquire()后正在等待的线程不可被终止
semaphore.acquireUninterruptibly();
//获取当前可用的许可数量,并把可用的许可数置0
semaphore.drainPermits();
//是否有正在等待的线程
semaphore.hasQueuedThreads();
//获取正在等待的线程数量
semaphore.getQueueLength();
//获取当前可用的许可数量
semaphore.availablePermits();
二、信号量的另一种形式:非公平信号量
非公平信号量,先运行的线程不一定可以申请到许可,后运行的线程不一定不可以申请到许可。
//创建5个公平信号量
Semaphore semaphore = new Semaphore(5,true);
//尝试获得许可,并返回获取结果 if(acquireSuc)...else...
boolean acquireSuc = semaphore.tryAcquire();
boolean acquireSuc = semaphore.tryAcquire(2);
//在3秒内尝试获得1个许可,并返回获取结果
boolean acquireSuc = semaphore.tryAcquire(3, TimeUnit.SECONDS);
//在3秒内尝试获得2个许可,并返回获取结果
boolean acquireSuc = semaphore.tryAcquire(2, 3, TimeUnit.SECONDS);
三、CountDownLatch的应用
这个东西和信号量正相反,CountDownLatch的内部维护着一个计数器,同时只有一个线程才可对计数器进行操作,当计数器为0的时候,释放所有阻塞的线程。
举个栗子:
public static void main(String[] args) {
//4条线程
int threadCount = 4;
//创建一个拥有5个计数器的countDownLatch
final CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < threadCount; i++) {
new Thread(new Runnable() {
@Override
public void run() {
//使计数器-1
countDownLatch.countDown();
System.out.println("计数器还剩" + countDownLatch.getCount());
}
}).start();
}
try {
//在这里阻塞 只有计数器为0的时候这行代码才会释放
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行完成啦!");
}
log:
12-19 11:52:25.350 22929-23074/lbx.myapplication I/System.out: 计数器还剩4
12-19 11:52:25.350 22929-23073/lbx.myapplication I/System.out: 计数器还剩3
12-19 11:52:25.350 22929-23075/lbx.myapplication I/System.out: 计数器还剩2
12-19 11:52:25.350 22929-23076/lbx.myapplication I/System.out: 计数器还剩1
因为计数器没有到0,所以“执行完成”这句话没有打印,将线程改成5条:
//5线程
int threadCount = 5;
log:
12-19 11:58:46.570 30347-30471/lbx.myapplication I/System.out: 计数器还剩4
12-19 11:58:46.570 30347-30473/lbx.myapplication I/System.out: 计数器还剩3
12-19 11:58:46.570 30347-30472/lbx.myapplication I/System.out: 计数器还剩2
12-19 11:58:46.570 30347-30474/lbx.myapplication I/System.out: 计数器还剩1
12-19 11:58:46.570 30347-30475/lbx.myapplication I/System.out: 计数器还剩0
12-19 11:58:46.570 30347-30347/lbx.myapplication I/System.out: 执行完成啦!
这时候完整的流程就执行完了。常用方法:
//使线程进入阻塞状态,计数为0的时候释放线程
countDownLatch.await();
//使线程进入阻塞状态,计数为0的时候释放线程或者阻塞到3秒的时候自动释放线程
countDownLatch.await(3, TimeUnit.SECONDS);
//使计数减1
countDownLatch.countDown();
//获取当前的计数
countDownLatch.getCount();