Java对余锁方面提供的API有很多,我这里将以我的开发经验,在各个阶段对Java锁的接触来解释对锁的认识。内容略“很黄很暴力”,纯清妹妹慎入!
在接触Thread
的时候,好像打包一样,买一送一的送给了我一个synchronized
。当时并不知道它是拿来干嘛的,只是知道在方法上面加一个这个,这方法就很安全,貌似和我的肩膀一样厚实安全。我们经常会写下面这样的代码:
public synchronized void safeMethod(){
//do something
}
但是这个时候synchronized
是做了什么申请,当时的我并不知道。随着码代码的日子越来越多,我知道了这其实是加锁了。“锁”是一个动词,锁什么呢?为了知道锁什么,不知道又过了多少个码代码的日夜,有一天灵光一闪,终于恍然大悟。其实这个锁什么很重要,如果不知道到底锁了什么那么今天的安全,可能是日后的一个坑。那么我们来看看到底锁了什么吧。
其实不知道锁什么,是一种对锁的错误使用,因为知道说什么,你就知道那里需要同步。上面的代码锁的对象其实是safeMethod
方法,那么所有线程到safeMethod
方法这里,就会进行一次列好队,各自拿上号码牌,有序的进入safeMethod
方法,并且只能有一个线程进去,不然这个方法就要“挤爆”了。这里说的的锁是锁住某个方法,可不可以锁住其他的东西呢?针对synchronized
还可以下面方式的使用:
public void lockBlock(){
synchronized{
}
}
public void lockField(){
synchronized(fieldVar){
}
}
public void lockObject(){
synchronized(this){
}
}
public void lockClass(){
synchronized(this.getClass()){
}
}
上面基础场景,它们锁住的范围一次在增大,第一个只锁住一段代码块,第二个只锁住一个变量,第三个锁住的是当前对象,第四个是锁住了类。通过无数个失眠的深夜,欣赏完岛国杂技之后的总结中发现,只要是锁住了什么东西,假如这个锁别其他线程占有了,就必须要等待其他线程的释放,你才能去占有这个锁。并且锁住的某个变量或者对象实体(类也是一个对象实体),那么对其的所有操作都串行化了(就是需要排队)。比如锁住的是一个Class实体,那么对该类的所有操作都会串行化,所以锁住类的影响范围是最大的。到现在我都不知道解释清楚了没。还是不看杂技了,专心写完再看。
为了更好的理解JVM里面锁,我们来看一段对话吧,设计三个角色一个是JVM
(很威严,老大范),一个是线程
(小罗罗),另一个是User
对象妹妹
在看肥皂剧之前,先看看事情发展的起因。JVM有一个仓库,仓库里面存放了很多对象,线程经常来JVM这里租借对象来玩玩(玩坏了不负责)。于是某一天,线程又来JVM这里租借一个对象玩玩,故事边这样开始了:
线程
:JVM
大人,我想找User
对象玩玩(表情很猥琐,声音很淫荡)
JVM
:你这贱奴,每天来找User
妹妹,她都被你玩坏了(声音洪亮,表情庄严)
线程
:大人,我知道User
妹妹很受欢迎,不知道她现在是否方便呀?
JVM
:让我问问User
妹妹
JVM
:User
,一位线程想来和你玩耍,问你是否方便
剧情到这里需要跳出来一下,这里有两种场景,场景1:
User
:我正在陪另一个哥哥玩耍呢,让他等会在进来。
JVM
:好嘞,那妹妹好好陪着这位玩耍。
JVM
:(对着线程
说)你来的不巧,User
妹妹正在陪着另一个高富帅呢,你等会吧。
线程
:好吧,我就在这里候着,看谁敢在我之前和User
妹妹玩耍。
场景2:
User
:哦?这里刚好有一位哥哥在和我玩呢。又来了一个,那不是玩的更Hight?速叫那位哥哥进来。
JVM
:小心点,我昨天刚给你换的床。
JVM
:(对着线程
说)你进去吧,User
妹妹呼唤你。
线程
:好嘞,User
妹妹!我来了!
….于是User
妹妹和她的哥哥们快乐的玩耍着。
上面两种场景可以映射到加了锁和没加锁的情况,不知道是否把剧情描述清楚了。这里的User妹妹代表的是锁的范围:方法(在JVM里面方法也是通过一个类来定义的),字段,类,对象。到此,我知道了锁,以及锁所涉及的哪些方面。锁的一个重要的方面就是范围,如何很好的控制锁的范围,才是锁的一个关键。
这种锁的同步方式是JVM提供给我的一种在并发情况下如何让代码更加的有安全感,由于是JVM出的,所以对其的信任度也比较高。官方出的东西,大家都知道,很稳定,但是性能上面还是有一定的瓶颈,虽然后面官方对这方面也有改进,但也是看到有别人家的东西之后才慢慢的去改进,就像EJB在Spring的逼迫下不断的改进一样。
从出生第一天我就知道了,条条大路通岛国的真理,于是我一直在寻求有没有其他途径来通向我心中的岛国(除了整天翻过社会主义的墙)。真理那就一定是对于任何事情进行抽象之后都可以复用的,这个和好的软件设计是一样的。既然JVM大人提供了一个很官方的实现,同时也公布了他的伟大思想,那就有NB的人来实现一个更好的(就像Rod johnson实现了他的Spring一样),这个NB的人就是我爱慕已久的Doug Lea,他通过使用JAVA实现了一个锁机制,让开发能够真正的感受到锁的存在,而JVM提供的锁是开发人员基本上感觉不到锁在哪里,需要YY一下才能知道它的存在。Doug lea提供锁的方式一般是如下形式:
Lock lock = new ReentrantLock();
public void doSomething(){
try{
lock.lock();
//safe block
}finally{
lock.unlock();
}
}
一看类名就知道是一把锁,不像JVM那么委婉。这种方式,需要先常见一把锁对象,这里可以理解为是一个锁的管理器,而lock.lock()
才是去申请一把锁,lock.unlock()
则是释放锁。不管是类的定义和方法名的定义都是如此的直白,不需要费脑细胞都知道怎么用,并且也很清楚的看到锁住了什么,即lock.lock()
和lock.unlock()
中间区域。下面再看一段对话看看lock.lock()
和lock.unlock()
做了什么?剧情设计到著名打杂演员线程
,新秀lock
(ReentrantLock对象)。
lock
是一个厕所管理员,线程
每天都会去那里解大便。故事是发生在一个早上,线程
和往常一样去lock
管理的测试解大便,发现外面没人,心里想可以顺利解决了!
线程
:lock
!开门,我要解大便,快不行了,拉裤子上了!
lock
:急什么呀,排队,这里面有人解大便呢,你等着!
线程
:能不能告诉里面哥们快点呀?
lock
:你就在外面候着,他出来了你才能进去!
线程
就这样一直等着,可是今天早上来解大便的人特别多,外面等的人多了起来。此时里面解大便的人出来了!线程
喜出望外!
接下来又有两个场景
场景1:
lock
:现在外面可以进来一个人了!
lock
这话落音,外面等着解大便的人争先恐后的挤进厕所,线程
虽然第一个来,但是被这人群挤哪去了都不知道!于是线程
没挤进去,继续在外面候着!
这样不知道过了多少回合线程
都没挤进厕所
就这样线程
被shi给憋死了!
场景2:
lock
:线程
你进去吧,谁叫你第一个来!再过会,估计你就拉在我厕所门口了!后面的排好队,不要插队了!
线程
就这样顺利的解决了,轻松的出来了!从此过上了幸福的生活!
不知道剧情是否有岛国杂技那么精彩。这里来进行一下剧评,这里的厕所可以理解为lock.lock()
和lock.unlock()
中间区域,而lock.lock()
则是厕所门的钥匙,lock.unlock()
是解完大便出了厕所。这里进入和出入测试都是需要经过lock
的。既然lock
可以定义为变量,那么就存在重用的可能,那对应上面的剧情,就是lock
掌管多个厕所,而且所有这么多测试里面,只能有一个人进去解大便(虽然和现实生活不一样,谁叫他是生活在java的世界里),那就存在严重浪费资源的情况,加入lock
掌管10个厕所,那么每次不是都浪费9个厕所?那不是又得憋死线程?所以合理的分配lock
管辖的范围是很关键的,最好肯定是一个厕所配备一个lock
来管理,但是有些时候确实存在lock
需要管理多个厕所的情况。比如:只有两个厕所,由于资源有限,不能一直让人解大便,而不清理厕所(要知道lock
可是洁癖!),所以每次只能让一个厕所让人来解大便,而另一个厕所需要进行清理。对应码代码就是存在同一个资源竞争的情况,比如一个资源在两个地方都会修改,防止一个地方的修改被另一个地方的修改覆盖而导致数据的不一致,所以在这两个地方需要共用一个lock
对象。
上面简单的阐述了Doug lea通过java实现的锁,其实里面详细的,建议还是阅读一下源码。Doug lea基于这些,实现了很多并发场景需要使用的类,比如读写锁,更加高效的HashMap以及安全的队列等,我这里就不都复制出来了!
上面都是在一个JVM环境中,如果在多个JVM环境中多个服务器中怎么很好的进行并发的控制,从而保持数据的一致性。
分布式锁他的大致思想和Doug lea的类似。只是锁的管理不在放在当前的JVM里面,而是放在一个中央服务器上(zookeeper,redis或者数据库等),然后申请和释放锁都需要和中央服务器通信。会在后面的博客中介绍我遇到分布式锁通过zookeeper解决的方案,这里就不做过多的介绍。
此时,听到电脑里的某个软件发出“叮…”,我的32G岛国杂技填满了我的D盘,好好学习一下岛国杂技是怎么练成的!额?手纸用完了…..