Semaphore 信号量
Semaphore 是一个计数信号量,必须由获取它的线程释放。Semaphore 是 Synchronized 的加强版,常用于限制可以访问某些资源的线程数量,例如通过 Semaphore限流。Semaphore 只有3个操作:初始化、增加、减少。
1 import java.util.Random; 2 import java.util.concurrent.ExecutorService; 3 import java.util.concurrent.Executors; 4 import java.util.concurrent.Semaphore; 5 6 public class StudySemaphore { 7 8 public static void main(String[] args) { 9 ExecutorService executorService = Executors.newCachedThreadPool(); 10 11 //信号量,只允许 3个线程同时访问 12 Semaphore semaphore = new Semaphore(3); 13 14 for (int i=0;i<10;i++){ 15 final long num = i; 16 executorService.submit(new Runnable() { 17 @Override 18 public void run() { 19 try { 20 //获取许可 21 semaphore.acquire(); 22 //执行 23 System.out.println("Accessing: " + num); 24 Thread.sleep(new Random().nextInt(5000)); // 模拟随机执行时长 25 //释放 26 semaphore.release(); 27 System.out.println("Release..." + num); 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 } 31 } 32 }); 33 } 34 35 executorService.shutdown(); 36 } 37 }
方法 acquire( int permits ) 参数作用:动态添加 permits 许可数量。可以这么理解, new Semaphore(6) 表示初始化了 6个通路, semaphore.acquire(2) 表示每次线程进入将会占用2个通路,semaphore.release(2) 运行时表示归还2个通路。没有通路,则线程就无法进入代码块。上面的代码中,semaphore.acquire() + semaphore.release() 在运行的时候,其实和 semaphore.acquire(1) + semaphore.release(1) 效果是一样的。
如果 acquire 的数量大于 release 的数量,则通路迟早会被使用完,如果线程比较多,得不到后续运行,出现线程堆积内存,最终 java进程崩掉;如果 acquire 的数量小于 release 的数量,就会出现并发执行的线程越来越多(换句话说,处理越来越快),最终也有可能出现问题。
acquire 的不可中断实现:semaphore.acquire() 和 semaphore.acquire(int permits) 是会抛出异常 InterruptedException的,如果在 acquire 和 release 之间的代码是一个比较慢和复制的运算,如内存占用过多,或者栈深度很深等,jvm会中断这块代码。使用 acquireUninterruptibly() 替换acquire()、使用 acquireUninterruptibly(int permits) 替换 acquire(int permits) ,才能不让 jvm 中断代码执行。acquireUninterruptibly 不会抛出 InterruptedException ,一个代码块一时执行不完,还会继续等待执行。
个人觉得,不要随便使用 acquireUninterruptibly ,因为 jvm中断执行,是自身的一种自我保护机制,保证 java进程的正常,除了特殊情况必须用 acquireUninterruptibly外,都应该使用 acquire ,将 release 放到 finally 块中。
其他一些常有工具方法:
【1】availablePermits() 表示返回 Semaphore 对象中的当前可用许可数,此方法通常用于调试,因为许可数量(通路)可能是实时在改变的。
【2】drainPermits() 方法可获取并返回立即可用的所有许可(通路)个数,并将可用许可置为0。
【3】getQueueLength() 获取等待许可的线程个数。
【4】getQueueLength() 和 hasQueuedThreads() 都是在判断当前有没有等待许可的线程信息时使用。
线程公平性,上面用的 Semaphore 构造方法是 Semaphore semaphore = new Semaphore(int permits)。其实,还有一个构造方法: Semaphore semaphore = new Semaphore(int permits , boolean isFair)。isFair 的意思是否公平,获得锁的顺序与线程启动顺序有关,就是公平,先启动的线程,先获得锁。isFair 不能100% 保证公平,只能是大概率公平。isFair 为 true,则表示公平,先启动的线程先获得锁。
tryAcquire(int permits , long timeout , TimeUint unit) :在指定时间 timeout内尝试获取 permits个通路。