简介

Semaphore是信号量的意思,在编程的世界里,线程能否执行,要看信号量是不是允许。

信号量模型

信号量模型可以简单概括为:一个计数器,一个等待队列,三个方法。
在信号量模型里,计数器和等待队列对外是透明的,所以只能通过信号量模型提供的三个方法来访问它们,这三个方法分别是:init()、down()、up()。我们可以通过一个图来形象化理解一下:

这三个方法详细的语义具体如下所示。

  • init():设置计数器的初始值。
  • down():计数器的值减1;如果此时计数器的值小于0,则当前线程将被阻塞,否则当前线程可以继续执行。
  • up():计数器的值加1;如果此时计数器的值小于或等于0,则唤醒等待队列中的一个线程,并将其从等待队列中移除。

这三个方法都是原子性的,并且这个原子性是由信号量模型的实现方保证的。我们看一下代码化的信号量模型:

class Semaphore{// 计数器int count;// 等待队列Queue queue;Semaphore(int c){this.count = c;}void down(){this.count--;if(this.count < 0){// 将当前线程插入等待队列// 阻塞当前线程}}void up(){this.count++;if(this.count <= 0){// 移除等待队列中的某个线程T// 唤醒线程T}}
}

在Java SDK 并发包里,down()up() 对应的是 acquire()release()

如何使用信号量

                  

举个例子,我们生活中日常见到的红绿灯。十字路口的红绿灯可以控制交通,得益于它的一个关键规则:车辆在通过路口前必须先检查是否是绿灯,只有绿灯才能同行。

信号量的使用也是类似的。我们以一个累加器的例子来说明一下信号量的使用。在累加器的例子里面,count += 1 操作是个临界区,只允许一个线程执行,也就是说要保证互斥。那这种情况用信号量怎么控制呢?

很简单,就像我们用互斥锁一样,只需要在进入临界区之前执行一下 down() 操作,退出临界区之前执行一下 up() 操作就可以了。下面是Java代码示例,acquire() 就是信号量里面的 down() 操作,release() 就是信号量里的 up() 操作。

static int count;
// 初始化信号量
static final Semaphore s = new Semaphore(1);
// 用信号量保证互斥
static void addOne(){s.acquire();try(){count += 1;} finally {s.release();}
}

也许到现在你还是不太明白,信号量是如何保证互斥的。

假设两个线程T1 和T2 同时访问 addOne() 方法,当它们同时调用 acquire() 的时候,由于 acquire() 是一个原子操作,所以只能有一个线程(假设T1)把 信号量里的计数器减为0,另一个线程(T2)则是将计数器减为-1。
对于线程T1,信号量里面的计数器的值是0,大于等于0,所以线程T1会继续执行;
对于线程T2,信号量里面的计数器的值是-1,小于0,按照信号量模型里对 down() 操作的描述,线程T2将被阻塞。
所以此时只有线程T1 会进入临界区执行 count +=1.

当线程T1执行 release() 操作,也就是 up() 操作的时候,信号量里计数器的值是-1,加1之后的值是0,小于等于0,按照信号量模型里对 up() 操作的描述,此时等待队列中的T2将会被保持唤醒。于是T2在T1执行完临界区代码之后才获得了进入临界区执行的机会,从而保证了互斥性。

快速实现一个限流器

上面的例子,我们用信号量实现了一个最简单的互斥锁功能。估计你会觉得奇怪,既然有 Java SDK 里面提供了 Lock, 为啥还要提供一个 Semaphore ? 其实实现一个互斥锁,仅仅是 Semaphore 的部分功能,Semaphore 还有一个功能是Lock不容易实现的,那就是: Semaphore 可以允许多个线程访问一个临界区。

现实中还有这种需求?有的。比较常见的需求就是我们遇到的各种池化资源,例如连接池、对象池、线程池等等。其中,我们可能最熟悉数据库连接池,在同一时刻,一定是允许多个线程同时使用连接池的,当然,每个连接在被释放前,是不允许其他线程使用的。

假设在工作中遇到了一个对象池的需求。所谓对象池指的是一次性创建出N个对象,之后所有的线程重复利用这N个对象,当然对象在被释放前,也是不允许其他线程使用的。对象池,可以用List 保存实例对象,这个很简单。但关键是限流器的设计,这里的限流,指的是不允许多于N个线程同时进入临界区。那如何快速实现一个这样的限流器呢?这种场景,信号量就是很好的解决方案。

信号量的计数器,在上面的例子中,我们设置成了1, 这个 1 表示只允许一个线程进入临界区,但如果我们把计数器的值设置成对象池里对象的个数N,就能完美解决对象池的限流问题了。下面就是对象池的示例代码。

class ObjPool<T, R>{final List<T> pool;// 用信号量实现限流器final Semaphore sem;// 构造函数ObjPool(int size, T t){pool = new Vector<T>(){};for(int i=0; i<size; i++){pool.add(t);}sem = new Semaphore(size);}// 利用对象池的对象,调用 funcR exec(Function<T, R> func){T t= null;sem.acquire();try{t = pool.remove(0);return func.apply(t);}finally{pool.add(t);sem.release();}}
}
// 创建对象池
ObjPool<Long, String> pool = new ObjPool<Long, String>(10, 2);
// 通过对象池获取t,之后执行
pool.exec(t -> {System.out.println(t);return t.toString();
});

我们用一一个List来保存对象实例,用 Semaphore 实现限流器。关键的代码是 ObjPool 里面的 exec() 方法,这个方法里面实现了限流的功能。在这个方法里面,我们首先调用 acquire() 方法(与之匹配的是在 finally 里面调用 release() 方法),假设对象池的大小是10,信号量的计数器初始化为10,那么前10个线程调用 acquire() 方法,都能继续执行,相当于通过了信号灯,而其他线程则会阻塞在 acquire() 方法上。对于通过信号灯的线程,我们为每个线程分配了-一个对象t (这个分配工作是通过 pool.remove(0) 实现的),分配完之后会执行一一个回调函数func,而函数的参数正是前面分配的对象t ;执行完回调函数之后,它们就会释放对象(这个释放工作是通过 pool.add(t) 实现的),同时调用 release() 方法来更新信号量的计数器。如果此时信号量里计数器的值小于等于0,那么说明有线程在等待,此时会自动唤醒等待的线程。

简言之,使用信号量,我们可以轻松地实现一个限流器, 使用起来还是非常简单的。
                   

【多线程】Semaphore:实现快速限流器相关推荐

  1. iOS学习笔记-109.多线程08——CGD快速迭代

    多线程08CGD快速迭代 一说明 二for迭代 三GCD的快速迭代 四使用for迭代移动文件示例 1 说明 2 示例代码 3 图示与结果 五使用for迭代移动文件示例 1 示例代码 2 图示与结果 多 ...

  2. [Python 多线程] Semaphore、BounedeSemaphore (十二)

    Semaphore 信号量,信号量对象内部维护一个倒计数器,每一次acquire都会减1,当acquire方法发现计数为0就阻塞请求的线程,直到其它线程对信号量release后,计数大于0,恢复阻塞的 ...

  3. Java多线程:Semaphore

    自从5.0开始,jdk在java.util.concurrent包里提供了Semaphore 的官方实现. Java 5.0里新加了4个协调线程间进程的同步装置,它们分别是: Semaphore, C ...

  4. linux 多线程 semaphore ,Linux下多线程编程-Pthread和Semaphore使用.doc

    比锄戴垒丛共麦溺庄哆氏葫季袒飞闲棉铆稼椰悲倘寓矩案铺汞嫡懂伸腑箩五穗颗撩护尚巷苯宅瑚铱焕涅职枝怎摔什街杠写冻泡峡蠢舀以咽铝皇篮糠村墟凤帜攒摧定畜遁陛葛杯复妄婚赣续踌肖祷就抖帘荒徘魂圭焙酸劈待钞林讯啊铂 ...

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

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

  6. 线程 信号量 java_JAVA多线程-Semaphore信号量

    一.概述 Semaphore(信号量) 是一个线程同步结构,用于在线程间传递信号,以避免出现信号丢失,或者像锁一样用于保护一个关键区域,可以控制同时访问的线程个数,并且通过acquire()方法获取一 ...

  7. Semaphore自白:限流器用我就对了!

    作者 | 王磊 来源 | Java中文社群(ID:javacn666) 转载请联系授权(微信ID:GG_Stone) 大家好,我是 Semaphore,我的中文名字叫"信号量",我 ...

  8. Java多线程-Semaphore(限流)

    Semaphore 是什么 Semaphore 通常我们叫它信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源. 可以把它简单的理解成我们停车场入口立着的那个显示 ...

  9. python实用程序育儿法_Python多线程 简明例子

    Python多线程 简明例子 (2010-03-11 15:15:09) Python多线程 简明例子 综述 多线程是程序设计中的一个重要方面,尤其是在服务器Deamon程序方面.无论何种系统,线程调 ...

最新文章

  1. 自动机器学习:团队如何在自动学习项目中一起工作?(附链接)
  2. 接口请求两次_事故分享之接口请求顺序错乱
  3. 直接送显示器!屏幕大了,打起代码才带劲!
  4. 配置Centos7 SSH密钥登陆及密码密钥双重验证
  5. python【力扣LeetCode算法题库】—两数之和
  6. 一文读懂领域迁移与领域适应的常见方法
  7. on java 8学习笔记
  8. 泛广电领域的卫星传输和公网传输
  9. 视图添加字段_使用ExploreByTouchHelper辅助类为自定义视图添加虚拟视图
  10. 金融数据分析与挖掘实战练习-1.9
  11. html设计有项目的页面,HTML+CSS项目开发经验总结(推荐)
  12. SLAM_相机与imu的融合基础知识
  13. 《HiWind企业快速开发框架实战》(2)使用HiWind创建自己的项目
  14. 阿里云实现人脸登录(人脸库 OSS)
  15. 推美妆、搭IP,出货千万的天猫精灵打上95后的主意
  16. VS2017编译配置和使用LOG4CPP
  17. word转pdf转换器2015注册码
  18. oracle 排序性能优化,Oracle优化之: 利用索引的有序性减少排序
  19. HTML测试报告模板
  20. 批量下载CMIP6数据

热门文章

  1. 一周小结(2016-05-16~2016-05-21)
  2. java-io-FileReader和FileWriter类
  3. 如何解决软键盘弹出引起的各种不适
  4. 【转】RunTime.getRunTime().addShutdownHook用法
  5. IIS7 经典模式和集成模式的区别分析(转载)
  6. ‘telnet‘ 不是内部或外部命令,也不是可运行的程序或批处理文件
  7. python multiprocessing遇到Can’t pickle instancemethod问题
  8. 那些拆中台的CTO,70%被裁了
  9. 腾讯离职领导的建议:如果可以,去做大数据,不要做报表
  10. postman压力测试_如何用Postman简单做接口自动化