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个通路。

posted @ 2020-11-14 13:38  Java程序员进阶  阅读(5)  评论(0编辑  收藏  举报