深刻JVM--高效并发(读书笔记)

Java内存模型与线程

  • Java内存模型java

    • Java内存模型的主要目的是定义程序中各类变量的访问规则,即关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节。此处的变量(Variables)与Java编程中所说的变量有所区别,它包括了实例字段、静态字段和构成数组对象的元素,可是不包括局部变量与方法参数,由于后者是线程私有的
      • 全部的变量都存储在主内存中,每条线程有本身的工做内存(相似缓存)
    • 线程的工做内存中保存了被该线程使用的变量的主内存副本,线程对变量的全部操做(读取、赋值等)都必须在工做内存中进行,而不能直接读写主内存中的数据
      • 主内存大体对应Java堆中的对象实例数据部分,工做内存基本对应虚拟机栈中的部分区域
    • 工做内存与主内存之间的交互
      • 不容许一个变量从主内存读取了但工做内存不接受,或者工做内存发起回写了但主内存不接受的状况出现
      • 变量在工做内存中改变了以后必须把该变化同步回主内存。
      • 不容许一个线程无缘由地把数据从线程的工做内存同步回主内存中。
      • 一个新的变量只能在主内存中“诞生”
      • 一个变量在同一个时刻只容许一条线程对其进行lock操做,但lock操做能够被同一条线程重复执行屡次,屡次执行lock后,只有执行相同次数的unlock操做,变量才会被解锁(可重入)
      • 若是对一个变量执行lock操做,会清空原来这个变量在工做内存的值,也就是要求从新从主内存read、load(lock操做保证了内存可见性
      • 若是变量没有被lock,就不能被unlock
      • 对变量进行unlock以前,须要先把变量store、write进主内存
    • 对于volatile型变量的特殊规则
      • 保证此变量对全部线程的可见性
        • 能够理解为volatile变量是“写直达”到主内存
        • 内存可见性没法保证并发安全
          • 读-判断-改 类型问题
      • 禁止指令重排序
      • 每次使用V前都必须先从主内存刷新最新的值,用于保证能看见其余线程对变量V所作的修改
      • 每次修改V后都必须马上同步回主内存中,用于保证其余线程能够看到本身对变量V所作的修改。
    • 针对long和double型变量的特殊规则
      • 容许虚拟机实现自行选择是否要保证64位数据类型的load、store、read和write这四个操做的原子性
      • 实际环境中,基本没有出现
    • 原子性、可见性、有序性
      • 原子性
        • sychronized等
      • 可见性
        • volatile、synchronized、final
      • 有序性
        • 若是在本线程内观察,全部的操做都是有序的;若是在一个线程中观察另外一个线程,全部的操做都是无序
        • 指令重排、工做内存与主内存之间的缓冲
      • 先行发生规则
        • 程序次序规则
          • 控制流顺序
        • 管程锁定规则
          • 一个unlock操做先行发生于后面对同一个锁的lock操做
        • volatile变量规则
          • volatile变量的写操做先行发生于后面对这个变量的读操做
        • 线程启动规则
          • Thread对象的start()方法先行发生于此线程的每个动做
        • 线程终止规则
          • 线程中的全部操做都先行发生于对此线程的终止检测
        • 对象中断规则
          • 对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发
        • 对象终结规则
          • 一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
        • 传递性
          • 若是操做A先行发生于操做B,操做B先行发生于操做C,那就能够得出操做A先行发生于操做C的结论
  • Java与线程编程

    • 线程的实现数组

      • 内核线程实现(1:1)
        • 运行在内核态下的线程
        • 由内核线程支持的轻量级进程
          • 相关调度由内核线程控制,因此有些切入内核态的花费
      • 用户线程实现(1:N)
        • 彻底创建在用户空间上的
          • 线程的调度在用户态上完成
          • 一个用户进程对应多个用户线程
      • 用户线程加轻量级进程混合实现(N:M)
    • Java线程调度缓存

      • 协同式
        • 线程本身执行完了,才回去通知其余线程来
        • 调用时间由线程本身决定
      • 抢占式
        • 线程执行时间由系统来分配
    • 状态转换安全

      • 新建并发

        • 建立后未启动
      • 运行函数

        • 可能正在执行、可能正在等待执行
      • 无限期等待性能

        • 只能等待其余线程显示唤醒
          • 没有设置时间参数的Object::wait()方法
          • 没有设置Timeout参数的Thread:join()方法
          • LockSupport::park()方法
      • 限期等待优化

        • 能够自身被唤醒
          • Thread::sleep()
          • 设置了Timeout参数的Object::wait()
          • 设置了Timeout参数的Thread::join()
          • LockSupport::parkNanos()方法
          • LockSupport::parkUntil()方法
      • 阻塞操作系统

        • 等待锁的释放
      • 结束

  • Java与协程

    • 协同的调度的轻量级用户线程?
      • 狭隘,存在不是协同调度的轻量级用户线程

线程安全与锁优化

  • 什么是线程安全

    • 当多个线程同时访问一个对象时,若是不用考虑这些线程在运行时环境下的调度和交替执行,也不须要进行额外的同步,或者在调用方进行任何其余的协调操做,调用这个对象的行为均可以得到正确的结果,那就称这个对象是线程安全的
  • 并发安全分级

    • 不可变、绝对线程安全、相对线程安全、线程兼容和线程对立

    • 不可变的对象是线程安全的

    • 绝对线程安全

      • 方法级别的很容易作到
      • 对象级别的基本没有,强如vector,它的遍历也不是线程安全的。
      • 相对线程安全
        • 只要求方法/操做级别的线程安全
      • 线程兼容
        • 能够经过在调用端使用同步手段(锁、原子操做)来保证在并发环境中安全使用
      • 线程对立
    • 线程安全的实现方法

      • 互斥同步(悲观策略)
        • 互斥量、信号量、临界区(操做系统概念)
        • synchronized
          • 非公平、不能超时退出、不能被中断、锁绑定一个条件
        • ReetrantLock
          • 公平、等待可中断、锁绑定多个条件
        • 二者性能差很少
      • 非阻塞同步(乐观策略)
        • 硬件支持,必需要求冲突与修改操做能够合并为“一个原子操做”
          • Test-and-Set
          • Compare-and-Swap
          • Load-Linked/Store-Conditional
          • Swap
        • CAS
          • CAS指令须要有三个操做数,分别是内存位置(在Java中能够简单地理解为变量的内存地址,用V表示)、旧的预期值(用A表示)和准备设置的新值(用B表示)。CAS指令执行时,当且仅当V符合A时,处理器才会用B更新V的值,不然它就不执行更新。可是,不论是否更新了V的值,都会返回V的旧值,上述的处理过程是一个原子操做,执行期间不会被其余线程中断。
          • ABA问题
        • 无同步方案
          • 线程本地化存储
            • ThreadLocal
              • ThreadLocalMap
              • 弱引用与内存泄露
  • 锁优化

    • 自旋锁与自适应锁

    • 自旋锁

      • 若是锁被其余线程占用,就先spin一下
      • 竞争激烈的状况下,效果很差
    • 自适应锁

      • 由虚拟机来判断是否要自旋,通常来讲是根据自旋时间与阻塞的比值来的。
    • 锁消除

      • 虚拟机会帮助判断优化掉没有意义的锁
    • 锁粗化

      • 适当放宽锁范围,有时候频繁加锁、释放锁
    • 轻量级锁

      • “对于绝大部分的锁,在整个同步周期内都是不存在竞争的”
      • 但若是确实存在锁竞争,除了互斥量的自己开销外,还额外发生了CAS操做的开销。所以在有竞争的状况下,轻量级锁反而会比传统的重量级锁更慢。
      • 就是每一个线程在尝试得到锁的时候,都会先进行CAS判断,若是判断成功(还没人得到锁),那就拿锁,修改状态。不然,阻塞,并将轻量锁膨胀为重量锁
    • 偏向锁

      • 偏向锁就是在无竞争的状况下把整个同步都消除掉,连CAS操做都不去作了

      • 某个线程得到了偏向锁,就经过CAS把这个线程进行记录,以后使用都不须要进行同步操做

        直到其余线程尝试获取这个锁,那么偏向模式解除,要么转成未锁定,要么转为轻量锁

相关文章
相关标签/搜索