竞态条件
当计算的正确性取决于相对时间或者调度器所控制的多线程交叉时,竞态条件就会发生。
if(a==10){
b = a/2.0;
}
当在单线程中,这段代码没有任何问题。在多线程环境下,如果a和b是局部变量,那么也没有问题。但是,假设a和b是实例变量或者类(static)变量,并且同时有两个线程访问了这个代码。
假设一条线程已经执行完if(a==10),在即将执行b=a/2.0时,被调度器暂停了,与此同时,调度器恢复了另一条线程改变了a的值,当前一条线程恢复执行,变量b的值却不等于5(如果a和b是局部变量,因为每个线程都有自己的局部变量拷贝,所以竟态条件不会发生)。
数据竞争
竟态条件经常会和数据竞争相混淆,数据竞争指的是两条或者两条以上的线程(在单个应用中)并发访问同一块内存区域,同时其中至少有一条是为了写,而且这些线程没有协调对那块区域的访问。当满足这些条件的时候,访问顺序就是不确定。依据这种顺序,每次运行都可能产生不同的结果。
private static Parser parser;
public static Parser getInstance(){
if(parser == null){
parser = new Parser();
}
return parser;
}
假设线程1 首先调用了getInstance()方法。 由于它检测到属性parser是空值,线程1就会实例化Parser并将引用赋给变量Parser。随后,当线程2调用getInstance()方法,它可能检测到parser已经包含了一个非空的引用,于是简单的返回了parser的值;另一种可能是,线程2检测到parser的值仍为null,于是创建了一个新的Parser对象。由于线程1写parser变量和线程2读paser变量之间没有先后顺序的保证,数据竞争就产生了。
缓存变量
为了提升性能,编译器Java虚拟机以及操作系统会协调在寄存器中或者处理器缓存中的缓存变量,而不是依赖主存。
public static String name;
public static void main(String[] args){
Runnable r = new Runnable()->{
name ="1231";
}
Thread t= new Thread(r);
t.start();
try{
t.join();
}catch (Exception e){
}
System.out.println(name);
}
类变量name示范了缓存变量的问题,改属性在Lambda表达式的上下文当中被一条工作线程访问并执行代码,name="123"; 然后默认主线程执行System.out.println(name);这个工作线程能够将name的返回值存储到自己的name变量的拷贝中。默认主线程很可能无法看到name="123"的赋值,并且它的本地拷贝会保持原来的默认的null值,这个null值会取代name的字符串表示并被打印出来。