Java 并发编程 可见性、原子性、有序性

并发编程的三大核心:可见性、原子性、有序性java

关于硬件

计算机的CPU、内存、IO设备在不停的更新迭代,可是,在这些硬件之间,存在着一个核心矛盾,就是三者之间的存在巨大的速度差别。

为了合理利用CPU的高性能,平衡三者之间的差别,web

CPU 增长了缓存机制,来均衡与内存之间的速度差别。

操做系统增长了进程、线程,经过线程切换来分时复用CPU资源,均衡CPU与I/O设备的速度差别

编译程序优化指令的执行顺序,使得缓存可以更加合理的利用

可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其余线程可以当即看到修改的值。

在单核时代,全部的线程都是在一颗CPU上执行,CPU缓存与内存的数据一致性容易解决。由于全部的线程都是操做同一个CPU的缓存,一个线程对缓存的写,对另一个线程来讲必定是可见的。

java怎么巧妙运用default可见性 java的可见性_java 可见 原子

在多核时代,每颗CPU都有本身的缓存,这时CPU缓存与内存的数据的一致性就没那么容易解决了。当多个线程在不一样的CPU上执行时,此时操做的时不一样CPU上的缓存。

java怎么巧妙运用default可见性 java的可见性_有序性_02

看一个程序实例:编程

public class Test {
private long count = 0;
private void test() {
int id = 0;
while (id++ < 100000) {
count += 1;
}
}
public long getCount() {
return count;
}
public static long testThread() throws InterruptedException {
final Test test = new Test();
Thread thread1 = new Thread(() -> test.test());
Thread thread2 = new Thread(() -> test.test());
thread1.start();
thread2.start();
thread1.join();
thread2.join();
return test.getCount();
}
}

这段代码会出现一个奇怪的如今,当两个线程执行完以后,发现代码的结果并非200000缓存

原子性

咱们把一个或者多个操做在CPU执行的过程当中不被中断的特性成为原子性多线程

首先咱们先了解下线程切换

多线程并不必定是在多核处理器下才能完成的,在单核处理器上也能够支持多线程。CPU经过给每一个线程分配必定的时间片断,时间片很是短,一般是几十毫秒,CPU不停的切换线程执行任务从而达到多线程的效果。

java怎么巧妙运用default可见性 java的可见性_多线程_03

在一个时间段内,若是一个进程进行一个IO操做,例如读个文件,这个时候该进程能够把本身标记为“休眠状态”并出让CPU的使用权,待文件读进内存,操做系统会把这个休眠的进程唤醒,唤醒后的进程就从新得到CPU的使用权了。并发

count += 1

count +=1 的CPU 指令:svg

指令1:首先,须要把变量count从内存加载到CPU的寄存器

指令2:在寄存器中执行+1的操做

指令3:将结果写入CPU缓存

咱们把一个或者多个操做在CPU执行的过程当中不被终端的特性成为原子性。CPU可以保证的院子操做是CPU指令级别的,而不是高级语言的操做符,所以,咱们须要在编编写代码时保证操做的原子性。

有序性

有序性指的是程序按照代码的前后顺序执行性能

编译器为了优化性能,有时候会改变程序中语句的前后顺序优化

int a = 1; // 语句A
int b = 2; // 语句B
int c = a + b; /// 语句C

在上段代码中,正常的执行顺序应该是 语句A >> 语句B >> 语句C。可是有时 JVM为了提升总体的效率会进行CPU指令重排致使执行的顺序多是语句B >> 语句A >> 语句C。

重排在单线程中不会出现问题,可是在多线程中就会出现数据不一致的问题。spa

实例:双重检查建立单例对象

public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

上面的代码中,在获取实例getInstance()的方法中,咱们首先判断singleton是否为空,若是为空,则锁定Singleton.class,并再次检查singleton是否为空,若是为空则建立一个Singleton实例。

问题

singleton = new Singleton(); 此段代码的执行流程:

分配一块内存空间M

在内存M上初始化Singleton对象

将singleton对象指向分配的内存空间

实际上 CPU会对这三句指令进行指令重排,优化后的路径为

分配一块内存空间M。

将M的地址赋值给singleton变量。

最后在内存M上初始化Singleton对象。

优化后的指令执行顺序有可能致使某个线程拿到的单例对象尚未初始化,以致于使用报错。

如何解决

课后思考 单例模式的实现

有人提出用volatile解决能够吗?

public class Singleton {
private static volatile Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}