Java多线程:Semaphore

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。一个计数信号量从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。拿到信号量的线程可以进入代码,否则就等待。通过acquire()和release()获取和释放访问许可通过信号量,我们可以控制我们程序的被访问量,比如某一时刻,最多只能同时允许20个线程访问,如果超过了这个值,那么其他的线程就需要排队等候。

Semaphore有两个构造函数,如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // 可以同时访问的数量,默认为非公平的,也就是说可以插队,并非先来的先获取信号量  
  2.     public Semaphore(int permits) {  
  3.         sync = new NonfairSync(permits);  
  4. }  
  5. // 信号量,可以设定是否为公平信号量  
  6.     public Semaphore(int permits, boolean fair) {  
  7.         sync = (fair)? new FairSync(permits) : new NonfairSync(permits);  
  8.     }  

下面我们来先看一个简单的例子,模拟限定客户端访问服务器的线程数量。代码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class SemaphoreDemo {  
  2.     public static void main(String[] args) {  
  3.         // 只能允许5个客户端线程访问服务器资源  
  4.         final Semaphore semaphore = new Semaphore(5);  
  5.         // 新建30个线程,模拟客户端同时有30个请求线程  
  6.         ExecutorService service = Executors.newFixedThreadPool(30);  
  7.         // 模拟提交30个请求给服务器  
  8.         for(int i=0; i<30; i++){  
  9.             service.execute(new Runnable() {  
  10.                 @Override  
  11.                 public void run() {  
  12.                     try {  
  13.                         // 获取许可  
  14.                         semaphore.acquire();  
  15.             System.out.println(Thread.currentThread().getName()+"线程持有信号量");  
  16.                         // 模拟请求服务器资源  
  17.                         Thread.sleep(2000);  
  18.                         System.out.println("当前可用的信号量为:"+semaphore.availablePermits());  
  19.                         // 访问完后,释放资源  
  20.                         System.out.println(Thread.currentThread().getName()+"线程释放信号量");  
  21.                         semaphore.release();  
  22.                         System.out.println("当前可用的信号量为:"+semaphore.availablePermits());  
  23.                     } catch (InterruptedException e) {  
  24.                         e.printStackTrace();  
  25.                     }  
  26.                 }  
  27.             });  
  28.         }  
  29.         // 释放线程池资源  
  30.         service.shutdown();  
  31.     }  
  32. }  

测试结果如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. pool-1-thread-1线程持有信号量  
  2. pool-1-thread-4线程持有信号量  
  3. pool-1-thread-3线程持有信号量  
  4. pool-1-thread-2线程持有信号量  
  5. pool-1-thread-5线程持有信号量  
  6. 当前可用的信号量为:0  
  7. pool-1-thread-2线程释放信号量  
  8. pool-1-thread-6线程持有信号量  
  9. 当前可用的信号量为:0  
  10. 当前可用的信号量为:0  
  11. pool-1-thread-1线程释放信号量  
  12. pool-1-thread-7线程持有信号量  
  13. 当前可用的信号量为:0  
  14. 当前可用的信号量为:0  
  15. pool-1-thread-3线程释放信号量  
  16. 当前可用的信号量为:0  
  17. pool-1-thread-5线程释放信号量  
  18. pool-1-thread-9线程持有信号量  
  19. 当前可用的信号量为:0  
  20. pool-1-thread-4线程释放信号量  
  21. 当前可用的信号量为:1  
  22. pool-1-thread-10线程持有信号量  

从测试结果可以看出,当前最多只允许5个线程持有信号量而进入流程。从某种意义上看,信号量就像是车库的放行杆,车库的车位(相当于公共资源)是一定的,当有车(相当于线程)请求进入车库的时候,只有车库有车位的时候,才放行,车位满的时候就等待。更进一步,信号量的特性如下:信号量是一个非负整数(车位数),所有通过它的线程(车辆)都会将该整数减一(通过它当然是为了使用资源),当该整数值为零 时,所有试图通过它的线程都将处于等待状态。在信号量上我们定义两种操作: Wait(等待) 和 Release(释放)。 当一个线程调用Wait(等待)操作时,它要么通过然后将信号量减一,要么一直等下去,直到信号量大于一或超时。Release(释放)实际上是在信号量 上执行加操作,对应于车辆离开停车场,该操作之所以叫做“释放”是因为加操作实际上是释放了由信号量守护的资源。

转自http://blog.csdn.net/liuchuanhong1/article/details/53539677


Semaphore为并发包中提供用于控制某资源同时可以被几个线程访问的类

实例代码:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. // 允许2个线程同时访问  
  2.         final Semaphore semaphore = new Semaphore(2);  
  3.         ExecutorService executorService = Executors.newCachedThreadPool();  
  4.         for (int i = 0; i < 10; i++) {  
  5.             final int index = i;   
  6.             executorService.execute(new Runnable() {  
  7.                 public void run() {  
  8.                     try {  
  9.                         semaphore.acquire();  
  10.                         // 这里可能是业务代码  
  11.                         System.out.println("线程:" + Thread.currentThread().getName() + "获得许可:" + index);  
  12.                         TimeUnit.SECONDS.sleep(1);  
  13.                         semaphore.release();  
  14.                         System.out.println("允许TASK个数:" + semaphore.availablePermits());    
  15.                     } catch (InterruptedException e) {  
  16.                         e.printStackTrace();  
  17.                     }  
  18.                 }  
  19.             });  
  20.         }  
  21.         executorService.shutdown();  


构造方法1:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public Semaphore(int permits) {  
  2.     sync = new NonfairSync(permits);  
  3. }  

permits 初始许可数,也就是最大访问线程数


构造方法2:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public Semaphore(int permits, boolean fair) {  
  2.     sync = (fair)? new FairSync(permits) : new NonfairSync(permits);  
  3. }  
permits 初始许可数,也就是最大访问线程数

fair 当设置为false时,线程获取许可的顺序是无序的,也就是说新线程可能会比等待的老线程会先获得许可;当设置为true时,信号量保证它们调用的顺序(即先进先出;FIFO)


主要方法:

void  acquire()   从信号量获取一个许可,如果无可用许可前 将一直阻塞等待,

void acquire(int permits)  获取指定数目的许可,如果无可用许可前  也将会一直阻塞等待

boolean tryAcquire()   从信号量尝试获取一个许可,如果无可用许可,直接返回false,不会阻塞

boolean tryAcquire(int permits)   尝试获取指定数目的许可,如果无可用许可直接返回false,

boolean tryAcquire(int permits, long timeout, TimeUnit unit)   在指定的时间内尝试从信号量中获取许可,如果在指定的时间内获取成功,返回true,否则返回false

void release()  释放一个许可,别忘了在finally中使用,注意:多次调用该方法,会使信号量的许可数增加,达到动态扩展的效果,如:初始permits 为1, 调用了两次release,最大许可会改变为2

int availablePermits() 获取当前信号量可用的许可


JDK 非公平Semaphore实现:

1.使用一个参数的构造创建Semaphore对象时,会创建一个NonfairSync对象实例,并将state值设为传入的值(permits ),

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public Semaphore(int permits) {  
  2.     sync = new NonfairSync(permits);  
  3. }  
NonfairSync间接的继承了AbstractQueuedSynchronizer实现
[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. final static class NonfairSync extends Sync {  
  2.         private static final long serialVersionUID = -2694183684443567898L;  
  3.   
  4.         NonfairSync(int permits) {  
  5.             super(permits);  
  6.         }  
  7.   
  8.         protected int tryAcquireShared(int acquires) {  
  9.             return nonfairTryAcquireShared(acquires);  
  10.         }  
  11.     }  
[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. abstract static class Sync extends AbstractQueuedSynchronizer {  
  2.     private static final long serialVersionUID = 1192457210091910933L;  
  3.   
  4.     Sync(int permits) {  
  5.         setState(permits);  
  6.     }  
AbstractQueuedSynchronizer 的setState方法

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. protected final void setState(int newState) {  
  2.     state = newState;  
  3. }  


2.调用tryAcquire方法时,实际是调用NonfairSync的nonfairTryAcquireShared方法,nonfairTryAcquireShared在父类Sync中实现,

Semaphore# tryAcquire方法:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1.     public boolean tryAcquire() {  
  2.         return sync.nonfairTryAcquireShared(1) >= 0;  
  3.     }  

Sync的nonfairTryAcquireShared方法

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. final int nonfairTryAcquireShared(int acquires) {  
  2.     for (;;) {  
  3.         int available = getState();  
  4.         int remaining = available - acquires;  
  5.         if (remaining < 0 ||  
  6.             compareAndSetState(available, remaining))  
  7.             return remaining;  
  8.     }  
  9. }  

nonfairTryAcquireShared方法通过获取当前的state,以此state减去需要获取信号量的个数,作为剩余个数,如果结果小于0,返回此剩余的个数;如果结果大于等于0,则基于CAS将state的值设置为剩余个数,当前步骤用到了for循环,所以只有在结果小于0或设置state值成功的情况下才会退出。

如果返回的剩余许可个数大于0,tryAcquire方法则返回true;其余返回false。


AbstractQueuedSynchronizer的compareAndSetState方法,

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. protected final boolean compareAndSetState(int expect, int update) {  
  2.     // See below for intrinsics setup to support this  
  3.     return unsafe.compareAndSwapInt(this, stateOffset, expect, update);  
  4. }  

3.release方法,释放一个许可 

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public void release() {  
  2.     sync.releaseShared(1);  
  3. }  
AbstractQueuedSynchronizer的releaseShared方法,

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public final boolean releaseShared(int arg) {  
  2.     if (tryReleaseShared(arg)) {  
  3.         doReleaseShared();  
  4.         return true;  
  5.     }  
  6.     return false;  
  7. }  
release方法间接的调用了Sync的tryReleaseShared方法,该方法基于Cas 将state的值设置为state+1,一直循环确保CAS操作成功,成功后返回true。

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. protected final boolean tryReleaseShared(int releases) {  
  2.     for (;;) {  
  3.         int p = getState();  
  4.         if (compareAndSetState(p, p + releases))  
  5.             return true;  
  6.     }  
  7. }  

根据上面分析,可以看得出,Semaphore采用了CAS来实现,尽量避免锁的使用,提高了性能。


转载自 http://blog.csdn.net/java2000_wl/article/details/23556859


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值