标签:
java5之后的java.util.concurrent包(J.U.C)是世界级并发大师Doug Lea的作品,里面主要实现了
atomic包下的类主要基于现代主流 CPU 都支持的一种指令,Compare and Swap(CAS),这个指令能为多线程编程带来更好的性能。引用《Java Concurrency in Practice》里的一段描述:
在这里,CAS 指的是现代 CPU 广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。这个指令会对内存中的共享数据做原子的读写操作。简单介绍一下这个指令的操作过程:首先,CPU 会将内存中将要被更改的数据与期望的值做比较。然后,当这两个值相等时,CPU 才会将内存中的数值替换为新的值。否则便不做操作。最后,CPU 会将旧的数值返回。这一系列的操作是原子的。它们虽然看似复杂,但却是 Java 5 并发机制优于原有锁机制的根本。简单来说,CAS 的含义是“我认为原有的值应该是什么,如果是,则将原有的值更新为新值,否则不做修改,并告诉我原来的值是多少”。
private volatile int value;
AtomicInteger里面只包含一个字段,用来记录当前值,定义为volatile是为了满足可见性。
// setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }
一开始定义了static变量Unsafe,AtomicInteger里面的方法都是对unsafe里面
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
方法的封装。
我们来看原子性的i++,
public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } }
在一个无限循环里面,首先获取当前值,然后调用
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
unsafe.compareAndSwapInt(this, valueOffset, expect, update)的含义是把this对象里面valueOffset这个位置(即value值)跟expect比较,如果相等,则修改为update,返回true;如果不相等,说明在获取到current之后有其他线程修改过value的值,则重新来一遍,一直到修改成功为止。这里就可以看出,理论上来说,这个方法是有可能永远不能返回的,实际而言,当并发冲突很严重,反复compareAndSet(current, next)失败,有可能也需要花费很多时间。
AtomicInteger里面的其他方法,基本类似;其他类包括AtomicLong,AtomicReference等也是基本对Unsafe里面compareAndSet的一个封装。
前面可以看到Unsafe类在实现atomic的重要性。为什么有Unsafe这个class呢,基本原因是Java不允许代码直接操作内存,好处是更安全,一般不会出现内存泄露,因为有JVM的GC;坏处是有些底层调用执行不了。我的理解是,Unsafe就是这个java安全围城通向比如c++这个不安全外围的一道门,所以叫Unsafe嘛。Unsafe里面基本都是native,即通过JNI调用c/c++等代码。大部分是直接内存操作,以及后面会讲到的挂起唤醒线程等,包括park和unpark。
前面到
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
就不是java代码了,如果想看实现的话,需要下载OpenJDK源码,里面是c++代码调用汇编代码,blabla。我不建议大家再往下继续了,原因有几个,一是我们用java等高级语言的目的就是为了避免纠结复杂的底层细节,站在更高层的角度思考问题,而是java里面还有更多的问题等待你去解决呢,更多的知识可以学习!如果你说你已经把java完全掌握了,包括把jdk源码,tomcat、spring,xxxxx源码都看过了,实在没得看了,那我会说,多陪陪家人吧~除非你是JVM开发工程师,哦,那不好意思,大神,当我啥都没说。。。。为了完整性,我贴几个参考链接http://www.blogjava.net/mstar/archive/2013/04/24/398351.html, http://zl198751.iteye.com/blog/1848575.
那么如果获取Unsafe呢?Unsafe有一个static方法可以获取Unsafe实例,如下
public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(2); if(var0.getClassLoader() != null) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }
可是你如果在自己代码里使用,可以编译通过,但是运行时候报错。因为里面限制了调用getUnsafe()这个方法的类必须是启动类加载器Bootstrap Loader。所以如果想在自己代码里面调用Unsafe的话(强烈建议不要这样子做),可以用Java的反射来实现:
static class UnsafeSupport { private static Unsafe unsafe; static { Field field; try { // 由反编译Unsafe类获得的信息 field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); // 获取静态属性,Unsafe在启动JVM时随rt.jar装载 unsafe = (Unsafe) field.get(null); } catch (Exception e) { e.printStackTrace(); } } public static Unsafe getInstance() { // return Unsafe.getUnsafe();//没有用,只能native获取,否则会抛异常 return unsafe; } }
获取到了Unsafe的实例之后,你照样可以自己实现Atomic类,再说一遍,强烈建议不要这样做!!!
Compare and Set 是一个非阻塞的算法,这是它的优势。因为使用的是CPU支持的指令,提供了比原有的并发机制更好的性能和伸缩性。可以认为一般情况下性能更好,并且也更容易使用(这才是关键啊)。
CAS操作容易导致ABA问题,也就是在做a++之间,a可能被多个线程修改过了,只不过回到了最初的值,这时CAS会认为a的值没有变。a在外面逛了一圈回来,你能保证它没有做任何坏事,不能!!也许它讨闲,把b的值减了一下,把c的值加了一下等等,更有甚者如果a是一个对象,这个对象有可能是新创建出来的,a是一个引用呢情况又如何,所以这里面还是存在着很多问题的,解决ABA问题的方法有很多,可以考虑增加一个修改计数(版本号),只有修改计数不变的且a值不变的情况下才做a++,atomic包下有AtomicStampedReference类做这个事情,这和事务原子性处理有点类似!
Refers
Java Concurrency(二)——J.U.C atomic包源码解读
标签:
原文地址:http://my.oschina.net/magicly007/blog/364102