volatile用法及原理

关键字volatile的主要作用是使变量在多个线程间可见。关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制,当一个变量定义为volatile,它具有内存可见性以及禁止指令重排序两大特性,为了更好地了解volatile关键字,我们可以先看Java内存模型。

1、java内存模型

java中的堆内存用来存储对象实例,堆内存是被所有线程共享的运行时内存区域,因此它存在内存可见性的问题。而局部变量方法定义的参数则不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。java内存模型定义了线程和主存之间的抽象关系:线程之间的共享变量存储在主存中,每个线程都有一个私有的本地内存,本地内存中存储了线程共享变量的副本。要注意的是本地内存是java内存模型的一个抽象,并不是真实的存在,它涵盖了缓存、写缓冲区、寄存器等区域。java内存模型控制线程之间的通信,决定了一个线程对主存共享变量的写入何时对另一个线程可见。抽象示意图如下:

线程A与线程B之间通信,要经历两个步骤:

  1. 线程A与线程A本地内存中更新过的共享变量刷新到主存中
  2. 线程B到主存中去读取线程A之前已更新过的共享变量,如:int i = 3;执行线程必须先在自己的工作线程中对变量i所在的缓存中进行赋值操作,然后再写入主存中,而不是直接把3直接写入主存。

2、原子性、可见性、有序性

1)原子性
对基本数据类型变量的读取和赋值操作是原子性操作,不可被中断,一个语句包含多个操作就不是原子性

2)可见性
指线程的可见性,一个线程修改的状态对另一个线程是可见的。volatile关键字提示线程每次都从主内存中读取变量,而不是从本地内存中读取,这样保证了同步数据的可见性。

3)有序性
java内存模型中允许编译器和处理器对指令进行重排,虽然重排过程不会影响到单线程执行的正确性,但是会影响到多线程并发执行的正确性。这时可以通过volatile来保证有序性,除了volatile,也可以通过synchronizaed和lock来保证有序性。

volatile关键字保证可见性和有序性,但不保证原子性。synchronized关键字使多个线程访问同一个资源具有同步性,而且它还具有将线程工作内存中的私有变量与公共内存中的变量同步的功能。

3、使用场景

1、状态标志

volatile boolean flag;
...
public void shutdown(){
  flag = true;
}

public void doWork(){
  while(flag){
    //TODO
  }
}

2、DCL双重检查单例模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我知道了。首先我需要解释一下 synchronized 关键字、volatile、ReentrantLock 类与阻塞队列 LinkedBlockingQueue<E> 的概念。 synchronized 关键字是 Java 中用于实现同步的关键字,可以用来修饰代码块、方法和静态方法,用于实现线程间的同步。 volatile 是一个关键字,用于修饰变量,用于实现多线程之间的可见性,即当一个线程修改了一个 volatile 变量的值时,其他线程可以立即看到这个修改。 ReentrantLock 类是一个可重入的互斥锁,可以用来实现线程间的同步,与 synchronized 关键字相比,ReentrantLock 提供了更多的功能,如公平锁和可中断锁等。 阻塞队列 LinkedBlockingQueue<E> 是一个基于链表实现的阻塞队列,在多线程编程中可以用来实现线程间的同步。 接下来,我将使用这些同步方法实现主线程与子线程的同步,并写出实验小结。 代码如下: ``` import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.locks.ReentrantLock; public class Main { private volatile boolean flag = false; private final ReentrantLock lock = new ReentrantLock(); private final LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(); public static void main(String[] args) { Main main = new Main(); main.start(); } private void start() { Thread t = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; lock.lock(); try { queue.put(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }); t.start(); lock.lock(); try { while (!flag) { Thread.sleep(100); } System.out.println(queue.take()); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } ``` 在这个例子中,主线程和子线程都需要修改和访问共享变量 flag,因此需要使用 volatile 关键字来实现可见性。 主线程需要等待子线程修改 flag 后才能继续执行,因此可以使用 while 循环不断检查 flag 是否被修改,这里使用了 Thread.sleep(100) 来减少 CPU 的占用率。 子线程需要将一个元素加入到队列中,并通知主线程可以继续执行,因此可以使用 ReentrantLock 和 LinkedBlockingQueue 来实现线程间的同步。 实验小结: 在多线程编程中,同步是一个非常重要的概念,可以用来避免线程间的竞争和冲突。在 Java 中,可以使用 synchronized 关键字、volatile、ReentrantLock 类和阻塞队列 LinkedBlockingQueue<E> 等方法来实现线程间的同步。在实际编程中,应根据情况选择不同的同步方法,以保证程序的正确性和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值