volatile能否保证线程安全
迪丽瓦拉
2025-05-28 02:04:26
0

目录

  • 一、volatile能否保证线程安全
  • 二、验证结论(代码在第三点)
    • 1.原子性
    • 2.可见性
      • 2.1 演示
      • 2.2 原因与解决
    • 3.有序性
      • 3.1 分析
      • 3.2 压测(记得打包)
      • 3.3 volatile位置不同影响分析
  • 三、代码篇

一、volatile能否保证线程安全

线程安全要考虑三个方面:可见性,有序性,原子性

  • 可见性指:一个线程对共享变量修改,另一个线程能看到最新的结果
  • 有序性指:一个线程内代码按编写顺序执行
  • 原子性指:一个线程内多行代码以一个整体运行,期间不能有其他线程的代码插队

起因和解决

原子性
起因:多线程下,不同线程的指令发生了交错导致的共享变量的读写混乱
解决:用悲观锁或乐观锁解决,volatile 并不能解决原子性

可见性
起因:由于编译器优化、或缓存优化、或 CPU 指令重排序优化导致的对共享变量所做的修改另外的线程看不到
解决:用 volatile 修饰共享变量,能够防止编译器等优化发生,让一个线程对共享变量的修改对另一个线程可见

有序性
起因:由于编译器优化、或缓存优化、或 CPU 指令重排序优化导致指令的实际执行顺序与编写顺序不一致
解决:用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果
注意:
volatile 变量写加的屏障是阻止上方其它写操作越过屏障排到 volatile 变量写之下
volatile 变量读加的屏障是阻止下方其它读操作越过屏障排到 volatile 变量读之上
volatile 读写加入的屏障只能防止同一线程内的指令重排

二、验证结论(代码在第三点)

1.原子性

结果是10(两个线程一个加一个减)
在这里插入图片描述
看看java虚拟机底层这一行代码是什么(balance -= 5)
在反编译下打开控制台
在这里插入图片描述
然后输入
在这里插入图片描述
在这里插入图片描述

getstatic是读取共享变量的值
得到了5
然后进行减
put上传上去共享变量的值

如果这样发生交错就变成15
在这里插入图片描述
我们可以自己debug一遍
当我们线程1没有给balance赋值时,直接运行线程2,会导致线程1并没有赋值,线程2跑完,最后运行线程1会导致10-5=5的情况
在这里插入图片描述

2.可见性

2.1 演示

一个线程对共享变量做了修改,另一个线程(main-foo)没有读到他
在这里插入图片描述
我们多加几个线程,会发现只有foo方法没有读到
在这里插入图片描述

2.2 原因与解决

JIT即时编译是在运行时动态编译字节码为机器码,相当于是在运行时对已编译代码的优化重新进行编译。

JIT代表即时(Just-in-Time),这意味着代码将在需要时(而不是在运行时之前)进行编译。
JVM维护着一个函数执行多少次的计数。 如果此计数超过预定义的限制,那么JIT会将代码编译为机器语言,该语言可以直接由处理器执行(不同于正常情况下,javac将该代码编译为字节码,然后再用java-解释器逐行解释此字节码,将其转换为机器代码并执行)。
同样,下次计算此函数时,将再次执行相同的编译代码,这与常规解释不同,在常规解释中,逐行代码被再次解释。 这使执行速度更快。

如果此计数超过预定义的限制,那么JIT会将代码编译为机器语言把while(!stop)变成while(!false)
在这里插入图片描述
解决方案一
添加Add VM options
在这里插入图片描述
-Xint(禁用JIT)
在这里插入图片描述
只要循环没有那么多次,那么JIT就不会认为是热点代码,就不会使用JIT
在这里插入图片描述
总结:禁用JIT是不可行的,因为JIT为我们提供了效率,没必要为了一个变量禁用JIT

解决方案二
用 volatile 修饰共享变量,能够防止编译器等优化发生,让一个线程对共享变量的修改对另一个线程可见
在这里插入图片描述
在这里插入图片描述

3.有序性

3.1 分析

在这里插入图片描述
会出现的结果概率
1,0出现的机率非常低(在多线程下)
在这里插入图片描述

3.2 压测(记得打包)

Case1进行压测
控制台输入

java -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -jar jcstress.jar -t com.donglin.utils.Reordering.Case1

在这里插入图片描述
出现了指令重排序(1,0)
在这里插入图片描述
Case2进行压测
把volatile添加在y变量上

java -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -jar jcstress.jar -t com.donglin.utils.Reordering.Case2

在这里插入图片描述
没有进行指令重排序(成功)
Case3进行压测
把volatile添加在x变量上

java -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -jar jcstress.jar -t com.donglin.utils.Reordering.Case3

在这里插入图片描述
出现了指令重排序
在这里插入图片描述

3.3 volatile位置不同影响分析

写变量是为了防止其他写操作越过屏障下来,读变量是为了防止其他读操作越过屏障上来
volatile加在y上出现一个屏障
在这里插入图片描述
volatile加在x上出现一个屏障
在这里插入图片描述
会导致y写操作可以上去,而y读操作可以下去
在这里插入图片描述

三、代码篇

导入依赖

       ch.qos.logbacklogback-classic1.2.3org.openjdk.jcstressjcstress-core0.14
org.apache.maven.pluginsmaven-shade-plugin2.2mainpackageshadejcstressorg.openjdk.jcstress.MainMETA-INF/TestList


工具类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.HashMap;
import java.util.Map;public class LoggerUtils {public static final Logger logger1 = LoggerFactory.getLogger("A");public static final Logger logger2 = LoggerFactory.getLogger("B");public static final Logger logger3 = LoggerFactory.getLogger("C");public static final Logger logger4 = LoggerFactory.getLogger("D");public static final Logger logger5 = LoggerFactory.getLogger("E");public static final Logger logger6 = LoggerFactory.getLogger("F");public static final Logger main = LoggerFactory.getLogger("G");private static final Map map = new HashMap<>();static {map.put("1", logger1);map.put("2", logger2);map.put("3", logger3);map.put("4", logger4);map.put("5", logger5);map.put("6", logger6);map.put("0", logger6);map.put("main", main);}public static Logger get() {return get(null);}public static Logger get(String prefix) {String name = Thread.currentThread().getName();if(!name.equals("main")) {int length = name.length();name = name.substring(length - 1);}return map.getOrDefault(name, logger1);}public static void main(String[] args) {logger1.debug("hello");logger2.debug("hello");logger3.debug("hello");logger4.debug("hello");logger5.warn("hello");logger6.info("hello");main.error("hello");}
}

证明原子性代码

import java.util.concurrent.CountDownLatch;// 原子性例子/**t1 100: getstatict20: getstatic 103: iconst_54: isub5: putstatic53: iconst_54: iadd5: putstatic15*/
public class AddAndSubtract {static volatile int balance = 10;public static void subtract() {int b = balance;b -= 5;balance = b;}public static void add() {int b = balance;b += 5;balance = b;}public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(2);new Thread(() -> {subtract();latch.countDown();}).start();new Thread(() -> {add();latch.countDown();}).start();latch.await();LoggerUtils.get().debug("{}", balance);}
}

证明可见性代码

import static com.donglin.utils.LoggerUtils.get;
// 可见性例子
// -Xint
public class ForeverLoop {static boolean stop = false;public static void main(String[] args) {new Thread(() -> {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}stop = true;get().debug("modify stop to true...");}).start();new Thread(() -> {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}get().debug("{}", stop);}).start();new Thread(() -> {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}get().debug("{}", stop);}).start();foo();}static void foo() {int i = 0;while (!stop) {i++;}get().debug("stopped... c:{}", i);}
}

证明有序性代码

import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.II_Result;// 有序性例子
// java -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -jar jcstress.jar -t day02.threadsafe.Reordering.Case1
// java -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -jar jcstress.jar -t day02.threadsafe.Reordering.Case2
// java -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -jar jcstress.jar -t day02.threadsafe.Reordering.Case3
public class Reordering {@JCStressTest@Outcome(id = {"0, 0", "1, 1", "0, 1"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "INTERESTING")@Statepublic static class Case1 {int x;int y;@Actorpublic void actor1() {x = 1;y = 1;}@Actorpublic void actor2(II_Result r) {r.r1 = y;r.r2 = x;}}@JCStressTest@Outcome(id = {"0, 0", "1, 1", "0, 1"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")@Outcome(id = "1, 0", expect = Expect.FORBIDDEN, desc = "FORBIDDEN")@Statepublic static class Case2 {int x;volatile int y;@Actorpublic void actor1() {x = 1;y = 1;}@Actorpublic void actor2(II_Result r) {r.r1 = y;r.r2 = x;}}@JCStressTest@Outcome(id = {"0, 0", "1, 1", "0, 1"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "ACCEPTABLE_INTERESTING")@Statepublic static class Case3 {volatile int x;int y;@Actorpublic void actor1() {x = 1;y = 1;}@Actorpublic void actor2(II_Result r) {r.r1 = y;r.r2 = x;}}
}

相关内容