目录
1 前言
本人使用jdk8版本。
Semaphore
(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
举个例子,比如××马路要限制流量,只允许同时有一百辆车在这条路上行使,其他的都必须在路口等待,所以前一百辆车会看到绿灯,可以开进这条马路,后面的车会看到红灯,不能驶入××马路,但是如果前一百辆中有5辆车已经离开了××马路,那么后面就允许有5辆车驶入马路,这个例子里说的车就是线程,驶入马路就表示线程在执行,离开马路就表示线程执行完成,看见红灯就表示线程被阻塞,不能执行。
2 应用场景
Semaphore
可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连接。
假如有一个需求,要从本地磁盘读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发地读取,但是如果读到内存后,还需要存储到数据库中(必须通过数据库连接来写),而数据库的连接数只有10个,这时我们必须控制只有10个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。
public class SemaphoreTest {
private static final int THREAD_COUNT = 30;
private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
private static Semaphore s = new Semaphore(10);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
threadPool.execute(new MyRunnable());
}
threadPool.shutdown();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
try {
s.acquire();
System.out.println("save data -- ";
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在代码中,虽然有30
个线程在执行,但是只允许10
个并发执行。Semaphore
的构造方法Semaphore(int permits)
接受一个整型的数字,表示可用的许可证数量。Semaphore(10)
表示允许10
个线程获取许可证,也就是最大并发数是10
。
Semaphore
的用法也很简单,首先线程使用Semaphore
的acquire()
方法获取一个许可证,使用完之后调用release()
方法归还许可证。还可以用tryAcquire()
方法尝试获取许可证。
3 其它方法
int availablePermits()
:返回此信号量中当前可用的许可证数。int getQueueLength()
:返回正在等待获取许可证的线程数。boolean hasQueuedThreads()
:是否有线程正在等待获取许可证。void reducePermits(int reduction)
:减少reduction
个许可证,是个protected
方法。Collection<Thread> getQueuedThreads()
:返回所有等待获取许可证的线程集合,是个protected
方法。
4 实现原理
Semaphore内部实现了一个AbstractQueuedSynchronizer的子类,并重写它的tryAcquireShared()、tryReleaseShared()等方法来实现“控制同时访问特定资源的线程数量”的功能。显然,实现基于同步器的共享锁,在构造Semaphore时传入的int参数就是用来指定共享锁可以被获取的最大数,超过最大数后再获取线程会阻塞,同时一旦一个线程释放自己的共享锁,阻塞队列中所有的等待线程会被唤醒来竞争锁。多个线程怎么来竞争锁呢?Semaphore里也引入同步器里的公平锁和非公平锁的机制,在构造Semaphore时也可以指定。
4.1 实现的同步器
公平锁与非公平锁只在获取锁时有区别,释放时用的是统一方法。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
Sync(int permits) { // 初始化锁状态,设置锁获取的最大数
setState(permits);
}
final int getPermits() { // 返回锁状态
return getState();
}
final int nonfairTryAcquireShared(int acquires) { // 尝试获取非公平锁
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
protected int tryAcquireShared(int acquires) { // 尝试获取公平锁
for (;;) {
// 判断是否有前驱节点,是就表情有线程比它更早请求锁,请求失败
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// 尝试释放锁,没有公平与非公平之分
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
}
4.2 执行过程
通过上面的数据库例子来介绍,构造Semaphore时将锁的状态设为10,然后同时开启30个线程往数据库中写,所有线程都会先调用Semaphore.acquire(),前10个线程会成功获取锁,每当获取成功时锁的状态会-1,进而执行保存数据的操作,最终锁的状态为0。当而另外20个线程再获取锁会失败进而阻塞,若此前10个线程中有线程保存完数据释放锁了,锁的状态+1,会唤醒这个20个线程去竞争锁(公平与非公平)。