JAVA锁机制

在开发Java多线程应用程序中,各个线程之间由于要共享资源,必须用到锁机制。Java提供了多种多线程锁机制的实现方式,常见的有synchronized、ReentrantLock、Semaphore、AtomicInteger等。每种机制都有优缺点与各自的适用场景,必须熟练掌握他们的特点才能在Java多线程应用开发时得心应手。

更多Java锁机制的详细介绍参见文档《Java锁机制详解》。

一、synchronized

几乎每一个Java开发人员都认识synchronized,使用它来实现多线程的同步操作是非常简单的,只要在需要同步的对方的方法、类或代码块中加入该关键字,它能够保证在同一个时刻最多只有一个线程执行同一个对象的同步代码,可保证修饰的代码在执行过程中不会被其他线程干扰。使用synchronized修饰的代码具有原子性和可见性,在需要进程同步的程序中使用的频率非常高,可以满足一般的进程同步要求(详见《Java多线程基础》)。

synchronized实现的机理依赖于软件层面上的JVM,因此其性能会随着Java版本的不断升级而提高。事实上,在Java1.5中,synchronized是一个重量级操作,需要调用操作系统相关接口,性能是低效的,有可能给线程加锁消耗的时间比有用操作消耗的时间更多。到了Java1.6,synchronized进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。在之后推出的Java1.7与1.8中,均对该关键字的实现机理做了优化。

需要说明的是,当线程通过synchronized等待锁时是不能被Thread.interrupt()中断的,因此程序设计时必须检查确保合理,否则可能会造成线程死锁的尴尬境地。

最后,尽管Java实现的锁机制有很多种,并且有些锁机制性能也比synchronized高,但还是强烈推荐在多线程应用程序中使用该关键字,因为实现方便,后续工作由JVM来完成,可靠性高。只有在确定锁机制是当前多线程程序的性能瓶颈时,才考虑使用其他机制,如ReentrantLock等。

二、ReentrantLock

可重入锁,顾名思义,这个锁可以被线程多次重复进入进行获取操作。ReentantLock继承接口Lock并实现了接口中定义的方法,除了能完成synchronized所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。

Lock实现的机理依赖于特殊的CPU指定,可以认为不受JVM的约束,并可以通过其他语言平台来完成底层的实现。在并发量较小的多线程应用程序中,ReentrantLock与synchronized性能相差无几,但在高并发量的条件下,synchronized性能会迅速下降几十倍,而ReentrantLock的性能却能依然维持一个水准,因此我们建议在高并发量情况下使用ReentrantLock。

ReentrantLock引入两个概念:公平锁与非公平锁。公平锁指的是锁的分配机制是公平的,通常先对锁提出获取请求的线程会先被分配到锁。反之,JVM按随机、就近原则分配锁的机制则称为不公平锁。ReentrantLock在构造函数中提供了是否公平锁的初始化方式,默认为非公平锁。这是因为,非公平锁实际执行的效率要远远超出公平锁,除非程序有特殊需要,否则最常用非公平锁的分配机制。

ReentrantLock通过方法lock()与unlock()来进行加锁与解锁操作,与synchronized会被JVM自动解锁机制不同,ReentrantLock加锁后需要手动进行解锁。为了避免程序出现异常而无法正常解锁的情况,使用ReentrantLock必须在finally控制块中进行解锁操作。通常使用方式如下所示:

[
复制代码

](javascript:void(0); "复制代码")

<pre>1 Lock lock = new ReentrantLock(); 2 try { 3 lock.lock(); 4 //...进行任务操作
5 } finally { 6 lock.unlock(); 7 }</pre>

[
复制代码

](javascript:void(0); "复制代码")

下面我们详细介绍有关ReentrantLock提供的可响应中断锁、可轮询锁请求、定时锁等机制与操作方式。

1、线程在等待资源过程中需要中断

ReentrantLock的在获取锁的过程中有2种锁机制,忽略中断锁和响应中断锁。当等待线程A或其他线程尝试中断线程A时,忽略中断锁机制则不会接收中断,而是继续处于等待状态;响应中断锁则会处理这个中断请求,并将线程A由阻塞状态唤醒为就绪状态,不再请求和等待资源。

lock.lock()可设置锁机制为忽略中断锁,lock.lockInterruptibly()可设置锁机制为响应中断锁。下述例子描述了,一个写线程和一个读线程分别操作同一个同一个对象的写方法和读方法,写方法需要执行10秒时间,主线程中在启动写线程writer和读线程reader后,启动了第三个线程,这个线程判断当程序执行5秒后,如果读线程依然处于等待状态,就将他中断,不再继续等待资源。

image

View Code

由例子可知,ReentrantLock.lockInterruptibly()方法可设置线程在获取锁的时候响应其他线程对当前线程发出的中断请求。但必须注意,此处响应中断锁是指正在获取锁的过程中,如果线程此时并非处于获取锁的状态,通过此方法设置是无法中断线程的,非阻塞状态可根据中断标记位Thread.currentThread().isInterrupted()在程序中手动设置中断,阻塞状态可通过抛出异常InterruptedException来中断线程,详细可参考博文《Java多线程基础》。

2、实现可轮询的锁请求

在synchronized中,一旦发生死锁,唯一能够恢复的办法只能重新启动程序,唯一的预防方法是在设计程序时考虑完善不要出错。而有了Lock以后,死锁问题就有了新的预防办法,它提供了tryLock()轮询方法来获得锁,如果锁可用则获取锁,如果锁不可用,则此方法返回false,并不会为了等待锁而阻塞线程,这极大地降低了死锁情况的发生。典型使用语句如下:

image

View Code

3、定时锁请求

在synchronized中,一旦发起锁请求,该请求就不能停止了,如果不能获得锁,则当前线程会阻塞并等待获得锁。在某些情况下,你可能需要让线程在一定时间内去获得锁,如果在指定时间内无法获取锁,则让线程放弃锁请求,转而执行其他的操作。Lock就提供了定时锁的机制,使用Lock.tryLock(long timeout, TimeUnit unit)来指定让线程在timeout单位时间内去争取锁资源,如果超过这个时间仍然不能获得锁,则放弃锁请求,定时锁可以避免线程陷入死锁的境地。

在上面的实例一中,其他线程在5秒后向正在等候锁的读线程发起中断请求,读线程响应请求并成功中断。也可以在读线程中设置定时锁,设定在5秒内争夺锁,超时则放弃锁,并结束当前的读线程,使用定时锁实现读方法代码如下:

image

View Code

三、Semaphore

上述两种锁机制类型都是“互斥锁”,学过操作系统的都知道,互斥是进程同步关系的一种特殊情况,相当于只存在一个临界资源,因此同时最多只能给一个线程提供服务。但是,在实际复杂的多线程应用程序中,可能存在多个临界资源,这时候我们可以借助Semaphore信号量来完成多个临界资源的访问。

Semaphore基本能完成ReentrantLock的所有工作,使用方法也与之类似,通过acquire()与release()方法来获得和释放临界资源。经实测,Semaphone.acquire()方法默认为可响应中断锁,与ReentrantLock.lockInterruptibly()作用效果一致,也就是说在等待临界资源的过程中可以被Thread.interrupt()方法中断。

此外,Semaphore也实现了可轮询的锁请求与定时锁的功能,除了方法名tryAcquire与tryLock不同,其使用方法与ReentrantLock几乎一致。Semaphore也提供了公平与非公平锁的机制,也可在构造函数中进行设定。

Semaphore的锁释放操作也由手动进行,因此与ReentrantLock一样,为避免线程因抛出异常而无法正常释放锁的情况发生,释放锁的操作也必须在finally代码块中完成

Semaphore支持多个临界资源,而ReentrantLock只支持一个临界资源,笔者认为ReentrantLock是Semaphore的一种特殊情况。Semaphore的使用方法与ReentrantLock实在太过相似,在此不再举例说明。

四、AtomicInteger

首先说明,此处AtomicInteger是一系列相同类的代表之一,常见的还有AtomicLong、AtomicLong等,他们的实现原理相同,区别在与运算对象类型的不同。令人兴奋地,还可以通过AtomicReference<V>将一个对象的所有操作转化成原子操作。

我们知道,在多线程程序中,诸如++i 或 i++等运算不具有原子性,是不安全的线程操作之一。通常我们会使用synchronized将该操作变成一个原子操作,但JVM为此类操作特意提供了一些同步类,使得使用更方便,且使程序运行效率变得更高。通过相关资料显示,通常AtomicInteger的性能是ReentantLock的好几倍。

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

推荐阅读更多精彩内容