第13章 显示锁

终于看到了这本书的最后一本分,呼呼呼,真不容易。其实说实在的,我不喜欢半途而废,有其开始,就一定要有结束,否则的话就感觉哪里乖乖的。

java5.0之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile。java5.0增加了一种新的机制:ReentrantLock。与之前提到过的机制相反,ReentrantLock并不是一种替代内置锁的方法,而是当内置锁机制不适用时,作为一种可选择的高级功能。

13.1 Lock与ReentrantLock(p227)

程序清单13-1中给出的Lock接口定义了一组抽象的加锁操作。与内置锁机制不同的是,Lock提供了一种无条件的,可轮询的,定时的以及可中断的锁获取操作,所有加锁和解锁操作都是显示的。

ReentrantLock实现了Lock接口(Reentrant:再进去,凹角,再进去的,凹角的),并提供了与synchronized相同的互斥性和内存可见性。ReentrantLock支持在Lock接口中定义的所有获取锁模式,并且与syncronized相比,他为处理锁的不可用性提供了更高的灵活性。

书上一段话就截了这部分,感觉这段有点翻译错误。程序清单13-2给出了Lock接口的标准使用形式。这种形式比使用内置锁复杂一些,必须在finally块中释放锁,否则如果早被保护的代码中抛出了异常,那么这个锁永远都无法释放。

ReentrantLock不能完全替代syncronized的原因:它更加危险,因为当程序的执行控制离开被保护的代码块时,不会自动清楚。虽然在finally块中释放锁并不困难,但也可能忘记。

13.1.1 轮询锁与定时锁(p228)

可定时的与可轮询的锁获取模式是由tryLock方法实现的,与无条件的锁获取模式相比,它具有更完善的错误恢复机制。如果不能获得所有需要的锁,那么可以使用可定时的锁或可轮询的锁获取方式,从而使你重新获得控制权,它会释放已经获得的锁,然后重新尝试获取所有锁。程序清单13-3给出了另一种方法来解决10.1.2节中动态顺序死锁的问题:使用tryLock来获取两个锁,如果不能同时获得,那么就回退并重新尝试。

在实现具有时间限制的操作时,定时锁同样非常有用。当在带时间限制的操作中调用了一个阻塞方法时,它能根据剩余时间来提供一个时限。如果操作不能在指定时间内给出结果,那么使程序提前结束。当使用内置锁时,在开始请求锁后,这个锁操作将无法取消,因此内置锁很难实现带有时间限制的操作。

程序清单6-17的旅游门户网站示例中,为询价的每个汽车租赁公司都创建另一个独立的任务。询价操作包含某种基于网络的请求机制,例如web请求。但在询价操作中同样可能需要实现对紧缺资源的独占访问,例如通向公司的直连线路。9.5节介绍了确保对资源进行串行访问的方法:一个单线程的Executor。另一种方法是使用一个独占锁来保护对资源的访问。程序清单13-4试图在Lock保护的共享通信线路上发送一条消息,如果不能在指定时间完成,代码就会失败。定时的tryLock能够在这种带有时间限制的操作中实现独占行为。

13.1.2 可中断的锁获取操作(p230)

可中断的锁获取操作能在可取消的操作中使用加锁。7..6节给出了几种不能响应中断的机制,例如请求内置锁。这些不可中断的阻塞机制将使得实现可取消的任务变得复杂。lockInterruptibly方法能够在获得锁的同时保持对中断的响应,并且由于它包含在Lock中,因此无须创建其它类型的不可中断阻塞机制。来个例子:

13.1.3 非块结构加锁(p231)

采用11章中的锁分段技术来降低链表中锁的粒度,为每个链表节点使用一个独立的锁,使不同的线程能独立地对链表的不同部分进行操作。每个节点的锁将保护链接指针以及在该节点中存储的数据,只有这样,才能释放前一个节点上的锁。(连锁式加锁(Hand-Over-Hand Locking)||锁耦合(Lock Coupling))

13.2 性能考虑因素

上图的原因是:java 6使用了改进后的算法来管理内置锁,使得内置锁和ReentrantLock在java6上的性能差异不是很大,而在java 5上就差很多了。

13.3 公平性

在ReentrantLock的构造函数中提供了两种公平性的选择:创建一个非公平的锁(默认)或者一个公平的锁。在公平的锁上,线程将按照它们发出请求的顺序来获得锁,但在非公平的锁上,则允许“插队”:当一个线程请求非公平的锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有等待线程并获得这个锁。(在Semaphore中同样可以选择采用公平或非公平的获取顺序)。在公平的锁中,如果有另一个线程持有这个锁或者有其他线程在队列中等待这个锁,那么新发出请求的线程将被放入队列中。在非公平的锁中,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中。(即使对于公平锁而言,可轮询的tryLock任然会“插队”)

图13-2给出了Map的性能测试,并比较由公平的以及非公平的ReentrantLock包装的HashMap性能。从图中可以看出,公平性把性能降低了越两个数量级。不必要的话,不要为公平性付出代价。

13.4 在synchronized和ReentrantLock之间进行选择(p234)

ReentrantLock在性能上似乎优于内置锁,但与显示锁相比,内置锁仍然具有很大的优势。内置锁为许多开发人员所熟悉,并且简介紧凑,而且许多现有的程序都已经使用了内置锁,如果将这两种机制混合使用,那么不仅容易令人困惑,也容易发生错误。

13.5 读 — 写锁

ReentrantLock实现了一种标准的互斥锁:每次只能有一个线程能持有ReentrantLock。但对于维护数据完整性来说,互斥锁通常是一种过于强硬的加锁规则,因此也就不必要地限制了并发性。

意思就是这个锁允许的情况只有两种:要么全是读操作,要么就是一个写操作,两者不能同时进行。

在程序清单13-6的ReadWriteLock中暴露了两个Lock对象,其中一个用于读操作,而另一个用于写操作。要读取由ReadWriteLock保护的数据,必须首先获得读取锁,当需要修改ReadWriteLock保护的数据时,必须首先获得写入锁。尽管这两个锁看上去是彼此独立的,但读取锁和写入锁知识 读—写 锁对象的不同视图。

在 读 — 写 锁实现的加锁策略中,允许多个读操作同时进行,但每次只允许一个写操作。与Lock一样,ReadWriteLock可以采用多种不同的实现方式,这些方式在性能,调度保证,获取优先性,公平性以及加锁语义等方面可能有所不同。

在读取锁和写入锁之间的交互可以采用多种实现方式。ReadWriteLock中的一些可选实现包括:

当锁的持有是将较长并且大部分操作都不会修改被守护的资源时,那么读—写锁能提高并发性。在程序清单13-7的ReadWriteMap中使用了ReentrantReadWriteLock来包装Map,从而使它能在多个读线程之间被安全地共享,并且仍然能避免“读-写”或“写-写”冲突。在实现中,ConcurrentHashMap的性能已经很好了,一次如果只需要一个并发的基于散列映射,那么就可以使用ConcurrentHashMap来代替这种方法,但如果需要多另一种Map实现提供并发性更高的访问,那么可是使用这项技术。

图13-3给出了分别用ReentrantLock和ReadWriteLock来封装ArrayList的吞吐量比较,每个操作随机地选择一个值并在容器中查找这个值,并且只有少量的操作会修改这个容器中的内容。


小结:

《java并发编程实战》读书笔记10--显示锁Lock,轮询、定时、读写锁的更多相关文章

  1. Java并发编程实战 读书笔记(一)

    最近在看多线程经典书籍Java并发变成实战,很多概念有疑惑,虽然工作中很少用到多线程,但觉得还是自己太弱了.加油.记一些随笔.下面简单介绍一下线程. 一  线程与进程   进程与线程的解释   个人觉 ...

  2. Java并发编程实战 读书笔记(二)

    关于发布和逸出 并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了.这是危及到线程安全的,因为其他线程有可能通过这个 ...

  3. 《java并发编程实战》笔记

    <java并发编程实战>这本书配合并发编程网中的并发系列文章一起看,效果会好很多. 并发系列的文章链接为:  Java并发性和多线程介绍目录 建议: <java并发编程实战>第 ...

  4. Java多线程编程实战读书笔记(一)

    多线程的基础概念本人在学习多线程的时候发现一本书——java多线程编程实战指南.整理了一下书中的概念制作成了思维导图的形式.按照书中的章节整理,并添加一些个人的理解.

  5. Java并发编程实战(3)- 互斥锁

    我们在这篇文章中主要讨论如何使用互斥锁来解决并发编程中的原子性问题. 目录 概述 互斥锁模型 互斥锁简易模型 互斥锁改进模型 Java世界中的互斥锁 synchronized中的锁和锁对象 synch ...

  6. java并发编程实战《三》互斥锁(上)

    互斥锁(上):解决原子性问题 原子性问题的源头是线程切换,操作系统做线程切换是依赖 CPU 中断的,所以禁止 CPU 发生中断就能够禁止线程切换. 在早期单核 CPU 时代,这个方案的确是可行的,而且 ...

  7. Java并发编程艺术读书笔记

    1.多线程在CPU切换过程中,由于需要保存线程之前状态和加载新线程状态,成为上下文切换,上下文切换会造成消耗系统内存.所以,可合理控制线程数量. 如何控制: (1)使用ps -ef|grep appn ...

  8. Java并发编程实践(读书笔记) 任务执行(未完)

    任务的定义 大多数并发程序都是围绕任务进行管理的.任务就是抽象和离散的工作单元.   任务的执行策略 1.顺序的执行任务 这种策略的特点是一般只有按顺序处理到来的任务.一次只能处理一个任务,后来其它任 ...

  9. Java并发编程实践读书笔记(2)多线程基础组件

    同步容器 同步容器是指那些对所有的操作都进行加锁(synchronize)的容器.比如Vector.HashTable和Collections.synchronizedXXX返回系列对象: 可以看到, ...

  10. Java并发编程实践读书笔记(5) 线程池的使用

    Executor与Task的耦合性 1,除非线程池很非常大,否则一个Task不要依赖同一个线程服务中的另外一个Task,因为这样容易造成死锁: 2,线程的执行是并行的,所以在设计Task的时候要考虑到 ...

随机推荐

  1. HDU.2734 Quicksum

    Quicksum Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Subm ...

  2. idea导入web项目tomcat

    概述 主要分为项目配置和tomcat配置两大步骤. 一.项目配置 打开idea,选择导入项 选择将要打开的项目路径后,继续选择项目的原本类型(后续引导设置会根据原本的项目类型更新成idea的项目),此 ...

  3. eclipse常见问题解决方案

    1.maven项目,启动报错ClassNotFoundException,原因是tomcat下\WEB-INF\classes目录中,java文件没有编译成class文件.解决方法: 在\WEB-IN ...

  4. HDU2031 进制转换

    #include <iostream> #include "string" #include "cstdio" #include "cst ...

  5. tomcat 启动报错 Cannot allocate memory

    Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=256m; support was removed in 8.0 ...

  6. 【BZOJ】1419 Red is good

    [算法]期望DP [题解]其实把状态表示出来就是很简单的期望DP. f[i][j]表示i张红牌,j张黑牌的期望. i=0时,f[0][j]=0. j=0时,f[i][0]=i. f[i][j]=max ...

  7. 【51NOD-0】1058 N的阶乘的长度

    [算法]数学 [题解]n!的位数相当于ans=log10(n!)上取整,然后就可以拆出来加了. 可以用log10(i)或log(i)/log(10) 阶乘好像有个斯特林公式…… #include< ...

  8. bzoj 1854 游戏 二分图匹配 || 并查集

    题目链接 Description lxhgww最近迷上了一款游戏,在游戏里,他拥有很多的装备,每种装备都有2个属性,这些属性的值用[1,10000]之间的数表示.当他使用某种装备时,他只能使用该装备的 ...

  9. Override 和 Overload 的含义和区别

    Override 1.方法重写.覆盖: 2.重写是父类与子类之间多态性的一种表现: 3.方法名,参数,返回值相同: 4.存在于子类和父类之间: 5.修饰为final的方法,不能被重写: Overloa ...

  10. Linux System.map文件【转】

    转自:http://blog.csdn.net/ysbj123/article/details/51233618 当运行GNU链接器gld(ld)时若使用了"-M"选项,或者使用n ...