《Java并发编程的艺术》读书笔记 第二章 Java并发机制的底层实现原理

《Java并发编程的艺术》读书笔记 第二章 Java并发机制的底层实现原理

1.volatile的应用

​ 之前看过并发相关的东西,对于volatile使用的好处有:可见性和防止指令重排序。volatile应用的时候,不会引起线程上下文的切换和调度。此外,当和synchronized联想比较的时候,能够想到一些联系或者区别。

volatile的定义和实现原理

​ ​ ​ ​ ​ ​ ​ Java语言规范第三版中对volatile的定义如下:

Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁要更加方便。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。

​ ​ ​ ​ ​ ​ ​ 那么,volatile如何保证可见性?以书上举的例子为例:

instance =new Singletion();//instance是volatile变量

​ ​ ​ ​ ​ ​ ​ 转变成汇编代码:

0x01a3be1d:movb $0x0,0x1104800(%esi);0x01a3de24: lock add1 $0x0,(%esp);


​ ​ ​ ​ ​ ​ ​ 有volatile变量修饰的共享变量进行写操作的时候会多出第二行汇编代码,Lock前缀的指令在多河处理器下会引发2件事情:

  • 将当前处理器缓存行的数据写回系统内存
  • 这个写回内存的操作会使其它CPU里缓存了该内存地址的数据无效
2.synchronized的实现原理与应用


​ ​ ​ ​ ​ ​ ​ Java中的每一个对象都可以看作锁,具体表现有以下3中形式:

  • 对于普通同步方法,锁是当前实例对象
  • 对于静态同步方法,锁是当前类的Class对象
  • 对于同步方法块,锁是Synchronized括号里配置的对象​

​ ​ ​ ​ ​ ​ ​ JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但是两者的实现细节不同,代码块同步使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的(JVM规范中没详细说),不过方法同步也可以用这两个指令实现。

​ ​ ​ ​ ​ ​ ​ monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之对应。任何对象都有一个monitor关联,当且一个monitor被持有后,将处于锁定状态,线程执行到monitorenter指令的时候,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

Java对象头

​ ​ ​ ​ ​ ​ ​ synchronized用的锁是存在Java对象头里的,如果对象是数组类型,则虚拟机用3个字宽存储对象头,如果对象是非数组类型,则用2字宽存储对象头。32位虚拟机中,1字宽=4字节,即32bit。

​ ​ ​ ​ ​ ​ ​ Java对象头里的Mark Word默认存储对象的HashCode、分代年龄等。在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下4种数据,其中32位JVM的Mark Word的默认存储结构为无锁状态。
在这里插入图片描述

锁的升级与对比

​ ​ ​ ​ ​ ​ ​ 锁的膨胀升级过程:无锁->偏向锁->轻量级锁->重量级锁。锁可以升级但是不能降级。

偏向锁

​ ​ ​ ​ ​ ​ ​ 当一个线程访问同步块并获取锁的时候,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁解锁,只需要测试对象头的Mark Word是否存储指向当前线程的偏向锁,如果测试成功,表示线程已经获得锁,如果失败,则需要再测试以下Mark Word中偏向锁的标识是否设置为1(1表示当前是偏向锁),若没设置,则使用CAS竞争锁,要是设置了,那么尝试使用CAS将对象头的偏向锁指向当前线程。

偏向锁的撤销

​ ​ ​ ​ ​ ​ ​ 当其它线程尝试竞争偏向锁的时候,持有偏向锁的线程才会释放线程。

​ ​ ​ ​ ​ ​ ​ 偏向锁在Java 6和Java 7中默认启用,不过在应用程序启动几秒钟后才激活,如果有必要可以使用JVM参数来关闭延迟-XX:BiasedLockingStartupDelay=0。关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后默认进入轻量级锁状态。

轻量级锁

轻量级锁加锁

​ ​ ​ ​ ​ ​ ​ 线程在执行同步块之前,JVM会现在栈帧中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针,若成功,当前线程获得锁,若失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

轻量级锁解锁

​ ​ ​ ​ ​ ​ ​ 使用原子CAS操作锁记录中的Mark Word替换回对象头中,如果成功,则表示没有竞争发生,如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。

3.原子操作的实现原理


​ ​ ​ ​ ​ ​ ​ 原子操作的意思是不可被中断的一个或一系列操作。书上在介绍这部分内容的时候,介绍了几个术语。

缓存行(Cache line):缓存的最小操作单位

比较并交换(Compare and Swap):CAS操作需要两个数值,一个旧值和一个新值,在操作期间先比较旧值有无发生变化,如果没有发生变化,才交换为新值,发生了变化则不交换。

内存顺序冲突(Memory order violation):内存顺序冲突u一般是由假共享引起的,假共享是指多个CPU同时修改一个缓存行的不同部分而引起一个CPU的操作无效,当出现这个内存顺序冲突的时候,CPU必须清空流水线。

处理器如何实现原子操作

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

​ ​ ​ ​ ​ ​ ​ 处理器提供总线锁定和缓存锁定两个机制保证复杂内存操作的原子性。

使用原子总线锁保证原子性

​ ​ ​ ​ ​ ​ ​ 第一个机制是通过总线锁保证原子性。总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号的时候,其它处理器的请求将被阻塞住,那么该处理器可以独占共享内存。
通过缓存锁保证原子性

​ ​ ​ ​ ​ ​ ​ 内存区如果被缓存在处理器上的缓存行中,并且来Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上声明LOCK#信号,而是修改内部的内存地址,并允许他的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会组织同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时,会使缓存行无效。

Java如何实现原子操作

​ ​ ​ ​ ​ ​ ​ 通过锁和循环CAS实现。

1.使用循环CAS

​ ​ ​ ​ ​ ​ ​ JVM中的CAS操作利用了处理器提供的CMPXCHG指令实现的,自旋CAS实现的基本思路就是循环进行CAS操作一直到成功为止。JDK1.5开始,JDK的并发包里提供了一些类来支持原子操作,比如AtomicBoolean(用原子方式更新的boolean值)、AtomicInteger(用原子方式更新的int值)和AtomicLong(用原子方式更新的long值)。

2.CAS实现原子操作的三大问题

​ 1)ABA问题

​ ​ ​ ​ ​ ​ ​ 一个值原来是A,变成了B,然后又变成了A。那么CAS检查的时候会发现它的值没变化,但实际我们都知道变化了。ABA问题的解决思路就是使用版本号。JDK1.5开始,Atomic包里提供AtomicStampedReference类解决ABA问题。其中该类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并检查当前标志是否等于预期标志,如果全部相等,就以原子方式将该引用和该标志的值设置为给定的更新值。

​ 2)循环时间长开销大

​ ​ ​ ​ ​ ​ ​ 如果长时间CAS自旋不成功,那么这对于CPU来说开销非常大。

补充下书中提到的处理器pause指令的作用

(1)延迟流水线执行指令,使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本

(2)可以避免在退出循环的时候因内存顺序冲突而引起CPU流水线被清空

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

​ ​ ​ ​ ​ ​ ​ 除了用锁解决该问题,还可以将多个共享变量合并成一个共享变量来操作。

3.锁机制实现原子操作

​ ​ ​ ​ ​ ​ ​ 锁机制保证只有获得锁的线程才能锁定的内存区域。除了偏向锁,JVM实现锁的方式都用了循环CAS。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值