老子说过,“天下大事必做于细,天下难事必做于易”。人要想有所作为,首先得从细微之处入手,从简单的事情做起。中国前国家队足球教练米卢也曾经说过:“态度决定一切。”,“态度决定一切,细节决定成败”确实是至理名言,在生活中、工作中,学习中,为人处事,都应该端正态度,注重细节,从小事做起,从身边做起。我们只有树立正确的态度,做好了细节,并且坚持下来,才容易成功。
继续总结多线程同步常用的方法或者类,之前介绍了CountDownLatch,CyclicBarriar和Exchanger和Phaser,这次介绍一个比较有名的类–Semaphore,大多数人看到它都不会陌生,但是要问怎么正确的使用它,很多人却回答不上来。
Semaphore–信号量
Semaphore的一个特别典型的应用场景是:线程执行是需要耗费CPU的时间片的,并且占用CPU的资源,例如上下文切换,如果不对线程数量进行控制,CPU工作时同时并发的线程数量可能会非常多,这样就会导致每个线程运行起来非常的缓慢,严重影响到系统的执行效率,它的工作效率将大幅下降。因此,此时我们就可以用信号量来进行控制,从而让CPU能够发挥出最大的效率。
1、定义
Semaphore中文翻译为信号量,它用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用公共资源。在创建Semaphore时会设置令牌,这里的令牌就可以看做允许访问特定资源的线程数量,通过设置它来控制线程数量,这个令牌可能是1,也可能是大于1的任意数字。哪个线程拿到(acquire)了令牌,它就可以去执行了,如果没有令牌则需要等待。 线程执行完毕,一定要归还(release)令牌,否则令牌会被用光,别的线程就无法获得令牌而只能永久等待。
可以这样形象的来理解,
Semaphore对应的功能就类似于公司只有一个厕所,而厕所里仅仅有3个茅坑,如果这时候有10个人都要想上厕所,那么显而易见同时只能有3个人能够占用这些茅坑,其他7个人只能在厕所门口等待,当有人从厕所出来后,才能再进入一个人。
2、常用方法
Semaphore的方法比较多, 我们只介绍一些最常用的方法,有兴趣想深入学习的可以去找相关资料再深入学习。
● Semaphore(int permits)
新建一个Semaphore对象,参数permits是初始化设置许可的数量,即令牌的数量。它表示最多允许permits个线程执行acquire()与release()之间的代码。
● Semaphore(int permits, boolean fair)
新建一个Semaphore对象,参数permits是初始化设置许可的数量,即令牌的数量。参数fair表明创建的Semaphore对象是公平信号量(fair为true)还是非公平信号量(fair为false)。所谓公平信号量表明获得锁的顺序与线程启动的顺序有关,启动早的线程优先获得令牌,但并不代表100%的能够获得信号量,仅仅是在概率上能够优先获得。而非公平信号量就是和公平无关的,线程无论谁先启动,这个参数都无效。
● acquire(int permits)
每调用一次该方法,则消耗permits个许可。
● acquire()
每调用一次该方法,则消耗一个许可。
● release()
每调用一次该方法,相当于许可加1。
● release(int permits)
每调用一次该方法,相当于加上permits个许可。在实际开发中,acquire()与release()成对出现,以保证每个线程用时消耗,用完释放。
● acquireUninterruptibly()
使得等待进入acquile()方法的线程不允许被中断。
● acquireUninterruptibly(int permits)
使得等待进入acquile()方法的线程不允许被中断,且获得锁后,将消耗permits个许可。
● availablePermits()
返回此Semaphore对象当前可用的许可数,此方法通常用于调试,因为许可的数量有可能在实时改变,并不是固定的数量。
● drainPermits()
返回此Semaphore对象当前可用的许可数,并将可用许可清零。
3、演示代码
public class TestSemaphore {
public static void main(String[] args) {
// 线程池
ExecutorService exec = Executors.newCachedThreadPool();
// 只能3个茅坑同时使用
final Semaphore semp = new Semaphore(3);
// 模拟10个人同时访问厕所
for (int index = 0; index < 10; index++) {
final int NO = index;
Runnable run = new Runnable() {
public void run() {
try {
// 获取空余的茅坑
semp.acquire();
System.out.println(“get a noun: “ + NO);
Thread.sleep((long) (Math.random() * 10000));
//使用完后,释放茅坑
semp.release();
System.out.println(“release,availablePermits : “+semp.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
exec.execute(run);
}
// 线程池退出
exec.shutdown();
}
}
执行结果如下:
get a noun: 0
get a noun: 2
get a noun: 1
release,availablePermits : 0
get a noun: 9
release,availablePermits : 1
get a noun: 8
release,availablePermits : 1
get a noun: 6
release,availablePermits : 1
get a noun: 4
release,availablePermits : 1
get a noun: 3
release,availablePermits : 1
get a noun: 5
release,availablePermits : 1
get a noun: 7
release,availablePermits : 1
4、总结
说实话,本司机开发了十几年程序了,Semaphore使用的情况还真的是很少,可以说几乎没有,但是这也不妨碍我们学习它,对它进行了解,说不定哪天就遇到了使用它的场景,到时我们能够立即想到用它来解决就OK了。另一方面学习它也扩展了我们的知识面,不至于在别人谈论时,我们还不知道它是一个什么东东。