Java JVM:线程安全与锁优化(八)

一、线程安全

  • 当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象为线程安全
  • 共同特征
    • 代码本身封装了所有必要的正确性保障手段(互斥同步等),令调用者无须关心多线程下的调用问题,更无须自己实现任何措施来保障多线程环境下的正确调用
    • 白话文:代码本身已经有了必要的正确性保障手段,像什么互斥同步这些,调用的时候就不需要自己实现任何操作保障多线程下正确调用
  • Java 语言中的线程安全
    • Java 语言中各种操作共享的数据分为以下五类
      • 不可变
        • 一定是线程安全的,把对象里面带有状态的变量都声明为 final
      • 绝对线程安全
        • 要达到不管运行时环境如何,调用者都不需要任何额外的同步措施,需要付出高昂的,甚至不切实际的代价
        • Vector 时一个线程安全的容器,所有方法都是 synchronized 修饰的
      • 相对线程安全
        • 通常意义上的线程安全,需要保证单次操作时线程安全的,调用时候不需要额外的操作
      • 线程兼容
        • 对象本身不安全,调用的时候使用同步手段来保证安全
      • 线程对立
  • 线程安全的实现方法
    • 互斥同步
      • 同步是指多个线程并发访问共享数据时,保证同一时刻只有一条线程使用,互斥时实现同步的手段
        • 互斥是因,同步是果
      • 最基本的就是 synchronized,编译后形成 monitorenter 和 monitorexit 两个字节码指令
        • 执行前者时,锁的计数器加一,后者减一,值为零就是无锁
        • 在执行时会无条件阻塞后面其他线程的进入
      • 重入锁(ReentrantLock)
        • 和 synchronized 一样时可重入的,多了三个高级功能
        • 等待可中断:持有锁长期不释放,其他线程可以放弃等待
        • 公平锁:按照申请锁的时间顺序来依次获得锁
        • 非公平是任何一个等待锁的线程都有机会获得(两个都是,Lock 可以改)
        • 锁绑定多个条件:ReentrantLock 可以同时绑定多个 Condition 对象
    • 非阻塞同步
      • 先进行操作,没有线程竞争操作就成功,有竞争会进行补偿措施(不断重试,直到没有竞争)
      • 这种乐观并发策略的实现不需要把线程阻塞挂起,这就是非阻塞同步
    • 无同步方案
      • 有些代码天生就是线程安全
      • 可重入代码:可以任意时刻中断去执行另外一段代码,返回后程序不出现错误
        • 不依赖全局变量、存储在堆上的数据和公用的系统资源
      • 线程本地存储:如果数据需要共享,需要看看共享数据的代码是否能保证在同一个线程中执行。

二、锁优化

  • 自旋锁与自适应自旋
    • 为了让线程等待,我们只须让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁
    • 如果锁占用时间很短,自旋等待的效果就会非常好,反之如果锁占用的时间很长,那么自旋只会白白消耗处理器资源
    • 自旋次数的默认值是十次,用户也可以使用参数 -XX:PreBlockSpin 来更改
    • 自适应意味着自旋的时间不再是固定的,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态决定的
      • 对于某个锁,自旋很少成功获得过,可以直接忽略自旋过程
  • 锁消除
    • 指虚拟机即时编译器在运行时,对一些代码要求同步,但是对被检测到不可能存在共享数据竞争对锁进行消除
  • 锁粗化
    • 如果虚拟机探测到有一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展到整个操作序列到外部
  • 轻量级锁
    • 设计的初衷是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗
    • 对象除了未被锁定的正常状态外,还有轻量级锁定、重量级锁定、GC 标记、可偏向等不同状态
    • 如果出现两条以上的线程争用同一个锁的情况,那轻量级锁就不再有效,必须要膨胀为重量级锁
    • 轻量级锁通过 CAS 操作成功避免了使用互斥量的开销
  • 偏向锁
    • 目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能
    • 偏向锁就是在无竞争的情况下把整个同步都消除掉,连 CAS 操作都不去做了
    • 偏就是偏心的偏,这个锁会偏向第一个获得它的线程
    • 一旦出现另外一个线程去尝试获取这个锁,偏向模式就马上宣告结束
    • 如果程序中大多数的锁都总是被多个不同的线程访问,那偏向模式就是多余的

猜你喜欢

转载自blog.csdn.net/baidu_40468340/article/details/128836808