Semaphore
是一个计数信号量,必须由获取它的线程释放。Semaphore
是Synchronized
的加强版,常用于限制可以访问某些资源的线程数量,例如通过Semaphore
限流。Semaphore
只有3个操作:初始化、增加、减少。
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class StudySemaphore {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
//信号量,只允许 3个线程同时访问
Semaphore semaphore = new Semaphore(3);
for (int i=0;i<10;i++){
final long num = i;
executorService.submit(new Runnable() {
@Override
public void run() {
try {
//获取许可
semaphore.acquire();
//执行
System.out.println("Accessing: " + num);
Thread.sleep(new Random().nextInt(5000)); // 模拟随机执行时长
//释放
semaphore.release();
System.out.println("Release..." + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
executorService.shutdown();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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个通路。