《Java并发编程的艺术》Java并发机制的底层实现原理(二)

Java并发机制的底层实现原理

1.volatile

volatile相当于轻量级的synchronized,在并发编程中保证数据的可见性,使用 valotile 修饰的变量,其内存模型会增加一个 Lock 前缀,而这个前缀在多核处理器中带来的效果是:

(1)会将处理器缓存行的数据回写到内存中;

(2)同时这个操作会使其它处理器里缓存了这个内存地址的数据无效;

2.synchronized

synchronized是重量级锁,其在JVM实现的原理是基于进入和退出 Monitor 对象来实现同步代码块和方法快。在同步代码块中, moitorenter 指令会插入代码块开始的位置,而 monitorexit 指令会插入在代码块结束位置和异常位置,同时这两个指令必须形成闭环。

对象头

在对象头里面,有一个 Mark Word 内容,用于存储对象的 HashCode  和锁信息。

锁的升级与对比

JDK1.6为了减少获得和释放锁带来的性能消耗,引入了 偏向锁 和 轻量级锁 。锁有四个状态(从低到高): 无锁状态 、 偏向锁状态 、 轻量级锁状态 、 重量级锁状态 。锁的状态只能升级不能降级

偏向锁

偏向锁是基于大多数情况下,不存在线程对锁的竞争,而且锁总是由同一个线程获得,于是有了偏向锁,用于减少一个线程获得锁的开销。

(1)偏向锁的撤销:

偏向锁会一直等到有线程竞争锁的时候才会撤销,在此之前,都通过对象头 Mark Word 里锁信息指向的线程ID来进行获得锁的操作。

(2)偏向锁的关闭:

偏向锁是默认开启的,如果程序中存在较多的线程对锁的竞争,那么可以在JVM配置 -XX:-UseBiasedLocking=false 来关闭偏向锁,否则偏向锁的撤销过程会有性能的消耗。

轻量级锁

(1)轻量级锁加锁:

线程获得锁的时候,会在线程的栈帧分配空间,然后将锁对象的 Mark Word 信息复制过来,然后线程使用CAS将对象头的 Mark Word替换为指向锁的指针。如果失败,表明其它锁竞争锁,则当前线程尝试自旋获得锁;

(2)轻量级锁解锁:

解锁的时候会将栈帧里的 Mark Word 信息使用CAS复制回对象头中,如果成功,则没有线程的竞争,如果失败,则有线程竞争会导致锁膨胀成重量级锁。

3.原子操作的实现原理

原子:不可被进一步分割的原子;原子操作:不可被中断的一个或一系列的操作;

处理器实现原子操作

(1)总线锁保证原子性:

当一个处理器操作一个数据时,总线锁锁住之后,其它处理器就不能操作这个数据了,其它处理器的操作会被阻塞住。

(2)缓存锁保证原子性:

正是因为总线锁会阻塞内存和CPU之间的通信,开销较大,所以有了缓存锁。一个处理器操作一个缓存行里的数据时,缓存锁会锁定这个数据的内存地址,只能由这个处理器进行操作,其它处理器的操作则会无效。

Java实现原子操作

(1)使用CAS算法, compareAndSwap ,比较并替换;

Java提供了并发包里的 AtomicInteger 、 AtomicBoolean 、 AtomicLong 等等类来实现对应基本变量的原子操作;

(2)CAS实现原子操作的三大问题:

  ①ABA问题:如果仅仅通过判断value是否一致来进行修改,那么可能出现两次修改之后回到原始值的情况,于是添加了版本号就行双重判断;

  ②循环时间开销大:CAS自旋会一直循环,此操作会给CPU带来一定的开销,如果JVM可以支持处理器提供的 pause 指令的话,那就可以进行一定的程度上效率的提升;

  ③只能保证一个共享变量的原子操作:如果需要对多个共享变量进行原子操作,那么可以使用Java并发包提供的 AtomicRefrence 类来实现多个共享变量的原子操作;

(3)使用上锁的机制来实现原子操作,而除了偏向锁之外,其它锁都是通过CAS自旋的方式来获得锁;

    原文作者:java锁
    原文地址: https://www.cnblogs.com/lcmlyj/p/10461164.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞