Volatile实现原理
1)将缓存行
写回到系统内存
2)
写回内存的操作会使在其他CPU里缓存了该内存的地址的数据无效
==>>
缓存一致性
缓存一致性协议:
每个处理器通过嗅探在总线上传播的数据来检查自己缓存的数值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改的时候,会重新从系统内存中把数据读到处理器缓存里。
Volatile优化:
将
字节追加到64字节,可以提高并发编程的性能。
原理:
如果队列的头结点和尾节点都不足64字节,处理器会将他们都读到同一个缓存行中,这样当一个处理器师徒修改头结点时,会锁定整个缓存行,在缓存一致性的作用下,会导致其他处理器不能访问自己的高速缓存尾结点。而队列的入队和出队需要,不停的修改头结点和尾结点,会严重影响队列的入队和出队效率。所以追加64字节的方法来填满高缓冲区,避免头结点和尾结点在同一个缓存行。使得头尾节点在修改时不会相互锁定。
synchronized实现原理
java对象头,Mark Word
,(synchronized用的锁存储在java对象头中)
1)偏向锁
2)轻量级锁:
JVM,先将对象头中的Mark Word复制到当前线程的栈帧中,然后尝试用CAS,将对象头中Mark Word替换为锁中的记录。成功则,获得锁,失败,则尝试自旋,获取锁。
3)比较:
偏向锁:加锁和解锁不需要额外消耗。。如果线程有锁竞争,会带来额外锁撤销的消耗。。适用于只有一个线程访问同步块的场景
轻量级锁:竞争的线程不会阻塞,提高了程序的响应速度。。如果始终得不到锁竞争的线程,
使用自旋
会消耗CPU。。追求响应时间,同步快执行速度非常快
重量级锁:线程竞争不适用自旋,不会消耗CPU。。
线程阻塞
,响应时间慢。。追求吞吐量,同步块执行速度较长
原子操作的实现原理:
CAS:比较并交换。先比较一个旧值和一个新值有没有发生变化,只有没有发生变化,才叫换成新值,发生了变化则不交换。
缓存行:Cache line。缓存的最小操作单位
1)处理器如何实现原子操作:
两种方式:
缓存加锁
或
总线加锁
基础是,处理器保证从系统内存中读取或者是写入一个字节是原子的,即其他处理器不能访问这个字节的内存地址。
=》》总线锁定,是在总线上输出LOCK#信号,则该处理器独占共享内存。阻塞其他处理器的请求。
=》》缓存锁定,基础是,处理器会高速缓存频繁使用的内存。缓存锁定,利用了
缓存一致性原理
来保证原子性,(
写回内存的操作会使在其他CPU里缓存了该内存的地址的数据无效
)
Java原子操作的实现:
通过
锁
和
循环CAS方式
1)循环CAS操作:
private AtomicInteger atomicI = new AtomicInteger(0); private void safeCount(){ for(;;){ int i = atomicI.get();//取值 boolean success = atomicI.compareAndSet(i, ++i);//CAS,比较,赋值 if(success){ break; } } } private void unsafeCount(){ i++; }
代码就是,不停的CAS,即不停的取值get(),然后(比较,赋值)compareAndSet
2)CAS原子操作的问题:
1)ABA问题:
即值由A->B->A变化的过程很快的话,CAS进行检查时,可能出现错误,觉得值没有变化,实际却是变化了,
解决:添加版本号:变成1A->2B->3A
2)循环时间开销大
3)只能保证一个共享变量的原子操作:
JDK1.5提供了 AtomicReference保证引用对象的原子性
3)锁实现原子操作
JVM实现锁的方式都用了循环CAS
,即当一个线程想进入同步快的时候使用循环CAS的方式来获取锁,当他退出同步块的时候使用循环CAS释放锁。