原子性( Atomicity )
原子性描述的是一个线程对共享变量的更新,从另外一个线程的角度看,它要么完成更新,要么尚未发生更新,而不是进行中的一种状态。因此原子性保证 一个线程所读取的共享变量的值要么是初始值要么是变量的相对新的值,不能是更新过程中的值,一个相对于半成品的值。
谁具有原子性?
Java 语言中除了 long,double以外的任何类型的变量的写操作均具有原子性。包括除去long和double的基本类型以及引用类型
备注:
- long 和 double 类型为 8 字节,在32bit的机器上需要分两次加载数值,这会导致其写操作不具有原子性。
- 基本类型的自增自减操作不具有原子性:因为自增操作如
i++
( i 为 int 类型)分为三步执行.
load(i,r); //i 加载的值加载至寄存器 r
increment(r);//寄存器加 1
store(i,r) ;//将r寄存器的值写入变量 i 的内存空间
多个线程的操作过程可能出现竞争导致最后的数值不是理想的数值。
可见性
多线程环境下,一个线程对共享变量进行更新之后,后须访问该变量的线程可能立刻就能读取到或者永远无法读取到这个变量。这里就涉及到了变量的可见性的问题。
情况一:JIT编译器的错误优化,导致变量不可见。
public class VisibilityDemo {
public static void main(String[] args) throws InterruptedException {
TimeConsumingTask timeConsumingTask = new TimeConsumingTask();
Thread thread = new Thread(new TimeConsumingTask());
thread.start();
Thread.sleep(10000);
timeConsumingTask.cancel();
}
}
class TimeConsumingTask implements Runnable{
private boolean toCancel = false;
@Override
public void run(){
while(!toCancel){
if(doExecute()){
break;
}
}
if(toCancel){
System.out.println("Task was cancelled");
}else{
System.out.println("Task done.");
}
}
public boolean doExecute(){
boolean isDone = false;
System.out.println("Executing...");
Tools.randomPause(50);
return isDone;
}
public void cancel(){
toCancel = true;
System.out.println(this+ "Cancelled.");
}
}
run 方法会被 JIT 优化成一个死循环,所以导致 程序无法退出
情况二:与计算机的存储系统有关。处理器并非与 RAM 直接交互而是使用了中间件如寄存器、高速缓存、无效化队列等。多个处理器环境下,一个处理器的高速缓存中的内容不能被另一个处理器直接读取,但是可以通过一致性缓存协议将其他处理器的缓存读取并更新到自己缓存中。
使用 volatile
关键字可以阻止JIT对代码进行可能导致错误的运行情况的优化而且可以使处理器执行刷新缓存的操作实现数据的可见性
网友评论