在 Java 领域,我们经常会说两个名词大家要有所区分:
主要是三个问题:
可见性
多核 CPU 缓存(寄存器) 和 内存间存在可见性问题
A 线程 使用 CPU-1 读取内存中变量 X = 1 ;
B 线程 使用 CPU-2 读取内存中变量 X = 1;
A B 线程操作变量都是在本核 CPU 缓存中执行操作,最后写回。这个操作过程对于 A B 两线程是不可见的。
这就是可见性问题,解决该问题即,禁用 CPU 缓存,Java 中 volatile 关键字
有序性
Java 程序在编译过程中为了优化性能,有时会改变程序中语句的先后顺序
int a = 1;
int b = 2;
优化后可能为
int b = 2;
int a = 1;
这类优化调整了语句的顺序但不影响程序的最终结果。
其实我们在聊有序性的时候,最有可能遇到的问题是单例初始化时的双重检查。下面是一个比较常见的双重检查获取单例对象的代码
public class Singleton {static Singleton instance;static Singleton getInstance(){if (instance == null) {synchronized(Singleton.class) {if (instance == null)instance = new Singleton();}}return instance;}
}
这个 getInstance()
方法看上去没有什么问题,但在指令重排后就有可能引发并发问题。
我们认为的 instance 赋值过程是
而实际指令重排后
这会导致在第二步的时候 instance 变量就不为 null 了,但此时对象还未初始化,如果多线程调用就可能返回一个未初始化的对象,从而导致程序错误。
同样的解决该问题的方法就是禁用指令重排,在 Java 中使用 volatile 关键字
原子性
CPU 能够保证的原子操作是 CPU 指令级别的,而一个 Java 语句可能会对应多个 CPU 指令,这就导致 Java 的单个语句可能并不是原子的,在线程切换的时候就可能出现并发问题,所以需要我们在 Java 语言的级别保证某些操作的原子性。
比如执行一个 +1 的操作
int count = 0;
void addOne(){count++;
}
如果有多个线程同时调用 addOne()
方法,这个最终的总数是不准确的。
对于 ++ 操作拆解成 CPU 指令至少需要三条:
在这个流程中多个线程同时调用 addOne 方法,线程 A 读取 count 值为 0,线程 B 读取 count 值也是 0
线程 A 执行完毕写回内存 count 值为 1,线程 B 执行完毕写回内存 count 值也为 1。我们期望这个值应该为 2 。
看上去似乎是可见性的问题 volatile 关键字修饰后能否解决这个问题。
其实是不能的,volatile 能解决一部分问题,它解决不了线程切换的问题。需要通过互斥锁来解决,在 Java 中即 synchronized
上一篇:携程vip贵宾卡怎么使用 ,工商携程vip卡有什么用 极速百科网 极速百科
下一篇:请大家推荐几本略带小黄的小说,完结了的异能小说,要好看的。带点黄。最好有10多个女人。 极速百科网 极速百科视频