Semaphore的作用:
在Java中,使用了synchronized关键字和Lock锁实现了资源的并发访问控制,在同一时间只允许唯一了线程进入临界区访问资源(读锁除外),这样子控制的主要目的是为了解决多个线程并发同一资源造成的数据不一致的问题。在另外一种场景下,一个资源有多个副本可供同时使用,比如打印机房有多个打印机、厕所有多个坑可供同时使用,这种情况下,Java提供了另外的并发访问控制--资源的多副本的并发访问控制,今天学习的信号量Semaphore即是其中的一种。
Semaphore实现原理初探:
Semaphore是用来保护一个或者多个共享资源的访问,Semaphore内部维护了一个计数器,其值为可以访问的共享资源的个数。一个线程要访问共享资源,先获得信号量,如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器值减去1,再访问共享资源。
如果计数器值为0,线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量。
就好比一个厕所管理员,站在门口,只有厕所有空位,就开门允许与空侧数量等量的人进入厕所。多个人进入厕所后,相当于N个人来分配使用N个空位。为避免多个人来同时竞争同一个侧卫,在内部仍然使用锁来控制资源的同步访问。
Semaphore的使用:
Semaphore使用时需要先构建一个参数来指定共享资源的数量,Semaphore构造完成后即是获取Semaphore、共享资源使用完毕后释放Semaphore。
- Semaphore semaphore = new Semaphore(10,true);
- semaphore.acquire();
-
- semaphore.release();
下面的代码就是模拟控制商场厕所的并发使用:
- public class ResourceManage {
- private final Semaphore semaphore ;
- private boolean resourceArray[];
- private final ReentrantLock lock;
- public ResourceManage() {
- this.resourceArray = new boolean[10];
- this.semaphore = new Semaphore(10,true);
- this.lock = new ReentrantLock(true);
- for(int i=0 ;i<10; i++){
- resourceArray[i] = true;
- }
- }
- public void useResource(int userId){
- semaphore.acquire();
- try{
-
- int id = getResourceId();
- System.out.print("userId:"+userId+"正在使用资源,资源id:"+id+"\n");
- Thread.sleep(100);
- resourceArray[id] = true;
- }catch (InterruptedException e){
- e.printStackTrace();
- }finally {
- semaphore.release();
- }
- }
- private int getResourceId(){
- int id = -1;
- lock.lock();
- try {
-
- for(int i=0; i<10; i++){
- if(resourceArray[i]){
- resourceArray[i] = false;
- id = i;
- break;
- }
- }
- }catch (Exception e){
- e.printStackTrace();
- }finally {
- lock.unlock();
- }
- return id;
- }
- }
- public class ResourceUser implements Runnable{
- private ResourceManage resourceManage;
- private int userId;
- public ResourceUser(ResourceManage resourceManage, int userId) {
- this.resourceManage = resourceManage;
- this.userId = userId;
- }
- public void run(){
- System.out.print("userId:"+userId+"准备使用资源...\n");
- resourceManage.useResource(userId);
- System.out.print("userId:"+userId+"使用资源完毕...\n");
- }
-
- public static void main(String[] args){
- ResourceManage resourceManage = new ResourceManage();
- Thread[] threads = new Thread[100];
- for (int i = 0; i < 100; i++) {
- Thread thread = new Thread(new ResourceUser(resourceManage,i));
- threads[i] = thread;
- }
- for(int i = 0; i < 100; i++){
- Thread thread = threads[i];
- try {
- thread.start();
- }catch (Exception e){
- e.printStackTrace();
- }
- }
- }
- }
最后,Semaphore除了控制资源的多个副本的并发访问控制,也可以使用二进制信号量来实现类似synchronized关键字和Lock锁的并发访问控制功能。
可修改Semaphores的公平性,在默认的情况下信号量的进入是不公平的。如果在初始化的第二个参数设定为true时,则会选择时间等待最久的一个进入。
通过有上限的Semaphore可以限制进入某代码块的线程数量。设想一下,在上面的例子中,如果BoundedSemaphore 上限设为5将会发生什么?意味着允许5个线程同时访问关键区域,但是你必须保证,这个5个线程不会互相冲突。否则你的应用程序将不能正常运行。
必须注意,release方法应当在finally块中被执行。这样可以保在关键区域的代码抛出异常的情况下,信号也一定会被释放。