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()不信你看:”

这里写图片描述

虽说加锁和释放锁都要在代码层次上显示的去操作,稍复杂一些,不是很优雅,但是我有synchronized所不具备的高级功能,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 线程的interrupt()方法时,就会进入到 catch 并执行下面的任务,不必要在read函数的获得锁上一直阻塞等待(B线程它一直想获得A线程的所持有的锁,但是迟迟没有得到)

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

比如A先来获得锁,获取失败,阻塞等待,然后B来获取锁,获取失败,阻塞等待,最后当这个锁释放的时候,按照公平锁的原则,A肯定会得到锁

在ReentrantLock的构造函数中,可以传入是否为公平锁的参数,如:

// 使用公平锁机制
Lock lock = new ReentrantLock(true);

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

比如现在有两个方法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也是很不错的


这里写图片描述

扫码关注,精彩不断

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章