Semphore(信号量)是用来控制同时访问特定资源的线程数量。
那么它到底是如何实现的呢,让我们点开源码一探究竟吧。
一、属性变量
private final Sync sync;
属性变量只有一个,那就是Sync类的对象
二、内部类
1、Sync类
abstract static class Sync extends AbstractQueuedSynchronizer
可见Sync类继承与AQS,这个类实现信号量的核心
2、NonfairSync类
static final class NonfairSync extends Sync
具有不公平的信号量
3、FairSync
static final class FairSync extends Sync具有公平性的信号量
二、构造方法
public Semaphore(int permits) { sync = new NonfairSync(permits); }
在只传入信号量个数的情况下,默认创建非公平的信号量
public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }
传入信号量个数并且还传入了boolean类型的变量fair时,如果fiar为true创建公平信号量,不然创建非公平的信号量
三、操作方法
1、acquire方法
public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
这个方法用来获取同步状态,获取成功线程便可执行临界区的代码,不然则等待其他线程对信号量的释放。
a、非公平信号量的获取
final int nonfairTryAcquireShared(int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }
将信号量减一,如果可用信号量小于0则返回负数,如果大于0,则使用CAS操作将信号量个数减1
b、公平信号量的获取
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; } }
首先调用hashQueuedPredecessors方法,这个方法时判断是否有线程比当前线程更早的请求同步状态。其他步骤和非公平信号量一致。
public final boolean hasQueuedPredecessors() { Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
如果h不等t,说明同步队列中有多个节点,并且头结点的后继节点如果不是当前线程,说明这个线程比当前更早的请求同步状态,所以需要等待前驱线程获取并释放锁之后才能继续获取锁。
2、release方法
public void release(int permits) { if (permits < 0) throw new IllegalArgumentException(); sync.releaseShared(permits); }release方法最后会调用Semphore重写的tryReleaseShared方法
protected final boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); int next = current + releases; if (next < current) // overflow throw new Error("Maximum permit count exceeded"); if (compareAndSetState(current, next)) return true; } }
tryReleaseShared方法执行步骤如下
1、获取信号量个数
2、信号量个数加1
3、如果加1后的信号量小于原先的信号量,说明溢出了,所以抛出异常
4、如果使用CAS操作将信号量个数更新为加1后的信号量个数,则返回true,表示归还信号量成功。
四、应用
public static void main(String[] args) { Semaphore mutex = new Semaphore(2); Thread t1 = new Thread(){ @Override public void run() { try { System.out.println("我是A 我要进去"); mutex.acquire(); System.out.println("我是A,我进来了"); Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); }finally { mutex.release(); } } }; t1.start(); Thread t2 = new Thread(){ @Override public void run() { try { System.out.println("我是B 我要进去"); mutex.acquire(); System.out.println("我是B,我进来了"); Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); }finally { mutex.release(); } } }; t2.start(); Thread t3 = new Thread(){ @Override public void run() { try { System.out.println("我是C 我要进去"); mutex.acquire(); System.out.println("我是C,我进来了"); //Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); }finally { mutex.release(); } } }; t3.start(); }上述代码定义了2个信号量个数的Semphore,说明同一时间最多只有两个线程能够进去执行代码。运行结果如下图所示。
可以看到,只有A线程和B线程进入了,而线程C却被挡在门外,当A线程和B线程休眠到期后,结果如下图所示
C进去了,并执行了代码