线程安全性问题可以总结为:可见性原子性有序性这几个问题,接下来我们通过代码来复现可见性、原子性的问题,后面章节我会从原理层面去分析这些问题。

可见性

  1. public class VisibleDemo {
  2. private static boolean stop = false;
  3. public static void main(String[] args) {
  4. Thread thread = new Thread(new Runner());
  5. thread.start();
  6. try {
  7. TimeUnit.SECONDS.sleep(1);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. stop = true;
  12. }
  13. private static class Runner implements Runnable {
  14. private int i;
  15. @Override
  16. public void run() {
  17. while (!stop) {
  18. i++;
  19. }
  20. System.out.println(i);
  21. }
  22. }
  23. }

在 main 方法中把 stop 变量改为 true,理论上线程应该终止,但是一般情况下,子线程一直在运行没有终止。说明在 main 线程中改变 stop 的值没有被子线程获取到,这就是多线程环境下的可见性问题。

原子性

  1. public class AtomicDemo {
  2. private static int count;
  3. public static void main(String[] args) {
  4. for (int i = 0; i < 1000; i++) {
  5. new Thread(new Runner()).start();
  6. }
  7. try {
  8. Thread.sleep(4000);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. System.out.println("运行结果:" + count);
  13. }
  14. private static class Runner implements Runnable {
  15. @Override
  16. public void run() {
  17. try {
  18. Thread.sleep(1);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. count++;
  23. }
  24. }
  25. }

在 main 方法中,循环创建1000个线程执行 count++,理论上 count 最终的运行结果应该是1000,但是我们发现运行结果会出现小于等于1000的情况,这就是多线程环境下的原子性问题。

  1. 运行结果:973

我们可能存在如下疑问:

  • 在属性 count 前面加上 volatile 关键字能否解决原子性问题?
  • 为什么 count++; 会存在原子性问题呢,它不是只有一条命令么?

注意:count++; 不是原子操作,通过 javap **命令反汇编查看如代码的指令信息。**

  1. public class Test {
  2. private int count = 0;
  3. public void incr() {
  4. count++;
  5. }
  6. public static void main(String[] args) {
  7. new Test().incr();
  8. }
  9. }
  1. public class test3.Test {
  2. public test3.Test();
  3. Code:
  4. 0: aload_0
  5. 1: invokespecial #10 // Method java/lang/Object."<init>":()V
  6. 4: aload_0
  7. 5: iconst_0
  8. 6: putfield #12 // Field count:I
  9. 9: return
  10. public void incr();
  11. Code:
  12. 0: aload_0
  13. 1: dup
  14. 2: getfield #12 // Field count:I
  15. 5: iconst_1
  16. 6: iadd
  17. 7: putfield #12 // Field count:I
  18. 10: return
  19. public static void main(java.lang.String[]);
  20. Code:
  21. 0: new #1 // class test3/Test
  22. 3: dup
  23. 4: invokespecial #21 // Method "<init>":()V
  24. 7: invokevirtual #22 // Method incr:()V
  25. 10: return
  26. }

通过上面的指令信息发现,count++ 其实是由三条指令组合的复合操作。

  1. getfield
  2. iadd
  3. putfield

volatile 关键字不能解决复合操作的原子性问题,所以在属性 count 前面增加 volatile 关键字不能解决原子性问题。

有序性

指的是在程序运行过程中,代码的执行顺序和我们编写代码的顺序可能是不一致的,因为在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/ygfq5z 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。