Android @Volatile

发布于:2024-11-02 ⋅ 阅读:(112) ⋅ 点赞:(0)

1. 概念介绍

  • @Volatile是Java中的一个关键字,用于修饰变量。它主要用于解决多线程环境下变量的可见性问题。当一个变量被声明为volatile时,这意味着该变量的值在多个线程之间是“可见”的。

2. 可见性问题背景

  • 在多线程环境中,如果没有适当的同步机制,一个线程对共享变量的修改可能不会及时被其他线程看到。这是因为每个线程可能会在自己的工作内存(缓存)中保留变量的副本。例如,线程A修改了一个共享变量的值,但线程B可能仍然在使用它自己缓存中的旧值,直到某个不确定的时间点才会更新这个值。

3. @Volatile的工作原理

  • 当一个变量被标记为volatile时,每次读取这个变量时,线程会直接从主内存中读取,而不是从自己的工作内存中读取。同样,每次修改这个变量时,线程会立即将新值刷新到主内存中。这样就保证了其他线程能够及时看到变量的最新值。
  • 例如,假设有一个共享的volatile变量count
    public class VolatileExample {
        @Volatile
        private static int count = 0;
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    count++;
                }
            });
            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    count++;
                }
            });
            thread1.start();
            thread2.start();
            try {
                thread1.join();
                thread2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Count: " + count);
        }
    }
    
  • 在这个例子中,count变量被声明为volatile。两个线程thread1thread2都对count进行自增操作。由于countvolatile的,每个线程在修改count后会立即将新值刷新到主内存,并且在读取count时会从主内存读取最新的值。不过需要注意的是,volatile并不能保证原子性,在这个例子中,count++操作实际上包含了读取、修改和写入三个步骤,可能会出现并发问题(最终的count值可能小于2000),但它确实保证了可见性。

4. 与其他同步机制的比较

  • synchronized的比较
    • synchronized关键字不仅保证了变量的可见性,还保证了操作的原子性。它通过对代码块或者方法进行加锁,使得同一时间只有一个线程能够访问被锁定的部分。而volatile主要关注的是变量的可见性,对于复杂的复合操作(如count++),它不能保证原子性。
    • 例如,使用synchronized来保证count++操作的原子性可以这样做:
      public class SynchronizedExample {
          private static int count = 0;
          public static void main(String[] args) {
              Thread thread1 = new Thread(() -> {
                  synchronized (SynchronizedExample.class) {
                      for (int i = 0; i < 1000; i++) {
                          count++;
                      }
                  }
              });
              Thread thread2 = new Thread(() -> {
                  synchronized (SynchronizedExample.class) {
                      for (int i = 0; i < 1000; i++) {
                          count++;
                      }
                  }
              });
              thread1.start();
              thread2.start();
              try {
                  thread1.join();
                  thread2.join();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println("Count: " + count);
          }
      }
      
  • java.util.concurrent.atomic包的比较
    • java.util.concurrent.atomic包提供了一系列原子类,如AtomicIntegerAtomicLong等。这些原子类在保证变量可见性的同时,还能保证操作的原子性。例如,AtomicIntegerincrementAndGet操作是原子的。
    public class AtomicExample {
        private static AtomicInteger count = new AtomicInteger(0);
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    count.incrementAndGet();
                }
            });
            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    count.incrementAndGet();
                }
            });
            thread1.start();
            thread2.start();
            try {
                thread1.join();
                thread2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Count: " + count.get());
        }
    }
    
    • volatile相比,原子类提供了更高级的并发控制功能,适用于需要原子操作的场景。而volatile更像是一种轻量级的可见性保证机制,用于简单的共享变量场景。

5. 防止指令重排序(在一定程度上)

  • 指令重排序介绍:在现代处理器和编译器优化中,为了提高程序执行效率,可能会对指令进行重新排序。在单线程环境下,这种重排序不会影响程序的正确性。但在多线程环境下,可能会导致问题。volatile关键字可以在一定程度上防止指令重排序。
  • 示例场景:考虑一个简单的单例模式实现(双重检查锁定模式),在这个模式中需要防止指令重排序。
    public class Singleton {
        private volatile static Singleton instance;
        private Singleton() {}
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        // 创建单例实例
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    
  • 在这个例子中,instance被声明为volatile。当创建instance时(instance = new Singleton();),这个操作实际上包含了三个步骤:分配内存空间、初始化对象、将对象引用赋值给instance变量。如果没有volatile,处理器或编译器可能会对这三个步骤进行重排序。例如,先将对象引用赋值给instance变量,然后再初始化对象。在其他线程访问instance时,可能会获取到一个还没有完全初始化的对象,导致程序出现错误。使用volatile可以防止这种指令重排序,保证了对象在被引用之前已经完成初始化。

参考地址

AI 豆包


网站公告

今日签到

点亮在社区的每一天
去签到