网站开发手机版,自动翻译wordpress,学校网站建设与维护方案,徐州市住房建设局网站首页大家好#xff0c;我是大明哥#xff0c;一个专注「死磕 Java」系列创作的硬核程序员。 本文已收录到我的技术网站#xff1a;https://skjava.com。有全网最优质的系列文章、Java 全栈技术文档以及大厂完整面经 回答
volatile 是一种轻量级的同步机制#xff0c;它能保证共… 大家好我是大明哥一个专注「死磕 Java」系列创作的硬核程序员。 本文已收录到我的技术网站https://skjava.com。有全网最优质的系列文章、Java 全栈技术文档以及大厂完整面经 回答
volatile 是一种轻量级的同步机制它能保证共享变量的可见性同时禁止重排序保证了操作的有序性但是它无法保证原子性。所以使用 volatile 必须要满足这两个条件
写入变量不依赖当前值。变量不参与与其他变量的不变性条件。
volatile 比较适合多个线程读一个线程写的场合典型的场景有如下几个
状态标志重检查锁定的单例模式开销较低的“读-写锁”策略
详解
volatile 使用条件
要想正确安全地使用 volatile 必须要具备这两个条件
写入变量不依赖当前值变量的新值不能依赖于之前的旧值。如果变量的当前值与新值之间存在依赖关系那么仅使用 volatile 是不够的因为它不能保证一系列操作的原子性。比如 i。变量不参与与其他变量的不变性条件如果一个变量是与其他变量共同参与不变性条件的一部分那么简单地声明变量为 volatile 是不够的。
第一个条件很好理解第二个条件这里需要解释下。
“变量不参与与其他变量的不变性条件”这里的“不变性条件”指的是一个或多个变量在程序执行过程中需要保持的条件或关系以确保程序的正确性。假设我们有两个变量它们需要满足某种关系例如a b 99。我们需要在多线程环境下保证这种关闭在任何时候都是成立的。如果这个时候我们只是将其中一个变量声明为 volatile虽然确保了这个变量的更新对其他线程立即可见但却不能保证这两个变量作为一个整体满足特定的不变性条件。在更新这两个变量的过程中其他线程可能会看到这些变量处于不一致的状态。在这种情况下我们就需要使用锁或者其他同步机制来保证这种关系的整体一致性。
volatile 使用场景
volatile 比较适合多个线程读一个线程写的场合。
状态标志
当我们需要用一个变量来作为状态标志控制线程的执行流程时使用 volatile 可以确保当一个线程修改了这个标志时其他线程能够立即看到最新的值。
public class TaskRunner implements Runnable {private volatile boolean running true; // 状态标志控制任务是否继续执行public void run() {while (running) { // 检查状态标志// 执行任务doSomething();}}public void stop() {running false; // 修改状态标志使得线程能够停止执行}private void doSomething() {// 实际任务逻辑}
}DCL 的单例模式
在实现单例模式时为了保证线程安全通常使用双重检查锁定Double-Checked Locking模式。在这种模式中volatile 用于避免单例实例的初始化过程中的指令重排序确保其他线程看到一个完全初始化的单例对象具体来说就是使用 volatile防止了Java 对象在实例化过程中的指令重排确保在对象的构造函数执行完毕之前不会将 instance 的内存分配操作指令重排到构造函数之外。
public class Singleton {// 使用 volatile 保证实例的可见性和有序性private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance null) { // 第一次检查避免不必要的同步synchronized (Singleton.class) { // 锁定if (instance null) { // 第二次检查确保只创建一次实例instance new Singleton();}}}return instance;}
}开销较低的“读-写锁”策略
这种策略一般都是允许多个线程同时读取一个资源但只允许一个线程写入的同步机制。这种“读-写锁”非常适合读多写少的场景我们可以利用 volatile 锁的机制减少公共代码路径的开销。如下
public class VolatileTest { private volatile int value; //读不加锁提供效率 public int getValue() { return value; } //写操作使用锁保证线程安全 public synchronized int increment() { return value; }
}在 J.U.C 中有一个采用“读-写锁”方式的类ReentrantReadWriteLock它包含两个锁一个是读锁另一个是写锁。
下面是伪代码
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class DataStructure {private final ReadWriteLock readWriteLock new ReentrantReadWriteLock();private final Object data ...; // 被保护的数据public void read() {readWriteLock.readLock().lock(); // 获取读锁try {// 执行读操作// 例如读取data的内容} finally {readWriteLock.readLock().unlock(); // 释放读锁}}public void write(Object newData) {readWriteLock.writeLock().lock(); // 获取写锁try {// 执行写操作// 例如修改data的内容} finally {readWriteLock.writeLock().unlock(); // 释放写锁}}
}
读操作 多个线程可以同时持有读锁因此多个线程可以同时执行 read() 方法。写操作 只有一个线程可以持有写锁并且在持有写锁时其他线程不能读取或写入。
这种“读-写锁”策略提高了在多线程环境下对共享资源的读取效率尤其是在读操作远远多于写操作的情况下。但是它也会让我们的程序变更更加复杂比如潜在的读写锁冲突、锁升级从读锁升级到写锁等问题。因此在实际应用中大明哥推荐直接使用 ReentrantReadWriteLock 即可无需头铁自己造轮子。