原子性,可见性,有序性是并发的三大特征,所谓原子性,就是一个操作要么全部执行,要么都不执行。
如下所示,在一个类中,定义一个静态变量int var=0,现在开启20个线程,每个线程都执行相同的操作,即对var实行10000次++操作,线程退出,然后打印var的值。
package com.xxx.cas;
public class PlusPlusOP {
public static int var = 0;
public static void plusAndPlus(){
var++;
}
public static void main(String[] args) {
Thread threads[] = new Thread[20];
for(int i=0;i<20;i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10000;i++){
plusAndPlus();
}
}
});
threads[i].start();
}
while(Thread.activeCount()>1){
Thread.yield();
}
System.out.println("var=>"+var);
}
}
默认,没有对原子操作做特殊的处理,我们的到的结果各种各样,但就不是200000。如下所示:
这是一个很常见的并发问题,涉及到的是原子操作,var++这个动作,其实分为了三个过程:
- 读取var的值
- ++操作
- 写入var的值
多线程并发的情况下,有可能第一个线程获取了var=10000,即将执行++操作,这时候另一个线程也来获取var,这时候获取的应该也是10000,当他们都执行完成的时候,本来应该是10002,但是实际上,因为并发的原因,导致了最终的结果是10001。所以我们看到的结果是一个小于200000的数字,程序在这种情况下运行,得到这个结果是正常的。再次运行,可能还是一个小于200000的结果。
那么如何解决这个原子性的问题呢,其实就是要保证var++这个动作不能被拆开,一旦进入读取var的值,那么接下来只有执行了写入var的值之后,别的线程才可以运行读取var的值操作。这就需要var++这个操作是同步的,解决办法可以有如下三种:
- synchronized同步代码块
- cas原子类工具
- lock锁机制
三种解决方案的具体实现:
synchronized同步代码块
cas原子类工具
lock锁
上面介绍的三种方式,解决并发编程中原子问题,使用synchronized关键字是最简单的,对代码的修改是最小的。