什么是原子量,原子量就是一次操作,要么成功,要么失败。比如java中的i++ 或i-- , 不具备原子性,每次读取的值都是不一样的,探究其原因,x86体系中,他的总线是32位的,i++的操作指令必须是分为2步实现,那是因为,为了确保原子性,jdk在atomic-AtomicXXX 类中,通过CAS来确保原子性。对原子量的读取可以读到最新,由volatile关键字来保证可见性。
对比一下下面的代码实现:
public class Incr { public AtomicInteger a = new AtomicInteger(0); public int incrAtomic(){ return a.getAndIncrement(); } public int getAtomic(){ return a.get(); } public int b = 0; public int incrInt(){ return b++; } public int getInt(){ return b; } }
public class MultiThread { private static Incr incr = new Incr(); public static void main(String[] args) { final CountDownLatch countDownLatch = new CountDownLatch(1); for (int i = 0; i < 100; i++) { new Thread(new Runnable() { @Override public void run() { try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } for (int j = 0; j < 100; j++) { incr.incrAtomic(); incr.incrInt(); } } }).start(); } countDownLatch.countDown(); try { Thread.sleep(60000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("AtomicInteger.incr: "+incr.getAtomic()); System.out.println("int.incr: " + incr.getInt()); } }
接着,我们来看看,JUC中的atomicInteger类是如何实现CAS的?
查看源码,我们可以清晰的看到作者就是大名顶顶的 Doug Lee , 在代码的提示中,实现都是通过CAS(comparAndSwapInt)来实现数据的更新的,的注释归注释,但还是要看看,下面的具体实现,一看果然,有很多类似于下面的那样的代码,出现。
为了一探究竟,先去看看 unsafe 的实现。
查看Unsafe 类的时候,发现他的大多数实现,都是 native 属性,也就是说,他把实现都留在了JVM上实现,
虚拟机内存模型中定义的访问操作与物理计算机处理的基本一致!
Java中volatile关键字原义是“不稳定、变化”的意思 。使用volatile和不使用volatile的区别在于JVM内存主存和线程工作内存的同步之上。volatile保证变量在线程工作内存和主存之间一致。
其实是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我. 也就是MainMomery 中,而不是缓存中。这里的volatile只是解决了存储i值得问题,至于获取和修改i值,确是没有做到同步。so , 有了volatile 并不意味着你万事大吉了,他也会很容易产生脏数据。
1:为什么会产生错误的数据?
多线程引起的,因为对于多线程同时操作一个整型变量在大并发操作的情况下无法做到同步,而Atom提供了很多针对此类线程安全问题的解决方案,因此解决了同时读写操作的问题。
2:为什么会造成同步问题?
Java多线程在对变量进行操作的时候,实际上是每个线程会单独分配一个针对i值的拷贝(独立内存区域),但是申明的i值确是在主内存区域中,当对i值修改完毕后,线程会将自己内存区域块中的i值拷贝到主内存区域中,因此有可能每个线程拿到的i值是不一样的,从而出现了同步问题。
3:为什么使用volatile修饰integer变量后,还是不行?
因为volatile仅仅只是解决了存储的问题,即i值只是保留在了一个内存区域中,但是i++这个操作,涉及到获取i值、修改i值、存储i值(i=i+1),这里的volatile只是解决了存储i值得问题,至于获取和修改i值,确是没有做到同步。
4:既然不能做到同步,那为什么还要用volatile这种修饰符?
主要的一个原因是方便,因为只需添加一个修饰符即可,而无需做对象加锁、解锁这么麻烦的操作。但是本人不推荐使用这种机制,因为比较容易出问题(脏数据),而且也保证不了同步。
5:那到底如何解决这样的问题?
第一种:采用同步synchronized解决,这样虽然解决了问题,但是也降低了系统的性能。
第二种:采用原子性数据Atomic变量,这是从JDK1.5开始才存在的针对原子性的解决方案,这种方案也是目前比较好的解决方案了。(推荐,cas无锁操作。)
/** 直接设置volatile变量的值 */ public final void set(int newValue) { value = newValue; } /** putOrderedInt,去掉了storeLoad内存屏障,只保证最终设置成功,不保证多处理环境下,其他处理器read到最新的值 */ public final void lazySet(int newValue) { unsafe.putOrderedInt(this, valueOffset, newValue); } /** loop循环,不断重试,直到成功 */ public final int getAndSet(int newValue) { for (;;) { int current = get(); if (compareAndSet(current, newValue)) return current; } } /** Atomic中n多方法通过loop来调用这个方法,类似乐观锁,expect表示期望的值,update是更新的值 */ public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } /** 代码跟compareAndSet没什么区别, 注释里面May fail spuriously and does not provide ordering guarantees,会导致伪失败,不保证指令有序 */ public final boolean weakCompareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }