线程安全要考虑三个方面:可见性,有序性,原子性
起因和解决
原子性
起因:多线程下,不同线程的指令发生了交错导致的共享变量的读写混乱
解决:用悲观锁或乐观锁解决,volatile 并不能解决原子性
可见性
起因:由于编译器优化、或缓存优化、或 CPU 指令重排序优化导致的对共享变量所做的修改另外的线程看不到
解决:用 volatile 修饰共享变量,能够防止编译器等优化发生,让一个线程对共享变量的修改对另一个线程可见
有序性
起因:由于编译器优化、或缓存优化、或 CPU 指令重排序优化导致指令的实际执行顺序与编写顺序不一致
解决:用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果
注意:
volatile 变量写加的屏障是阻止上方其它写操作越过屏障排到 volatile 变量写之下
volatile 变量读加的屏障是阻止下方其它读操作越过屏障排到 volatile 变量读之上
volatile 读写加入的屏障只能防止同一线程内的指令重排
结果是10(两个线程一个加一个减)
看看java虚拟机底层这一行代码是什么(balance -= 5)
在反编译下打开控制台
然后输入
getstatic是读取共享变量的值
得到了5
然后进行减
put上传上去共享变量的值
如果这样发生交错就变成15
我们可以自己debug一遍
当我们线程1没有给balance赋值时,直接运行线程2,会导致线程1并没有赋值,线程2跑完,最后运行线程1会导致10-5=5的情况
一个线程对共享变量做了修改,另一个线程(main-foo)没有读到他
我们多加几个线程,会发现只有foo方法没有读到
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 修饰共享变量,能够防止编译器等优化发生,让一个线程对共享变量的修改对另一个线程可见
会出现的结果概率
1,0出现的机率非常低(在多线程下)
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
出现了指令重排序
写变量是为了防止其他写操作越过屏障下来,读变量是为了防止其他读操作越过屏障上来
volatile加在y上出现一个屏障
volatile加在x上出现一个屏障
会导致y写操作可以上去,而y读操作可以下去
导入依赖
ch.qos.logback logback-classic 1.2.3 org.openjdk.jcstress jcstress-core 0.14
org.apache.maven.plugins maven-shade-plugin 2.2 main package shade jcstress org.openjdk.jcstress.Main META-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;}}
}