独享仍是共享,你选择哪种锁?

2021年11月25日 阅读数:3
这篇文章主要向大家介绍独享仍是共享,你选择哪种锁?,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。


 

以前在的文章中已经写了公平锁和非公平锁了,接下来就该介绍第二种锁了,他就是共享锁和独享锁,顾名思义,独享,只能被一个线程 所持有,而共享,就是说能够被多个线程所共有。java

锁的分类

1.公平锁/非公平锁node

2.可重入锁3.独享锁/共享锁4.互斥锁/读写锁5.乐观锁/悲观锁6.分段锁7.偏向锁/轻量级锁/重量级锁8.自旋锁

面试

以前的第一次分享中咱们已经说过了公平锁和非公平锁了,此次咱们组要来解析一下这个独享锁和共享锁。安全

独享锁

独享锁其实有不少名称的,有人称它为独享锁,有人也称它为独占锁,其实大体上都是一个意思,多线程

独享锁,只可以被一个线程所持有,并发

而他的实例咱们以前的公平锁和非公平锁也都说过一次,咱们能够再看一下这个实例,框架

 

 

ReentrantLock(独享)

ReentrantLock是基于AQS来实现的,那什么是AQS呢?oop

AQS全称AbstractQueuedSynchronizer,若是说使用翻译软件来看“摘要排队同步器”,可是不少人喜欢称它为抽象队列同步器。 其实叫什么却是没有那么重要,只要记住英文,这才是最重要的。ui

 

AQS它定义了一套多线程访问共享资源的同步器框架,不少类都是依赖于AQS来好比说咱们一会将要介绍的ReentrantLock。this

你看源码

/*    查询是否有任何线程正在等待与此锁相关联的给定条件。    请注意,因为超时和*中断可能随时发生,    此方法主要用于监视系统状态*/ public boolean hasWaiters(Condition condition) {    if (condition == null)        throw new NullPointerException();    if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))        throw new IllegalArgumentException("not owner");    return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);}

这里就指明了咱们说的ReentrantLock是依赖AQS的,而AQS它是JUC并发包中的一个核心的一个组件。 也是不可或缺的组件。

 

AQS解决了子啊实现同步器时涉及当的大量细节问题,例如获取同步状态、FIFO同步队列。基于AQS来构建同步器能够带来不少好处。它不只可以极大地减小实现工做,并且也没必要处理在多个位置上发生的竞争问题。

 

在基于AQS构建的同步器中,只能在一个时刻发生阻塞,从而下降上下文切换的开销,提升了吞吐量。

 

AQS的主要使用方式是继承,子类经过继承同步器并实现它的抽象方法来管理同步状态。

我们能够看一下

public abstract class AbstractQueuedSynchronizer    extends AbstractOwnableSynchronizer    implements java.io.Serializable {    private static final long serialVersionUID = 7373984972572414691L;

而它典型的例子ReentrantLock中:

使用一个int类型的成员变量state来表示同步状态,当state>0时表示已经获取了锁

这就是咱们以前看的int c = getState();

而当c等于0的时候说明当前没有线程占有锁,它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操做,因此AQS能够确保对state的操做是安全的。

关于AQS我就解释这么多把,若是想深刻了解的能够仔细的研究一下,而在这个ReentrantLock中的源码是这样的

/**它默认是非公平锁*/public ReentrantLock() {    sync = new NonfairSync();}   /**   建立ReentrantLock,公平锁or非公平锁   */public ReentrantLock(boolean fair) {     sync = fair ? new FairSync() : new NonfairSync(); } /** 而他会分别调用lock方法和unlock方法来释放锁 */ public void lock() {         sync.lock();     } public void unlock() {         sync.release(1);     }

可是其实他不只仅是会调用lock和unlock方法,由于咱们的线程不可能一点问题没有,若是说进入到了waiting状态,在这个时候若是没有unpark()方法,就没有办法来唤醒他, 因此,也就接踵而至出现了tryLock(),tryLock(long,TimeUnit)来作一些尝试加锁或者说是超市来知足某些特定的场景的需求了。

 

ReentrantLock会保证method-body在同一时间只有一个线程在执行这段代码,或者说,同一时刻只有一个线程的lock方法会返回。其他线程会被挂起,直到获取锁。

 

从这里咱们就能看出,其实ReentrantLock实现的就是一个独占锁的功能:有且只有一个线程获取到锁,其他线程所有挂起,直到该拥有锁的线程释放锁,被挂起的线程被唤醒从新开始竞争锁。

 

而在源码中经过AQS来获取独享锁是经过调用acquire方法,其实这个方法是阻塞的,

/***以独占模式获取,忽略中断。经过至少调用tryAcquire实现成功返回。不然线程排队,可能重复阻塞和解除阻塞,调用tryAcquire直到成功。此方法可用于实现方法lock。*/ public final void acquire(int arg) {        if (!tryAcquire(arg) &&            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))            selfInterrupt();    }

 

它经过tryAcquire(由子类Sync实现)尝试获取锁,这也是上面源码中的lock方法的实现步骤

 

而没有获取到锁则调用AQS的acquireQueued方法:

final boolean acquireQueued(final Node node, int arg) {        boolean failed = true;        try {            boolean interrupted = false;            for (;;) {                final Node p = node.predecessor();                if (p == head && tryAcquire(arg)) {                    setHead(node);                    p.next = null; // help GC                    failed = false;                    return interrupted;                }                if (shouldParkAfterFailedAcquire(p, node) &&                    parkAndCheckInterrupt())                    interrupted = true;            }        } finally {            if (failed)                cancelAcquire(node);        }    }

这段的意思大体就是说 当前驱节点是头节点,而且独占时才返回

而在下面的if判断中,他会去进行阻塞,并且还要去判断是否打断,若是咱们的节点状态是Node.SIGNAL时, 完蛋了,线程将会执行parkAndCheckInterrupt方法,知道有线程release的时候,这时候就会进行一个unpark来循环的去获取锁。 而这个方法经过LockSupport.park(this)将当前的线程挂起到WATING的状态,就须要咱们去执行unpark方法了来唤醒他,也就是我说的那个release, 经过这样的一种FIFO机制的等待就实现了LOCK的操做。

 

这上面的代码只是进行加锁,可是没有释放锁,若是说咱们得到了锁不进行释放,那么很天然的出现一种状况,死锁!

因此必需要进行一个释放,

咱们来看看内部是怎么释放锁的

 public void unlock()              { sync.release(1); } public final boolean release(int arg) {         if (tryRelease(arg)) {             Node h = head;             if (h != null && h.waitStatus != 0)                 unparkSuccessor(h);             return true;         }         return false;     }

unlock方法间接调用AQS的release(1)来完成释放

tryRelease(int)方法进行了特殊的断定,若是成立则会将head传入unparkSuccessor(Node) 方法中而且返回true,不然返回的就是false。

public final boolean release(int arg) {        if (tryRelease(arg)) {            Node h = head;            if (h != null && h.waitStatus != 0)                unparkSuccessor(h);            return true;        }        return false;    }

 

而他在执行了unparkSuccessor方法中的时候,就已经意味着要真正的释放锁了。 这其实就是独享锁进行获取锁和释放锁的一个过程!有兴趣的能够去源码中把注释翻译一下看看。

 

共享锁

从咱们以前的独享所就能看得出来,独享锁是使用的一个状态来进行锁标记的,共享锁其实也差很少,可是JAVA中有不想定力两个状态,因此区别出现了, 他们的锁状态时不同的。

 

基本的流程是同样的,主要区别在于判断锁获取的条件上,因为是共享锁,也就容许多个线程同时获取,因此同步状态的数量同时的大于1的,若是同步状态为非0,则线程就能够获取锁,只有当同步状态为0时,才说明共享数量的锁已经被所有获取,其他线程只能等待。

 

最典型的就是ReentrantReadWriteLock里的读锁,它的读锁是能够被共享的,可是它的写锁确每次只能被独占。

 

咱们来看一下他的获取锁和释放锁的代码体现。

    //获取锁指定离不开这个lock方法,    public void lock() {                sync.acquireShared(1);    }    //acquireShared()首先会经过tryAcquireShared()来尝试获取锁。    //若是说获取不到那么他就回去执行  doAcquireShared(arg);直到获取到锁才会返回    //你看方法名do是否是想到了do-while呢?    public final void acquireShared(int arg) {            if (tryAcquireShared(arg) < 0)                doAcquireShared(arg);    }    // tryAcquireShared()来尝试获取锁。    protected int tryAcquireShared(int arg) {            throw new UnsupportedOperationException();    }    //只有这个方法获取到锁了才会进行返回    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);            }        }     //上面的这些方法所有都是在AbstractQueuedSynchronizer中     //而他经过Sync来调用的acquireShared     //而Sync则是继承的AbstractQueuedSynchronizer     abstract static class Sync extends AbstractQueuedSynchronizer      而他调用的tryAcquireShared则是在ReentrantReadWriteLock中     protected final int tryAcquireShared(int unused) {                 Thread current = Thread.currentThread();                 //获取状态                 int c = getState();                 //若是说锁状态不是0 而且获取锁的线程不是current线程 返回-1                 if (exclusiveCount(c) != 0 &&                     getExclusiveOwnerThread() != current)                     return -1;                 //统计读锁的次数                 int r = sharedCount(c);                 //若无需等待,而且共享读锁共享次数小于MAX_COUNT,则会把锁的共享次数加一,                 //不然他会去执行fullTryAcquireShared                 if (!readerShouldBlock() &&                     r < MAX_COUNT &&                     compareAndSetState(c, c + SHARED_UNIT)) {                     if (r == 0) {                         firstReader = current;                         firstReaderHoldCount = 1;                     } else if (firstReader == current) {                         firstReaderHoldCount++;                     } else {                         HoldCounter rh = cachedHoldCounter;                         if (rh == null || rh.tid != getThreadId(current))                             cachedHoldCounter = rh = readHolds.get();                         else if (rh.count == 0)                             readHolds.set(rh);                         rh.count++;                     }                     return 1;                 }                 return fullTryAcquireShared(current);             }        /** fullTryAcquireShared()会根据是否须要阻塞等待        读取锁的共享计数是否超过限制”等等进行处理。        若是不须要阻塞等待,而且锁的共享计数没有超过限制,        则经过CAS尝试获取锁,并返回1。*/      final int fullTryAcquireShared(Thread current) {          /*           * This code is in part redundant with that in           * tryAcquireShared but is simpler overall by not           * complicating tryAcquireShared with interactions between           * retries and lazily reading hold counts.           */          HoldCounter rh = null;          for (;;) {              int c = getState();              if (exclusiveCount(c) != 0) {                  if (getExclusiveOwnerThread() != current)                      return -1;                  // else we hold the exclusive lock; blocking here                  // would cause deadlock.              } else if (readerShouldBlock()) {                  // Make sure we're not acquiring read lock reentrantly                  if (firstReader == current) {                      // assert firstReaderHoldCount > 0;                  } else {                      if (rh == null) {                          rh = cachedHoldCounter;                          if (rh == null || rh.tid != getThreadId(current)) {                              rh = readHolds.get();                              if (rh.count == 0)                                  readHolds.remove();                          }                      }                      if (rh.count == 0)                          return -1;                  }              }              if (sharedCount(c) == MAX_COUNT)                  throw new Error("Maximum lock count exceeded");              if (compareAndSetState(c, c + SHARED_UNIT)) {                  if (sharedCount(c) == 0) {                      firstReader = current;                      firstReaderHoldCount = 1;                  } else if (firstReader == current) {                      firstReaderHoldCount++;                  } else {                      if (rh == null)                          rh = cachedHoldCounter;                      if (rh == null || rh.tid != getThreadId(current))                          rh = readHolds.get();                      else if (rh.count == 0)                          readHolds.set(rh);                      rh.count++;                      cachedHoldCounter = rh; // cache for release                  }                  return 1;              }          }      }

 

以上的源码就是共享锁的一个获取锁的过程

接下来确定是要进行锁的释放了

unlock()

    public void unlock() {            sync.releaseShared(1);    }    //和获取锁的过程相似,他首先会经过tryReleaseShared()去尝试释放共享锁。尝试成功,则直接返回;尝试失败,    //则经过doReleaseShared()去释放共享锁。     public final boolean releaseShared(int arg) {        if (tryReleaseShared(arg)) {            doReleaseShared();            return true;        }        return false;    }    //是尝试释放共享锁第一步。    protected final boolean tryReleaseShared(int unused) {        Thread current = Thread.currentThread();        if (firstReader == current) {            // assert firstReaderHoldCount > 0;            if (firstReaderHoldCount == 1)                firstReader = null;            else                firstReaderHoldCount--;        } else {            HoldCounter rh = cachedHoldCounter;            if (rh == null || rh.tid != getThreadId(current))                rh = readHolds.get();            int count = rh.count;            if (count <= 1) {                readHolds.remove();                if (count <= 0)                    throw unmatchedUnlockException();            }            --rh.count;        }        for (;;) {            int c = getState();            int nextc = c - SHARED_UNIT;            if (compareAndSetState(c, nextc))                // Releasing the read lock has no effect on readers,                // but it may allow waiting writers to proceed if                // both read and write locks are now free.                return nextc == 0;        }    }    //持续执行释放共享锁    private void doReleaseShared() {            /*             * Ensure that a release propagates, even if there are other             * in-progress acquires/releases.  This proceeds in the usual             * way of trying to unparkSuccessor of head if it needs             * signal. But if it does not, status is set to PROPAGATE to             * ensure that upon release, propagation continues.             * Additionally, we must loop in case a new node is added             * while we are doing this. Also, unlike other uses of             * unparkSuccessor, we need to know if CAS to reset status             * fails, if so rechecking.             */            for (;;) {                Node h = head;                if (h != null && h != tail) {                    int ws = h.waitStatus;                    if (ws == Node.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;            }        }

以上的代码就是共享锁和非共享锁的源码。须要注意的时候,在这里其实很乱,有些方法是定义在ReentrantReadWriteLock中的, 而有一些方法是定义在AbstractQueuedSynchorizer中的,因此在来回切换看代码的时候尤为要注意,不要出现失误。

 

总结

独享锁:同时只能有一个线程得到锁。

共享锁:能够有多个线程同时得到锁。

关于独享锁和共享锁,你明白了吗?

 


 

Java 极客技术公众号,是由一群热爱 Java 开发的技术人组建成立,专一分享原创、高质量的 Java 文章。若是您以为咱们的文章还不错,请帮忙赞扬、在看、转发支持,鼓励咱们分享出更好的文章。 关注公众号,你们能够在公众号后台回复“博客园”,免费得到做者 Java 知识体系/面试必看资料。