Java中的锁

这里主要说一下Lock接口相关的实现部分

Lock接口与AbstractQueueSynchronizer(AQS)

Lock接口,该接口提供了编程式的获取/释放锁的能力,对于复杂的锁访问,通过Lock来进行锁控制非常好。


队列同步器:之前讲过,阻塞队列之后线程之间在竞争获取临界区资源时会进入同步队列,在Lock实现时,基本都可以借助于队列同步器(AbstractQueueSynchronizer,AQS)实现,它是面相锁的实现者的,它简化了锁的实现方式,屏蔽了同步状态管理,线程排队,等待与唤醒等底层操作,锁是面相使用者而言的,它提供了多线程之间对资源的访问,屏蔽了实现细节。锁和同步器很好的隔离了使用者和实现者所需关注的领域。

队列同步器采用模版方法模式定义了统一的访问流程,子类只需要根据自己的需求重写部分模版,如独占式/共享式的获取/释放锁,自己管理状态分配(实际上就是一个int类型的数据,我们把它当作临界资源分配标志,当我们修改时都要通过CAS保证线程安全),具体的同步交给同步队列器做就好了。

队列同步器大量采用CAS操作来完成安全的设置值操作,其队列部分采用虚拟的FIFO双向链表,同步队列器基本结构如图

同步队列器基本结构

队列中的节点信息如图


队列中的节点信息

可以看到节点信息持有了线程状态,前后节点,对应的线程等。这里基本上可以看出来就是通过同步队列器的同步队列来控制多线程访问临街资源了。

维护队列的原则,只有头节点拥有同步状态,当头节点释放时,唤醒后继节点,该节点线程检查前驱节点是否为头节点与尝试获取同步状态,如果成功将自己设置为头节点,然后方法返回。 对应的图如下:

独占式获取同步状态流程
节点自旋获取同步状态

自旋获取同步状态时所使用的是虚线,实际上在每次被唤醒时若不是前驱节点为首节点或竞争获取同步状态失败,都会将自身挂起等待下次唤醒,所以这个自旋并不是真的完全自旋。


对于重入锁来说,也就是对同步状态第一次获取时绑定获取的线程,在线程重入时,状态累加,在线程释放时,状态累减。直到0那么清理绑定线程,允许其他线程进入。

对于读写锁来说,由于只有一个int类型来做同步状态,需要将int分解为高16位与低16位。
写锁是一个支持重入的排他锁,当有读锁被获取,或其他锁持有写锁时会等待。
读锁是一个支持重进入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问 (或者写状态为0)时,读锁总会被成功地获取。
锁降级,当持有写锁时可以再持有读锁,然后释放写锁,那么就只持有读锁了,也就达到了锁降级。

可以看到基本的套路都是,为了实现不同的锁模式,继承AQS后只需要自己进行分配同步状态就可以很简单的实现锁。这样我们自己也可以继承AQS实现自定义的锁。


Condition实现等待/通知

Condition:任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、 wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。

刚才说的是同步队列实现部分,现在这个是等待队列实现部分。


同步队列与等待队列理解图

一个锁对象可以产生多个Condition对象,每个Condition对象都持有有一个虚拟的单向队列,节点信息和AQS中的节点信息一致。当线程调用wait()方法时,会将当前线程加入到等待队列并释放锁(如果从队列(同步队列和等待队列)的角度看await()方法,当调用await()方法时,相当于同步队列的首节点(获取了锁的节点)移动到Condition的等待队列中。),然后通过判断自己是否在同步队列中来进行自旋(会阻塞等通知),当其他线程调用该condition的signal()方法时,则去除队列头节点加入到同步队列中,然后唤醒该线程,那么刚才的自旋就会被打开,接着进行状态判断,进入获取同步状态的竞争中(有可能又进入同步队列的自旋,同步队列的自旋主要是获取锁,这两个自旋不要弄混了)。具体的await()代码流程如图

await()与signal()代码理解图

这里很明显可以看出等待队列的自旋只是等待别人将自己移到同步队列,然后唤醒自己进入竞争同步资源,等待获取到同步资源(锁)线程也就真的被唤醒了

之前的Object对象的监视器方法wait(),notify()实际上也就一样的同步队列/等待队列,只不过那个对象就一个等待队列,通过Lock和Condition对象可以产生一个同步队列和多个等待队列。用这个方法实现读写分别阻塞的队列是非常经典的方式。


其他

之前写过的并发编程基础大体讲述了一下对象,监视器,同步队列,等待队列的关系,这里侧重讲了一下实现部分。实际上如果没有复杂的锁操作或者等待/通知的要求,更推荐使用synchronized进行代码块的锁,在JDK1.6之后synchronized在JVM层也实现了很多的优化,性能并不比AQS差,而且随着JDK的升级synchronized还能获得更多的优化。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容