Java中Volatile变量的使用

技术文档网 2021-04-25
  1. Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性

    1. volatile 变量不能用作线程安全计数器

    2. 线程能够自动发现 volatile 变量的最新值

  2. Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束

  3. 单独使用 volatile 还不足以实现计数器、互斥锁或任何具有与多个变量相关的不变式(Invariants)的类

  4. 使用条件

    1. 对变量的写操作不依赖于当前值。

    2. 该变量没有包含在具有其他变量的不变式中。

即只有在状态真正独立于程序内其他内容时才能使用 volatile 然而,使用 volatile 的代码往往比使用锁的代码更加容易出错

  1. 性能 使用 volatile 变量的主要原因是其简易性:在某些情形下,使用 volatile 变量要比使用相应的锁简单得多。 使用 volatile 变量次要原因是其性能:某些情况下,volatile 变量同步机制的性能要优于锁。 volatile 变量不会像锁那样造成线程阻塞,因此也很少造成可伸缩性问题。 在某些情况下,如果读操作远远大于写操作,volatile 变量还可以提供优于锁的性能优势

  2. 使用模式

    1. 状态标志(boolean): 通常只有一种状态转换
      volatile boolean shutdownRequested;
      ...
      ...

      public void shutdown() { shutdownRequested = true; }

      public void doWork() {
         while (!shutdownRequested) {
            // do stuff
         }
      }
  1. 一次性安全发布 (one-time safe publication) 被发布的对象必须是线程安全的,或者是有效的不可变对象(有效不可变意味着对象的状态在发布之后永远不会被修改)

    public class BackgroundFloobleLoader {
        public volatile Flooble theFlooble;
    
        public void initInBackground() {
            // do lots of stuff
            theFlooble = new Flooble();  // this is the only write to theFlooble
        }
    }
    
    public class SomeOtherClass {
        public void doWork() {
            while (true) {
                // do some stuff...
                // use the Flooble, but only if it is ready
                if (floobleLoader.theFlooble != null)
                    doSomething(floobleLoader.theFlooble);
            }
        }
    }
    
  2. 独立观察(independent observation) 定期 “发布” 观察结果供程序内部使用

    public class UserManager {
        public volatile String lastUser;
    
        public boolean authenticate(String user, String password) {
            boolean valid = passwordIsValid(user, password);
            if (valid) {
                User u = new User();
                activeUsers.add(u);
                lastUser = user;
            }
            return valid;
        }
    }
    
  3. “volatile bean” 模式 a. 在 volatile bean 模式中,JavaBean 被用作一组具有 getter 和/或 setter 方法 的独立属性的容器。 volatile bean 模式的基本原理是:很多框架为易变数据的持有者(例如 HttpSession)提供了容器, 但是放入这些容器中的对象必须是线程安全的。

    b. 在 volatile bean 模式中,JavaBean 的所有数据成员都是 volatile 类型的, 并且 getter 和 setter 方法必须非常普通 —— 除了获取或设置相应的属性外,不能包含任何逻辑 (这将禁止具有数组值的属性)

    c. 对于任何 volatile 变量,不变式或约束都不能包含 JavaBean 属性

    ```` @ThreadSafe public class Person {

    private volatile String firstName;
    private volatile String lastName;
    private volatile int age;
    
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
    
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
    

    } ```

  4. 开销较低的读-写锁策略 如果读操作远远超过写操作,您可以结合使用内部锁和 volatile 变量来减少公共代码路径的开销 锁一次只允许一个线程访问值,volatile 允许多个线程执行读操作

    @ThreadSafe
    public class CheesyCounter {
        // Employs the cheap read-write lock trick
        // All mutative operations MUST be done with the 'this' lock held
        @GuardedBy("this") private volatile int value;
    
        public int getValue() { return value; }
    
        public synchronized int increment() {
            return value++;
        }
    }
    

相关文章

  1. 基于-SLF4J-MDC-机制的日志链路追踪配置属性

    ums: # ================ 基于 SLF4J MDC 机制的日志链路追踪配置属性 ================ mdc: # 是否支持基于 SLF4J MDC

  2. ajax-跨域访问

    ajax 跨域访问 <!DOCTYPE html> <html xmlns:th="http://www.w3.org/1999/xhtml"> <head>

  3. 给第三方登录时用的数据库表-user_connection-与-auth_token-添加-redis-cache

    spring: # 设置缓存为 Redis cache: type: redis # redis redis: host: 192.168.88.88 port

  4. Java动态代理

    Jdk动态代理 通过InvocationHandler和Proxy针对实现了接口的类进行动态代理,即必须有相应的接口 应用 public class TestProxy { public

  5. Java读取classpath中的文件

    public void init() { try { //URL url = Thread.currentThread().getContextClassLo

随机推荐

  1. 基于-SLF4J-MDC-机制的日志链路追踪配置属性

    ums: # ================ 基于 SLF4J MDC 机制的日志链路追踪配置属性 ================ mdc: # 是否支持基于 SLF4J MDC

  2. ajax-跨域访问

    ajax 跨域访问 <!DOCTYPE html> <html xmlns:th="http://www.w3.org/1999/xhtml"> <head>

  3. 给第三方登录时用的数据库表-user_connection-与-auth_token-添加-redis-cache

    spring: # 设置缓存为 Redis cache: type: redis # redis redis: host: 192.168.88.88 port

  4. Java动态代理

    Jdk动态代理 通过InvocationHandler和Proxy针对实现了接口的类进行动态代理,即必须有相应的接口 应用 public class TestProxy { public

  5. Java读取classpath中的文件

    public void init() { try { //URL url = Thread.currentThread().getContextClassLo