Semaphore、CountDownLatch、CyclicBarrier、Exchanger、Phaser均是concurrent包提供的用于多线程同步的对像,使用它们可以相对方便的处理复杂的同步场景。本博文介绍Semaphore的原理及实例
Semaphore
实现了经典的信号量机制。信号量其实是一个非负整数表示可用共享资源的数目,主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。其内部是用一个计数器控制对共享资源的访问,计数据大于0资源允许访问,等于0是拒绝访问共享资源的,在访问共享资源前要先调用acquire方法,如果计数据大于0则可以访问资源,访问后要调用release方法释放共享资源,如果等于0则调用线程阻塞于此方法。
分析下Semaphore类的源码,首先可以肯定一个简单的计数器是不能实现同步的,因为计数器本身的同步也是需要解决的,Semaphore是其通一个链式的queue来实现的AbstractQueuedSynchronizer,其中一个成员是private volatile int state;调用acquire方法会检查是否大于0,调用release方法会使state加1,AbstractQueuedSynchronizer其中有一个内部类Node,Node有一个成员Thread,这个成员就是存储调acquire阻塞时的线程,所以AbstractQueuedSynchronizer其实是一个包含计数器的,线程链式队列
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
/**
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
/**
* Link to predecessor node that current node/thread relies on
* for checking waitStatus. Assigned during enqueuing, and nulled
* out (for sake of GC) only upon dequeuing. Also, upon
* cancellation of a predecessor, we short-circuit while
* finding a non-cancelled one, which will always exist
* because the head node is never cancelled: A node becomes
* head only as a result of successful acquire. A
* cancelled thread never succeeds in acquiring, and a thread only
* cancels itself, not any other node.
*/
volatile Node prev;
/**
* Link to the successor node that the current node/thread
* unparks upon release. Assigned during enqueuing, adjusted
* when bypassing cancelled predecessors, and nulled out (for
* sake of GC) when dequeued. The enq operation does not
* assign next field of a predecessor until after attachment,
* so seeing a null next field does not necessarily mean that
* node is at end of queue. However, if a next field appears
* to be null, we can scan prev's from the tail to
* double-check. The next field of cancelled nodes is set to
* point to the node itself instead of null, to make life
* easier for isOnSyncQueue.
*/
volatile Node next;
/**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
*/
volatile Thread thread;
下面来看下构造函数,构造函数有两个,下面的是带有公平设置的。permits是共享资源的数目也可以理解成允许同时并行的线程数,fair是公平设置,可以理解成,如要为true,在调用aquire时先检查下队列中有没有等待的线程,有等待的线程就要进入队列等待,也就是保证了先到先得。如果为false,在调用aquire时是需要检查下队列中有没有等待的线程,也就是后来者可以先获得许可。
//不公平竞争
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
//公平竞争boolean fair=true
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
使用实例:
/**
* 其两个线程替执行count++,最终正确的结果应该是10,
* 如果把Semampore的permits设为2或者不用Semaphore结果就是不可预知的,这种情况请自己偿试下
*/
public class SemaphoreDemo1 {
private static int count = 0;//共享资源
//一般情况下都是使用没有公平设置的Semaphore
private static Semaphore smp = new Semaphore(1);
public static void main(String[] args){
//创建线程ThreadA并启动
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("ThreadA" + " 尝式获得许可");
smp.acquire();
System.out.println("ThreadA" + " 获得许可,开行访问共享资源");
// count++执行5次
for(int i=0; i < 5; i++) {
count++;
System.out.println("ThreadA" + ": " + count);
Thread.sleep(1000);
}
} catch (InterruptedException exc) {
System.out.println(exc);
}
smp.release();
}
},"ThreadA").start();
//创建线程ThreadB并启动
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("ThreadB" + " 尝式获得许可");
smp.acquire();
System.out.println("ThreadB" + " 获得许可,开行访问共享资源");
// count++执行5次
for(int i=0; i < 5; i++) {
count++;
System.out.println("ThreadB" + ": " + count);
Thread.sleep(1000);
}
} catch (InterruptedException exc) {
System.out.println(exc);
}
smp.release();
}
},"ThreadB").start();
}
}
执行如果如下:
ThreadA 尝式获得许可
ThreadA 获得许可,开行访问共享资源
ThreadA: 1
ThreadB 尝式获得许可
ThreadA: 2
ThreadA: 3
ThreadA: 4
ThreadA: 5
ThreadB 获得许可,开行访问共享资源
ThreadB: 6
ThreadB: 7
ThreadB: 8
ThreadB: 9
ThreadB: 10
Process finished with exit code 0