第10章 避免活跃性危险

10.1 死锁

-10.1.1 锁顺序死锁

最简单的一种死锁形式:

-10.1.2 动态的锁顺序死锁

可以通过下面的方法来解决:

-10.1.3 在协作对象之间发生死锁

-10.1.4 开放调用

如果在调用某个方法时不需要持有锁,那么这种调用就被称为开放调用。

-10.1.5 资源死锁

当多个线程在相同资源上等待时,也会发生死锁。

10.2 死锁的避免与诊断

-10.2.1 支持定时的锁

显示使用Lock类中的定时tryLock功能(见13章)来代替内置锁机制。

-10.2 通过线程转储信息来分析死锁

10.3 其他活跃性危险

-10.3.1 饥饿

当线程由于无法访问它所需要的资源而不能继续执行时,就发生了"饥饿"。引发饥饿最常见的资源就是CPU时钟周期。

-10.3.2 糟糕的响应性

-10.3.3 活锁

这种问题虽然不会阻塞线程,但也不能继续执行,因为线程将不断重复执行相同操作,而且总会失败。活锁通常发生在处理事务消息的应用程序中:如果不能成功处理某个消息,那么消息处理机制将回滚整个事务,并将它重新放到队列的开头。如果消息处理器在处理某种特定类型的消息时存在错误并导致它失败,那么每当这个消息从队列中取出并传递到存在错误的处理器时,都会发生事务回滚。由于这条消息又被放到队列开头,因此处理器将被反复调用,并返回相同结果。虽然处理消息的线程并没有阻塞,但也无法继续执行下去。要解决这种问题,需要在重试机制中引入随机性。

第11章 性能与可伸缩性

11.1 对性能的思考

11.1.1 性能与可伸缩性

11.1.2 评估各种性能权衡因素

11.2 Amdahl定律

在增加计算机资源的情况下,程序在理论上能够实现最高加速比,这个值取决于可并行组件与串行组件所占的比重。假定F是必须被串行执行的部分,那么根据Amdahl定律,在包含N个处理器的机器中,最高的加速比是:

要预测应用程序在某个多处理器系统中将实现多大的加速比,还需要找出任务中的串行部分。

11.2.1 示例:在各种框架中隐藏的串行部分

11.3 线程引入的开销

11.3.1 上下文切换

JVM和操作系统的开销。除此之外,当一个新的线程被切换进来时,它所需要的数据可能不在当前处理器的本地缓存中因此上下文切换将导致一些缓存缺失,因而线程在首次调度运行时会更加缓慢。

11.3.2 内存同步

内存栅栏(Memory Barrier)。内存栅栏可以刷新缓存,使缓存无效,刷新硬件的写缓冲,以及停止执行管道。现代的JVM能通过优化来去掉一些不会发生竞争的锁,从而减少不必要的同步开销。一些更完备的JVM能通过逸出分析(Escape Analysis)来找出不会发布到堆的本地对象引用。即使不进行逸出分析,编译器也可以执行锁粒度粗化(Lock Coarsening)将邻近的同步代码块用一个锁合并起来。

11.3.3 阻塞

JVM在实现阻塞行为时,可以采用自旋等待(通过循环不断地尝试获取锁,知道成功)或者通过操作系统挂起被阻塞的线程。

11.4 减少锁的持有时间

11.4.1 缩小锁的范围

来个例子:

由于在AttributeStore中只有一个状态变量attributes,因此可以通过将线程安全性委托给其他的类来进一步提升它的性能。通过用线程安全的Map(Hashtable,SynchronousMap或ConcurrentHashMap)来代替attributes, AttributeStore可以将确保线程安全性的任务委托给顶层的线程安全容器来实现。

11.4.2 减小锁的粒度

降低线程请求锁的频率,可以通过锁分解和锁分段等技术来实现。采用相互独立的锁来保护独立的状态变量。虽然能减小锁操作的粒度,实现更高的伸缩性,然而,使用的锁越多发生死锁的风险也就越高。如果一个锁要保护多个相互独立的变量,那么可以将这个锁分解为多个锁,并且每个锁只保护一个变量,从而提高可伸缩性。来个例子:

将上面的代码改一下,不用ServerStatus锁来保护用户状态和查血状态,而是每个状态都通过一个锁来保护,如下面的程序所示:

11.4.3 锁分段

在某些情况下,可以将锁分解技术进一步扩展为对一组独立对象上的锁分解,这中情况被称为锁分段。

(看起来很牛逼的样子)。锁分段的一个劣势在于:与采用单个锁来实现独占访问相比,要获取多个锁来实现独占访问将更加困难且开销更高。

11.4.4 避免热点域

锁分解和锁分段都能使不同的线程在不同的数据(或者同一个数据的不同部分)上操作,而不会相互干扰。如果程序采用锁分段技术,那么一定要表现出在锁上的竞争频率高于在锁保护的数据上发生竞争的频率。

当每个操作都请求多个变量时,锁的粒度将很难降低。这是在性能和可伸缩性之间的相互制衡的另一个方面,一些常见的优化措施,例如将一些反复计算的结果缓存起来,都会引入一些“热点域”,而这些热点域往往会限制可伸缩性。

11.4.5 一些替代独占锁的方法

第三种降低竞争锁的影响的技术就是放弃使用独占锁,从而有助于使用一种友好并发的方式来管理共享状态。例如使用并发容器,读-写锁,不可变对象以及原子变量。

11.4.6 检测cpu的利用率

11.4.7 向对象池说“不”

11.5 示例:比较Map的性能

在单线程环境下,ConcurrentHashMap的性能比同步的HashMap的性能略好一点,但在并发环境中则要好得多。在ConcurrentHashMap的实现中假设,大多是常用的操作都是获取某个已经存在的值,因此它对各种get操作进行了优化从而提供最高的性能和并发性。在同步的Map的实现中,可伸缩性的最主要阻碍在于整个Map中只有一个锁,因此每次只能有一个线程能够访问这个Map。不同的是,ConcurrentHashMap对大多数读操作并不会加锁,并且在写入操作以及其他一些需要锁的读操作中使用了锁分段技术。

11.6 减少上下文切换的开销

当任务在运行和阻塞这两个状态之间转换时,就相当于一个上下问切换。在服务器应用程序中,发生阻塞的原因之一就是在处理请求时产生的各种日志消息。为了说明如何通过减少上下文切换的次数来提高吞吐量,我们将对两种日志方法的调度进行分析。(这部分没看明白在分析什么....汗.... 先pass掉)


《java并发编程实战》读书笔记8--死锁,性能与可伸缩性,锁粒度锁分解锁分段的更多相关文章

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

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

  2. 《Java并发编程实战》第十一章 性能与可伸缩性 读书笔记

    造成开销的操作包含: 1. 线程之间的协调(比如:锁.触发信号以及内存同步等) 2. 添加�的上下文切换 3. 线程的创建和销毁 4. 线程的调度 一.对性能的思考 1 性能与可伸缩性 执行速度涉及下 ...

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

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

  4. java并发编程实战:第十一章----性能和可伸缩性

    线程的最主要目的是提高程序的运行性能,但性能的提升会导致复杂性的提升,又会导致安全性和活跃性的风险 一.对性能的思考 提升性能意味着用更少的资源做更多地事情.要想通过并发来获得更好的性能,就要更有效地 ...

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

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

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

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

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

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

  8. java并发编程实战《五》死锁

    一不小心就死锁了,怎么办? 在上一篇文章中,我们用 Account.class 作为互斥锁,来解决银行业务里面的转账问题,虽然这个方案不存在并发问题,但是所有账户的转账操作都是串行的,性能太差. 向现 ...

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

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

  10. Java并发编程实战(4)- 死锁

    在这篇文章中,我们主要讨论一下死锁及其解决办法. 目录 概述 死锁案例 死锁的原因和预防 破坏占用且等待条件 破坏不可抢占条件 破坏循环条件 使用等待-通知机制 Java中的等待-通知机制 条件曾经满 ...

随机推荐

  1. Vue推荐资料

    推荐博文(我是看过,才敢说的偶): 基础教学: 菜鸟语法教程:https://cn.vuejs.org/v2/guide/syntax.html  http://www.runoob.com/vue2 ...

  2. 运行Jar包程序Shell

    启动: #!/bin/bash set -e JAVA_HOME=/usr/local/java# 检查是否有项目名 appName=$ if [ "$appName" == &q ...

  3. 【神仙题】【P1600】【NOIP2016D1T2】天天爱跑步

    传送门 Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游 ...

  4. Linux IO Scheduler

    一直都对linux的io调度算法不理解,这段时间一直都在看这方面的内容,下面是总结和整理的网络上面的内容.生产上如何建议自己压一下.以实际为准. 每个块设备或者块设备的分区,都对应有自身的请求队列(r ...

  5. bzoj 1123 [POI2008]BLO Tarjan求割点

    [POI2008]BLO Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1540  Solved: 711[Submit][Status][Discu ...

  6. JavaScript中this的用法详解

    JavaScript中this的用法详解 最近,跟身边学前端的朋友了解,有很多人对函数中的this的用法和指向问题比较模糊,这里写一篇博客跟大家一起探讨一下this的用法和指向性问题. 1定义 thi ...

  7. You can't specify target table 'table' for update in FROM clause

    delete from table1 where ID not in(select max(ID) ID from table1 group by row1) and row1 ) # 出现错误 # ...

  8. (转)Linux下使Shell 命令脱离终端在后台运行

    转自: http://www.linuxidc.com/Linux/2011-05/35723.htm 方法如下: (1)输入命令: nohup 你的shell命令 & (2)回车,使终端回到 ...

  9. log4net 性能测试

    1.执行事务:20260 次 写日志:        耗时11.59分 不写日志:    耗时11.55分 异步日志:    耗时12.49分 (个人电脑,.net 线程池调用线程写日志可能比主线程直 ...

  10. Item 1----------考虑用静态工厂方法代替构造器

    读书,有时候,我感觉总是有点绕和不具体.我阅读了代码,理解代码后,才有一种理解和把握的感觉. 优点三.   把某个对象的构建放给客户端来实现. 比如下面的实现,客户端Test,获取Service的实例 ...