Java并发系列

ReentrantReadWriteLock基础介绍

简介 之前我们主要介绍了ReentrantLock和其相关的Condition的内容。今天我们主要会过一下ReentrantReadWriteLock的代码。现实中,读写锁的使用非常多。如果你也是一个大数据工程师,同时使用过Spark的话,就会知道Spark内部有一个BlockInfoManager的类。在大数据计算中,常用的是基于复制的模式,读多写少的任务非常常见,有读写锁可以有效的减少锁竞争,提高数据查询速率。今天这一节,我们就简单介绍一下ReentrantReadWriteLock和它背后的一点数学知识。 读写锁的3条规定: 允许多个线程同时读共享变量 只允许一个线程写共享变量 如果写线程正在执行写出操作,那么禁止其他线程读写共享变量。 ReentrantReadWriteLock的特殊属性:锁降级:遵循先获取写锁,获取读锁,再释放写锁的次序。写锁可以降级成读锁 ReentrantReadWriteLock的实现的接口是ReadWriteLock: public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading */ Lock…

Condition的源码分析

这篇我们会继续讲解一个经常和ReentrantLock配套使用的组建:Condition。对于Condition来说,一切的故事开始于: public Condition newCondition() { return sync.newCondition(); } sync对象封装了该方法: final ConditionObject newCondition() { return new ConditionObject(); } ConditionObject就是Condition的实现类,该对象也在AQS的文件中。它有2个成员对象: /** First node of condition queue. */ private transient Node firstWaiter; /**…

ReentrantLock 源码分析 Part 2

今天我们继续更新Java并发系列,往期内容大家可以点击Java并发系列查看。接着上一次内容,这次主要会讲解一下ReentrantLock的解锁流程. unlock函数 上次我们提到了ReentrantLock的使用范式中: class X { private final ReentrantLock lock = new ReentrantLock(); //.. public void m() { lock.lock(); try { //.. method body } finally { lock.unlock(); } }…

ReentrantLock 源码分析 Part 1

今天主要讨论的主题是Java的ReentrantLock的由来和加锁的原理。 为什么需要ReentrantLock 作为程序员,还是需要尽量减少重复造轮子这种事情。但是Java既然已经有了synchronized关键词,Java的大佬们再设计Lock是不是一种重复的轮子呢?当然不是,synchronized有一个问题,我们无法主动释放资源。如下所示: sychronize (a) { sychronize (b) { //.. method body } } 假设我们获取了资源a,但是无法获取资源b。此时我们也无法释放资源a。那么整个系统可能就会处在死锁状态。为了解决这个问题,就需要新的轮子来解决。那就是今天想要介绍的主角ReentrantLock。 使用lock的范式 在这个类的一开始作者就贴心的写上了Java并发所需要用到的范式。因为这些代码在多线程中执行,为了确保就算程序出错抛出了异常,所以finally中务必不要忘记加入解锁的相关代码,否则容易造成不知名的死锁情况。 class X { private final ReentrantLock lock = new ReentrantLock(); //.. public void…

CLH锁简单解释

CLH队列是由Craig, Landin 和 Hagersten设计的队列。它是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。 首先,我们来看一下基础的检查并设置(test-and-set)自旋锁。假设锁是一个布尔值,我们用一个伪代码进行演示。 func acquire(lock): while true: if atomic_test_and_set(lock.flag): return 如果当前竞争很少的话,那这段代码的运行速度会非常快。但是如果有十个线程同时竞争同一个锁呢?你会有9个线程在不断尝试,这会消耗大量的CPU资源。我们有一些常用的方法可以减少这样的CPU浪费:队列锁。 队列锁意味着你给每个线程一个自有的锁,这样就不存在竞争了。然后你可以将它们连接在一起形成一个队列。这可能比较抽象,我们用一个例子来说明: 现在你想获取一个锁。首先你会试图将自己放入一个队列。如果放入成功,且队列为空,则你成功获取锁,这时候你会将自己标记为成功。如果放入成功,但是队列不为空,你会将自己标记为等待,并监听自己之前的节点。这个时候你可以选择自旋或者sleep。Java的AQS实现利用了sleep-waiting模式,避免自旋浪费CPU资源。 如果你获取锁成功,且使用完毕释放锁了。你会将自己成功标记去除 func acquire(lock, node): node->flag = true prev = node->prev = swap(lock, node) while node->flag:…