一、Semaphore
1.1 概念
- 信号量 Semaphore 是一个控制访问多个共享资源的计数器,和 CountDownLatch 一样,其本质上是一个“共享锁”。
Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。
1.2 示例
- 为了简单起见我们假设停车场仅有5个停车位。一开始停车场没有车辆所有车位全部空着,然后先后到来三辆车,停车场车位够,安排进去停车,然后又来三辆,这个时候由于只有两个停车位,所有只能停两辆,其余一辆必须在外面候着,直到停车场有空车位。当然,以后每来一辆都需要在外面候着。当停车场有车开出去,里面有空位了,则安排一辆车进去(至于是哪辆,要看选择的机制是公平还是非公平)。从程序角度看,停车场就相当于信号量Semaphore ,其中许可数为5,车辆就相对线程。当来一辆车时,许可数就会减1 。当停车场没有车位了(许可数== 0 ),其他来的车辆需要在外面等候着。如果有一辆车开出停车场,许可数 + 1,然后放进来一辆车。信号量 Semaphore 是一个非负整数( >=1 )。当一个线程想要访问某个共享资源时,它必须要先获取 Semaphore。当 Semaphore > 0 时,获取该资源并使Semaphore – 1 。如果Semaphore值=0,则表示全部的共享资源已经被其他线程全部占用,线程必须要等待其他线程释放资源。当线程释放资源时,Semaphore 则 +1 。
1.2.1 示例代码
public class Park {
static Semaphore semaphore = new Semaphore(5);
static class Car implements Runnable {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 开始准备停车,剩余车位是: " + semaphore.availablePermits() + " -- " + new Date());
SleepTools.randomSecond(30);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println(Thread.currentThread().getName() + " 线程停车完毕,剩余车位是: " + semaphore.availablePermits() + " -- " + new Date());
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Car(), "[Car-" + (i + 1) + "]");
thread.start();
}
}
}
[Car-4] 开始准备停车,剩余车位是: 2 -- Tue May 28 12:19:49 CST 2019
[Car-1] 开始准备停车,剩余车位是: 4 -- Tue May 28 12:19:49 CST 2019
[Car-6] 开始准备停车,剩余车位是: 0 -- Tue May 28 12:19:49 CST 2019
[Car-2] 开始准备停车,剩余车位是: 3 -- Tue May 28 12:19:49 CST 2019
[Car-5] 开始准备停车,剩余车位是: 1 -- Tue May 28 12:19:49 CST 2019
[Car-2] 线程停车完毕,剩余车位是: 0 -- Tue May 28 12:19:52 CST 2019
[Car-3] 开始准备停车,剩余车位是: 0 -- Tue May 28 12:19:52 CST 2019
[Car-8] 开始准备停车,剩余车位是: 0 -- Tue May 28 12:19:53 CST 2019
[Car-5] 线程停车完毕,剩余车位是: 1 -- Tue May 28 12:19:53 CST 2019
[Car-7] 开始准备停车,剩余车位是: 0 -- Tue May 28 12:19:59 CST 2019
[Car-4] 线程停车完毕,剩余车位是: 1 -- Tue May 28 12:19:59 CST 2019
[Car-6] 线程停车完毕,剩余车位是: 1 -- Tue May 28 12:20:02 CST 2019
[Car-10] 开始准备停车,剩余车位是: 0 -- Tue May 28 12:20:02 CST 2019
[Car-3] 线程停车完毕,剩余车位是: 2 -- Tue May 28 12:20:09 CST 2019
[Car-9] 开始准备停车,剩余车位是: 1 -- Tue May 28 12:20:09 CST 2019
[Car-8] 线程停车完毕,剩余车位是: 2 -- Tue May 28 12:20:09 CST 2019
[Car-1] 线程停车完毕,剩余车位是: 2 -- Tue May 28 12:20:10 CST 2019
[Car-7] 线程停车完毕,剩余车位是: 3 -- Tue May 28 12:20:26 CST 2019
[Car-10] 线程停车完毕,剩余车位是: 4 -- Tue May 28 12:20:29 CST 2019
[Car-9] 线程停车完毕,剩余车位是: 5 -- Tue May 28 12:20:33 CST 2019
我们看到最开始的5个线程可以立刻获取到许可进行停车,后面的车辆就需要前面的车辆停车完毕之后才能进行停车。
但是我们看到的打印顺序会有些不一样,比如Car-5停车完毕之后,Car-8才能开始停车,但是打印的顺序会有不一样,
最开始的5个线程打印顺序也不一样,如果要求一样的话可以把release和后面的打印交换位置,但是计算许可的时候就需要+1
因为那样打印的时候还没有归还许可。
1.3 源码分析
1.3.1 构造方法
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
1.3.2 Sync
- semaphore的同步功能实现,使用AQS的state代表许可数目,子类实现公平和非公平的模式
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 final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current)
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current)
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
1.3.3 NonfairSync
static final class NonfairSync extends java.util.concurrent.Semaphore.Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
1.3.4 FairSync
static final class FairSync extends java.util.concurrent.Semaphore.Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
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;
}
}
}
1.3.5 获取许可
1.3.5.1 acquire()
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
1.3.5.2 acquireUninterruptibly()
public void acquireUninterruptibly() {
sync.acquireShared(1);
}
1.3.5.3 tryAcquire()
- 尝试获取许可,获取成功返回true,反之返回false,方法不会阻塞,会立即返回
public boolean tryAcquire() {
return sync.nonfairTryAcquireShared(1) >= 0;
}
1.3.5.4 tryAcquire(long timeout, TimeUnit unit)
public boolean tryAcquire(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
1.3.5.5 acquire(int permits)
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
1.3.5.6 acquireUninterruptibly(int permits)
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
}
1.3.5.7 tryAcquire(int permits)
- 尝试获取指定数量的许可,获取成功返回true,反之返回false,线程不会阻塞,会立即返回
public boolean tryAcquire(int permits) {
if (permits < 0) throw new IllegalArgumentException();
return sync.nonfairTryAcquireShared(permits) >= 0;
}
1.3.5.8 tryAcquire(int permits, long timeout, TimeUnit unit)
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}
- 小结:其实上面8个方法中有4中场景,另外4中只是指定了许可的数量,默认前面四种是获取1个许可,
1.可以响应中断(阻塞式,可以响应中断)
2.不能响应中断(阻塞式,不能响应中断)
3.不会阻塞(尝试获取,立即返回)
4.超时阻塞(阻塞获取,支持超时和字段)
1.3.6 释放许可
1.3.6.1 release()
public void release() {
sync.releaseShared(1);
}
1.3.6.2 release(int permits)
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
1.3.7 其他API
1.3.7.1 availablePermits()
public int availablePermits() {
return sync.getPermits();
}
1.3.7.2 drainPermits()
public int drainPermits() {
return sync.drainPermits();
}
1.3.7.3 reducePermits()
protected void reducePermits(int reduction) {
if (reduction < 0) throw new IllegalArgumentException();
sync.reducePermits(reduction);
}
1.3.7.4 isFair()
1.3.7.5 hasQueuedThreads()
- 返回值来表示是否有其他线程正在等待获取信号量,但是不准确,这个方法主要是用来判断系统状态
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
1.3.7.6 getQueueLength()
public final int getQueueLength() {
return sync.getQueueLength();
}
1.3.7.7 getQueuedThreads()
protected Collection<Thread> getQueuedThreads() {
return sync.getQueuedThreads();
}
二、Exchanger
2.1 概念
- Exchanger 是一个用于线程间协作的工具类,Exchanger用于进行线程间的数据交换,它提供一个同步点,在这个同步点,两个线程可以交换彼此
的数据。这两个线程通过exchange 方法交换数据,如果第一个线程先执行exchange 方法,它会一直等待第二个线程也执行exchange 方法,当两个
线程都到达同步点时,这两个线程就可以交换数据。 - Exchanger 使用是非常简单的,但是实现原理和前面几种工具比较却是最难的,前面几种工具都是通过同步器或者锁来实现,而Exchanger是一种
无锁算法,和前面SynchronousQueue 一样,都是通过循环 cas 来实现线程安全,因此这种方式就会显得比较抽象和麻烦。
2.2 示例
public class ExchangeTest {
private static final Exchanger<ArrayList<Integer>> exchange = new Exchanger<>();
public static void main(final String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
ArrayList<Integer> arr1 = new ArrayList<>();
ArrayList<Integer> arr11 = new ArrayList<>();
arr1.add(1);
arr1.add(2);
arr1.add(3);
try {
arr11 = exchange.exchange(arr1);
} catch (InterruptedException e) {
e.printStackTrace();
}
SleepTools.randomSecond(5);
System.out.println("Thread1原集合:"+arr1.toString());
System.out.println("Thread1新集合:"+arr11.toString());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
ArrayList<Integer> arr2 = new ArrayList<>();
ArrayList<Integer> arr22 = new ArrayList<>();
arr2.add(11);
arr2.add(22);
arr2.add(33);
try {
arr22 = exchange.exchange(arr2);
} catch (InterruptedException e) {
e.printStackTrace();
}
SleepTools.randomSecond(5);
System.out.println("Thread2原集合:" + arr2.toString());
System.out.println("Thread2新集合:" + arr22.toString());
}
}).start();
}
}
Thread2原集合:[11, 22, 33]
Thread2新集合:[1, 2, 3]
Thread1原集合:[1, 2, 3]
Thread1新集合:[11, 22, 33]
2.3 源码分析
- Exchanger部分的源码相对比较抽象,没有使用到AQS和锁机制,采用CAS来实现的,而且相对来说使用场景不多,以后有时间再分析吧。
三、小结
- 我们重点是分析了Semaphore的使用和源码部分,Semaphore也是基于AQS来实现的,结合之前的CountDownLatch也是基于AQS来实现的,我们也看到了AQS在并发编程中的重要性,因为还没有重点分析AQS,所以实际上关于AQS的部分API我们还不是很清楚里面的实现和细节,另外Sync继承自AQS后我们看到其自身的代码是很简单的,后面我们分析AQS后就能明白,这样的简单是建立在AQS的基础之上的,AQS也是整个并发编程的基石。
四、参考