第10章 避免活跃度危险
1. 锁顺序死锁
如果多个线程试图通过不同的顺序获得多个相同的锁,则会发生死锁。
如果所有线程以通用的固定秩序获得锁,程序就不会出现锁顺序死锁问题了。
2. 动态的锁顺序死锁
3. 协作对象间的死锁
当外部方法被调用时正持有锁,则其可能会获得其他锁(产生死锁的危险),或者遭遇严重超时的阻塞。当你持有锁的时候会延迟其他试图获得该锁的线
程。
4. 开放调用
当调用的方法不需要持有锁时,这被称为开放调用,依赖于开放调用的类会具有更好的行为,并且比那些需要获得锁才能调用的方法相比,更容易与其
他的类合作。
避免和诊断死锁
1. 尝试定时的锁
可以使用每个显式的Lock类中定时的tryLock特性,来替代使用内部锁机制。
2. JVM可以使用线程转储来识别死锁。线程转储包括每个运行中线程的栈追踪信息,以及与之相似并随之发生的异常。
尽管死锁是遇到的最主要的活跃度危险,并发程序中仍然可能遇到一些其他的活跃度危险,包括:饥饿,丢失信号和活锁。
1. 饥饿
当线程访问它所需要的资源时却被永久拒绝,以至于不能再继续进行,这样就发生了饥饿。
但是要抵制使用线程优先级的诱惑,因为这会增加平台依赖性,并且可能引起活跃度问题。大多数并发应用程序可以对所有线程使用相同的优先级。
2. 弱响应性
不良的锁管理也可能引起弱响应性。
3. 活锁
活锁是线程中活跃度失败的另一种形式,尽管没有被阻塞,线程却依然不能继续,因为它不断重试相同的操作,却总是失败。
第11章 性能与可伸缩性
首先要保证程序是正确的,然后再让它更快-而后只有当性能需求和评估标准需要程序运行得更快时,才去进行改进。在设计并发应用程序的时候,最大
可能地改进性能,通常并不是最重要的事情。
尽管目标是希望全面提升性能,与单线程方法相比,使用多线程总会引入一些性能的开销。这些开销包括:与协调线程相关的开销(加锁、信
号、内存同步),增加的上下文切换,线程的创建和消亡,以及调度的开销。
3. 可伸缩性
可伸缩性指的是:当增加计算资源的时候(比如增加额外CPU数量、内存、存储器、I/O带宽),吞吐量和生产量能够相应地得以改进。
4. 对性能的权衡进行评估
避免不成熟的优化。首先使程序正确,然后再加快-如果它运行得还不够快。
线程引入的开销
1. 切换上下文
2. 内存同步
不要过分担心非竞争的同步带来的开销。基础的机制已经足够快了,在这个基础上,JVM能够进行额外的优化,大大减少或消除了开销。关注那些真正
发生了锁竞争的区域中性能的优化。
3. 阻塞
减少锁的竞争
1. 并发程序中,对可伸缩首要的威胁是独占的资源锁。
有3种方式来减少锁的竞争:
1. 减少持有锁的时间
2. 减少请求锁的频率
3. 或者用协调机制取代独占锁,从而允许更强的并发性。
2. 缩小锁的范围
3. 减少锁的粒度
减小持有锁的时间比例的另一种方式是让纯种减少调用它的频率(因此减少发生竞争的可能性)。这可以通过分拆锁和分离锁来实现,也就是采用相互独
立的锁,守卫多个独立的状态变量,在改变之前,它们都由一个锁守护的。这些技术减小了锁发生时的粒度,潜在实现了更好的可伸缩性-但是使用更多
的锁同样会增加死锁的风险。
监测CPU利用率
1. 不充足的负载
2. I/O限制
3. 外部限制
4. 锁竞争
向“对象池”说“不”
程序的可伸缩性是由必须连续执行的代码比例决定的。因为Java程序中串行化首要的来源是独占的资源锁,所以可伸缩下这些方式提升:减少用于获取
锁的时间,减小锁的粒度,减少锁的占用时间,或者用非独占或非阻塞锁来取代独占锁。
1. 锁顺序死锁
如果多个线程试图通过不同的顺序获得多个相同的锁,则会发生死锁。
如果所有线程以通用的固定秩序获得锁,程序就不会出现锁顺序死锁问题了。
2. 动态的锁顺序死锁
3. 协作对象间的死锁
当外部方法被调用时正持有锁,则其可能会获得其他锁(产生死锁的危险),或者遭遇严重超时的阻塞。当你持有锁的时候会延迟其他试图获得该锁的线
程。
4. 开放调用
当调用的方法不需要持有锁时,这被称为开放调用,依赖于开放调用的类会具有更好的行为,并且比那些需要获得锁才能调用的方法相比,更容易与其
他的类合作。
避免和诊断死锁
1. 尝试定时的锁
可以使用每个显式的Lock类中定时的tryLock特性,来替代使用内部锁机制。
2. JVM可以使用线程转储来识别死锁。线程转储包括每个运行中线程的栈追踪信息,以及与之相似并随之发生的异常。
尽管死锁是遇到的最主要的活跃度危险,并发程序中仍然可能遇到一些其他的活跃度危险,包括:饥饿,丢失信号和活锁。
1. 饥饿
当线程访问它所需要的资源时却被永久拒绝,以至于不能再继续进行,这样就发生了饥饿。
但是要抵制使用线程优先级的诱惑,因为这会增加平台依赖性,并且可能引起活跃度问题。大多数并发应用程序可以对所有线程使用相同的优先级。
2. 弱响应性
不良的锁管理也可能引起弱响应性。
3. 活锁
活锁是线程中活跃度失败的另一种形式,尽管没有被阻塞,线程却依然不能继续,因为它不断重试相同的操作,却总是失败。
第11章 性能与可伸缩性
首先要保证程序是正确的,然后再让它更快-而后只有当性能需求和评估标准需要程序运行得更快时,才去进行改进。在设计并发应用程序的时候,最大
可能地改进性能,通常并不是最重要的事情。
尽管目标是希望全面提升性能,与单线程方法相比,使用多线程总会引入一些性能的开销。这些开销包括:与协调线程相关的开销(加锁、信
号、内存同步),增加的上下文切换,线程的创建和消亡,以及调度的开销。
3. 可伸缩性
可伸缩性指的是:当增加计算资源的时候(比如增加额外CPU数量、内存、存储器、I/O带宽),吞吐量和生产量能够相应地得以改进。
4. 对性能的权衡进行评估
避免不成熟的优化。首先使程序正确,然后再加快-如果它运行得还不够快。
线程引入的开销
1. 切换上下文
2. 内存同步
不要过分担心非竞争的同步带来的开销。基础的机制已经足够快了,在这个基础上,JVM能够进行额外的优化,大大减少或消除了开销。关注那些真正
发生了锁竞争的区域中性能的优化。
3. 阻塞
减少锁的竞争
1. 并发程序中,对可伸缩首要的威胁是独占的资源锁。
有3种方式来减少锁的竞争:
1. 减少持有锁的时间
2. 减少请求锁的频率
3. 或者用协调机制取代独占锁,从而允许更强的并发性。
2. 缩小锁的范围
3. 减少锁的粒度
减小持有锁的时间比例的另一种方式是让纯种减少调用它的频率(因此减少发生竞争的可能性)。这可以通过分拆锁和分离锁来实现,也就是采用相互独
立的锁,守卫多个独立的状态变量,在改变之前,它们都由一个锁守护的。这些技术减小了锁发生时的粒度,潜在实现了更好的可伸缩性-但是使用更多
的锁同样会增加死锁的风险。
监测CPU利用率
1. 不充足的负载
2. I/O限制
3. 外部限制
4. 锁竞争
向“对象池”说“不”
程序的可伸缩性是由必须连续执行的代码比例决定的。因为Java程序中串行化首要的来源是独占的资源锁,所以可伸缩下这些方式提升:减少用于获取
锁的时间,减小锁的粒度,减少锁的占用时间,或者用非独占或非阻塞锁来取代独占锁。