共享式获取与独占式获取最主要的区别在于同一时刻能否有多个线程同时获取到同步状态。以文件的读写为例,若一个程序在对文件进行读操作,那么这一时刻对于该文件的写操作均被阻塞,而读操作能够同时进行。写操作要求对资源的独占式访问,而读操作可以是共享式访问,两种不同的访问模式在同一时刻对文件或资源的访问情况,如下图所示。
![]()
在上图中左半部分,共享式访问资源时,其他共享式的访问均被允许,而独占式访问被阻塞,右半部分是独占式访问资源时,同一时刻其他访问均被阻塞。
通过调用同步器的acquireShared(int arg)方法可以共享式地获取同步状态,该方法代码如下:
public final void acquireShared(int arg){
if(tryAcquireShared(arg) <0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg){
final Node node = addWaiter(Node.SHARED);
Boolean failed=true;
try{
Boolean interrupted = false;
for(;;){
final Node p =node.predecessor();
if(p == head ){
int r=tryAcquireShared(arg);
if(r>0){
setHeadAndPropagate(node,r);
p.next=null;//help GC
if (interrupted)
selfInterrupt();
failed=false;
return;
}
}
if()shouldParkAfterFailedAcquire(p,node) && parkAndCheckInterrupt())
interrupted=true;
}
}finally{
if (failed)
cancelAcquire(node);
}
}
在acquireShared(int arg)方法中,同步器调用tryAcquireShared(int arg)方法尝试获取同步tryAcquireShared(int arg)方法返回值为int类型,当返回值大于等于0时,表示能够获取到同步状态。因此,在共享式获取的自旋过程中,成功获取到同步状态并退出自旋的条件就是tryAcquireShared(int arg)方法返回值大于等于0。可以看到,在doAcquireShared(int arg)方法的自旋过程中,如果当前节点的前驱为头节点时,尝试获取同步状态,如果返回值大于等于0,表示该次获取同步状态成功并从自旋过程中退出。
共享式获取需要释放同步状态,通过调用releaseShared(int arg)方法可以释放同步状态,该方法代码如下所示。
public final boolean releaseShared(int arg){
if(tryReleaseShared(arg)){
doReleaseShared();
return true;
}
return false;
}
该方法在释放同步状态之后,将会唤醒后续处于等待状态的节点。对于能够支持多个线程同时访问的并发组件(比如Semaphore),它和独占式主要区别在于tryReleaseShared(int arg)方法必须确保同步状态(或者资源数)线程安全释放,一般是通过循环和CAS来保证的,因为释放同步状态的操作会同时来自多个线程。