Java 并发系列之二:java 并发机制的底层实现原理
1. 处理器实现原子操作
2. volatile
- /**
- 补充:
- 主要作用:内存可见性,是变量在多个线程中可见,修饰变量,解决一写多读的问题。
- 轻量级的synchronized,不会造成阻塞。性能比synchronized好得多,不支持原子性操作。为了保证原子性要使用atomic对象,只能保证本身方法的原子性,不能保证多次操作的原子性。(解决方法synchronized)
- 原理:当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将变量上的操作与其他内存操作一起重排序。不会被缓存在寄存器或者对其他处理器不可见的地方,因此读取volatile类型的变量时总会返回最新写入得值。
- 缺点:如果在代码中使用volatile变量来控制状态的可见性,通常比使用锁的代码更脆弱,也更难理解。就加锁机制可以确保可见性又可以确保原子性。
- 何时使用volatile关键字:仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它们。如果在验证正确性时需要对可见性进行复杂的判断,那么就不要使用volatile变量。
正确使用:volatile变量的正确使用方式包括:确保它们自身状态的可见性,确保它们所引用对象的状态的可见性,以及标识一些重要的程序生命周期事件的发生(例如,初始化或关闭)- 使用volatile关键字需要满足的三个条件
- 1.对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
- 2.该变量不会与其他状态变量一起纳入不变性条件中。
- 3.在访问变量时不需要加锁。
- 典型用法:检查某个状态标记判断是否退出循环。
- 抄录来源:https://www.jianshu.com/p/54b5aef07505*/
3. synchronized
图片来自:https://blog.csdn.net/weixin_36152448/article/details/82380343
4. CAS
5. 锁的内存语义
6. txt
- Java 并发机制的底层实现原理
- 处理器实现原子操作
- 两个机制
- 1. 通过总线锁来保证原子性
- 处理器提供的LOCK 信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞,那么该处理器就可以独占共享内存。
- Lock前缀指令会引起处理器缓存回写到内存
- 一个处理器的缓存回写到内存会导致其他处理器的缓存失效
- 总线锁将CPU和内存之间的通信锁住了,导致锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁比缓存锁开销大。
- 2. 通过缓存锁来保证原子性
- 为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存
- 缓存锁定
- 指内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上声言Lock 信号,而是修改内部的内存地址,并允许他的缓存一致性机制来保证操作的原子性
- 缓存一致性
- 缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写已经被锁定的缓存行的数据时,会使缓存行无效。
- 每个处理器通过嗅探在总线上的传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读取到处理器缓存里。
- 保证内部缓存、系统内存和其他处理器的缓存的数据在总线上保持一致
- 不会使用缓存锁的情况
- 1. 当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行时,处理器会调用总线锁定
- 2. 有些处理器不支持缓存锁定
- 最近的处理器里,LOCK #信号一般不锁总线,而是锁缓存
- Intel提供很多Lock前缀的指定来实现
- 位测试和修改指令
- BTS, BTR, BTC
- 交换指令
- XADD,CMPXCHG
- 操作数和逻辑指令
- ADD OR
- 被这些指令操作的内存区域会上锁,导致其他处理器不能同时访问它
- Lock前缀指令在多核处理器下会引发两件事情
- Lock前缀指令会引起处理器缓存回写到系统内存
- 一个处理器的缓存回写到内存会导致在其他CPU里缓存了该内存地址的数据无效
- volatile
- 特性
- volatile可见性:对一个volatile的读,总可以看到任意线程对这个变量最终的写
- volatile原子性:volatile对单个读/写具有原子性(64位Long、Double),但是复合操作除外,例如 i++
- volatile有序性:编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
- 内存语义
- 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新到主内存中
- 当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主存中读取共享变量
- volatile的写-读与 锁的释放-获取 有相同的内存效果:volatile写和锁的释放有相同的内存语义,volatile读和锁的获取有相同的内存语义。这也是增强volatile的原因,提供了一种比锁更轻量级的线程之间的通信机制。
- 重排序规则
- 1. 当第二个操作是volatile写操作时,不管第一个操作是什么,都不能重排序
- 2. 当第一个操作是volatile读操作时,不管第二个操作是什么,都不能重排序
- 3. 当第一个操作是volatile写操作,第二个操作是volatile读操作,不能重排序
- 内存语义的实现
- 内存屏障
- 内存屏障是一组处理器指令,用于实现对内存操作的顺序限制
- 基于保守策略的JMM内存屏障插入策略
- 在每个volatile写操作的前面插入一个StoreStore屏障
- 保证之前的都能刷新到主存
- 在每个volatile写操作的后面插入一个StoreLoad屏障
- 保证先写后读,能提高效率
- 在每个volatile读操作的后面插入一个LoadLoad屏障
- 在每个volatile读操作的后面插入一个LoadStore屏障
- 实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障
- x86处理器只会对写读作重排序,故只有一个屏障StoreLoad即可正确实现volatile写-读的内存语义
- 操作系统语义
- 主存、高速缓存(线程私有)缓存一致?
- 解决方案
- 通过在总线加 Lock 锁的方式,有些是缓存锁定
- 通过缓存一致性协议 MESI协议(修改、独占、共享、无效)
- 实现原则
- Lock前缀指令会引起处理器缓存回写到内存
- 一个处理器的缓存回写到内存会导致其他处理器的缓存失效
- 内存模型
- 内存屏障 限制重排序
- happens-before中的volatile规则
- volatile VS synchronized
- volatile是轻量级的synchronized
- 使用恰当,它比synchronized使用和执行成本更低,因为不会引起线程上下文的切换和调度
- synchronized
- 同步、重量级锁
- 只有使用Synchronized线程处于阻塞,其他Lock, AQS, LockSupport等线程都是处于等待状态
- 原理
- synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证变量的内存可见性。
- 锁对象
- 1. 普通同步方法锁,是当前实例对象
- 2. 静态同步方法,锁是当前类的class对象
- 3. 同步方法块,锁是括号里面的对象
- java中的每一个对象都可以作为锁
- 实现机制
- Java对象头
- synchronized用的锁是保存在Java对象头里的
- 包括两部分数据
- Mark Word(标记字段)
- Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据,它会根据对象的状态复用自己的存储空间
- 包括:哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
- Klass Pointer(类型指针)
- monitor
- java 中 Object和Class中都关联了一个Monitor,一个monitor只能被一个线程拥有
- Owner 活动线程
- 初始时为NULL表示当前没有任何线程拥有该monitor record, 当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL
- 实现
- 同步代码块采用 monitorenter、monitorexit指令显示的同步
- 同步方法使用ACC_SYNCHRONIZED标记符隐式的实现
- 锁优化
- 自旋锁
- 该线程等待一段时间,不会被立即挂起,循环看持有锁的线程是否会很快释放锁
- 自旋数字难以控制(XX: preBlockSpin)
- 存在理论:线程的频繁挂起、唤醒负担较重,可以认为每个线程占有锁的时间很短,线程挂起再唤醒得不偿失。
- 缺点
- 自旋次数无法确定
- 适应性自旋锁
- 自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定
- 自旋成功,则可以增加自旋次数,如果获取锁经常失败,那么自旋次数会减少
- 锁消除
- 若不存在数据竞争的情况,JVM会消除锁机制
- 判断依据
- 变量逃逸
- 锁粗化
- 将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。例如for循环内部获得锁
- 轻量级锁
- 在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗
- 通过CAS(CompareandSwap),即比较并替换,来获取和释放锁
- 缺点
- 在多线程环境下,其运行效率比重量级锁还会慢
- 性能依据
- 对于绝大部分的锁,在整个生命周期内部都是不会存在竞争的
- 偏向锁
- 为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径
- 主要尽可能避免不必要的CAS操作,如果竞争锁失败,则升级为轻量级锁
- CAS
- Compare And Swap, 整个JUC体系最核心、最基础理论,Java中通过锁和CAS实现原子操作
- 内存地址V,旧的预期值A,要更新的值B,当且仅当内存地址V中的值等于旧的预期值A时才会将内存值V得值修改为B,否则什么也不干
- native中存在四个参数
- JVM中的CAS操作利用了处理器提供的CMPXCHG指令实现的。
- 缺陷
- ABA问题
- 检查不到值的变化,实际上却变化了
- 解决方案
- 变量前追加版本号版本号
- AtomicStampedReference
- 这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子的方式将该引用和该标志的值设置为给定的更新值。
- 循环时间太长
- 自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销
- 解决方法
- JVM如果能支持处理器提供的pause指令,那么效率一定会提升
- pause作用
- 1. 可以延迟流水线执行指令(depipeline),使CPU不会消耗过多的执行资源
- 2. 避免在退出循环的时候因内存顺序冲突而引起的CPU流水线被清空,从而提高CPU的执行效率
- 只能保证一个共享变量原则操作
- 对多个共享变量操作时,CAS无法保证操作的原子性,需要用锁
- 解决方案
- AtomicReference类保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作
- 原子操作类Atomic
- java.util.concurrent.atomic里的原子操作类提供了线程安全地更新一个变量的方式
- 4大类型13个原子操作类
- 基本类型类
- AtomicBoolean
- AtomicInteger
- AtomicLong
- 数组
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
- 引用
- AtomicReference
- AtomicReferenceFieldUpdater
- AtomicMarkableReference
- 属性
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
- AtomicStampedReference
- 核心底层
- CAS
- Unsafe只提供了3中CAS方法
- final native boolean compareAndSwapObject()
- final native boolean compareAndSwapInt()
- final native boolean compareAndSwapLong()
- CAS V.S. 锁
- JVM中除了偏向锁,其他锁(轻量级锁、互斥锁)的实现方式都用了循环CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。
- 锁的内存语义
- 锁的功能
- 让临界区互斥执行
- 锁的内存语义
- 锁的释放与获取的内存语义
- 释放锁的线程向获取同一个锁的线程发送消息
- 线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存
- 线程获取锁时,JMM会把该线程对应的本地内存置为无效,从而使被监视器保护的临界区代码必须从内存中读取共享变量
- volatile的写读与 锁的释放获取 有相同的内存效果:volatile写和锁的释放有相同的内存语义,volatile读和锁的获取有相同的内存语义。这也是增强volatile的原因,提供了一种比锁更轻量级的线程之间的通信机制。
- 锁的释放与获取的内存语义两种实现
- 1. 利用volatile 变量的 写读 锁具有的内存语义
- 2. 利用CAS(AQS里面的一个调用本地的方法,调用处理器cmpxchg指令添加lock前缀)所附带的volatile读和volatile写的内存语义
- 公平锁和非公平锁的内存语义
- 公平锁和非公平锁释放时,最后都要写一个volatile变量state
- 公平锁获取时,首先会去读volatile变量。
- 非公平锁获取时,首先会用CAS更新volatile变量,CAS同时具有volatile读和volatile写的内存语义
- JUC 包的实现
- 通用化的实现模型
- 1. 声明共享变量为volatile
- 2. 使用CAS的原子条件更新来实现线程之间的同步
- 3. 同时配合volatile的读写和CAS具有的volatile读写的内存语义来实现线程之间通信
- 4. 基础类基于123:AQS、非阻塞结构、原子变量类
- 5. 高层类基于4:Lock、同步器、阻塞队列、Executor、并发容器
- 锁的升级与对比
- JAVA1.6中,锁一共有4中状态,级别从低到高依次是:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态
- 这几种状态会随着竞争情况的逐渐升级,锁可以升级但是不能降级,这种策略是为了提高获得锁和释放锁的效率
- 无锁
- 偏向锁
- 背景
- 大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
- 偏向锁使用了一种等到竞争出现才释放锁的机制。当其他线程尝试竞争偏向锁时持有偏向所的线程才会释放锁。
- 重要操作
- 获得偏向锁
- 1. 一个线程访问同步块并获得锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID
- 2. 以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要看对象头Mark Word里面是否存储着指向当前线程的偏向锁,如果有,表示线程已经获得了锁,如果没有,下一步
- 3. 判断Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁),如果没有,使用CAS竞争锁; 如果有,则尝试使用CAS将对象头的偏向锁线程ID指向当前线程
- 撤销偏向锁
- 其他线程访问同步块,尝试获得偏向锁,失败,启动撤销偏向锁
- 1. 首先暂停持有偏向锁的线程
- 2. 解锁,将线程ID设为空
- 3. 恢复线程
- 关闭偏向锁
- java 7里默认是启用的
- 关闭延迟
- XX:BiasedLockingStartupDelay=0
- 关闭偏向锁,程序默认会进入轻量级锁
- XX:UseBiasedLocking=false
- 应用
- 优点
- 加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差别
- 缺点
- 如果线程间存在锁竞争,会带来额外的锁撤销的消耗
- 使用场景
- 适用于只有一个线程访问同步块场景
- 轻量级锁
- 注意,轻量级锁解锁失败,锁就会膨胀成为重量级锁,就不会恢复到轻量级锁状态,当线程处于这个状态,其他线程试图获取锁时,会被阻塞住,当持有所得线程释放锁之后会唤醒这些线程,被唤醒的线程会进行新一轮的夺锁之争。
- 重要操作
- 轻量级加锁
- 1. 访问同步块,分配空间并复制Mark Word到栈 Displaced Mark Word
- 2. CAS尝试将对象头中Mark Word替换为指向锁记录的指针,成功则获得锁,执行同步体;失败表示其他线程竞争锁,当前线程通过使用自旋来获取锁
- 轻量级解锁
- 1. 解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头
- 2. 如果成功,表示没有竞争发生,如果失败,表示当前锁存在竞争(因为别的线程在争夺锁),锁就会膨胀成为重量级锁(别的线程阻塞),释放锁并唤醒等待的线程
- 应用
- 优点
- 竞争的线程不会阻塞,提高了程序的响应速度
- 缺点
- 如果始终得不到锁竞争的线程,使用自旋会消耗CPU
- 使用场景
- 追求响应时间
- 同步块执行速度非常快
- 重量级锁
- 应用
- 优点
- 线程竞争不使用自旋,不会消耗CPU
- 缺点
- 线程阻塞,响应时间缓慢
- 使用场景
- 追求吞吐量
- 同步块执行速度较长
7. 参考网址
- 参考来源:http://cmsblogs.com/wp-content/resources/img/sike-juc.png
- 《Java并发编程的艺术》_方腾飞PDF 提取码:o9vr
- http://ifeve.com/the-art-of-java-concurrency-program-1/
- Java并发学习系列-绪论
- Java并发编程实战
- 死磕 Java 并发精品合集
/**
线程试图通过类似于数绵羊的传统方法进入休眠状态。为了使这个示例能正确执行,asleep必须为volatile变量。否则,当asleep被另一个线程修改时,执行判断的线程却发现不了。
**/
volatile boolean asleeep;
...
while(!asleep)
countSomeSheep();
Java 并发系列之二:java 并发机制的底层实现原理的更多相关文章
- 《Java并发编程的艺术》Java并发机制的底层实现原理(二)
Java并发机制的底层实现原理 1.volatile volatile相当于轻量级的synchronized,在并发编程中保证数据的可见性,使用 valotile 修饰的变量,其内存模型会增加一个 L ...
- 那些年读过的书《Java并发编程的艺术》一、并发编程的挑战和并发机制的底层实现原理
一.并发编程的挑战 1.上下文切换 (1)上下文切换的问题 在处理器上提供了强大的并行性就使得程序的并发成为了可能.处理器通过给不同的线程分配不同的时间片以实现线程执行的自动调度和切换,实现了程序并行 ...
- Java并发机制的底层实现原理之volatile应用,初学者误看!
volatile的介绍: Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现 ...
- 【java并发编程艺术学习】(三)第二章 java并发机制的底层实现原理 学习记录(一) volatile
章节介绍 这一章节主要学习java并发机制的底层实现原理.主要学习volatile.synchronized和原子操作的实现原理.Java中的大部分容器和框架都依赖于此. Java代码 ==经过编译= ...
- java高并发系列 - 第2天:并发级别
由于临界区的存在,多线程之间的并发必须受到控制.根据控制并发的策略,我们可以把并发的级别分为阻塞.无饥饿.无障碍.无锁.无等待几种. 阻塞 一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继 ...
- 当我们说线程安全时,到底在说什么——Java进阶系列(二)
原创文章,同步发自作者个人博客,转载请以超链接形式在文章开头处注明出处http://www.jasongj.com/java/thread_safe/ 多线程编程中的三个核心概念 原子性 这一点,跟数 ...
- Java 设计模式系列(二二)责任链模式
Java 设计模式系列(二二)责任链模式 责任链模式是一种对象的行为模式.在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链.请求在这个链上传递,直到链上的某一个对象决定处理此请求 ...
- java RPC系列之二 HTTPINVOKER
java RPC系列之二 HTTPINVOKER 一.java RPC简单的汇总 java的RPC得到技术,基本包含以下几个,分别是:RMI(远程方法调用) .Caucho的Hessian 和 Bu ...
- Java Thread系列(二)线程状态
Java Thread系列(二)线程状态 一.线程的五种状态 新建状态(New):新创建了一个线程对象,尚未启动. 就绪状态(Runnable):也叫可运行状态.线程对象创建后,其他线程调用了该对象的 ...
随机推荐
- grafana的用户及权限的配置
一.概述 在上几篇文章中,已经在grafana中,配置了几个Dashboard.需要给开发人员查看,但是我又不想让开发人员,随便更改Dashboard中的配置.需要创建一个只读账号才行. 二.添加用户 ...
- jmeter 如何获取一小时之前的时间戳
正确答案: ${__intSum(${__time(/1000,)},-3600,)} 如果还要显示毫秒 ${__longSum(${__time},-3600000,)}
- Redis(七)持久化(Persistence)
前言 前文中介绍到Redis时内存的K-V数据结构存储服务器.Redis的高性能原因之一在于其读写数据都是在内存中进行.它的架构实现方式决定了Redis的数据存储具有不可靠性,易丢失,因为RAM内存在 ...
- 《 .NET并发编程实战》阅读指南 - 第1章
先发表生成URL以印在书里面.等书籍正式出版销售后会公开内容.
- 2019-11-29-C#-反射调用私有事件
原文:2019-11-29-C#-反射调用私有事件 title author date CreateTime categories C# 反射调用私有事件 lindexi 2019-11-29 08: ...
- 02 .NET CORE 2.2 使用OCELOT -- 路由
继续学习.NET CORE 2.2 使用OCELOT https://www.jianshu.com/p/05ccf87a3091 https://www.jianshu.com/p/585396dc ...
- mac下搭建Apache服务器环境
mac下自带了一个Apache服务环境,所以不需要另外去下载,直接配置就好了. 一.启动Apache服务 在终端下输入 sudo apachectl start , 启动Apache服务.在浏览器输入 ...
- Visual Studio Code创建C#项目
Visual Studio Code是一个支持跨平台的文本编辑器,同其他文本文本编辑器一样,不但占用磁盘空间小,性能也比较快:近几年由于不断的升级和许多开发者提供大量的插件,它已经成为了一个非常强大的 ...
- 学习笔记之Web
学习笔记之JavaScript - 浩然119 - 博客园 https://www.cnblogs.com/pegasus923/p/9436340.html 学习笔记之PHP - 浩然119 - 博 ...
- TinyMCE常用插件
Advanced Tables 基于table插件的增强表格插件,添加了排序功能. tinymce.init({ plugins: 'table advtable', menubar: 'table' ...