首页 > 编程笔记 > Java笔记 阅读:32

Java中volatile的作用(非常详细)

volatile 是 Java 中的一个关键字,提供了一种轻量级的同步机制,它用来保证多线程访问共享变量时能够正确地读取和更新该变量的值。

与 synchronized 关键字相比,volatile 关键字更轻量级,因为它不会引起线程上下文的切换和调度。

在 Java 中,线程之间的通信通常是通过共享变量进行的。当一个变量使用 volatile 修饰时,它会具有以下两个特性:
volatile 关键字的用法(使用场景)主要有以下几个。

volatile作为状态标志

当应用程序的某个状态被一个线程设置之后,其他线程会读取该状态并将其作为下一步的计算依据。这时,可以使用 volatile 变量作为同步机制,一个线程能够“通知”另外一个线程某个事件的发生,而这些线程无须使用锁,避免了锁的开销和相关问题。

状态标志示例代码如下:
public class StatusFlagExample {
    private volatile boolean isReady = false;
    public void prepare() {
      // 准备工作的代码
      isReady = true;
    }
    public void waitForReady() throws InterruptedException {
        while (!isReady) {
          Thread.sleep(100);
          // 等待一段时间后再次检查
        }
        // 继续执行后续操作
    }
}
在这个示例中,isReady 变量使用 volatile 修饰,以保证所有线程都能看到最新的值。当 prepare() 方法被调用时,它会将 isReady 变量设置为 true,表示准备工作已完成。其他线程可以调用 waitForReady() 方法来等待 isReady 变量的值变为 true,然后继续执行后续操作。这种方式利用了 volatile 关键字的可见性特性,避免了使用锁的开销和相关问题。

volatile保障可见性

使用 volatile 关键字可以保障一个线程修改了某个变量的值后,其他线程能够立即看到最新的值。这是由于 volatile 关键字会强制线程将变量值从主内存刷新到工作内存,并且在修改变量时立刻将工作内存中发生的改变写回到主内存中。

当在多线程环境下需要修改或检查一个值时,volatile 可以用于确保所有线程都能看到这个值最新的状态。例如,在生产环境中,如果有一个线程负责检查库存,而其他线程负责更新库存,那么可以使用 volatile 关键字来确保所有线程都能看到最新的库存数量。

示例代码如下:
public class Inventory {
    private volatile int stock; //库存数量
    public Inventory(int stock) {
        this.stock = stock;
    }
    public void sell() {
        if (stock > 0) {
            stock--;
        }
    }
    public void updateStock(int newStock) {
        if (newStock > 0) {
            stock = newStock;
        }
    }
    public int getStock() {
        return stock;
    }
}
在上述示例中,stock 变量使用 volatile 修饰,这意味着任何线程在修改或检查 stock 的值时,都会看到该变量最新的值。这样就可以避免出现超卖的情况。

volatile代替锁使用

当多个线程共享一个变量时,通常需要使用锁来保障对这个变量的更新操作的原子性,以避免数据不一致。利用 volatile 关键字的写入原子性,可以将变量封装成一个对象,将更新操作通过新建对象并将该对象赋值给 volatile 变量来实现。

下面是一个使用 volatile 关键字代替锁的完整示例,代码如下:
public class SharedResource {
    private volatile boolean isAvailable = false;
    public void acquire() {
       while (isAvailable) {
            // 等待资源可用
       }
       // 资源已获取,进行相关操作
    }
    public void release() {
       isAvailable = true;
       // 通知其他线程资源已释放
    }
}
在上述代码中,SharedResource 类中的 isAvailable 变量使用 volatile 修饰。当一个线程调用 acquire() 方法时,它会循环检查 isAvailable 变量的值,直到该值为 false,即表示资源可用。

一旦线程获得资源,它可以在 acquire() 方法中进行相关操作。当线程完成操作后,它会调用 release() 方法释放资源,并将 isAvailable 变量的值设置为 true。

其他线程在调用 acquire() 方法时,会立即看到 isAvailable 变量的最新值,并继续循环等待直到资源可用。这种方式避免了使用锁的开销和相关问题,提高了程序的性能。

使用 SharedResource 类的示例代码如下:
public class SharedResourceExample {
    public static void main(String[] args) {
        SharedResource resource = new SharedResource();
        // 线程1获取资源并执行操作
        new Thread(() -> {
            resource.acquire();
            try {
                // 执行相关操作
            } finally {
                resource.release();
            }
        }).start();
        // 线程2等待资源可用
        new Thread(() -> {
            resource.acquire();
            try {
                // 执行相关操作
            } finally {
                resource.release();
            }
        }).start();
    }
}
在上述代码中,创建了一个 SharedResource 对象 resource。线程 1 首先调用 acquire() 方法获取资源并执行操作,完成后释放资源。线程 2 在调用 acquire() 方法时需要等待,直到线程 1 释放资源后才获取资源并执行操作。通过这种方式,多个线程可以共享同一个资源,而不需要使用锁来同步访问。

volatile简易版读写锁

通过 volatile 变量和锁的混合使用实现简易版读写锁。锁保障写入操作的原子性,volatile 保证读取操作的可见性。这种读写锁允许线程读取到共享变量的非最新值。
public class ReadWriteLock {
    private volatile boolean writing = false; // 表示是否有线程正在写入数据
    private volatile int readers = 0; // 表示当前读线程的数量
    public synchronized void write(Object newValue) throws InterruptedException {
        while (writing) {
          // 如果已经有线程正在写入数据,当前线程需要等待
          wait(); // 当前线程进入等待状态,直到被唤醒或超时抛出异常
        }
        writing = true; // 标记有线程正在写入数据,阻止其他线程进行读取或写入操作
        // 写入数据
        notifyAll(); // 唤醒所有等待的线程,包括等待读取和等待写入的线程
    }
    public synchronized void read() throws InterruptedException {
        while (writing) {
            // 如果已经有线程正在写入数据,当前线程需要等待
            wait(); // 当前线程进入等待状态,直到被唤醒或超时抛出异常
        } if (readers == 0) {
            // 如果之前没有读线程在读取数据,则标记有读线程正在读取数据
             reading = true;
        }
        readers++; // 读线程数量加1
        notifyAll(); // 唤醒所有等待的线程,包括等待读取和等待写入的线程
        // 读取数据
        readers--; // 读线程数量减1
        if (readers == 0) {
            // 如果所有读线程都已完成数据读取,则标记没有读线程正在读取数据
            reading = false;
        }
    }
}
在上述示例代码中,使用 volatile 关键字来确保 writing 和 readers 变量的可见性和有序性。在 write() 方法中,通过 while 循环和 wait() 方法来实现等待写入和唤醒等待写入的线程。在 read() 方法中,同样通过 while 循环和 wait() 方法来实现等待读取和唤醒等待读取的线程。

通过 notifyAll() 方法来唤醒所有等待的线程,并使用 reading 变量来标记是否有读线程正在读取数据。在读取完数据后,通过 readers-- 和检查 readers 是否为 0 来更新读线程数量和标记没有读线程正在读取数据。

相关文章