JMM详解:关键字

12/31/2023 Java

目录


参考:


# JMM的关键字

# volatile 关键字

很多并发编程都使用了 volatile 关键字,主要的作用包括两点:

  • 保证线程间变量的可见性
  • 禁止 CPU 进行指令重排序

可见性

volatile修饰的变量,当一个线程改变了该变量的值,其他线程是立即可见的。普通变量则需要重新读取才能获得最新值。

示例:

public class VolatileSafe {

    private volatile boolean shutDown;
    
    public void close() {
        shutDown = true;
    }

    public void doWork(){
        while (!shutDown){
            System.out.println("safe...");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

在这个例子中,对变量shutDown的修改是原子性的,它是用关键字volatile修饰的,修改对其他线程立即可见,保证了线程安全。

volatile 变量的修改为什么可以做到立即可见?

  • 当写一个 volatile 变量时,JMM 会把对该线程对应的工作内存中的共享变量值刷新到主内存中
  • 当读一个 volatile 变量时,JMM 会把该线程对应的工作内存置为无效,使得线程只能从主内存中重新读取共享变量
667853-20220929155030855-1108120297

volatile 一定能保证线程安全吗?

volatile 不能一定能保证线程安全。

示例:

public class VolatileTest extends Thread {

    private static volatile int count = 0;

    public static void main(String[] args) throws Exception {
        Vector<Thread> threads = new Vector<>();
        for (int i = 0; i < 100; i++) {
            VolatileTest thread = new VolatileTest();
            threads.add(thread);
            thread.start();
        }
        //等待子线程全部完成
        for (Thread thread : threads) {
            thread.join();
        }
        //输出结果,正确结果应该是1000,实际却是984
        System.out.println(count);//984
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                //休眠500毫秒
                Thread.sleep(500);
            } catch (Exception e) {
                e.printStackTrace();
            }
            count++;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

可见性不能保证操作的原子性,前面说过了 count++ 不是原子性操作,会当做三步:

  1. 先读取 count 的值,
  2. 然后 +1,
  3. 最后赋值回去 count 变量。

需要保证线程安全的话,需要使用 synchronized关键字 或者 lock 锁,给 count++ 这段代码上锁:

private static synchronized void add() {
    count++;
}
1
2
3
上次更新时间: 9/25/2024, 9:17:45 AM