Java并发编程|第九篇:Semaphore 信号量
文章目录
系列文章
1.介绍
semaphore
也可以说是信号量,可以控制线程访问的数量,它有两个方法,第一acquire()
获得一个许可,如果没有就等待,第二release()
释放一个许可;有点类似限流的功能。
2.例子
这个例子可以理解为10辆车,去抢5个车位
/**
* 限流
* 1.设置线程个数new Semaphore(5)
* 2.semaphore.acquire();线程获取lock
* 3.semaphore.release();线程释放lock
* 4.当线程获取5个锁之后,其他线程阻塞
* */
public class SemaphoreDemo {
static class Car extends Thread {
private int num;
private Semaphore semaphore;
public Car(int num, Semaphore semaphore) {
this.num = num;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
//抢占车位
semaphore.acquire();
System.out.println("第"+num+"抢占车位");
Thread.sleep(100);//停车100ms
semaphore.release();//车开走
System.out.println("第"+num+"释放");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//设置5个车位
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 10; i++) {
new Car(i, semaphore).start();
}
}
}
执行结果:
3.源码分析
以停车的代码为例
构造方法
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
最后调用的是Sync()
构造,设置 setState
设置 state
值 为 5 ,有5个车位
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
Sync(int permits) {
setState(permits);
}
...此处省略N行代码
acquire()
这个方法和
CoutDownLatch
里面调用的方法是一样的
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
acquireSharedInterruptibly()
将线程加入到
AQS
共享锁队列
tryAcquireShared(arg)<0
表示没有可用的线程数量,执行doAcquireSharedInterruptibly()
将线程加入AQS共享锁队列
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)//没有可用的线程数量
doAcquireSharedInterruptibly(arg);
}
后面的方法就不做展开了可以参考
对应的方法即可
区别在于其中几个方法实现方式
Sync类
Sync
这个类在Semaphore
里面有两种实现
非公平NonfairSync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
nonfairTryAcquireShared()
1.先获取目前的可用数量
state
值,比如说第一次为 5 个车位2.然后 remaining = 5 - 1 = 4
3.判断 remining < 0 返回负值,表示没有可用的车位
4.CAS将
available
值替换为 4 如果成功,返回 4,表示有 4 个车位可以停车
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
公平FairSync
和非公平的区别就是,在执行之前先
hasQueuedPredecessors()
判断一下,是否有线程在排队
static final class FairSync extends 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();//获得当前的state值
int remaining = available - acquires;//可用的数量 - 已经获得锁的数量
if (remaining < 0 ||
compareAndSetState(available, remaining))//CAS 将可用数量替换为remaining
return remaining;
}
}
}
release()
释放,也是和
CoutDownLatch
中的countDown()
完全一样,参照之前的文章即可
public void release() {
sync.releaseShared(1);
}
tryReleaseShared()
这个方法是
Semaphore
自己的实现
通过tryReleaseShared()
可以增加1
个可用线程数,及当前state
+ 1
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();//获得当前可以用的线程数
int next = current + releases;//可以用线程数+1
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))//修改当前的可用线程数+1
return true;
}
}
总结
整个的实现原理和CoutDownLatch
非常相似,都是采用AQS的共享锁实现。
区别在于
第一:加入共享锁队列的条件不一样,当tryAcquireShared()<0
时,表示没有可用线程数量,此时将线程加入到AQS
的共享锁队列
第二:唤醒线程节点的条件也不一样,tryAcquireShared()>0
,表示可用线程数 > 0,可用线程数量增加(通过tryReleaseShared()
增加 1
个可用线程数),线程在去竞争锁,执行程序
4.参考
腾讯课堂->咕泡学院->mic老师->并发编程基础