【Java】之 volatile



一、简介


happen before 是时钟顺序的先后,并不能保证线程交互的可见性。

即:存在某线程对副本操作,但对于其他线程都是不可见的。

可见性:指某线程修改共享变量的指针对其他线程来说都是可见的,它反映的是指令执行的实时透明度

每个线程都有独占的内存区域,如操作栈、本地变量表等
线程本地内存保存了引用变量在堆内存中的副本,线程对变量的所有操作都在本地内存区域中进行,执行结束后再同步到堆内存中。

线程执行或线程切换都是纳秒级的。

volatile: 挥发,不稳定。

当使用 volatile修饰变量时,意味着任何对此变量的操作都会在内存中进行,不会产生副本,以保证共享变量的可见性,局部阻止了指令重排的发生。

volatile解决的是多线程共享变量的可见性问题,类似于 synchronized,但不具备sychronized的互斥性



二、案例



(1)双检查锁(Double-checked Locking)

如下代码:

class LazyInitDemo {
    
    private static TransactionService service = null;
    
    public static TransactionService getTransactionService() {
        
        if (service == null) {
            // 或者 TransactionService.class
            synchronized (LazyInitDemo.class) {
                if (service == null) {
                    service == new TransactionService();
                }
            }
        }
        
        return service;
    }
}

调用getTransactionService()可能会得到初始化未完成的对象

原因:与 JVM 的编译优化有关
线程1 执行new TransactionService(), 构造方法还未被调用,编译器仅仅为该对象分配了内存空间并设为默认值
线程2 调用getTransactionService(),由于 service != null,但是此时service对象还没有被赋予真正有效的值,从而无法取到正确的 service 单例对象。

解决方法:加上volatile
private static volatile TransactionService service = null;


(2)volatile 不具互斥性

public class VolatileNotAtomic {
    private static volatile long count = 0L;
    private static final int NUMBER = 10000;

    public static void main(String[] args) {
        
        Thread subtractThread = new SubtractThread();
        subtractThread.start();
        
        for (int i = 0; i < NUMBER; ++i) {
            count ++;
        }
        
        // 等待减法线程结束
        while (subtractThread.isAlive()){}

        System.out.println("count 最后的值: " + count);
    }
    
    private static class SubtractThread extends Thread {
        
        @Override
        public void run() {
            for (int i = 0; i < NUMBER; ++i) {
                count --;
            }
        }
    }
}

结果基本不为0, 因为--++并不是原子操作。

字节码如下:

// 1. 读取 count 并压入操作栈顶
GETSTATIC count: I

// 2. 常量 1 压入操作栈顶
ICONST_1

// 3. 取出最顶部两个元素进行相加
IADD

// 4. 将刚才得到的和赋值给 count
PUTSTATIC count: I

解决方案:针对--++,可以加锁,如sychronized



三、实际场景


  1. 能实现 count++ 原子操作的其他类有:AtomicLongLongAdder

JDK8 推荐使用 LongAdder类,它比 AtomicLong性能更好,有效地减少了乐观锁的重试次数

  1. 一读多写的并发场景,使用volatile修饰变量则非常合适

最典型的应用:CopyOnWriteArrayList

发布了404 篇原创文章 · 获赞 270 · 访问量 42万+

猜你喜欢

转载自blog.csdn.net/fanfan4569/article/details/101197939