synchronized与Lock 擂台之战

面试官:说说synchronized和Lock(或ReentrantLock)的区别

Java 1.5之后,对共享变量访问的协调机制除了之前的synchronized和volatile又多了一个Lock,深刻理解synchronized与Lock,并熟悉两者的应用场景对编写并发程序有着非常重要的作用

部落新添大将

话说JDK1.5之前,并发部落 synchronized 和 volatile 可谓红人,无人不知,无人不晓

当多个线程访问同一共享变量时,只需在操作该共享变量的方法上加一个synchronized,就可以保证同一时刻只有一个线程操作该共享变量(还有其他用法)

这使得多个线程按照要求合理的占用和释放资源,所以并发部落如此昌盛,synchronized 功不可没

JDK1.5的到来,打破了这种局面

一个名为Lock的接口出世,听说这个Lock刚出世就神通广大,不仅有着和synchronized一样的功能,在锁的获取上还可以定时获取,轮询获取和可中断获取等等一系列高级的技能

这消息传到了synchronized的耳中,心中很是不爽,决定找个擂台与Lock一决胜负,可是想到Lock有那么多的优势,自己心中顿时没了底气,所以他决定对自己升级一下再去PK

synchronized找到了JDK老大诉苦,说要改造改造自己

JDK老大说道:“之前一直有人抱怨你慢,因为线程要获得锁和释放锁都要进行一次重量级的系统调用,我早都想着给你优化优化”

“好啊好啊”,synchronized说道

“其实我这里已经有方案了,你暂且回去,等JDK1.6的到来吧”JDK回道

“好的”,synchronized回复到。

新synchronized问世

终于JDK1.6到来了,synchronized在锁的获取和释放上有了重大改进,引入了偏向锁、轻量级锁和重量级锁,这次synchronized信心大增,决定去找Lock PK

synchronized 到了 Lock 跟前,说道:“久闻Lock兄神通广大,今日一见,不知神通在何处?”

Lock 一看这家伙是来挑事的,自己也不甘示弱,“神通之处你自然看不出来,用时方显神通”,Lock回应道

synchronized气的咬牙切齿,但这也正和自己心意,“哦,那我想见识见识,明日部落有一场擂台比武,不知Lock兄能否夺得桂冠”

“那是必然”,Lock回应道

“那明日一决雌雄”,synchronized甩下一句就走了

擂台比武

次日,两人都来到了擂台旁,并发部落的人几乎都来了,都想看看synchronized和Lock的好戏

用法PK

synchronized说道:“首先我是一个关键字,我常常被人称为内置锁,我的使用特别简单,如果你想让某一个方法在同一时刻只能由一个线程访问,那么只需要在方法上加上一个synchronized,如下:

这样对变量 i 的修改就线程安全了”

synchronized说道,“对了,更为让人清爽的是,我的加锁和释放锁都是隐式的,不需要程序员们在代码层次上手动的去加锁和释放锁,是不是很优雅?”

听完synchronized的一番自述后,虽说在用法上稍逊synchronized,但是Lock也不甘示弱,说道:

“我是一个接口,可以有无数的子类去实现我,ReentrantLock就是一个

我的使用也很简单呀,当你想给某一段代码加锁的话,只需要在代码块之前调用 lock() 方法,在之后调用 unlock() 不信你看”:

虽说加锁和释放锁都要在代码层次上显示的去操作,稍复杂一些,不是很优雅,但是我的锁获取和释放更加灵活

比如说有一个先获取锁A,再获取锁B,获取锁B之后释放锁A,然后再释放锁B的需求,我想什么时候获取锁和释放锁,直接调用 lock()unlock() 就OK了

Lock拿出自己的优势来弥补了一下自己的不足

性能PK

早有准备的synchronized暗自窃喜,在这方面现在自己不比Lock差,synchronized说道,“性能这块我现在引入了偏向锁,轻量级锁和重量级锁,这些改变我的性能比之前提高了许多”

“提高了许多现在才和我差不多”Lock 插了一刀,气的synchronized无话可说

用途PK

synchronized这块很是心虚,论用途,Lock比自己多,但是自己还是心理给自己打气,说道:

“并发控制这块的需求,基本上我都可以解决,而且用法很优雅,许多程序员已经习惯了我的存在,并且在发生异常的时候,JVM老大会自动释放锁,这样就避免了死锁的产生”

synchronized抓住Lock一定要调用 unlock() 方法释放锁的缺点不放

“不像有些人,如果不主动 调用 unlock() 释放锁,就很可能造成死锁”,synchronized又补了一句

Lock立马回应道:

“我虽有些许不足,但是我的高级功能很强大,synchronized可以实现的功能我都可以实现,除此之外,我还有 等待可中断可实现公平锁以及锁可以绑定多个条件等高级功能”

台下的观众眼中放光,特别想听听这些都是什么东西,之前都没见过

只见Lock清了清嗓门,一个一个的解释起来了

① 所谓等待可中断就是一个线程去获得一个锁的时候,由于很长时间没有获取到锁,可以放弃等待,处理其他事情

比如有两个线程 A 和 B,A获得了锁,B想获得该锁,那么B线程就挂起了

如果A迟迟不肯放锁,当该锁的获取是可以响应中断(调用lock.lockInterruptibly()),那么当其他线程中断该线程的时候,则B线程就可以响应中断,去做其他事情了

//在获取锁时被中断,抛出 InterruptedException

lock.lockInterruptibly()

如下图:

run() 方法是 B 线程(继承了Thread)的方法,如果不想让B线程等待,只需要让其他线程调用 B 线程的 interrupt() 方法,就会进入到 catch 并执行下面的任务

不必要在read函数的获得锁上一直阻塞等待(一直阻塞等待是因为B线程它一直想获得A线程的所持有的锁,但是迟迟没有得到)

②可实现公平锁:所谓公平锁,就是在多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁

比如A先来获得锁,获取失败,阻塞等待,然后B来获取锁,获取失败,阻塞等待,最后当这个锁释放的时候,按照公平锁的原则,A肯定会得到锁在ReentrantLock的构造函数中,可以传入是否为公平锁的参数,如:

// 使用公平锁机制

Lock lock = new ReentrantLock(true);

③ 所谓 锁绑定多个条件是指,一个 ReentrantLock 对象可以绑定多个 Condition对象(通过 lock.newCondition 创建一个Condition)

比如现在有两个方法methodA和methodB,有两个线程阻塞在methodA上,有一个线程阻塞在methodB上,现在想单独唤醒methodA上的线程 或者单独唤醒阻塞在methodB上的线程

如果是wait()和notify或notifyAll() 机制,则需要两个锁而ReentrantLock只需要一把锁就可以完成,一把锁可以new 多个Condition

在调用 lock.lock() 后 可以调用 Condition 的 await() 方法使线程处于等待状态,释放锁,如下:

当有两个线程进入methodA时,一个获得锁[ lock.lock() ]后又释放锁[ conditionA.await() ],进入等待状态

另一个线程同样也进入等待状态,当其他线程调用 conditionA.signalAll() [也可调用 conditionA.signal() ] 的时候,可以唤醒在conditionA上等待的所有线程:如下

这样,在conditionA上等待的线程就被全部唤醒了,这和 notifyAll() 的作用一样,只是这里的condition 可以由一个 lock 创建很多

同样的也可以在methodB中使用 conditionB, 最后用 conditionB.signalAll() 来唤醒在 conditionB 上等待的所有线程

如果是wait 和 notify 的话,就需要多个锁了

听完Lock的自述后,大家都赞不绝口,地下一片掌声

最后的胜负

这时候裁判大人出场了,听完两位大侠的叙述,我看两人各有优缺

synchronized 为许多开发人员所熟悉,并且简洁紧凑,许多现有的程序都已经使用了synchronized

而 Lock 有许多高级功能,在一些特定的场合能派上大用处,但是Lock的危险性很高,如果忘记在finally块中调用 unlock,那么就埋下了一颗定时炸弹

所以只有当synchronized 不能满足需求时,才可以使用ReentrantLock

现在我宣布,synchronized 略胜一筹,但Lock也是很不错的

原文发布于微信公众号 - 趣谈编程(gh_51e791ad9174)

原文发表时间:2017-10-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术与生活

设计模式-命令模式

Client:确定具体的命令和接受者; Command:抽象命令接口,一般是接口类或者抽象类 ConcreteCommand:具体的命令执行,调用接受者 Inv...

13950
来自专栏章鱼的慢慢技术路

浅谈单片机中C语言与汇编语言的转换

47430
来自专栏芋道源码1024

Dubbo源码解析 —— 服务引用原理

前言 经过上一篇dubbo源码解析-简单原理、与spring融合的铺垫,我们已经能简单的实现了dubbo的服务引用.其实上一篇中的代码,很多都是从dubbo源码...

34080
来自专栏Golang语言社区

Go语言实战: 编写可维护Go语言代码建议

大家好, 我在接下来的两个会议中的目标是向大家提供有关编写Go代码最佳实践的建议。

25030
来自专栏刘望舒

热修复原理之热修复框架对比和代码修复

题外话 今天听到了著名物理学家史蒂夫霍金去世消息,潸然泪下。作为一个天文迷,感谢霍金带给我的那些天文知识。十分敬佩霍金的身残志坚,他在全身瘫痪无法言语情况下仍旧...

35040
来自专栏AhDung

【VBS】vbs指定编码保存文本文件(含xml、ini什么的)

- 让用户填写一些信息,待安装完成后把这些信息写入软件安装目录中的指定ini、xml文件中

12610
来自专栏吉浦迅科技

DAY35:阅读流程控制语句

15940
来自专栏人人都是极客

Linux内核内存管理算法Buddy和Slab

有了前两节的学习相信读者已经知道CPU所有的操作都是建立在虚拟地址上处理(这里的虚拟地址分为内核态虚拟地址和用户态虚拟地址),CPU看到的内存管理都是对page...

62060
来自专栏java一日一条

编写可靠 Shell 脚本的 8 个建议

这八个建议,来源于键者几年来编写 shell 脚本的一些经验和教训。事实上开始写的时候还不止这几条,后来思索再三,去掉几条无关痛痒的,最后剩下八条。毫不夸张地说...

13320
来自专栏XAI

Highcharts AJAX JSON JQuery 实现动态数据交互显示图表 柱形图

这是第一篇实例的步骤与代码。还有整个项目的结构图。 http://my.oschina.net/xshuai/blog/345117 原创的博文。转载注明出处...

59560

扫码关注云+社区

领取腾讯云代金券