Semaphore的注意点

2023-05-16


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 1s
        new 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);
    }

    @Override
    public boolean tryAcquire(int permits)  {
        if (super.tryAcquire(permits)) {
            threadSet.put(Thread.currentThread(), object);
            return true;
        }
        return false;
    }

    @Override
    public 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);
    }

    @Override
    public void acquire() throws InterruptedException {
        super.acquire();
        threadSet.put(Thread.currentThread(), object);
    }

    @Override
    public void release() {
        final Thread thread = Thread.currentThread();
        if (threadSet.containsKey(thread)) {
            super.release();
            threadSet.remove(thread);
        }
    }


    @Override
    public 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;
        }
        @Override
        public 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的区别

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Semaphore的注意点 的相关文章

  • Linux复习: semaphore.h信号量和生产者消费者

    点击查看demo代码 demo运行结果如图 借用网上的一段话 在线程世界里 xff0c 生产者就是生产数据的线程 xff0c 消费者就是消费数据的线程 在多线程开发当中 xff0c 如果生产者处理速度很快 xff0c 而消费者处理速度很慢
  • linux系统编程:线程同步-信号量(semaphore)

    线程同步 信号量 semaphore 生产者与消费者问题再思考 在实际生活中 只要有商品 消费者就可以消费 这没问题 但生产者的生产并不是无限的 例如 仓库是有限的 原材料是有限的 生产指标受消费指标限制等等 为了进一步 解决好生产者与消费
  • notifier chain — 内核通知链

    大多数内核子系统都是相互独立的 因此某个子系统可能对其它子系统产生的事件感兴趣 为了满足这个需求 也即是让某个子系统在发生某个事件时通知其它的子系统 Linux内核提供了通知链的机制 通知链表只能够在内核的子系统之间使用 而不能够在内核与用
  • Java多线程通信-Semaphore(信号量)

    一 semaphone 信号量 Semaphone 信号量 是一个同步工具类 用来控制同时访问某个资源的线程数量 还可以用来实现某些资源池 或者给容器添加边界 Semaphone管理着一组 虚拟 的许可 permit 许可的初始数量可通过构
  • 尝试捕获信号量的正确方法

    将信号量操作包装在 try catch 块中的正确方法是什么 如果获取操作在获取一定数量 但不是全部 请求的许可后被中断 会发生什么情况 你怎么知道要再次释放多少个 发布是否应该在 最终 块中进行 但是如果操作被中断 您是否可能会发布未获得
  • Ada 中的信号量

    我得到了以下代码并要求实现一个信号量 with Ada Text IO use Ada Text IO with Id Dispenser with Semaphores use Semaphores procedure Philos is
  • C# - 使用 StreamReader 并行化 While 循环会导致 CPU 过高

    SemaphoreSlim sm new SemaphoreSlim 10 using FileStream fileStream File OpenRead using StreamReader streamReader new Stre
  • 信号量和条件的区别(ReentrantLock)

    有谁知道这些方法之间的区别acquire and release java util concurrent Semaphore and await and signal new ReentrantLock newCondition 您能为每
  • 了解 posix 进程间信号量

    根据我的理解 信号量应该可以跨相关进程使用 而无需将其放置在共享内存中 如果是这样 为什么下面的代码会死锁 include
  • CountDownLatch 与信号量

    使用有什么好处吗 java util concurrent CountdownLatch 代替 java util concurrent Semaphore 据我所知 以下片段几乎是等效的 1 信号量 final Semaphore sem
  • 浴室同步和线程队列

    对于家庭作业 我们被要求解决浴室同步问题 我一直在努力思考如何开始 当一个人进入洗手间 personEnterRestrrom 函数 时我想要做什么 如果他们是女性并且没有男性在他们进入的洗手间里 如果不是 他们会进入等待女性的队列 我想为
  • 如何在 Web Api 操作中锁定长异步调用?

    我有这样的场景 我有一个 WebApi 和一个端点 触发时会执行大量工作 大约 2 5 分钟 这是一个具有副作用的 POST 端点 我想限制执行 以便如果向此端点发送 2 个请求 不应该发生 但安全总比遗憾更好 其中一个请求将必须等待以避免
  • Java-允许一个线程更新值,其他线程等待并跳过关键部分

    您好 我遇到一种情况 我必须只允许一个线程更新变量 有一个触发器 它可能会调用多个线程来更新此变量 但是更新应该仅由到达关键部分的第一个线程发生一次 理想情况下 流程应如下所示 线程1 调用 Thread 2 和 Thread 3 来更新由
  • 信号量如何以及为什么可以发出比初始化时更多的许可?

    我正在阅读 Java 并发实践 一书 在一个关于java util concurrent Semaphore 以下几行出现在书中 这是对其实施 虚拟许可 对象的评论 该实施没有实际的许可对象 并且Semaphore做 不将分配的许可与线程关
  • 信号量和同步

    我不太明白 javadocs 中信号量描述中的以下内容 注意 没有同步锁 当 acquire 被调用时持有 会阻止一个项目被 回到了水池 信号量 封装同步 需要限制对池的访问 与任何同步分开 需要保持一致性 泳池本身 有人可以帮助我理解这一
  • PHP 无需等待 sem_acquire?

    不是特定的代码问题 而是一般的代码问题 我试图在工作项目中使用信号量来限制可以同时访问某些进程的用户数量 据我了解如下 iKey ftock sSomeFileLocation sOneCharacterString Generate th
  • 限制异步任务

    我想运行一堆异步任务 并限制在任何给定时间可以等待完成的任务数量 假设您有 1000 个 URL 并且您只想一次打开 50 个请求 但是 一旦一个请求完成 您就会打开与列表中下一个 URL 的连接 这样 每次始终打开 50 个连接 直到 U
  • 我需要处置 SemaphoreSlim 吗?

    根据文档 a SemaphoreSlim不使用 Windows 内核信号量 是否有任何特殊资源被使用SemaphoreSlim这使得打电话很重要Dispose当 的时候SemaphoreSlim将不再使用 如果您访问AvailableWai
  • 使用易失性变量和信号量 - Java

    我从线程 信号量 易失变量等开始 我想知道当我使用信号量时是否有必要将变量定义为易失性 我的意思是 有 2 个线程 一个增加变量 另一个减少变量 例如 显然 在每次访问之前 我有一个互斥体 它随时控制只有一个线程正在 玩 变量 有必要定义为
  • 如何在c中创建信号量?

    我正在尝试重新创建一个 黑盒 库 在我的计算机科学课程中 当我们应该使用信号量时 在我们的纸质期末考试中 我们会得到一个 sem h 文件 有 3 个函数 一个用于创建具有初始数量令牌的新信号量 一个用于从信号量中取出令牌 一个用于将令牌放

随机推荐

  • 七大主流排序算法时间效率比较(基于C语言)

    这段时间在温故一些常见的排序算法 xff0c 顺手便把常见的一些比较著名的排序算法对同一个目标样本做了个比较 样本存于文件中 xff0c 可以根据需要进行替换 我调试的数据量较小 xff0c 发现简单算法 xff08 冒泡 xff0c 选择
  • image caption笔记(十):一些实现过程中的小细节

    1 beam search 一方面可以提升指标 xff0c 另一方面也可以解决生成的句子不通顺的问题 因此 xff0c 不管是在训练测试的过程中都要开beamsearch xff0c 对提高指标有帮助 还有就是 xff0c 如果训练的时候
  • C# 多进程之间的通讯方式

    前言 C 中可能大多数人针对于多线程之间的通讯 xff0c 是熟能生巧 xff0c 对于AsyncLocal 和ThreadLocal以及各个静态类中支持线程之间传递的GetData和SetData方法都是信手拈来 xff0c 那多进程通讯
  • setInterval()之坑

    首先看下面这个例子 xff1a lt DOCTYPE html gt lt html lang 61 34 en 34 gt lt head gt lt meta charset 61 34 UTF 8 34 gt lt title gt
  • window.open()session丢失问题解决方法

    最近要实现两个项目间的跳转 xff0c 避免二次登录 xff0c 出现了window open session丢失问题 xff0c 说下解决方法 一开始是这样写的 A项目调用B的登录请求 xff0c 返回成功 xff0c 即跳到B项目相应页
  • Java解析XML和json几个小例子

    一 DOM4J解析xml 案例1 xff1a 解析如下xml lt xml version 61 34 1 0 34 encoding 61 34 UTF 8 34 gt lt employee gt lt name gt 张三 lt na
  • Struts2拦截器简单实例

    利用拦截器实现权限控制 用户 admin 有全部的访问访问权限 用户 zhangsan有 a jsp b jsp的 访问权限 用户 lisi有 a jsp c jsp的 访问权限 如果用户没有访问权限 页面统一跳转到 permissionD
  • http请求循环调用返回数据抓取不全解决

    最近碰到一个问题 xff0c 就是频繁调用http请求 xff0c 返回的数据出现缺失的情况 特此记录下解决过程 先上代码 普通的get调用方法 public String sendGet String urlStr throws Inte
  • shiro 报错

    最近 xff0c 在学shiro的碰到如下报错 xff1a 登录认证失败 xff01 xff01 xff01 org apache shiro authc AuthenticationException Authentication fai
  • Oracle修改用户密码引发的问题

    前几天通过plsql登录数据库时 xff0c 提示密码过期 xff0c 需要修改密码 xff0c 那就修改呗 改完过了会 xff0c 再登录 xff0c 发现用户被锁了 那就去解锁下吧 一 登录数据库服务器 su oracle sqlplu
  • 《计算机程序的构造和解释》学习笔记——过程抽象

    这篇博文里说到程序 61 数据 43 过程 xff0c 数据分为不同类型 xff0c 每种类型有不同的操作过程 例如 xff0c 两个指针变量相加是无意义的 xff0c 所以对指针类型来说加法操作是 不允许 的 在汇编层 xff0c 每一个
  • c语言初阶-数组

    今天我们来认识一下数组和操作符吧 目录 1 数组 1 1数组的概念 1 2数组的定义 1 3数组的下标访问 1 数组 1 1数组的概念 所谓数组 xff08 array xff09 xff0c 就是具有相同数据类型的集合 xff0c 存放的
  • OkHttp-ConnectInterceptor源码解析

    ConnectInterceptor源码解析 本文基于okhttp3 10 0 1 概述 ConnectInterceptor主要是用于建立连接 xff0c 并再连接成功后将流封装成对象传递给下一个拦截器CallServerIntercep
  • 第六章 信号量集

    第六章 信号量集 6 1 信号量集的结构 6 1 1 基本概念 信号量集 xff1a 信号量集实质上就是一个多输入 多输出的组合逻辑 xff0c 输入为其他任务发出的多个信号 xff0c 输出为这多个输入逻辑运算的结果 6 1 2 信号量集
  • 嵌入式软件工程师岗位需求

    嵌入式软件工程师岗位需求 A 嵌入式软件开发工程师岗位职责 a 负责嵌入式操作系统的移植和应用开发 xff1b b 负责移动互联网的应用开发 xff1b c 负责嵌入式系统图形界面的开发 xff1b d 负责系统及软件设计文档的编写 xff
  • Marlink通信协议简介

    http qgroundcontrol org mavlink start mavlink协议介绍 https pixhawk ethz ch mavlink 消息简介 MAVLink简介 Mavlink协议最早由 苏黎世联邦理工学院 计算
  • List、Stack、Queue、Set和Map定义和常用方法

    集合 Java容器类库中的两种主要类型 1 每个槽只保存一个元素 Collection xff1a 描述所有序列容器的共性的根接口 xff1b List xff1a 以特定的顺序保存一组元素 xff1b Set xff1a 元素不能重复 x
  • MDK移植freeRTOS及多任务创建

    MDK移植freeRTOS及多任务创建 一 移植前准备二 freeRTOS移植三 创建任务 本文所使用的硬件为野火的指南者开发板 xff0c 购买后 xff0c 商家会提供已经移植好freeRTOS的工程可以直接使用进行开发 但如果没有现成
  • ubuntu如何进入修复模式,单用户模式,在进入系统前调出root用户的终端进行修复

    在进入Ubuntu界面前选择 Advanced options for Ubuntu 这一项进入 进入后在当前界面选择 recovery mode 这一项进入 进入后在当前界面选择 root 这一项进入 此时会在底部出现这样的画面 xff0
  • Semaphore的注意点

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