扫描下方二维码或者微信搜索公众号 菜鸟飞呀飞
,即可关注微信公众号,阅读更多 Spring源码分析
和 Java并发编程
文章。
在Java并发领域,总会提到原子操作,而Java作为一门高级语言,为了实现原子操作,提供了两种解决方案:1)加锁;2)通过CAS来实现,同时JDK在1.5开始,在JUC包下,并发编程大神Doug Lea提供了很多原子类,这些原子类都是通过CAS来实现的。接下来本文将主要介绍CAS相关的知识。
在Java中可以通过Unsafe类实现CAS操作,而Unsafe类最终调用的是native方法,即具体实现是由JVM中的方法实现的。而JVM中通过C++调用处理器的指令 cmpxchg
来实现的。
java.util.concurrent.atomic
包中提供了很多原子类,这些类的操作均是原子操作,它们都是根据Unsafe类的CAS方法来实现原子操作的,如: compareAndSwapInt()、compareAndSwapLong()、compareAndSwapObject()
这三个方法均是CAS方法。 public class Demo { // 创建一个Integer类型的原子类 private static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) { List<Thread> threadList = new ArrayList<>(10); // 创建10个线程,每个线程中的run()方法将count累加10000次 for (int i = 0; i < 10; i++) { threadList.add(new Thread(()-> { // 每个线程累加10000次 for (int j = 0; j < 10000; j++) { // 实现递增,incrementAndGet()方法等价于非原子操作里面的 count++ count.incrementAndGet(); } })); } // 将10个线程启动 for (int i = 0; i < 10; i++) { threadList.get(i).start(); } // 让主线程休眠10秒钟,休眠10秒钟是为了让前面的10个线程全部执行完成,如果10秒钟不够,可以设置得更加长一点 try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } // 打印count的值 // 最终发现从控制台打印出来的结果为100000 System.out.println(count.get()); } } 复制代码
public class AtomicInteger extends Number implements java.io.Serializable { private static final Unsafe unsafe = Unsafe.getUnsafe(); public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } } 复制代码
public final class Unsafe { private static final Unsafe theUnsafe = new Unsafe(); public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); // 循环调用compareAndSwapInt(),直到设置成功 // 如果设置失败,则通过getIntVolatile()方法从内存中获取新的值,然后再进行CAS操作 } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; } // native方法由JVM实现 public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); } 复制代码
jdk8-b120
版本的OpenJDK。 jdk-jdk8-b120/hotspot/src/share/vm/prims/unsafe.cpp
。在unsafe.cpp文件中接近末尾的地方,可以看到如下代码(只截取了部分源码)。 static JNINativeMethod methods_18[] = { {CC"getObject", CC"("OBJ"J)"OBJ"", FN_PTR(Unsafe_GetObject)}, {CC"putObject", CC"("OBJ"J"OBJ")V", FN_PTR(Unsafe_SetObject)}, {CC"getObjectVolatile",CC"("OBJ"J)"OBJ"", FN_PTR(Unsafe_GetObjectVolatile)}, {CC"putObjectVolatile",CC"("OBJ"J"OBJ")V", FN_PTR(Unsafe_SetObjectVolatile)}, {CC"getAddress", CC"("ADR")"ADR, FN_PTR(Unsafe_GetNativeAddress)}, {CC"putAddress", CC"("ADR""ADR")V", FN_PTR(Unsafe_SetNativeAddress)}, {CC"allocateMemory", CC"(J)"ADR, FN_PTR(Unsafe_AllocateMemory)}, {CC"reallocateMemory", CC"("ADR"J)"ADR, FN_PTR(Unsafe_ReallocateMemory)}, {CC"freeMemory", CC"("ADR")V", FN_PTR(Unsafe_FreeMemory)}, {CC"objectFieldOffset", CC"("FLD")J", FN_PTR(Unsafe_ObjectFieldOffset)}, {CC"staticFieldOffset", CC"("FLD")J", FN_PTR(Unsafe_StaticFieldOffset)}, {CC"staticFieldBase", CC"("FLD")"OBJ, FN_PTR(Unsafe_StaticFieldBaseFromField)}, {CC"ensureClassInitialized",CC"("CLS")V", FN_PTR(Unsafe_EnsureClassInitialized)}, {CC"arrayBaseOffset", CC"("CLS")I", FN_PTR(Unsafe_ArrayBaseOffset)}, {CC"arrayIndexScale", CC"("CLS")I", FN_PTR(Unsafe_ArrayIndexScale)}, {CC"addressSize", CC"()I", FN_PTR(Unsafe_AddressSize)}, {CC"pageSize", CC"()I", FN_PTR(Unsafe_PageSize)}, {CC"defineClass", CC"("DC_Args")"CLS, FN_PTR(Unsafe_DefineClass)}, {CC"allocateInstance", CC"("CLS")"OBJ, FN_PTR(Unsafe_AllocateInstance)}, {CC"monitorEnter", CC"("OBJ")V", FN_PTR(Unsafe_MonitorEnter)}, {CC"monitorExit", CC"("OBJ")V", FN_PTR(Unsafe_MonitorExit)}, {CC"tryMonitorEnter", CC"("OBJ")Z", FN_PTR(Unsafe_TryMonitorEnter)}, {CC"throwException", CC"("THR")V", FN_PTR(Unsafe_ThrowException)}, {CC"compareAndSwapObject", CC"("OBJ"J"OBJ""OBJ")Z", FN_PTR(Unsafe_CompareAndSwapObject)}, // 对应Unsafe.java文件中的compareAndSwapInt()方法,Unsafe_CompareAndSwapInt()方法时对应的C++方法 {CC"compareAndSwapInt", CC"("OBJ"J""I""I"")Z", FN_PTR(Unsafe_CompareAndSwapInt)}, {CC"compareAndSwapLong", CC"("OBJ"J""J""J"")Z", FN_PTR(Unsafe_CompareAndSwapLong)}, {CC"putOrderedObject", CC"("OBJ"J"OBJ")V", FN_PTR(Unsafe_SetOrderedObject)}, {CC"putOrderedInt", CC"("OBJ"JI)V", FN_PTR(Unsafe_SetOrderedInt)}, {CC"putOrderedLong", CC"("OBJ"JJ)V", FN_PTR(Unsafe_SetOrderedLong)}, {CC"park", CC"(ZJ)V", FN_PTR(Unsafe_Park)}, {CC"unpark", CC"("OBJ")V", FN_PTR(Unsafe_Unpark)} } 复制代码
compareAndSwapInt()、compareAndSwapLong()、compareAndSwapObject()
这三个方法,分别对应unsafe.cpp文件中的 Unsafe_CompareAndSwapInt()、Unsafe_CompareAndSwapLong()、Unsafe_SetOrderedObject()
。所以当我们调用 Unsafe.compareAndSwapInt()
方法时,最终会调用到unsafe.cpp文件中的 Unsafe_CompareAndSwapInt()
方法。Unsafe_CompareAndSwapInt()源码如下: UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) UnsafeWrapper("Unsafe_CompareAndSwapInt"); oop p = JNIHandles::resolve(obj); jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); // 核心代码是Atomic::cmpxchg() return (jint)(Atomic::cmpxchg(x, addr, e)) == e; UNSAFE_END 复制代码
Unsafe_CompareAndSwapInt()
的核心代码是 Atomic::cmpxchg(x, addr, e)
,它的作用最终是调用处理器的 CMPXCHG
指令。针对不同的操作系统, Atomic::cmpxchg()
有不同的实现。例如针对64位linux的系统,它最终调用的是 jdk-jdk8-b120/hotspot/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp
文件中的 Atomic::cmpxchg()
方法;针对windows操作系统,它调用的是 jdk-jdk8-b120/hotspot/src/os_cpu/windows_x86/vm/atomic_windows_x86.inline.hpp
目录下的 Atomic::cmpxchg()
方法。 Atomic::cmpxchg()
的源码如下。 inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { int mp = os::is_MP(); __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)" : "=a" (exchange_value) : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp) : "cc", "memory"); return exchange_value; } 复制代码
/__asm__
表示的是嵌入一段汇编代码, LOCK_IF_MP(%4)
表示的是向缓存或者总线加锁, cmpxchgl
是汇编语言中 比较并交换
,能保证原子操作。(关于硬件层面的指令,这里就不深入研究了,有兴趣的朋友可以去了解下) CMPXCHG
指令来实现原子操作。 CAS操作的目的是为了实现原子操作,在Java中通过CAS实现的原子操作,实际上最终还是落地在处理器上,由处理器来实现原子操作。那么处理器是如何实现原子操作的呢?
i++
的场景,进行两次i++操作,当2个CPU同时将 i
写入到内存时,最终内存中的结果是2,而我们期望的应该是3。显然这种操作结果是不正确的。 缓存锁定是指它会锁住这块内存区域的缓存并回写到内存,并使用缓存一致性机制来确保修改的原子性,缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据
。(这一句话引用的是《Java并发编程的艺术》一书汇总的第二章第2.1节第10页)这个时候由于不会锁住总线,即保证了原子操作,也保证了性能。目前处理器在某些场景下会使用缓存锁定来代替总线锁定。 CAS虽然解决了原子性,但同时它也存在着三大问题。
casPair()
方法最终也是调用了Unsafe类中的CAS方法。AtomicStampedReference类的compareAndSet()方法的源码如下。 public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; // 先检查期望的引用是否等于当前引用,期望的标识是否等于当前的标识 // 然后才执行CAS操作(casPair()也是调用了CAS方法) return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); } 复制代码
private boolean casPair(Pair<V> cmp, Pair<V> val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); } 复制代码
参考资料: