AQS应用-Semaphore

生活(工作)

生活不止眼前的苟且,还有无止境的工作。在光云的这段时光,是没有休息的,休假是奢侈的,要随时待命的。不知道能不能坚持下来,能坚持多久。

场景

昨晚的会议貌似又是一大波紧急需求,要被这些为某个用户定制化的紧急需求逼死了,还好我的报表需求比较轻松。
好吧,随便抱怨下,就强行切入今天的话题了。
说到这一波紧急需求,假设咱们的开发总共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!!!

猜你喜欢

转载自blog.csdn.net/qq_28605513/article/details/84317588