Java 并发编程实战
王宝令
资深架构师
72485 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
学习攻略 (1讲)
Java 并发编程实战
15
15
1.0x
00:00/00:00
登录|注册

14 | Lock和Condition(上):隐藏在并发包中的管程

非阻塞地获取锁的API
支持超时的API
支持中断的API
三种方案
Condition接口
Lock接口
不在调用其他对象的方法时加锁
访问可变的成员变量时加锁
更新对象的成员变量时加锁
破坏不可抢占条件
性能问题
解决方法
同步问题
互斥问题
转账程序存在的死锁问题
深思熟虑的设计
Lock接口的方法
Doug Lea的三个最佳实践
等待队列
构造函数
示例
概念
Lock的实现原理
Happens-Before规则
区别
对管程的实现
课后思考
总结
用锁的最佳实践
公平锁与非公平锁
可重入锁
可见性保证
为什么提供另一种实现
Java SDK并发包
Lock&Condition(上):隐藏在并发包中的管程

该思维导图由 AI 生成,仅供参考

Java SDK 并发包内容很丰富,包罗万象,但是我觉得最核心的还是其对管程的实现。因为理论上利用管程,你几乎可以实现并发包里所有的工具类。在前面《08 | 管程:并发编程的万能钥匙》中我们提到过在并发编程领域,有两大核心问题:一个是互斥,即同一时刻只允许一个线程访问共享资源;另一个是同步,即线程之间如何通信、协作。这两大问题,管程都是能够解决的。Java SDK 并发包通过 Lock 和 Condition 两个接口来实现管程,其中 Lock 用于解决互斥问题,Condition 用于解决同步问题
今天我们重点介绍 Lock 的使用,在介绍 Lock 的使用之前,有个问题需要你首先思考一下:Java 语言本身提供的 synchronized 也是管程的一种实现,既然 Java 从语言层面已经实现了管程了,那为什么还要在 SDK 里提供另外一种实现呢?难道 Java 标准委员会还能同意“重复造轮子”的方案?很显然它们之间是有巨大区别的。那区别在哪里呢?如果能深入理解这个问题,对你用好 Lock 帮助很大。下面我们就一起来剖析一下这个问题。

再造管程的理由

你也许曾经听到过很多这方面的传说,例如在 Java 的 1.5 版本中,synchronized 性能不如 SDK 里面的 Lock,但 1.6 版本之后,synchronized 做了很多优化,将性能追了上来,所以 1.6 之后的版本又有人推荐使用 synchronized 了。那性能是否可以成为“重复造轮子”的理由呢?显然不能。因为性能问题优化一下就可以了,完全没必要“重复造轮子”。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Java SDK中的Lock&Condition实现了管程,解决了并发编程中的互斥和同步问题。相比于synchronized,Lock提供了更多的灵活性和功能,如支持中断、超时和非阻塞地获取锁。这些功能使得Lock能够更好地满足并发编程中的需求,特别是在处理不可抢占条件时更为有效。Lock的可见性是通过Happens-Before规则和volatile变量来保证的,而ReentrantLock支持可重入,使得线程可以重复获取同一把锁。另外,ReentrantLock还提供了公平锁和非公平锁两种策略,分别影响等待队列中线程的唤醒顺序。除了并发大师Doug Lea推荐的三个最佳实践外,还可以参考一些业界广为人知的规则,如减少锁的持有时间、减小锁的粒度等。Lock&Condition的使用为并发编程提供了更多的选择和灵活性,能够更好地满足复杂的并发需求。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 并发编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(124)

  • 最新
  • 精选
  •  
    我觉得:不会出现死锁,但会出现活锁

    作者回复: 👍

    2019-03-30
    8
    120
  • 小华
    有可能活锁,A,B两账户相互转账,各自持有自己lock的锁,都一直在尝试获取对方的锁,形成了活锁

    作者回复: 👍

    2019-03-30
    5
    96
  • xiyi
    存在活锁。这个例子可以稍微改下,成功转账后应该跳出循环。加个随机重试时间避免活锁

    作者回复: 👍👍👍

    2019-03-30
    77
  • bing
    文中说的公平锁和非公平锁,是不按照排队的顺序被唤醒,我记得非公平锁的场景应该是线程释放锁之后,如果来了一个线程获取锁,他不必去排队直接获取到,应该不会入队吧。获取不到才进吧

    作者回复: 是的,高手👍👍👍

    2019-03-30
    5
    69
  • J.M.Liu
    1.这个是个死循环啊,有锁没群,都出不来。 2.如果抛开死循环,也会造成活锁,状态不稳定。当然这个也看场景,假如冲突窗口很小,又在单机多核的话,活锁的可能性还是很小的,可以接受

    作者回复: 👍👍👍

    2019-03-30
    35
  • 森呢
    老师,你好,这是我第二遍研读你的课程了,每一遍都收获很大。第一次写留言有点紧张。 你上面写的jdk利用内存模型的三条规则来保证可见性,是正确的。但我觉得好像描述的理由好像不充分,我不知道我理解的对不对,请老师解答一下 我的理解应该是 :1)释放锁成功后,写state的值 (unlock>state-=1) 顺序性 2)获取锁前,读state值(state>lock)顺序性 3)传递性 unlock>lock 下面是jdk的源码 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread();//获取当前线程实例 int c = getState();//获取state变量的值,即当前锁被重入的次数 if (c == 0) { //state为0,说明当前锁未被任何线程持有 if (compareAndSetState(0, acquires)) { //以cas方式获取锁 setExclusiveOwnerThread(current); //将当前线程标记为持有锁的线程 return true;//获取锁成功,非重入 } } else if (current == getExclusiveOwnerThread()) { //当前线程就是持有锁的线程,说明该锁被重入了 int nextc = c + acquires;//计算state变量要更新的值 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc);//非同步方式更新state值 return true; //获取锁成功,重入 } return false; //走到这里说明尝试获取锁失败 }

    作者回复: 感谢补充😄state必须是volatile变量,否则是不会有unlock>lock的,我比你更紧张😂

    2019-07-04
    5
    25
  • linqw
    class Account { private int balance; private final Lock lock = new ReentrantLock(); // 转账 void transfer(Account tar, int amt){ boolean flag = true; while (flag) { if(this.lock.tryLock(随机数,NANOSECONDS)) { try { if (tar.lock.tryLock(随机数,NANOSECONDS)) { try { this.balance -= amt; tar.balance += amt; flag = false; } finally { tar.lock.unlock(); } }//if } finally { this.lock.unlock(); } }//if }//while }//transfer } 感觉可以这样操作

    作者回复: 点个大大的赞!不过还可以再优化一下,如果阻塞在tar.lock.tryLock上一段时间,this.lock是不能释放的。

    2019-04-07
    15
    25
  • 海鸿
    突然有个问题: cpu层面的原子性是单条cpu指令。 java层面的互斥(管程)保证了原子性。 这两个原子性意义应该不一样吧? 我的理解是cpu的原子性是不受线程调度影响,指令要不执行了,要么没执行。而java层面的原子性是在锁的机制下保证只有一个线程执行,其余等待,此时cpu还是可以进行线程调度,使运行中的那个线程让出cpu时间,当然了该线程还是掌握锁。 我这样理解对吧?

    作者回复: 对

    2019-03-30
    4
    19
  • 姜戈
    我也觉得是存在活锁,而非死锁。存在这种可能性:互相持有各自的锁,发现需要的对方的锁都被对方持有,就会释放当前持有的锁,导致大家都在不停持锁,释放锁,但事情还没做。当然还是会存在转账成功的情景,不过效率低下。我觉得此时需要引入Condition,协调两者同步处理转账!

    作者回复: 用condition会更复杂

    2019-03-30
    14
  • Q宝的宝
    老师,本文在讲述如何保证可见性时,分析示例--“线程 T1 对 value 进行了 +=1 操作后,后续的线程 T2 能否看到 value 的正确结果?“时,提到三条Happen-Before规则,这里在解释第2条和第3条规则时,似乎说反了,正确的应该是,根据volatile变量规则,线程T1的unlock()操作Happen-Before于线程T2的lock()操作,所以,根据传递性规则,线程 T1 的 value+=1操作Happen-Before于线程T2的lock()操作。请老师指正。

    作者回复: 火眼金睛👍👍👍👍,这就改过来

    2019-03-30
    3
    14
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部