概述
1.Semaphore介绍
2.Semaphore源码分析
3.Semaphore示例
Semaphore介绍
Semaphore是一个计数信号量,在计数器不为0的时候对线程放行,一旦计数器为0,所有新请求的线程都会被阻塞,包括增加请求到已经许可的线程,也就是说Semaphore是不可重入的。每一次线程请求都会导致计数器减1,每一次线程是否都会导致线程加1,达到0,则挂起新请求的线程。
应用场景,比如经常用到的一些连接池,缓存池等都是可以通过Semaphore来实现的。
Semaphore只是控制线程的数量,并不能实现同步,所以如果需要同步还是加锁或使用同步机制。信号量只是在信号不够的时候挂起线程,但是并不能保证信号量足够的时候获取对象和返还对象是线程安全的。
Semaphore中有sync对象,sync对象来自Sync类,Sync类继承自AQS。Sync又分为fair和NonFair。所以信号量又分为公平信号量和非公平信号量。区别在哪里呢?
在释放信号量的时候没有区别,不同的是获取信号量。公平信号量在获取信号量时会判断FIFO队列的节点是否为队列头,如果是才会去获取。而非公平信号量不会考虑是否为队列头,只能能够获取到就会获取。Semaphore默认为非公平信号量。
公平信号和非公平信号在于第一次尝试能否获取信号时,公平信号量总是将当前线程加入到AQS的CLH队列中进行排队,从而根据AQS的CLH队列的顺序FIFO依次获取信号量。而对于非公平信号量来说,第一次立即尝试能否获取到信号量,一旦信号量的剩余数大于请求数,那么线程立即得到了放行,不需要再进行AQS队列进行排队,只有剩余数<=0(信号量不够的时候)才会进入到AQS队列。
所以非公平信号量的吞吐量总是要比公平信号量的吞吐量要大,但是需要强调的是非公平信号量和非公平锁一样存在“饥渴死”的现象,也就是说活跃线程可能总是拿到信号量,而非活跃线程可能难以拿到信号量。而对于公平信号量由于总是靠请求的线程的顺序来获取信号量,所以不存在此问题。
Semaphore源码分析
构造函数
public Semaphore(int permits) { //构造函数,参数为许可数,这里默认用的是Nonfair方式
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }
公平信号量
acquire方法
public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); //sync的方法,默认参数为1,即一次获取一个许可 }
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) //线程是否中断 throw new InterruptedException(); if (tryAcquireShared(arg) < 0) //获取许可失败 doAcquireSharedInterruptibly(arg); }
FairSync的tryAcquireShared()方法
protected int tryAcquireShared(int acquires) { for (;;) { if (hasQueuedPredecessors()) //判断是否有前节点,即是否为队列头部 return -1; int available = getState(); //获取state int remaining = available - acquires; //remain数减 if (remaining < 0 || compareAndSetState(available, remaining)) //如果小于0或者CAS设置新的许可数失败,则返回remaining数 return remaining; } }
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.SHARED); //添加节点到队列,共享锁方式 boolean failed = true; try { for (;;) { //自旋 final Node p = node.predecessor(); //获取前一节点 if (p == head) { //如果是头节点 int r = tryAcquireShared(arg); //获取共享锁 if (r >= 0) { //获取成功 setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && //非头部节点则阻塞线程 ,与之前锁机制里面介绍的一样 parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
信号量的释放
public void release() { sync.releaseShared(1); //默认参数也为1 }
public final boolean releaseShared(int arg) { //释放信号量 if (tryReleaseShared(arg)) { //尝试释放成功 doReleaseShared(); //唤醒线程,释放锁 return true; } return false; }
protected final boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); //获取当前的state int next = current + releases; //新的value为state+1 if (next < current) // overflow //溢出 throw new Error("Maximum permit count exceeded"); if (compareAndSetState(current, next)) //如果通过CAS设置新的信号量成功,则返回true return true; } }
private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; //获取头结点的线程状态 if (ws == Node.SIGNAL) { //如果头节点为SIGNAL,则意味着下一节点的线程需要被唤醒 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) //头结点状态设置为空,如果失败的话继续循环 continue; // loop to recheck cases unparkSuccessor(h); //唤醒头节点下一节点的线程 } else if (ws == 0 && //如果头结点对应的线程状态为空,更改头节点线程状态 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed,如果头节点发生变化 break; } }
非公平信号量
非公平信号量与公平信号量的区别就在于第一次获取信号量不一样,就是tryAcquireShared方法
final int nonfairTryAcquireShared(int acquires) { for (;;) { //可以看到,这里没有判断是否为队列头部节点 int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }
Semaphore示例
示例1,有个对象池,用于生产对象,对象保存在一个队列中,每次get的时候获取队列头部的对象,如果没有则生产一个。return对象则把当前使用的对象返回到对象池中。
public class ObjectPoolTest<T> { //对象池 public interface ObjectFactory<T>{ //工厂方法 T makeObject(); } class Node{ //队列数据结构 T object; Node next; } int capacity; ObjectFactory<T> objectFactory; ReentrantLock lock; final Semaphore semaphore; Node header; Node tail; public ObjectPoolTest(int capacity,ObjectFactory objectFactory){ this.objectFactory=objectFactory; this.capacity=capacity; this.semaphore=new Semaphore(this.capacity); this.header=null; this.tail=null; this.lock=new ReentrantLock(); } public T getObject(){ //获取对象 try { semaphore.acquire(); //信号量获取许可 } catch (InterruptedException e) { e.printStackTrace(); } return getNextObject(); } public T getNextObject(){ //从队列中获取对象,这里要使用lock,因为Semaphore并不能保证线程安全 lock.lock(); try{ Node h=header; if(header==null){ return objectFactory.makeObject(); }else{ Node tmp=header; header=h.next; if(header==null){ tail=null; } h.next=null; return tmp.object; } }finally { lock.unlock(); } } public void returnObject(T obj){ //释放对象 semaphore.release(); //调用release returnNextObject(obj); //将对象返回到队列中 } public void returnNextObject(T obj){ lock.lock(); try{ Node n=new Node(); n.next=null; n.object=obj; if(tail==null){ header=tail=n; }else{ tail.next=n; tail=n; } }finally { lock.unlock(); } } }
测试类
public class ObjectPoolTestImpl extends ObjectPoolTest{ public ObjectPoolTestImpl(int capacity, ObjectFactory objectFactory) { super(capacity, objectFactory); } static class ObjectFactoryClass implements ObjectFactory{ @Override public Object makeObject() { return new Student("aaa",123); } } public static void main(String[] args){ ExecutorService executorService=Executors.newFixedThreadPool(30); //线程池大小30 ObjectPoolTest objectPoolTest=new ObjectPoolTest(5,new ObjectFactoryClass()); //传递的信号量大小为5 for(int i=0;i<30;i++){ executorService.execute(new Runnable() { @Override public void run() { Object obj=objectPoolTest.getObject(); //获取对象并打印 System.out.println(obj+" "+Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } objectPoolTest.returnObject(obj); //释放对象 } }); } } }
看下输出结果:可以看到通过对象池获取的对象
aaa.blob.Student@7bc51ffb pool-1-thread-1 aaa.blob.Student@5b917b6c pool-1-thread-4 aaa.blob.Student@8dde312 pool-1-thread-3 aaa.blob.Student@7d7c4860 pool-1-thread-2 aaa.blob.Student@366b83a4 pool-1-thread-5 aaa.blob.Student@7bc51ffb pool-1-thread-6 aaa.blob.Student@7d7c4860 pool-1-thread-9 aaa.blob.Student@8dde312 pool-1-thread-8 aaa.blob.Student@5b917b6c pool-1-thread-7 aaa.blob.Student@366b83a4 pool-1-thread-10 aaa.blob.Student@7bc51ffb pool-1-thread-11 aaa.blob.Student@8dde312 pool-1-thread-15 aaa.blob.Student@366b83a4 pool-1-thread-14 aaa.blob.Student@5b917b6c pool-1-thread-12 aaa.blob.Student@7d7c4860 pool-1-thread-13 aaa.blob.Student@7bc51ffb pool-1-thread-16 aaa.blob.Student@8dde312 pool-1-thread-17 aaa.blob.Student@366b83a4 pool-1-thread-18 aaa.blob.Student@5b917b6c pool-1-thread-19 aaa.blob.Student@7d7c4860 pool-1-thread-20 aaa.blob.Student@7bc51ffb pool-1-thread-21 aaa.blob.Student@8dde312 pool-1-thread-22 aaa.blob.Student@366b83a4 pool-1-thread-23 aaa.blob.Student@5b917b6c pool-1-thread-24 aaa.blob.Student@7d7c4860 pool-1-thread-25 aaa.blob.Student@7bc51ffb pool-1-thread-26 aaa.blob.Student@8dde312 pool-1-thread-27 aaa.blob.Student@7d7c4860 pool-1-thread-30 aaa.blob.Student@5b917b6c pool-1-thread-28 aaa.blob.Student@366b83a4 pool-1-thread-29
这里的对象看起来有点乱,通过Linux命令加工一下,
cat test.txt | awk -F'@' '{print $2}' | awk '{print $1}' | sort
则变成:可以看到,实际new出来的对象只有5个,而30个线程执行的时候,只不过是通过信号量分别来acquire和release,这样是不是和连接池很像。
366b83a4
366b83a4
366b83a4
366b83a4
366b83a4
366b83a4
5b917b6c
5b917b6c
5b917b6c
5b917b6c
5b917b6c
5b917b6c
7bc51ffb
7bc51ffb
7bc51ffb
7bc51ffb
7bc51ffb
7bc51ffb
7d7c4860
7d7c4860
7d7c4860
7d7c4860
7d7c4860
7d7c4860
8dde312
8dde312
8dde312
8dde312
8dde312
8dde312
示例2,这里来通过信号量来实现一个生产者消费者模式
生产者消费者模式之前已经使用wait()/notify()、Condition来实现过,这里再通过信号量来实现一下
这里需要设置3个信号量:
一个用来表示生产者满的信号
一个用来表示消费者为空的信号
一个用来核心调度生成/消费的信号
public class SemaphoreDepotTest { public static void main(String[] args){ for (int i = 0; i <= 3; i++) { // 生产者 new Thread(new Producer()).start(); // 消费者 new Thread(new Consumer()).start(); } } static Store store=new Store(); static class Producer implements Runnable{ static int num=1; @Override public void run() { int n=num++; while(true){ try { store.produce(n); System.out.println(Thread.currentThread().getName()+" produce "+n); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } static class Consumer implements Runnable{ @Override public void run() { while(true){ try { System.out.println(Thread.currentThread().getName()+" consumer "+store.consumer()); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } static class Store{ private Semaphore prodSema=new Semaphore(10); //生产信号,达到10后就停止生成 private Semaphore consSema=new Semaphore(0); //消费信号,为0时,表示无法消费 private Semaphore coreSema=new Semaphore(1); //核心信号,同时只有一个线程在执行 Object[] items=new Object[10]; //存储数组 int putNum,takeNum,count; public void produce(Object x) throws InterruptedException { prodSema.acquire(); //获取生产信号 coreSema.acquire(); //获取核心信号 try{ items[putNum]=x; if(++putNum==items.length){ putNum=0; } count++; }finally { consSema.release(); //释放消费信号 coreSema.release(); } } public Object consumer() throws InterruptedException { consSema.acquire(); //必须在coreSemaphore前面,判断是否为空,否则core先acquire了,但是consumer为空,这样就会造成consumer挂起,而生产者也无法acquire到core,Producer里面是一样的 coreSema.acquire(); try{ Object tmp=items[takeNum]; if(++takeNum==items.length){ takeNum=0; } count--; return tmp; }finally { coreSema.release(); prodSema.release(); //释放生产信号 } } } }
测试结果为:能够实现生产者消费者,且不断的在进行
Thread-0 produce 1
Thread-1 consumer 1
Thread-2 produce 2
Thread-3 consumer 2
Thread-4 produce 3
Thread-5 consumer 3
Thread-6 produce 4
Thread-7 consumer 4
Thread-0 produce 1
Thread-2 produce 2
Thread-4 produce 3
Thread-6 produce 4
Thread-0 produce 1
Thread-2 produce 2
Thread-4 produce 3
Thread-6 produce 4
Thread-2 produce 2
Thread-0 produce 1
Thread-1 consumer 1
Thread-3 consumer 2
Thread-5 consumer 3