Java:volatile
Java中的原子性操作与同步问题
1.在Java中,原子操作是指不能被线程调度机制中断的操作,一旦操作开始,那么它一定可以在可能发生的"上下文切换"之前(切换到其它线程执行)执行完毕。
2.依赖原子性来处理同步问题时很棘手并且很危险的事情。
如下面的程序,尽管return i;确实是原子性操作,但是缺少同步使得其数值可以在处于不稳定的中间状态时被读取,尽管变量i添加上了volatile关键字。
正确的方法时同时将getValue()和evenIncrement()都是synchronized的。
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 4 5 public class AtomicityTest implements Runnable { 6 7 private int i = 0; 8 9 public int getValue() { 10 return i; 11 } 12 13 private synchronized void evenIncrement() { 14 i++; 15 i++; 16 } 17 18 @Override 19 public void run() { 20 while (true) { 21 evenIncrement(); 22 } 23 } 24 25 public static void main(String[] args) { 26 ExecutorService exec = Executors.newCachedThreadPool(); 27 AtomicityTest at = new AtomicityTest(); 28 exec.execute(at); 29 while (true) { 30 int val = at.getValue(); 31 if (val % 2 != 0) { 32 System.out.println("error..." + val + " is odd."); 33 System.exit(0); 34 } 35 } 36 37 } 38 39 } 40 /** 41 * 程序运行结果: 42 * error...53683 is odd. 43 */
再如下面的程序,一方面虽然用volatile关键字修饰了SerialNumberGenerator.serialNumber成员,但是nextSerialNumber()中的++运算并不是原子操作,所以还是会出现问题。
解决问题得方法就是把nextSerialNumber()方法改为synchronized的。
1 public class SerialNumberGenerator { 2 3 private static volatile int serialNumber = 0; 4 5 public static int nextSerialNumber() { 6 return serialNumber++; 7 } 8 }
1 public class CircularSet { 2 3 private int[] array; 4 private int len; 5 private int index = 0; 6 7 public CircularSet(int size) { 8 array = new int[size]; 9 len = size; 10 for (int i = 0; i < len; i++) array[i] = -1; 11 } 12 13 public synchronized void add(int i) { 14 array[index] = i; 15 index = (index + 1) % len; 16 } 17 18 public synchronized boolean contains(int val) { 19 for (int i = 0; i < len; i++) { 20 if (array[i] == val) return true; 21 } 22 return false; 23 } 24 25 }
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 4 5 public class SerialNumberChecker { 6 7 private static final int SIZE = 10; 8 private static CircularSet serials = new CircularSet(1000); 9 private static ExecutorService exec = Executors.newCachedThreadPool(); 10 11 public static void main(String[] args) { 12 13 for (int i = 0; i < SIZE; i++) { 14 exec.execute(new Runnable() { 15 16 @Override 17 public void run() { 18 while (true) { 19 int serial = SerialNumberGenerator.nextSerialNumber(); 20 if (serials.contains(serial)) { 21 System.out.println("error... " + serial + " duplicate."); 22 System.exit(0); 23 } 24 serials.add(serial); 25 } 26 } 27 28 }); 29 } 30 } 31 32 } 33 34 /** 35 * 程序执行结果: 36 * error... 8298 duplicate. 37 * error... 9049 duplicate. 38 * error... 9051 duplicate. 39 * error... 9045 duplicate. 40 * error... 9050 duplicate. 41 * error... 9048 duplicate. 42 * error... 9047 duplicate. 43 * error... 9046 duplicate. 44 */