在java谈到线程的同步方式,很多初学者(比i如我)最开始的理解是 内置锁sychronized、显示的Lock的lock()和unLock()和 volatile 这三种方式来控制线程的同步,但是在学习了volatile之后才发现 ,它在大多数的时候是不具备控制同步的能力。它的主要作用并不是控制同步。
Volatile的原子性: 原子性可以应用于除了long和double之外的所有基本类型之上的“简单操作”。对于读取和写入除long和double之外的基本类型这样的操作,可以保证他们会被当作不可分(原子)的操作来操作内存,但是虚拟机可以将64位的(long和double变量)的读取和写入当作两个分离的32位操作来执行,这样就产生了一个读取和写入操作中间有一个上下文切换的操作,从而导致不同的任务可以看到不同的结果的可能性,当你定义long和double变量时,使用volatile关键字,就会获得(简单的赋值与返回操作的)原子性,因此 原子操作可以由线程机制保证其不可中断,
volatile的可视性:在多处理系统相对于单处理系统而言,可视性要比原子性问题多的多,一个任务作出的修改,即使不中断的原子性操作,对其他任务也是不可见的,由于虚拟机的运行优化机制,比如说修改 修改后的结果只暂时的存在本地缓存当中,不会立即的去刷新主存,因此不同的任务对应状态不同也会有不同的结果,同步机制会强制一个任务在作出修改的同事必须保证在应用中是可视的,如果没有同步机制的话 可视性就无法保证了,而volatile确保了应用中的可视性,在把一个域标记为volatile的时候,在对这个域进行了写入操作,那么所有的读取操作就都可以看到这个修改,即便是使用了本地缓存,volatile域也会立即被刷新到主存中,而读操作就是发生在主存中,
使用volatile的情况:如果当一个域需要同时被多个任务访问的时候,那么就很有必要使用volatile或者同步来保证在发生写入操作的时候刷新主存,
volatile不适用于递增(i++),因为volatile只是适用与类中只有一个可变的域,i++ 这种域的值依赖于它的前一个值 就相当于两个可变的域了,下面两个实例演示
package test.thread.sx; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SynchronizedTestMethod implements Runnable { private int i = 0; public void f1() { /** * 解析下jvm的命令 1 alodad-0 2 dup 3 getFiled 4 ioonst_1 5 iadd 6 putFiled * s所以这个 第三部是取出i的值 第6步那里才会把新的值put进i值、他们之间会有其他任务方法可能修改这个值 所以这个操作不是原子性的操作 */ i++; } public void f2() { i += 3; } public int getValue() { return i; } private synchronized void evenIncrement() { i++; i++; } @Override public void run() { while (true) { evenIncrement(); } } public static void main(String[] args) { ExecutorService exc = Executors.newCachedThreadPool(); SynchronizedTestMethod s = new SynchronizedTestMethod(); exc.execute(s); while(true){ int val = s.getValue(); if(val%2!=0){ System.out.println(val); System.exit(0); } } //理想的输出结果是永远不输出 因为evenIncrement() 是同步的方法所以每次都会把i加2 才会被其他线程访问 ,如果getvalue也是同步的 那么只有当evenIncrement()方法释放对象锁才会被访问 那么取出来的i值永远都会是偶数 //永远不会是奇数,但是事实证明getVlaue 不是同步的 所以导致了 在evenIncrement()方法的i值没有put结束时 没有执行完的情况下,就会被return i返回值。 } }
第二个小实例
package test.thread.sx; public class CirularSet { private int[] arry; private int len; private int index = 0; public CirularSet(int size) { arry = new int[size]; len = size; /** * 把arry数组 中的数据 都设置为-1 搞什么 -1 是永远不会和nextserialNUmber 产生的值相等 */ for (int i = 0; i < size; i++) { arry[i] = -1; } } /** * 模拟arrylist 把新添加的数据添加到数组后头 但是有一个问题 index不加限制的话 索引越界怎么办 数组是不会自己增加长度的 * * @param i */ public synchronized void add(int i) { arry[index] = i; index = ++index % len; } public synchronized boolean contains(int val) { for (int i = 0; i < len; i++) { if (arry[i] == val) { return true; } } return false; } }
package test.thread.sx; /** * 验证volatile是否会对递增不是原子性操作产生影响 * @author Administrator * */ public class SerialNumberGenerator { private static volatile int serialNumber=0; /** * 这里如果不加同步的话 会导致 返回的值处在不稳定的状态 会比如说 seriNumber 在1298的时候 计算完成为1299挂起 * 另一个线程执行完++后为1300 然后返回1300 然后上面的线程重新开始运行继续返回1300 所以会产生相同值。 * @return */ public static int nextserialNUmber(){ return serialNumber++; } }
package test.thread.sx; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SerialNumberChecker { private static final int size = 10; /** * 创建一个1000长度的数组 */ private static CirularSet serials = new CirularSet(1000); private static ExecutorService exc = Executors.newCachedThreadPool(); static class SerialChecker implements Runnable { @Override public void run() { while (true) { //如果说 递增 i++的非原子性操作 可以由于volatile 修饰i 就可以让i++ 变得同步的话 //那理想的结果就是无限循环i++ 然后检查数组里数据 查看是没有重复的 就添加到数组里 , //但是事实是有重复的会推出 int serial = SerialNumberGenerator.nextserialNUmber(); if (serials.contains(serial)) { System.out.println("重复的" + serial); System.exit(0); } serials.add(serial); } } } public static void main(String[] args) throws NumberFormatException, InterruptedException { for(int i=0;i<size;i++){ exc.execute(new SerialChecker()); } } }