Java 多线程的 volatile 和 AQS

  Java 内存模型(Java Memory Model,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了 Java 程序在各种平台下对内存的访问都能保证效果一致的机制及规范。

在这里插入图片描述

  JMM 是一种规范,是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。目的是保证并发编程场景中的原子性、可见性和有序性。

  • 原子性
      在 Java 中,为了保证原子性,提供了两个高级的字节指令 Monitorenter 和 Monitorexit。这两个字节码,在 Java 中对应的关键字就是 Synchronized。因此,在 Java 中可以使用 Synchronized 来保证方法和代码块内的操作是原子性的。

  • 可见性
      Java 中的 Volatile 关键字修饰的变量在被修改后可以立即同步到主内存。被其修饰的变量在每次使用之前都从主内存刷新。因此,可以使用 Volatile 来保证多线程操作时变量的可见性。只不过实现方式不同。

  • 有序性
      在 Java 中,可以使用 SynchronizedVolatile 来保证多线程之间操作的有序性。区别:Volatile 禁止指令重排。Synchronized 保证同一时刻只允许一条线程操作。

一、volatile 底层实现


在这里插入图片描述

1.1 作用


  保证数据的“可见性”:被 volatile 修饰的变量能够保证每个线程获取该变量的最新值,从而避免出现数据脏读的现象。

禁止指令重排:在多线程操作情况下,指令重排会导致计算结果不一致。

1.2 底层实现


  观察加入 volatile 关键字和没有加入 volatile 关键字时所生成的汇编代码发现,加入 volatile 关键字时,会多出一个 lock 前缀指令

  lock 前缀指令实际上相当于一个内存屏障(也称内存栅栏),内存屏障会提供 3 个功能:

  1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;
  2. 它会强制将对缓存的修改操作立即写入主存;
  3. 如果是写操作,它会导致其他 CPU 中对应的缓存行无效。

1.3 单例模式中的 volatile 的作用


  防止代码读取到 instance 不为 null 时,instance 引用的对象有可能还没完成初始化。

class Singleton {
    
    
	private volatile static Singleton instance = null; // 禁止指令重排
	private Singleton() {
    
    }
	public static singleton getInstance() {
    
    
		if(instance == null) {
    
     // 减少加锁的损耗
			synchronized(Singleton.class){
    
    
				if(instance == null) {
    
     // 确认是否初始化完成
					instance = new Instance();
				}
			}
		}
		return instance;
	}
}

二、AQS 思想


在这里插入图片描述

  AQS 的全称为(AbstractQueuedSynchronizer)抽象的队列式的同步器,是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,如:基于 AQS 实现的 lockCountDownLatchCyclicBarrierSemaphore 需解决的问题:

状态的原子性管理线程的阻塞与解除阻塞队列的管理

  AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH(虚拟的双向队列)队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

2.1 lock


  是一种可重入锁,除了能完成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询请求、定时锁等避免多线程死锁的方法。默认为非公平锁,但可以初始化为公平锁;通过方法 lock()unlock() 来进行加锁与解锁操作;

2.2 CountDownLatch


  通过计数法(倒计时器),让一些线程堵塞直到另外一个线程完成一系列操作后才被唤醒;该工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。具体可以使用 countDownLatch.await() 来等待结果。多用于多线程信息汇总。

2.3 CompletableFuture


  通过设置参数,可以完成过 CountDownLatch 同样的多平台响应问题,但是可以针对其中部分返回结果做更加灵活的展示。

2.4 CyclicBarrier


  字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,让一组线程达到一个屏障(也可以叫做同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过 CyclicBarrierawait() 方法。可以用于批量发送消息队列信息、异步限流。

2.5 Semaphore


  信号量主要用于两个目的,一个是用于多个共享资源的互斥作用,另一个用于并发线程数的控制。SpringHystrix 限流的思想

三、happens-before


  用来描述和可见性相关问题:如果第一个操作 happens-before 第二个操作,那么我们就说第一个操作对于第二个操作是可见的

常见的 happens-beforevolatile、锁、线程生命周期。

猜你喜欢

转载自blog.csdn.net/qq_45609369/article/details/143227747