上一篇介绍了CountDownLatch和CyclicBarrier的使用,本篇介绍下另外一个阻塞工具Semaphore,有点类似CountDownLatch和CyclicBarrier,它也能实现线程阻塞,也能计数,但是更像是一种线程调度,具体看一下使用方法吧。
构造方法
public Semaphore(int permits) {//permits代表最多允许多少个线程同时访问
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {// fail表示是否公平,先等待的优先
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
它有几个主要的方法
public void acquire() throws InterruptedException { } //获取一个许可
public void acquire(int permits) throws InterruptedException { } //获取permits个许可
public void release() { } //释放一个许可
public void release(int permits) { } //释放permits个许可
使用的时候,先调用acquire()获取许可,如果当前没有可用的许可,则一直等待,直到有可用的许可。
1、简单用法
比如笔者现在到饭点了,假如说公司食堂有5个座位,公司有8个人,这样的话同时最多支持5个人就餐,那剩下的人只能等着,有人吃完饭后,让出座位,让其他人吃饭。
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(5);// 总共有5个座位
for (int i = 0; i < 8; i++) {// 总共有8个人需要吃饭
People people = new People(semaphore, i);
people.start();
}
}
static class People extends Thread {
Semaphore semaphore;
int id;
public People(Semaphore semaphore, int id) {
this.semaphore = semaphore;
this.id = id;
}
@Override
public void run() {
try {
this.semaphore.acquire();
System.out.println("have dinner begin " + id);
sleep((long) (Math.random() * 5000));//模拟线程耗时各不不同
System.out.println("have dinner end ****" + id);
this.semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
have dinner begin 0
have dinner begin 3
have dinner begin 4
have dinner begin 2
have dinner begin 1
have dinner end ****0
have dinner begin 5
have dinner end ****1
have dinner begin 6
have dinner end ****3
have dinner begin 7
have dinner end ****6
have dinner end ****4
have dinner end ****2
have dinner end ****7
have dinner end ****5
这样,只要有线程让出资源,立马就有线程补上去。
2、需要多个资源
当调用acquire(int permits)方法,permits>1的时候,就是需要一次占用多个资源,比如上面的demo,如果是线程内部是这样:
this.semaphore.acquire(5);
System.out.println("have dinner begin " + id);
sleep((long) (Math.random() * 5000));// 模拟线程耗时各不不同
System.out.println("have dinner end ****" + id);
this.semaphore.release(5);
执行结果:
have dinner begin 0
have dinner end ****0
have dinner begin 1
have dinner end ****1
have dinner begin 2
have dinner end ****2
have dinner begin 3
have dinner end ****3
have dinner begin 4
have dinner end ****4
have dinner begin 5
...
可能有的人比较胖,一次占了5个座位(餐厅座位是有多小啊),这样一次只能有一个人吃饭了。
3、acquire(int permits)和release(int permits)不一样
3.1 release个数大于acquire个数
this.semaphore.acquire(1);
System.out.println("have dinner begin " + id);
sleep((long) (Math.random() * 5000));// 模拟线程耗时各不不同
System.out.println("have dinner end ****" + id);
this.semaphore.release(3);
System.out.println("可用的座位数:" + this.semaphore.availablePermits());
执行结果:
have dinner begin 0
have dinner begin 3
have dinner begin 2
have dinner begin 1
have dinner begin 4
have dinner end ****0
可用的座位数:3
have dinner begin 5
have dinner begin 7
have dinner begin 6
have dinner end ****3
可用的座位数:3
have dinner end ****5
可用的座位数:6
have dinner end ****1
可用的座位数:9
have dinner end ****4
可用的座位数:12
have dinner end ****7
可用的座位数:15
have dinner end ****2
可用的座位数:18
have dinner end ****6
可用的座位数:21
最终又扩容了,本来是8个座位,最后每个人吃完饭走的时候,释放了自己的座位,又多创造了2个座位,最终变成21个座位了
3.2 release个数小于acquire个数
this.semaphore.acquire(3);
System.out.println("have dinner begin " + id);
sleep((long) (Math.random() * 5000));// 模拟线程耗时各不不同
System.out.println("have dinner end ****" + id);
this.semaphore.release(1);
System.out.println("可用的座位数:" + this.semaphore.availablePermits());
执行结果:
由于每次占用3个座位,用完后,释放一个座位,另外2个座位本来不用了,但是没有释放,那么别人也用不了,造成资源浪费,同时也影响往下执行。
总结:
Semaphore能锁住资源,比较适合在资源固定,使用者较多的时候,也就是僧多粥少的情况下使用,另外,acquire个数和release个数最好一致。