class="MsoNormal">第三部分:活跃度、性能和测试
10 避免活跃度危险
如果所有线程以通用的固定秩序获得锁,程序就不会出现顺序锁死锁问题了。
当调用的方法不需要持有锁时,这被称为是“开放调用”。依赖于开放调用的类会具有更好的行为,并且比那些需要获得锁才能调用的方法相比,更容易与其他类合作。
?
在程序中尽量使用开放调用,依赖于开放调用的程序,相比于那些在持有锁时还调用外部方法的程序,更容易执行死锁自由度的分析。
?
10.2避免和诊断死锁
10.2.1尝试定时锁
另一项监测死锁和从死锁中恢复的技术,是使用每个显示Lock类中定时tryLock特性,来替代使用内部锁机制。
?
在内部锁的机制中,只要没有获得锁,就会永远保持等待,而显示的锁会使你能够定义超时的时间,在规定时间之后,tryLock还没有获得锁就返回失败。
?
通过使用超时,尽管这段时间比你预期能够获得锁的时间长很多,你仍然可以在意外发生后重新获得控制权。
?
10.2.2通过线程转储分析死锁
JVM使用“线程转储”来帮助你识别死锁的发生。
线程转储包括每个运行中线程的栈追踪信息,以及与之相应并随之发生的异常,锁信息等。
?
在生成线程转储前,JVM在表示“正在等待”关系的(有向)图中搜索循环来寻找死锁。如果发现了死锁,他就会包括死锁的识别信息,其中参与了哪些锁和线程,以及程序中造成了不良后果的锁请求发生在哪里。
?
10.3其他的活跃度危险
除了死锁,并发程度中其他活跃度危险:饥饿、丢失信号、活锁。
10.3.1饥饿
当线程访问它所需要的资源时,却被永久拒绝,以至于不能再继续进行,这样就发生了“饥饿”:最常见的是饥饿资源是CPU周期。
?
活锁
尽管没有被阻塞,线程依然不能继续,它不断尝试相同的操作,却总失败。
?
第11章性能和可伸缩性
改进性能意味着使用更少的资源做更多的事情。
?
引入多线程总会引入一些性能的开销:与协调线程相关的开销(加锁、信号、内存同步),增加上下文切换、线程的创建和消亡、调度的开销。
?
可伸缩性指的是:当增加计算资源时(如CPU、内存、存储器、I/O、带宽)吞吐量和生产量能相应得以改进。
?
避免不成熟的优化。首先使程序正确,然后再加快。如果它运行的不够快。
?
所有的并发程序都或多或少有一些串行资源。
?
线程引入的开销:
1》切换上下文:线程的调度需要操控OS和JVM中共享的数据结构
Unix的vmstat和Win下的platfmon工具都能报告上下文切换次数和内核占用的时间等信息。高内核占用率(10%)象征着频繁的调度活动,很可能是I/O阻塞、锁竞争引起。
?
2》内存同步:Synchronized和volatile提供的可见性保证要求:使用一个特殊的、名为“存储关卡memory barrier”的指令,来刷新缓存,使缓存无效;刷新硬件的写缓冲,并延迟执行的传递。
?
同步还造成了共享内存总线上的通信量(带宽有限、所有线程共享总线)
?
3》阻塞:非竞争的同步由JVM掌控;竞争的同步需OS活动,均为开销。
?
11.4减少锁的竞争:能改进性能和可伸缩性
并发程序中,对可伸缩性首要的威胁是独占的资源开销。
2个原因影响着锁的竞争性:锁被请求的频率;每次持有锁的时间
?
因此有3种方式来减少锁的竞争:减少持有锁的时间;减少请求锁的频率;用协调机制取代独占锁,从而允许更强的并发性。
11.4.1缩小锁的范围:快进快出
11.4.2减少锁的粒度
减少持有锁的时间比例的另一种方式是让线程减少调用它的频率。可以通过分拆锁和分离锁实现——采用相对独立的锁,守卫多个独立的状态变量,在改变之前,它们都是由一个锁守护的。
?
第12章测试并发程序
并发类的测试基本分为2类:安全性和活跃度(性能测试:吞吐量、响应性、可伸缩性)的测试。
?
第13章显示锁
在Java 5.0之前,用于调节共享对象访问的机制只有Synchronized与volatile。Java5.0提供了新选择:ReentrantLock(在内部锁局限时,提供可选的高级特性——内部锁不能中断那些正在等待获取锁的线程,并且请求锁失败的情况下,必须无限等待)
?
ReentrantLock实现了Lock接口,提供了与Synchronized相同的互斥和内存可见性的保证,获得ReentrantLock的锁与进入Synchronized块有相同的内存语义;释放ReentrantLock锁与退出Synchronized块有相同的内存语义。
?
13.1.1可轮询的和可定时的锁请求:由tryLock()实现
在内部锁中,死锁只能由重启来解决;tryLock()释放已获得的锁,再重新尝试。
13.1.2可中断的锁获取操作:在可取消的活动中取消:LockInterruptibly
?
13.3公平性
ReentrantLock构造函数中提供了2种公平性的选择:创建非公平锁(默认)和公平锁。
线程按顺序请求获得公平锁,然而允许一个非公平锁“闯入”:当请求这样的锁时,如果锁的状态变为可用,线程的请求可以在等待线程的队列中向前跳跃,获得该锁。
?
在内部锁不能够满足使用时,ReentrantLock才被作为更高级的工具。当你需要以下高级特性时,才应该使用:可定时的、可轮询的、与可中断的锁获取操作,公平队列或非块结构的锁,否则请用Synchronized。
?
13.5读写锁ReentrantReadWriteLock
有2个Lock对象的接口,一个用于读,一个用于写。读取ReadWriteLock守护的数据,必须先获取读锁,当需要修改ReadWriteLock守护的数据,须先获得写锁。
在公平锁中,选择权给等待时间最长的线程;如果锁由读锁获取,而另一个线程请求写入锁,则不再允许读者获取读锁,直到写锁被受理,并释放写锁。
第15章原子变量与非阻塞同步机制
在Java 5.0中,使用原子变量类(如AtomicInteger/AtomicReference)能高效的构建非阻塞算法.
原子变量提供了与volicatile类型变量相同的内存语义,同时还支持原子更新_使它能够更加理想的用于计算器,序列发生器,统计数据收录等.
?
15.1 锁的优势
Volatile变量与锁相比更轻量级的同步机制,因为它不会引起上下文切换和线程调度.
加锁还有一个缺点:当1个线程正在等待锁时,他不能做任何事情.
?
原子变量类型共有12个:计量器(AtomicInteger AtomicLong AtomicBoolean AtomicReference)、域更新器(field updater)、数组、复合变量。
?
第16章 Java存储模型JMM
JMM提供了JVM的一种最小保证:什么时候写入一个变量时会对其他线程可见。
16.1.1
一张架构的存储模型告诉了应用程序可以以它的存储系统中得到何种担保,用以在需要共享数据时,得到额外的存储协调保证。
Java提供了自己的存储模型,JVM会通过在适当的位置上插入存储关卡,来解决JVM与底层平台的存储模型的差异化
16.1.2重排序
各种能够引起操作延迟或错误执行的不同原因,都可以归为“重排序”
16.1.3Java存储模型的简介
Java存储模型的定义是通过动作的形式进行描述的。所谓动作,包括变量的读写,监视器的加锁与释放锁,线程的启动与拼接。
?
JMM为所有程序内部的动作定义了一个偏序关系,要想保住执行动作B的线程能够看到动作A的结果(无论AB是否在同一个线程),AB之间必须满足偏序关系。如果AB不满足偏序关系,则JVM可以对其任意的重排序。