独享还是共享,你选择哪一种锁


前言

今天博主将为大家分享独享还是共享,你选择哪一种锁?(独享锁/共享锁),不喜勿喷,如有异议欢迎讨论!

有一个强大的地基才能写出健壮的程序!

顾名思义,独享,只能被一个线程 所持有,而共享,就是说可以被多个线程所共有。


锁的分类

公平锁/非公平锁

  • 可重入锁
  • 独享锁/共享锁
  • 互斥锁/读写锁
  • 乐观锁/悲观锁
  • 分段锁
  • 偏向锁/轻量级锁/重量级锁
  • 自旋锁

独享锁

独享锁其实有很多名称的,有人称它为独享锁,有人也称它为独占锁,其实大致上都是一个意思,

独享锁,只能够被一个线程所持有,

而他的实例我们之前的公平锁和非公平锁也都说过一次,我们可以再看一下这个实例,


ReentrantLock(独享)

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

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

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

/*查询是否有任何线程正在等待与此锁相关联的给定条件。请注意,由于超时和*中断可能随时发生,此方法主要用于监视系统状态
*/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 AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements 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 GCfailed = 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 GCif (interrupted)selfInterrupt();failed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}//上面的这些方法全部都是在AbstractQueuedSynchronizer中//而他通过Sync来调用的acquireShared//而Sync则是继承的AbstractQueuedSynchronizerabstract static class Sync extends AbstractQueuedSynchronizer 而他调用的tryAcquireShared则是在ReentrantReadWriteLock中protected final int tryAcquireShared(int unused) {Thread current = Thread.currentThread();//获取状态int c = getState();//如果说锁状态不是0 并且获取锁的线程不是current线程 返回-1if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)return -1;//统计读锁的次数int r = sharedCount(c);//若无需等待,并且共享读锁共享次数小于MAX_COUNT,则会把锁的共享次数加一,//否则他会去执行fullTryAcquireSharedif (!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 reentrantlyif (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;elsefirstReaderHoldCount--;} 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 casesunparkSuccessor(h);}else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))continue;                // loop on failed CAS}if (h == head)                   // loop if head changedbreak;}}

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


到这里独享还是共享,你选择哪一种锁?(独享锁/共享锁),分享完毕了!下一波将分享《Redis的下载安装》和SpringBoot-Jpa-Redis案例分享!


最后

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

  • 共享锁:可以有多个线程同时获得锁。

  • 更多参考精彩博文请看这里:《陈永佳的博客》

  • 喜欢博主的小伙伴可以加个关注、点个赞哦,持续更新嘿嘿!

独享还是共享,你选择哪一种锁?(独享锁/共享锁)相关推荐

  1. 独享与共享带宽有哪些区别?如何选择?

    一般来说服务器托管提供的带宽主要有两种形式: 1.独享带宽www.tfyum.net 2.共享带宽 www.tfyum.net 那么,独享和共享之间有哪些区别呢?今天小编就具体地给大家说一下独享和共享 ...

  2. 解析服务器独享与共享的区别

    首先,笔者要谈的是概念问T.现在的服务器租用托管商提供的带宽,主要为两种形式,一种是独享带宽.比如独享1兆.独享10兆.独享百兆,指的就是你的这台服务器可以单独享受此带宽,而不用和别的服务器分享,不会 ...

  3. 揭秘IDC商家——独享与共享带宽的区别

    100M共享与2M.5M.10M独享有什么区别?那个好? 当然独享好啊,虽然100M听着好像很好,可是如果不是独享的话,有的时候用着还不如2M独享,共享是你很别的一些客户共同使用这一个带宽资源,而独享 ...

  4. 共享虚拟主机和服务器,独享和共享虚拟主机区别

    独享和共享 独享虚拟主机,是指独享一部分服务器资源的虚拟主机,比如独享CPU.独享内存.独享带宽等. 共享虚拟主机,是我们常见的普通虚拟主机,服务器资源,大家共享,包括CPU.内存.带宽等. 独享虚拟 ...

  5. 什么是带宽?宽带独享?共享?

    所谓带宽(bandwidth)可以通俗理解为单位时间内访问网络的的最大数据流量.如果使用100M网络交换机,局域网带宽可以达到100M:如使用10M交换机则只能达到10M. 主要是访问互联网的速度,这 ...

  6. Java锁详解:“独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁+线程锁”

    在Java并发场景中,会涉及到各种各样的锁如公平锁,乐观锁,悲观锁等等,这篇文章介绍各种锁的分类: 公平锁/非公平锁 可重入锁 独享锁/共享锁 乐观锁/悲观锁 分段锁 自旋锁 线程锁 乐观锁 VS 悲 ...

  7. java 对变量加锁_Java最全锁剖析:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁...

    乐观锁 VS 悲观锁 乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度,在Java和数据库中都有此概念对应的实际应用. 1.乐观锁 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会 ...

  8. Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等...

    http://blog.51cto.com/13919357/2339446 Java 中15种锁的介绍 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容 ...

  9. Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等

    Java 中15种锁的介绍 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容如下: 公平锁 / 非公平锁 可重入锁 / 不可重入锁 独享锁 / 共享锁 互 ...

最新文章

  1. C++ 编译 找不到标识符 问题
  2. 欧几里德算法与扩展欧几里德算法
  3. 智能型住宅自动控制与管理系统分析
  4. linux开发亿连手机互联,亿连手机互联车载版下载-亿连手机互联车机版v6.6.1 安卓版-腾牛安卓网...
  5. dj电商-电子商务常见的商业模式
  6. Go语言并发编程简介
  7. java 去除jsonarray里面jsonarray的重复和合并数据
  8. redhat as4 上安装 MySQL5
  9. 给一个网址传递参数,并接收返回的参数
  10. PHP培训领航者兄弟连IT教育推出兄弟会教育模式
  11. 图像增强算法Python实现之Retinex(含代码)
  12. 2022陕西省安全员B证操作证考试题库及模拟考试
  13. Python 查看已安装的软件包及版本
  14. 计算机无线网卡连接网络,无线网络连接不稳定
  15. 如何使用python进行自动网上考试
  16. Javaweb——监听器
  17. Corral the Cows POJ - 3179(二分+前缀和+离散化)
  18. kubernetes指令合集
  19. 144均线的神奇用法
  20. Java语言的File类总结

热门文章

  1. 两亚太国家宣布其央行不会发行数字货币
  2. 【分享】免费的AI绘画网站(5个)
  3. 迁移学习笔记3: TCA, Finetune, 与Triplet Network(元学习)
  4. MySQL 删除数据后自增长主键id依旧占用问题
  5. STM32 CAN总线故障检测功能的使用
  6. 有道云怎么换行_『42』怎样令为知笔记中的长网址换行?
  7. 教程:这个难到几乎无人通关的游戏,在它面前就是渣!
  8. pikachu通关全集
  9. 如何用微信群、微信霸屏进行引流
  10. 去除摩尔纹,治愈强迫症, 来卷网盘赛