Java 中的锁:Synchronized、Lock、Volatile、CAS、Concurrent包

未完待续。。。

本文主要探究Java 中的锁,包括:

Synchronized要从下面三个方面去理解

  • 【代码表现上】、【 锁升级】、【 锁在不同系统层面的实现】

  • 1、代码表现上

    • (1)Synchronized锁对象和同步方法时:只对该对象有效(对象锁:锁this或者锁其他对象);具体见Synchronized方法锁、对象锁、类锁区别
    • (2)Synchronized锁类和静态方法时:对所有对象生效(类锁:static、对象.class)。
    • (3)是否互斥执行(发生阻塞), 主 要 判 断 是 不 是 同 一 把 锁 就 行 \color {red}{主要判断是不是同一把锁就行} ,比如一个类中两个方法,一个static锁,一个非static锁,那么不会发生阻塞,因为不是同一把锁,记住,类的class对象归根到底也是对象。
  • 2、锁升级

    • 刚创建对象的时候,对象头的markword未存放锁信息,含有该对象的hashcode。
    • 无锁 -> 偏向锁:在对象头的markword中存放线程ID。
    • 偏向锁 -> 轻量级锁:竞争的线程,采用CAS方式,将自己线程栈中的LR(Lock Record)对象存放到对象头的markword中,此时对象的hashcode存在于线程栈的LR中。
    • 轻量级锁 -> 重量级锁:
      • 操作:去操作系统申请锁,此时状态由用户态转换成内核态。
      • 升级条件:自旋超过一定次数(比如10次),或者自旋线程超过CPU核数的一半。1.6以后进行改进,加入了自适应自旋。
    • 为什么要引入重量级锁:因为轻量级锁的CAS操作相当的耗CPU资源。
  • 3、锁在不同系统层面的实现

    • java层:关键字:Synchronized
    • .class层:monitorenter 和 monitorexit
    • 运行态:自动锁升级、锁消除、锁粗化、逃逸分析
    • 汇编层:lockxchg

Volatile需要刨根问底

  • volatile能干什么?

    • 1、保证此变量对所有线程的可见性
    • 2、禁止指令重排序优化
  • volatile是如何保证此变量对所有线程的可见性?

    • 理解一:CPU修改数据,首先是对缓存的修改,然后再同步回主存,在同步回主存的时候,如果其他CPU也缓存了这个数据,就会导致其他CPU缓存上的数据失效(通过嗅探总线数据传播,检查缓存对应的主存地址是否被修改过),这样,当其他CPU再去它的缓存读取这个数据的时候发现缓存已失效,就必须从主存重新获取。
    • 理解二:使用 volatile 关键字会强制将修改的值立即写入主存;
      • 使用 volatile 关键字的话,当线程 2 进行修改时,会导致线程 1 的工作内存中缓存变量 stop 的 缓 存 行 无 效 \color{red}缓存行无效 (反映到硬件层的话,就是CPU 的 L1 或者 L2 缓存中对应的缓存行无效);由于线程1的工作内存中缓存变量 stop 的缓存行无效,所以线程 1 再次读取变量 stop 的值时会去主存读取
  • volatile既然能保证可见性,为什么不能保证原子性?

    • 为什么volatile能保证有序性不能保证原子性
    • 当 i=5 的时候A,B两个线程同时读入了 i 的值, 然后A线程执行了 temp = i + 1的操作, 要注意,此时的 i 的值还没有变化,然后B线程也执行了 temp = i + 1的操作,注意,此时A,B两个线程保存的 i 的值都是5,temp 的值都是6, 然后A线程执行了 i = temp (6)的操作,此时i的值会立即刷新到主存并通知其他线程保存的 i 值失效, 此时B线程需要重新读取 i 的值那么此时B线程保存的 i 就是6,同时B线程保存的 temp 还仍然是6, 然后B线程执行 i=temp (6),所以导致了计算结果比预期少了1。
  • volatile是如何防止指令重排序优化的

    • java层:加入volatile关键字
    • 字节码层:ACC_VOLATILE
    • JVM层:加入内存屏障
    • hotspot层:锁总线
  • 内存屏障又是什么?

    • 是一种指令,通过在每个volatile读写操作前后加入相应的屏障,完成对值的控制
  • DCL锁为什么要加volatile?

    • DCL的问题出现在new一个新的对象,不是原子操作,存在指令重排,其冲突存在于多线程,一个创建对象的时候指令重排,但是对象没有init,另一个线程调用了该对象,出现对象未初始化错误。
    • DCL单例模式为什么还需要加volatile
  • volatile是否有锁?

    • 有,其在编译层的实现是lock
  • volatile的应用场景

    • 1、在原子性操作的场景,可以使用volatile代替其他锁,效率比较高

并发中的基础的概念

  • 两个规则

    • as-if-serial规则:as-if-serial规则是指不管如何重排序(编译器与处理器为了提高并行度),(单线程)程序的结果不能被改变。这是编译器、Runtime、处理器必须遵守的语义。
    • happens-before规则:某些操作是一定严格区分执行顺序的,比如线程起停、加解锁、volatile
  • 缓存一致性协议

    • 当 CPU 写数据时,如果发现操作的变量是共享变量,即在其他 CPU 中也存在该变量的副本,会发出信号通知其他 CPU 将该变量的缓存行置为无效状态,因此当其他 CPU 需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
  • 程序局部性原理。

    • 时间局部性(Temporal Locality):如果一个信息项正在被访问,那么在近期它很可能还会被再次访问。比如循环、递归、方法的反复调用等。
    • 空间局部性(Spatial Locality):如果一个存储器的位置被引用,那么将来他附近的位置也会被引用。
  • 缓存行

    • 每个缓存里面都是由缓存行组成的,缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。
    • 最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享

并发中的CAS和AQS

  • 【CAS】
  • 【AQS】

Concurrent包主要看这几个类

  • 【locks】

    • ReentrantLock
    • ReadWriteLock
  • 【atomic】

  • 【BlockingQueue】

  • CountDownLatch

    • 作用:多线程控制工具类,统一管理多个线程达到某种状态。
    • 主要方法:
      • CountDownLatch(int count) //实例化一个倒计数器,count指定计数个数
      • countDown() // 计数减一
      • await() //等待,当计数减到0时,所有线程并行执行
      • 在每个方法的finally中加入latch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。
    • CountDownLatch典型用法:
      • 1、某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
      • 2、实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的CountDownLatch(1),将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await(),当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒。
    • 注意事项:
      CountDownLatch是一次性的,计算器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。
    • 参考博客:CountDownLatch的理解和使用
  • CyclicBarrier

    • 作用:控制多线程的统一终点,类似于可循环利用的CountDownLatch
    • 主要方法:
      • await()
    • 注意事项:
      • 线程调用 await() 表示自己已经到达栅栏
      • BrokenBarrierException 表示栅栏已经被破坏,破坏的原因可能是其中一个线程 await() 时被中断或者超时
    • 参考博客:CyclicBarrier 使用详解
  • 【Semaphore】

猜你喜欢

转载自blog.csdn.net/ljfirst/article/details/108039297