生活(工作)
生活不止眼前的苟且,还有无止境的工作。在光云的这段时光,是没有休息的,休假是奢侈的,要随时待命的。不知道能不能坚持下来,能坚持多久。
场景
昨晚的会议貌似又是一大波紧急需求,要被这些为某个用户定制化的紧急需求逼死了,还好我的报表需求比较轻松。
好吧,随便抱怨下,就强行切入今天的话题了。
说到这一波紧急需求,假设咱们的开发总共5个(当然绝对不止,这里举个例子),昨天产品讨论的这波需求20个,而且个个紧急标红,要求下周上线投入使用【今天周三了】。
要知道,我们开发不可能同时开发两个需求,除了一些人才。所以这20个需求要给5个开发做,势必不能一个个做,也就是这些需求要等待开发资源空闲,才有机会被安排开发。
这是一个信号量的概念,多个线程竞争有限的资源。
再举个例子,就是停车场,停车场内车位有限,当没有车位的时候,门卫放下栏杆,外面的车需要等待,当有车子处停车场时,出来几辆就可以进去几辆。
OK,这里要说的就是并发编程中的信号量,Semaphore。
下面来了解一下信号量。
简述
先来看下如何创建信号量,有两个构造器:
//permits 初始化信号量需要指定资源的个数
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
//信号量提供两种模式,公平与非公平,通过fair参数指定,不指定则默认非公平。
//两种模式决定了共享资源获取的排序策略。
//Semaphore 只有一个成员变量 sync
//有三个内部类 sync FairSync NonfailSync
// sync 继承自AQS ,另外两个继承自Sync
//公平模式 实例化FairSync 否则另一个
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
在写完上面的一点点之后,看了些前辈的博客,把信号量里的源码细节【事实上就是AQS】写的很详细,确实写的很好。But我前面已经写了AQS的源码解析,这里就不在赘述了,具体的细节代码可以自行去看咯。
这里仅关注资源的获取和释放。
关于资源的获取:
信号量中对资源的获取提供了两种方式:响应中断获取资源,不响应中断获取资源。
分别是acquire(),acquireUninterruptibly()。这两个方法分别调用到AQS中的两个方法。
//acquire() 在获取资源前,会先判断线程是否被中断,被中断即抛出异常
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//acquireUninterruptibly() 在获取资源前,不会判断是否被中断。而是在获取资源后,自我中断
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
关于公平非公平:
信号量在公平与非公平模式下的区别在于获取资源的排队策略。可以通过tryAcquireShared窥探。
FairSync和NonfairSync分别重写了tryAcquireShared。下面来看下这两个方法是如何实现公平非公平:
//公平模式
protected int tryAcquireShared(int acquires) {
for (;;) {
//如果等待队列中有正在等待的线程,直接返回-1,使线程进入等待队列,按顺序获取资源
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
//非公平模式 调用到Sync的方法
//不会判断等待队列中是否有在等待的线程,而是直接cas,如果获取成功,岂不是对于正在等待的线程不公平了
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
关于资源的释放:
资源的释放对于是否公平模式没有任何区别,最后调用到AQS的tryReleaseShared。具体可以看之前的AQS。
关于超时:
获取资源,有提供超时的方法,超时没有获取到资源就直接返回false不在阻塞。
还有两个方法用于耗尽和缩减资源
//耗尽
public int drainPermits() {
return sync.drainPermits();
}
//缩减
protected void reducePermits(int reduction) {
if (reduction < 0) throw new IllegalArgumentException();
sync.reducePermits(reduction);
}
这两个方法有两个注意点:
1、不可逆
2、针对剩余资源
案例
public class SEMTest {
static Semaphore semaphore = new Semaphore(5, false);
public static void main(String[] args) {
new Thread(new Need("1", semaphore, 4)).start();
new Thread(new Need("2", semaphore, 1)).start();
new Thread(new Need("3", semaphore, 3)).start();
new Thread(new Need("4", semaphore, 4)).start();
new Thread(new Need("5", semaphore, 1)).start();
new Thread(new Need("6", semaphore, 1)).start();
new Thread(new Need("7", semaphore, 1)).start();
new Thread(new Need("8", semaphore, 2)).start();
new Thread(new Need("9", semaphore, 2)).start();
new Thread(new Need("10", semaphore, 3)).start();
new Thread(new Need("11", semaphore, 1)).start();
}
static class Need implements Runnable{
private String name;
private Semaphore semaphore;
private int num;
@Override
public void run() {
try {
semaphore.acquire(num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Long start = System.currentTimeMillis();
try {
Thread.sleep(Long.valueOf(new Random().nextInt(5000)));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(String.format("需求:%s ,由%s人做完,耗时:%s ms,当前时间 %s", name,num,System.currentTimeMillis()-start,new Date()));
semaphore.release(num);
}
public Need(String name, Semaphore semaphore, int num) {
super();
this.name = name;
this.semaphore = semaphore;
this.num = num;
}
}
}
输出结果:
需求:1 ,由4人做完,耗时:220 ms,当前时间 Thu Nov 22 00:07:37 CST 2018
需求:4 ,由4人做完,耗时:522 ms,当前时间 Thu Nov 22 00:07:38 CST 2018
需求:2 ,由1人做完,耗时:2533 ms,当前时间 Thu Nov 22 00:07:40 CST 2018
需求:5 ,由1人做完,耗时:2248 ms,当前时间 Thu Nov 22 00:07:40 CST 2018
需求:6 ,由1人做完,耗时:1020 ms,当前时间 Thu Nov 22 00:07:41 CST 2018
需求:3 ,由3人做完,耗时:4468 ms,当前时间 Thu Nov 22 00:07:42 CST 2018
需求:8 ,由2人做完,耗时:790 ms,当前时间 Thu Nov 22 00:07:43 CST 2018
需求:7 ,由1人做完,耗时:3368 ms,当前时间 Thu Nov 22 00:07:44 CST 2018
需求:10 ,由3人做完,耗时:2251 ms,当前时间 Thu Nov 22 00:07:46 CST 2018
需求:9 ,由2人做完,耗时:2275 ms,当前时间 Thu Nov 22 00:07:46 CST 2018
需求:11 ,由1人做完,耗时:4631 ms,当前时间 Thu Nov 22 00:07:50 CST 2018
思考
信号量的常见应用场景?
资源控制 例如连接池,数据源等
后记
该睡觉了,明天还要上班,mmp!!!