Java 5中引入了新的锁机制——java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作。Lock接口有3个实现它的类:ReentrantLock、ReetrantReadWriteLock.ReadLock和ReetrantReadWriteLock.WriteLock,即重入锁、读锁和写锁。lock必须被显式地创建、锁定和释放,为了可以使用更多的功能,一般用ReentrantLock为其实例化。为了保证锁最终一定会被释放(可能会有异常发生),要把互斥区放在try语句块内,并在finally语句块中释放锁,尤其当有return语句时,return语句必须放在try字句中,以确保unlock()不会过早发生,从而将数据暴露给第二个任务。因此,采用lock加锁和释放锁的一般形式如下:

Lock lock = new ReentrantLock();//默认使用非公平锁,如果要使用公平锁,需要传入参数true

........

lock.lock();

try {

//更新对象的状态

//捕获异常,必要时恢复到原来的不变约束

//如果有return语句,放在这里

finally {

lock.unlock(); //锁必须在finally块中释放

     Lock lock = new ReentrantLock();//默认使用非公平锁,如果要使用公平锁,需要传入参数true
........
lock.lock();
try {
//更新对象的状态
//捕获异常,必要时恢复到原来的不变约束
//如果有return语句,放在这里
} finally {
lock.unlock(); //锁必须在finally块中释放
}

ReetrankLock与synchronized比较


性能比较

在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。相比之下使用Java提供的Lock对象,性能更高一些。Brian Goetz对这两种锁在JDK1.5、单核处理器及双Xeon处理器环境下做了一组吞吐量对比的实验,发现多线程环境下,synchronized的吞吐量下降的非常严重,而ReentrankLock则能基本保持在同一个比较稳定的水平上。但与其说ReetrantLock性能好,倒不如说synchronized还有非常大的优化余地,于是到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。

下面浅析以下两种锁机制的底层的实现策略。

互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因而这种同步又称为阻塞同步,它属于一种悲观的并发策略,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。synchronized采用的便是这种并发策略。

随着指令集的发展,我们有了另一种选择:基于冲突检测的乐观并发策略,通俗地讲就是先进性操作,如果没有其他线程争用共享数据,那操作就成功了,如果共享数据被争用,产生了冲突,那就再进行其他的补偿措施(最常见的补偿措施就是不断地重拾,直到试成功为止),这种乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步被称为非阻塞同步。ReetrantLock采用的便是这种并发策略。

在乐观的并发策略中,需要操作和冲突检测这两个步骤具备原子性,它靠硬件指令来保证,这里用的是CAS操作(Compare and Swap)。JDK1.5之后,Java程序才可以使用CAS操作。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState,这里其实就是调用的CPU提供的特殊指令。现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起。

Java 5中引入了注入AutomicInteger、AutomicLong、AutomicReference等特殊的原子性变量类,它们提供的如:compareAndSet()、incrementAndSet()和getAndIncrement()等方法都使用了CAS操作。因此,它们都是由硬件指令来保证的原子方法。

用途比较

基本语法上,ReentrantLock与synchronized很相似,它们都具备一样的线程重入特性,只是代码写法上有点区别而已,一个表现为API层面的互斥锁(Lock),一个表现为原生语法层面的互斥锁(synchronized)。ReentrantLock相对synchronized而言还是增加了一些高级功能,主要有以下三项:

1、等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情,它对处理执行时间非常上的同步块很有帮助。而在等待由synchronized产生的互斥锁时,会一直阻塞,是不能被中断的。

2、可实现公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序排队等待,而非公平锁则不保证这点,在锁释放时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁时非公平锁,ReentrantLock默认情况下也是非公平锁,但可以通过构造方法ReentrantLock(ture)来要求使用公平锁。

3、锁可以绑定多个条件:ReentrantLock对象可以同时绑定多个Condition对象(名曰:条件变量或条件队列),而在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含条件,但如果要和多于一个的条件关联的时候,就不得不额外地添加一个锁,而ReentrantLock则无需这么做,只需要多次调用newCondition()方法即可。而且我们还可以通过绑定Condition对象来判断当前线程通知的是哪些线程(即与Condition对象绑定在一起的其他线程)。

《Java并发编程实践》一书给出了使用 ReentrantLock的最佳时机:

当你需要以下高级特性时,才应该使用:可定时的、可轮询的与可中断的锁获取操作,公平队列,或者非块结构的锁。否则,请使用synchronized。

JAVA并发2的更多相关文章

  1. 多线程的通信和同步(Java并发编程的艺术--笔记)

    1. 线程间的通信机制 线程之间通信机制有两种: 共享内存.消息传递.   2. Java并发 Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式执行,通信的过程对于程序员来说是完全透 ...

  2. 【Java并发编程实战】----- AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...

  3. 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport

    在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...

  4. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...

  5. 【Java并发编程实战】-----“J.U.C”:CLH队列锁

    在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格按照FIFO的队列.他能够确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...

  6. 【Java并发编程实战】-----“J.U.C”:CountDownlatch

    上篇博文([Java并发编程实战]-----"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一 ...

  7. 【Java并发编程实战】-----“J.U.C”:CyclicBarrier

    在上篇博客([Java并发编程实战]-----"J.U.C":Semaphore)中,LZ介绍了Semaphore,下面LZ介绍CyclicBarrier.在JDK API中是这么 ...

  8. 【Java并发编程实战】-----“J.U.C”:ReentrantReadWriteLock

    ReentrantLock实现了标准的互斥操作,也就是说在某一时刻只有有一个线程持有锁.ReentrantLock采用这种独占的保守锁直接,在一定程度上减低了吞吐量.在这种情况下任何的"读/ ...

  9. Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...

  10. JAVA并发编程J.U.C学习总结

    前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www. ...

随机推荐

  1. Android-Uiautomator:[5]停止monkey测试

    方法/步骤 1 其实停止很简单,无非就是结束掉monkey的进程即可 如何停止呢 2 ps命令  查找uiautomator的进程 打开cmd命令行窗口 输入: adb shell ps | grep ...

  2. 【基础练习】【vector】codevs3393 序列倒置

    版权信息 转载请注明出处 [ametake版权全部]http://blog.csdn.net/ametake欢迎来看 题目非常easy,本质还是练习一下vector 题目描写叙述 Descriptio ...

  3. 日积月累:ProguardGui进行jar包代码混淆

    前面文章<Proguard进行源代码混淆>讲解过怎么使用Proguard工具对Android的源代码进行混淆的方法(感兴趣的朋友可以访问:http://blog.csdn.net/p106 ...

  4. RDIFramework.NET平台代码生成器V1.0发布(提供下载)

    RDIFramework.NET平台代码生成器V1.0发布(提供下载)   RDIFramework.NET(.NET快速开发整合框架)框架做为信息化系统快速开发.整合的框架,其目的一至是给用户和开发 ...

  5. (转) Android平台上关于IM的实践总结

    前言 IM通信在互联网发展到现在已经是码农的世界里人尽皆知的技术,特别在当下移动互联网迅猛发展的时代这种技术的开发也更加火热,其中老牌的代表作就有QQ和MSN,和最近新崛起的微信,默默,易信,来往等眼 ...

  6. POJ 1556 The Doors 线段判交+Dijkstra

    The Doors Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 6734   Accepted: 2670 Descrip ...

  7. EventBus 事件总线 案例

    简介 地址:https://github.com/greenrobot/EventBus EventBus是一个[发布 / 订阅]的事件总线.简单点说,就是两人[约定]好怎么通信,一人发布消息,另外一 ...

  8. java语言基础特性

    使用Java5中的类型安全枚举和注解(以及元注解)编程 http://www.importnew.com/11053.html

  9. TreeView 数据绑定及选中命令处理

    昨天接近下班,一个群里面的网友,问treeView绑定后  选中命令怎么来处理,怎么没有效果,而且用MVVM的方式来写:快下班了本来想远程帮他看下,结果就说写个Demo给他看:再加上选中传参: 下面分 ...

  10. linux定时任务crond那些事!

    1.定时任务crond介绍 1.1 crond是什么 crond是linux系统中用来定期执行命令或指定程序任务的一种服务或软件. 特殊需求:(秒级别)crond服务就无法搞定了,一般工作中写脚本守护 ...