Java并发之原子性
1.什么是原子性
众所周知,原子是构成物质的基本单位,所以原子的意思代表着——“不可分”。
由不可分性可知,具有原子性的操作是拒绝线程调度器中断的。 简而言之——不被线程调度器中断的操作,如:
赋值或者return。比如”a = 1;”和 “return a;”这样的操作都具有原子性。
2.为什么需要原子性
现实情况是,“ i++; ”这个编程中很普通的一个操作都不具有原子性,这涉及到Java的内存模型。它在虚拟机中 操作 执行的顺序是
(1). 将变量从主存复制到工作内存(read,load)
(2). 执行代码,改变变量值(use,assign)
(3). 从工作内存同步回主存(store,write)
这里的操作是指括号中的read,write等由虚拟机实现的、具有原子性的操作。笔者分为(1)(2)(3)只是为了更简洁的描述这个过程。
这些操作之间是可以被线程调度器中断的。
下述例子本意是想用10个线程,每个线程都对inc自增1000,当程序结束时inc值能到10000。事实却是无论多少次运行程序,得到的都是小于10000的数。
该例可以说明,“ i++ ”语句执行期间,是可能会被线程调度器中断的。可以想象一种情况:当一个线程读inc变量进其工作内存后,被调度器切换,另一个线程马上又读inc进它的的工作内存,毫无疑问,这两个线程所有剩下操作执行完后,得到的是相同的结果,在程序上体现的是,两次自增计算只表现为一次。
public class Atomicity {
private volatile boolean canceled = false;
private int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Atomicity a = new Atomicity ();
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++)
service.execute(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 1000; j++)
a.increase();
}
});
service.shutdown();
while (!a.canceled) {
if (service.isTerminated()) {
a.canceled = true;
} else {
Thread.yield();
}
}
System.out.println(a.inc);
}
public boolean isCanceled() {
return canceled;
}
public void setCanceled(boolean canceled) {
this.canceled = canceled;
}
}
这里的 volatile 修饰变量是为了保证此变量在多个线程之间的可见性,即一个线程对变量的修改会立即让其他所有线程知晓。关于可见行在下篇文章论述。
到这里,在并发情况下非原子性的操作引发的问题便初窥一角。程序要提供一种办法去保证代码运行结果在并发环境中的正确性。
3.如何保证原子性
(1)synchronized关键字
(2)Lock对象
(3)原子类