2、原子性
原子性指的是什么意思?
不可分割的,完整的,也即某个线程则横在做某个具体业务时,中间不可以被加塞或者分割。需要整体成功,或者同时失败。
MyData myData = new MyData(); for (int i = 0; i < 20; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { myData.addPlusPlus(); // 方法内执行 this.number++; } }, "T" + i).start(); } while (Thread.activeCount() > 2) { Thread.yield(); } System.out.println(myData.number);
this.number++;
是非线程安全的的操作所以结果不一定是 20000, 如果要保证原子性可以增加 synchronized
关键字进行同步操作。或者使用 JUC 提供的 AtomicInteger
线程安全类。
为什么数值少于 20000 ,我们通过 javap -c
命令来看看
为了方便查看字节码。我修改了一下 ++ 程序
public class OnePlus { volatile int number = 0; public void addPlusPlus() { this.number++; } }
字节码指令分析和查看
如何解决原子性问题?不用 sync
public class OnePlus { AtomicInteger number = new AtomicInteger(0); public void addPlusPlus() { number.getAndIncrement(); } }
3、有序性和指令重排序
在计算机执行程序时,为了提高性能,编译器和处理器的常常会对指令做重排, 一般分为三种情况。
源代码 ==> 编译器优化的重排 ==>指令并行的重排 ==> 内存系统的重排 ==> 最终执行的指令。
但线程的环境里面取保程序最终执行结果和代码顺序执行的结果一致。
处理器在进行重排序时必须要考虑之前的数据依赖性
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是否无法确定的,结果无法预测。
重排序1
public class MySort { int x = 11; //语句 1 int y = 12; //语句 2 x = x + 5; //语句 3 y = x * x; //语句 4 }
可能的执行顺序:
1234
2134
1324
问题:请问第4可以重新排序后称为第一条吗?不能因为数据依赖性
重排序2
int a, b, y = 0;
线程1 | 线程2 |
x = a | y = b |
b = 1 | a = 2 |
x =0 y = 0 |
如果编译器对这段代码执行重排优化后,可能出现一下情况
线程1 | 线程2 |
b = 1 | a = 2 |
x = a | y = b; |
x = 2 y =1 |
指令重排3
public class MyReSortSeqDemo { int a = 0; boolean flag = false; public void method1() { a = 1; //语句1 flag = true; //语句2 } public void method2() { if (flag) { a = a + 5; //语句3 System.out.println("*** retValue " + a); } } }