线程安全性问题可以总结为:可见性、原子性、有序性这几个问题,接下来我们通过代码来复现可见性、原子性的问题,后面章节我会从原理层面去分析这些问题。
可见性
public class VisibleDemo {
private static boolean stop = false;
public static void main(String[] args) {
Thread thread = new Thread(new Runner());
thread.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
stop = true;
}
private static class Runner implements Runnable {
private int i;
@Override
public void run() {
while (!stop) {
i++;
}
System.out.println(i);
}
}
}
在 main 方法中把 stop 变量改为 true,理论上线程应该终止,但是一般情况下,子线程一直在运行没有终止。说明在 main 线程中改变 stop 的值没有被子线程获取到,这就是多线程环境下的可见性问题。
原子性
public class AtomicDemo {
private static int count;
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(new Runner()).start();
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("运行结果:" + count);
}
private static class Runner implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
}
}
在 main 方法中,循环创建1000个线程执行 count++,理论上 count 最终的运行结果应该是1000,但是我们发现运行结果会出现小于等于1000的情况,这就是多线程环境下的原子性问题。
运行结果:973
我们可能存在如下疑问:
- 在属性 count 前面加上 volatile 关键字能否解决原子性问题?
- 为什么 count++; 会存在原子性问题呢,它不是只有一条命令么?
注意:count++; 不是原子操作,通过 javap **命令反汇编查看如下代码的指令信息。**
public class Test {
private int count = 0;
public void incr() {
count++;
}
public static void main(String[] args) {
new Test().incr();
}
}
public class test3.Test {
public test3.Test();
Code:
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #12 // Field count:I
9: return
public void incr();
Code:
0: aload_0
1: dup
2: getfield #12 // Field count:I
5: iconst_1
6: iadd
7: putfield #12 // Field count:I
10: return
public static void main(java.lang.String[]);
Code:
0: new #1 // class test3/Test
3: dup
4: invokespecial #21 // Method "<init>":()V
7: invokevirtual #22 // Method incr:()V
10: return
}
通过上面的指令信息发现,count++ 其实是由三条指令组合的复合操作。
getfield
iadd
putfield
volatile 关键字不能解决复合操作的原子性问题,所以在属性 count 前面增加 volatile 关键字不能解决原子性问题。
有序性
指的是在程序运行过程中,代码的执行顺序和我们编写代码的顺序可能是不一致的,因为在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。
作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/ygfq5z 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。