-
原子性
-
概念:一个操作不可被中断,要么执行完毕,要么不执行。
-
对基本数据类型变量的读取和赋值是原子性操作。
x = 3; // 语句1 y = x; // 语句2 x++; // 语句3
- 语句1,是原子操作
- 语句2,不是原子操作,包含两个操作,先读取x的值,再将x的值写入工作内存。
- 语句3,不是原子操作,包含三个操作,读取x的值、对x值进行加1、向工作内存写入新值。
只有简单的读取和赋值(将数字赋值给某个变量)才是原子操作。
-
如何保证原子性?
-
通过synchronized关键字来保证原子性
虚拟机提供了字节码指令
monitorenter
和monitorexit
来隐式的使用lock和unlock操作,这个两个字节码反映到Java代码中就是同步块——synchronized关键字。所以具备原子性。 -
通过Lock接口保证原子性。
可重入锁ReentantLock是Lock接口的实现类。
-
通过Atomic类型保证原子性
-
-
-
可见性
-
概念:是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程的修改结果,另一个线程马上可以知道。
-
Java内存模型的可见性是通过变量修改后将新值同步回主内存,在变量读写前先从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现的。
-
volatile变量和普通变量的区别
- volatile变量会保证修改的值立即被更新到主内存,所以对其他线程是可见的。当有其他线程需要读取该值时,其他线程会去主内存中读取新值。(简而言之:写操作,强制刷新内存;读操作,直接读取内存)
- 普通变量被修改之后,不会立即被写入主内存,何时被写入主内存也是不确定的。当其他线程去读取该值时,此时主内存中可能还是原来的旧值。
-
如何保证可见性?
-
通过volatile关键字来保证可见性
-
通过synchronized关键字定义同步代码块或者同步方法来保证可见性
同步快的可见性是由"对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行sotre、write操作)"这条规则获得的。
-
通过final关键字来禁止重排序,保证可见性
被final修饰的字段在构造器中一旦被初始化完成,并且构造器中没有把"this"的引用传递出去(引用逃逸,其他线程中可能访问到初始化一半的对象),那么其他线程就能看见final字段的值。
-
通过Lock接口保证可见性
-
通过Atomic类型保证可见性
-
-
-
有序性
-
概念:即程序执行顺序按照代码的先后顺序执行。
-
重排序:Java内存模型中允许编译器和处理器对指令进行重排序,虽然重排序过程不会影响到单线程执行的正确性,但是会影响到多线程并发执行的正确性。
-
编译器重排
int x = 1; int y = 2; // 重排序优化前 int a1 = x * 1; int b1 = y * 1; int a2 = x * 2; int b2 = y * 2; // 重排序优化后 int a1 = x * 1; int a2 = x * 2; int b1 = y * 1; int b2 = y * 2;
CPU只读取一次x和y的值,不需要反复读取寄存器来交替x和y的值。
-
处理器重排
// 初始化 int x = 0; int y = 0; int a = 0; int y = 0; // 处理器A执行 a = 1; // A1 x = b; // A2 // 处理器B执行 b = 2; // B1 y = a; // B2
处理器A1写
a=1
是先写到处理A的缓存区,此时内存中a=0
。如果这时处理器B从内存中读取a,读到的值将是0。写缓存区没有及时刷新,使得处理器执行的读写操作域内存上的顺序不一致。
-
-
如何保证有序性?
-
通过volatile关键字禁止指令重排序来保证有序性
-
通过synchronized关键字定义同步代码块或者方法来保证有序性
一个变量在同一个时刻只允许一条线程对其进行lock操作。
-
通过Lock接口来保证有序
-
-