什么是原子操作,即不能被线程太调度机制中断的操作。就是只要这段操作开始了,一定是在线程切换之前完成的。
看一下下面这个有意思的例子
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AtomicityTest implements Runnable{
private int i = 0;
private boolean flag = true;
private int getValue() {
return i;
}
//用synchronized修饰的方法
private synchronized void increase() {
i++;
i++;
}
public void exit() {
flag = false;
}
@Override
public void run() {
while(flag) {
increase();
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
AtomicityTest test = new AtomicityTest();
exec.execute(test);
while(true) {
int val = test.getValue();
if(val % 2 != 0) {
System.out.println(val);
test.exit();
break;
}
}
exec.shutdown();
}
}输出结果
1main方法中,启动了一个缓存线程池,并下发了一个任务(Runnable)。
任务也很简单,就是有一个变量i初始为0,然后不停的对其进行两次++操作,加的过程用synchronized加锁;
然后main中再启动一个循环,获取任务中的i中,如果i是奇数则结束程序。
理论上,每次对i都是进行的加2操作,是不可能取到奇数的,但是现实就是这么残酷。。。程序依旧结束了。。。
把程序修改了一下private synchronized int getValue() 给这个方法也加锁,然后程序便不会停止了。原因就在这里,getValue不是原子性操作、、、
这里只是引入,下面的例子更有意思
SerialNumberGenerator.java
public class SerialNumberGenerator {
//private static volatile int serialNumber = 0;//换成这个也是一样,不是它的问题
private static int serialNumber = 0;
public static int next() {
return serialNumber++;//不安全。。。
}
}SerialNumberChecker.java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class CircularSet {
private int[] array;
private int len;
private int index = 0;
public CircularSet(int size) {
array = new int[size];
len = size;
for(int i = 0; i < size; i++) {
array[i] = -1;
}
}
public synchronized void add(int i) {
array[index] = i;
index = ++index % len;//保证不越界
}
public synchronized boolean contains(int val) {
for(int i = 0; i < len; i++) {
if(array[i] == val) {
return true;
}
}
return false;
}
}
public class SerialNumberChecker {
private static final int SIZE = 10;
private static CircularSet circularSet = new CircularSet(1000);
private static ExecutorService exec = Executors.newCachedThreadPool();
static class SerialChecker implements Runnable {
@Override
public void run() {
while(true) {
int serial = SerialNumberGenerator.next();
if(circularSet.contains(serial)) {
System.out.println("出现了重复: " + serial);
System.exit(0);
}
circularSet.add(serial);
}
}
}
public static void main(String[] args) {
for(int i = 0; i < SIZE; i++) {
exec.execute(new SerialChecker());
}
}
}输出结果(每次不一样)
出现了重复: 4128
出现了重复: 6314
出现了重复: 6324
代码中circularSet.add(serial);之前都有一步if(circularSet.contains(serial)) 判断操作,理论上是不可能出现重复的,但是事实就是出现重复了。。。
这段判断确实没有问题,问题出在SerialNumberGenerator.next()这个方法,
如果给它加一个synchronized修饰,就不会出现问题了。
看看里面的内容 return serialNumber++;
return是原子操作,不会出现同步问题,关键就在于这个递增。java中递增不是原子操作,所以才会出现多个线程在“同时”取serialNumber时还没来得急完成递增的操作,从而取到了以前出现的数据。