目录

Java并发机制底层实现原理

概念介绍

非线程安全: 多个线程对同一对象中的实例变量并发访问时产生

方法内的私有变量是安全的 对私安全 对实危险

线程安全: 获得实例变量的值经同步处理

同步: 在方法上加上synchroized关键字

脏读: 获得到的数据是被更改过

volatile应用

volatile是轻量级的 synchronized,它在多处理器开发中保证了共享变量的“可见性

可见性: 一个线程修改共享变量 其他线程能读到修改的值

voliatile定义

为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量

变量在内存的工作过程

  • read
  • load加载

从主存复制到当前线程

  • use操作
  • asgin赋值

执行代码 上面3步非原子性

  • store
  • write

当前内存去刷新主存中对应变量的值

多环境中 use assgin是多次出现 但并不是原子性 说不定加载完后 主存就刷新了值

volatile的汇编形式

instance = new Singleton(); // instance是volatile变量
// 0x01a3de1d: movb $0×0,0×1104800(%esi);0x01a3de24: lock addl $0×0,(%esp);

注意Lock指令 lock指令在多核处理器下会引发了两件事情

   	1.  将当前处理器**缓存行**的数据写回到系统内存
 	2.  写回内存的操作会**使在其他CPU里缓存了该内存地址的数据无效**

volatile使用优化

在JDK7并发包里 类LinkedTransferQueue 使用volatile变量时,用一种追加字节的方式来优化队列出队和入队的性能

/** 队列中的头部节点 */
private transient f?inal PaddedAtomicReference<QNode> head;
/** 队列中的尾部节点 */
private transient f?inal PaddedAtomicReference<QNode> tail;
static f?inal class PaddedAtomicReference <T> extends AtomicReference T> {
// 使用很多4个字节的引用追加到64个字节
Object p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, pa, pb, pc, pd, pe;
PaddedAtomicReference(T r) {
super(r);
}
} public class AtomicReference <
V> implements java.io.Serializable {
private volatile V value;
// 省略其他代码
}

PaddedAtomicReference 将共享变量追加到64字节 (pe ~ p0) * 4 + value = 64

追加到64字节 提升效率的原因是 酷睿i7、酷睿等处理器(但不是所有的处理器缓存行都为64字节宽)的L1 L2 L3 缓存的高速缓存行是64个字节宽

并且不支持部分填充缓存行 所以头尾结点不足64字节 则会放入一个缓存行 多处理器下 一个处理器修改了自己缓存行的头节点 会锁定整个缓存行 对应的 由于在缓存一致性机制 其他的处理器就不能访问尾结点 即时它没修改头节点

synchronized实现原理及应用

synchronized 同步块是解决sync申明方法上,执行长时间的任务其他线程等待时间过长的问题

synchronized表现为3种形式

  1. 对于普通同步方法,锁是当前实例对象。即该方法所属对象的锁
  2. 对于静态同步方法,锁是当前类的Class对象。
  3. 对于同步方法块,锁是Synchonized括号里配置的对象

sync方法与synchroized(this)与synchroized(非this对象)对比

  • sync方法与synchroized(this)二者都是锁定当前对象
  • 访问同一对象带有sync synchroized(this)的代码都是同步的
  • sync(非this对象x)中程序与sync方法是异步的 不与其他锁this同步方法抢this

synchronized声明的方法一定是排队运行,且只有共享资源的读写访问才需要同步化

synchronized在JVM中实现原理是 monitorenter和monitorexit指令实现的

  • 对于monitorenter 它是在编译后插入到同步代码块的开始位置
  • 对于monitorexit 它是插入到方法结束处和异常处

java对象头

对象头的长度

对象头的存储结构 Mark Word

Mark Word状态变化

synchronized用的锁是存在Java对象头中

synchronized和volatile的区别

  • 关键字volatile是线程同步的轻量级的实现 s比较重
  • v仅能修饰变量 s修饰方法 代码块
  • v保证不会发生阻塞 s会出现阻塞
  • v保证可见,不保证原子 s保证原子 间接保证可见
  • s解决的是多个线程之间访问资源的同步性

原子操作的实现原理

原子操作涉及的基本术语

32位IA-32处理器使用基于对缓存加锁总线加锁的方式来实现多处理器之间的原子操 作 处理器保证从系统内存中读取或者写入一个字节是原子的

  1. 总线加锁

使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞

  1. 缓存加锁

总线锁定把CPU和内存之间的通信锁住 使得锁定期间 其他处理器不能操作其他内存地址的数据

需要花销小的操作 内存区域如果被缓存在处理器的缓存行中 执行锁操作回写到内存时 处理器变为修改内部的内存地址

Java如何实现原子操作

  1. 使用循环CAS实现原子操作

CAS存在的问题

  1. ABA问题

    CAS需要在操作值的时候,是检查值有没有发生变化,如果没有发生变化 则更新 若值A->B->A 则会误以为没有变化

    解决思路: 引入版本号 1A->2B->3A

  2. 循环时间长开销大

    长时间不成功,给CPU带来非常大的执行开销

  3. 只能保证一个共享变量的原子操作

    多个共享变量操作时,循环CAS就无法保证操作的原子性

  4. 通过锁

除了偏向锁,JVM实现锁的方式都用了循环CAS