seamphore大家玩的都比较多,使用起来也很简单,获取令牌和释放,但是其中坑却不少,而且会让人很难发现,希望能通俗易懂的小例子讲明白其中的几个道理。


一、线程都被阻塞了?

public class demo2 {static Semaphore semaphore = new Semaphore(1);public static void method1() {try {System.out.println(Thread.currentThread().getName()+" ,当前等待队列的线程数" + semaphore.getQueueLength());semaphore.acquire(1);for (; ; ) {//模拟长业务}} catch (InterruptedException e) {System.out.println("get semaphore interrupted...");} finally {semaphore.release(1);System.out.println(Thread.currentThread().getName() + "线程走了,可用数量 " + semaphore.availablePermits());}}public static void main(String[] args) {new Thread(demo2::method1).start();new Thread(demo2::method1).start();new Thread(demo2::method1).start();new Thread(demo2::method1).start();new Thread(demo2::method1).start();new Thread(demo2::method1).start();}
}


现象:
执行完上面的代码,我们可以发现,程序没停止且大量的线程都被阻塞在队列中了。

原因:
因为acquire具有阻塞性,会将获取不到令牌的线程阻塞在队列中,而在生产中,我们的业务如果有大量的任务要跑,很可以产生大量的任务挤压在队列,最后导致oom;解决方案也很简单,就是用tryAcquire来代替,获取不到立刻(或者执行时间内返回)

二、谁动了我的令牌?

public class demo {static Semaphore semaphore = new Semaphore(1);public static void method1(int i) {try {boolean b = semaphore.tryAcquire(1);System.out.println(Thread.currentThread().getName() + "线程尝试获取 ..结果为:" + b);if (b) {System.out.println(Thread.currentThread().getName() + "线程进来了,当前可用数量 " + semaphore.availablePermits());TimeUnit.SECONDS.sleep(i);}} catch (InterruptedException e) {System.out.println("get semaphore interrupted...");} finally {semaphore.release(1);System.out.println(Thread.currentThread().getName() + "线程走了,可用数量 " + semaphore.availablePermits());}}public static void main(String[] args) {new Thread(() -> demo.method1(1)).start(); // 线程1,sleep 1snew Thread(() -> demo.method1(0)).start();  // 线程2, no sleep}
}


现象:
正常我们release操作都会在finally里,但是执行完上面的代码,我们可以发现,令牌数量惊奇的变成了2,比我们的初始值1还多。

原因:
线程1虽然没有获取锁,但是还是会执行了finally里的release操作,而release操作只会将state(AQS的同步值)+1,即不会和线程绑定,也不会去判断state的值有没有超过初始值的大小,所以令牌数量被无情的增加了。

方案1: 普通的if判断

方案2: Seamphore类进行增强(增强的方法,根据需要)

原理:存储获取令牌的线程,释放的时候判断线程有没有获取过令牌

public class SafeSemaphore extends Semaphore {private static final Object object = new Object();private ConcurrentHashMap<Thread, Object> threadSet;public SafeSemaphore(int permits) {super(permits);threadSet = new ConcurrentHashMap<>(permits);}@Overridepublic boolean tryAcquire(int permits)  {if (super.tryAcquire(permits)) {threadSet.put(Thread.currentThread(), object);return true;}return false;}@Overridepublic void release(int permits) {final Thread thread = Thread.currentThread();if (threadSet.containsKey(thread)) {super.release(permits);threadSet.remove(thread);}}public SafeSemaphore(int permits, boolean fair) {super(permits, fair);threadSet.put(Thread.currentThread(), object);}@Overridepublic void acquire() throws InterruptedException {super.acquire();threadSet.put(Thread.currentThread(), object);}@Overridepublic void release() {final Thread thread = Thread.currentThread();if (threadSet.containsKey(thread)) {super.release();threadSet.remove(thread);}}@Overridepublic boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException {if (super.tryAcquire(timeout, unit)) {threadSet.put(Thread.currentThread(), object);return true;}return false;}}

三、怎么永远到轮不到我?

public class demo4 {public static void main(String[] args) throws InterruptedException {Semaphore semaphore = new Semaphore(2);new Thread(new MyRunnable(1, semaphore), "thread-A").start();new Thread(new MyRunnable(2, semaphore), "thread-C").start();}static class MyRunnable implements Runnable {private int n;private Semaphore semaphore;public MyRunnable(int n, Semaphore semaphore) {this.n = n;this.semaphore = semaphore;}@Overridepublic void run() {try {semaphore.acquire(n);System.out.println("剩余可用许可证: " + semaphore.drainPermits());} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release(n);System.out.println(Thread.currentThread().getName() + "释放。。。。");}}}}

现象:
我们发现线程C永远得不到执行,于是开始了思考。。。。

我们尝试把C线程改成如下代码,也就是只获取一个令牌,却正常执行了,难道是令牌数量在作怪?

恍然大悟:
线程A虽然获取了1个释放了1个,但是注意drainPermits这个方法的作用是获取剩余令牌并且清空剩余令牌,因此获取剩余1个可用令牌后,可用令牌为0了,如果线程C需要一个令牌那么等A执行完了释放了就可以执行,看起来一切正常,但是当线程c需要大于等二个令牌的时候,即使A释放了也满足不了C,(因为原来的令牌被清空了)导致线程C一直无法执行,而阻塞,所以我们应该使用availablePermit获取剩余可用令牌,而不是drainPermits。

验证

四、总结

1.尽量使用tryAcquire 避免阻塞
2.释放操作放在finally中,一定要判断是否获取过信号量
3.获取可用令牌数区分availablePermitsdrainPermits的区别

Semaphore的注意点相关推荐

  1. Java并发编程之CountDownLatch、CyclicBarrier和Semaphore

    前言 本文为对CountDownLatch.CyclicBarrier.Semaphore的整理使用 CountDownLatch CountDownLatch类位于java.util.concurr ...

  2. java 多线程 信号_Java多线程——Semaphore信号灯

    Semaphore [ˈseməfɔːr]可以维护当前访问自身的线程个数,并提供了同步机制.使用Semaphore可以控制同时访问资源的线程个数(即允许n个任务同时访问这个资源),例如,实现一个文件允 ...

  3. linux内核 semaphore,2.4内核里semaphore源码的一个疑问

    博主你好, 请教一个问题. __down()里面有一段代码,  我觉得不那么保险.我先把__down的源码贴出来: ========================================== ...

  4. JAVA中的并发工具 -- CountDownLatch、CyclicBarrier、Semaphore

    2019独角兽企业重金招聘Python工程师标准>>> CountDownLatch CountDownLatch允许一个或多个线程等待其他线程完成操作. CountDownLatc ...

  5. 信号量Semaphore一篇文章叫你明白

    已经习惯了阿里面试官的冷笑:用过Semaphore吧,不妨说说? 本质就是 信号量模型,模型图如下: 其中的 计数器 和 等待队列 对外部是透明的,仅能通过提供的三大方法访问它们. 详细说说哪三大方法 ...

  6. 别再和面试官说不懂信号量Semaphore了!

    已经习惯了阿里面试官的冷笑:用过Semaphore吧,不妨说说? 本质就是 信号量模型,模型图如下: 其中的 计数器 和 等待队列 对外部是透明的,仅能通过提供的三大方法访问它们. 详细说说哪三大方法 ...

  7. 15.并发工具类(解析hashtable,ConcurrentHashMap1.7与1.8的区别以及Semaphore)

    3. 并发工具类 3.1 并发工具类-Hashtable Hashtable出现的原因:在集合类中HashMap是比较常用的集合对象,但是HashMap在多线程环境下可能会出现线程不安全的情况,为了保 ...

  8. LeetCode 1115. Print FooBar Alternately--多线程并发问题--Java解法--CyclicBarrier, synchronized, Semaphore 信号量

    此文首发于我的个人博客:zhang0peter的个人博客 LeetCode题解专栏:LeetCode题解 LeetCode 所有题目总结:LeetCode 所有题目总结 题目地址:Print FooB ...

  9. Semaphore(信号量)

    Semaphore是什么 Semaphore通常我们叫它信号量,可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源. 使用场景 通常用于哪些资源有明确访问数量限制的场景,常 ...

  10. Redisson 分布式锁源码 11:Semaphore 和 CountDownLatch

    前言 Redisson 除了提供了分布式锁之外,还额外提供了同步组件,Semaphore 和 CountDownLatch. Semaphore 意思就是在分布式场景下,只有 3 个凭证,也就意味着同 ...

最新文章

  1. 二元查找树变双向链表
  2. 设置弹性框项目之间距离的更好方法
  3. python基础知识填空-Python基础知识练习题(一)
  4. ABAP-DOI技术的优化
  5. [sh]rm -rf*的防护和普通用户执行命令
  6. ABAP 后台作业的一个状态查询工具
  7. [蓝桥杯]字符串对比-模拟
  8. OJ1065: 统计数字字符的个数(C语言)
  9. docker启动停止操作命令
  10. Oracle ERP权限管理
  11. 时尚达人必备的潮流壁纸桌面!
  12. 阅读笔记16-架构师推荐:提高90%开发效率的工具推荐
  13. 利用StringUtils工具类进行String为空的判断
  14. duilib加载资源
  15. excel-自定义函数及使用
  16. Burp新手抓包教程(HTTPS抓包)
  17. java生成pdf合同
  18. note2便携式WLAN热点开启后没连接时自动关闭时长在哪设置
  19. 计算机安装xp蓝屏怎么办,xp系统装win7系统蓝屏怎么办
  20. 地级市面板数据一(2000-2019):国民经济核算+人口结构+各行业从业人员(stata版)

热门文章

  1. 企业网管管理员、桌面运维 系统管理员 有出路吗?
  2. B-样条曲线教程(B-spline Curves Notes)目录
  3. python_正则表达式、正则函数、正则计算器
  4. 云服务如何搭建数据库_云服务器怎么配置数据库
  5. windows mobile 设备连接
  6. 基于墨刀的视频剪辑及分享APP
  7. 新老主播怎么做?需要注意的那几点直播内容和技巧分析问题
  8. gopro6 连接电脑_如何将外部麦克风连接到GoPro
  9. python编辑游戏脚本_用PYTHON做一个简单的游戏脚本(基础,详细)
  10. sdnuoj1105(椭圆面积 注意输出格式)