Java并发编程的艺术
ch1 并发编程的调整
1.1上下文切换
- 在单核CPU的情况下,也存在上下文切换的概念(时间片)
任务的状态从保存到再加载就是一次切换
- 在数据量少的情况下,串行有可能比并发编程效率高
使用Lmbench测试上下文切换时长,vmstat统计切换次数
减少上下文切换的方法
- 无锁并发编程
- CAS算法(Java的
Atomic
包) - 使用最少线程,避免不必要的创建
实战减少上下文切换
- 用
jstack
命令dump线程信息,查看对应PID
的线程在做什么 - 统计线程分别处于什么状态(onobject-monitor为
waiting
状态)
若Jboss中有大量线程处于await状态,,可以配置
maxThreads
的数量。
1.2死锁
避免死锁的几个方法
- 避免一个线程同时获取多个锁
- 避免一个线程在锁内同时占用多个资源,尽量保存1:1的关系
- 尝试使用定时锁(到期释放),
lock.tryLock(timeout)
- 对于数据库锁,加解锁必须在一个连接中。
1.3资源限制
硬件问题:通过“数据ID%机器数”,让对应编号的机器处理数据
软件问题:资源池复用资源,数据库和Socket
连接复用。
CH2 Java并发机制的底层实现原理
Java代码编译后变成字节码,由类加载器加载到
JVM
中,最终转换为汇编在CPU上执行;
volatile
- 保证可见性,不保证原子性
- 比
synchronized
的使用成本低(不引起上下文切换和调度)
处理器不直接和内存通信,而是先将内存数据度到内部缓存,但最终不确定写回到内存的时间。如果声明了
volatile
,JVM向处理器发送一条前缀为LOCK的指令,讲变量所在缓存行的数据读回到内存。
实现原理为lock
指令,(1)将当前处理器缓存行的数据写回系统内存,(2)这个写回内存的操作使在其他CPU里切换了该内存地址的数据无效。(缓存一致性协议:处理器嗅探在总线上传播的数据查看自己所缓存对比看是否过期,如果过期,设置为无效状态。)
Lock前缀指令会引起处理器缓存写回到内存,一个处理器写回到内存会导致其他处理器的缓存无效。
volatile的使用优化
采用追加字节码的方式,配合特点型号的处理器(缓存为64个字节宽,避免头节点和尾节点加载到同一个缓存行,让其在修改时不会互相锁定,当缓存行非64位或者共享变量不会被频繁读写时,不必要将字节码追加至64位(在JDK7中失效,会淘汰和重新排列无用字段,需要使用其他追加字节的方式)。
Synchronized的使用和实现
在1.6中引入偏向锁和轻量级锁,避免获得释放锁造成的消耗。
- 对于普通方法,锁是当前实例对象
- 静态同步方法,锁是当前类的Class对象、
- 同步方法块,是
Synchronized
括号里配置的对象。
基于monitorenter
和monitorexit
指令。获得enter
时,会尝试获得锁。分别位代码块的开始和方法结束和异常处。
Java对线头
Synchronized锁存在对象头中,若对象为数组类型,使用3个字宽(word),非数组为2word,在32位虚拟中,1字宽=4字节。
- Java对象头里的
Mark Word
默认存储对象的Hashcode
,分代年龄和锁标记。在运行期间,MW中的数据随着锁标志位的变化而变化。
锁的升级和对比
- 无锁状态
- 偏向锁
- 轻量级锁
- 重量级锁
锁升级过程中只能升级不能降级。