(鎖) 系列篇
1、synchronized實現 |
(1)synchronized實現同步的基礎:Java中的每一個對象都可以作為鎖。具體表現為以下3種形式:
- 對於普通同步方法,鎖是當前實例對象。
public class SynchronizedTest { // ...... public synchronized void test1(){ } }
- 對於靜態同步方法,鎖是當前類的Class對象。
public class SynchronizedTest { // ...... public static synchronized void test2(){ } }
- 對於同步方法塊,鎖是Synchonized括號里配置的對象。
public class SynchronizedTest { // ...... Object lock = new Object() ; public void test3(){ synchronized (lock){ } } }
(2)對於SynchronizedTest.java使用javap反解析出匯編指令(open-jdk 1.8.0):
javac SynchronizedTest.java
javap -v SynchronizedTest.class
// ..............
{ //....... public synchronized void test1(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 7: 0 public static synchronized void test2(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED Code: stack=0, locals=0, args_size=0 0: return LineNumberTable: line 11: 0 public void test3(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: aload_0 1: getfield #3 // Field lock:Ljava/lang/Object; 4: dup 5: astore_1 6: monitorenter 7: aload_1 8: monitorexit 9: goto 17 12: astore_2 13: aload_1 14: monitorexit 15: aload_2 16: athrow 17: return Exception table: from to target type 7 9 12 any 12 15 12 any LineNumberTable: line 15: 0 line 17: 7 line 18: 17 StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 12 locals = [ class com/nancy/sync/SynchronizedTest, class java/lang/Object ] stack = [ class java/lang/Throwable ] frame_type = 250 /* chop */ offset_delta = 4 } SourceFile: "SynchronizedTest.java"
由此看出同步方法使用ACC_SYNCHRONIZED標識進入該方法需要同步,同塊代碼塊使用monitorenter / monitorexit來標識同步代碼塊保護區。雖然實現的形式不一致,實質都是使用moniter對象頭Mark Word的標志位實現。
(3)synchronized特點
- 重量鎖高並發場景下效率低下(早期依賴於底層的操作系統的Mutex Lock來實現,需要內核態和用戶態之間轉換)
- 保證線程對變量訪問的可見性、排他性、原子性
可見性:修改變量保證其他線程可見
排他性:同一時刻只能一個線程訪問
原子性:對變量的操作保證原子執行,例如:i++就是經典的讀改寫操作
2、cas(Compare And Swap) |
(1)cas實現
cas類似樂觀鎖,利用操作系統指令原子執行操作,一般結合自旋實現。例如
AtomicInteger#getAndIncrement方法:
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // 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); } } private volatile int value; public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } // ..... } public final class Unsafe { public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; } /** Volatile version of {@link #getInt(Object, long)} */ public native int getIntVolatile(Object o, long offset); /** * Atomically update Java variable to <tt>x</tt> if it is currently * holding <tt>expected</tt>. * @return <tt>true</tt> if successful */ public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); // ......... }
通過不斷自旋實現原子更新操作,可想高並發場景下雖去掉了鎖的概念,但過多無效空旋也會占用CPU時間。
(2)cas實現副作用:可能引發典型"A-B-A問題":
cas執行操作時會檢查該值與期望值A是否一致(即檢查內存中某個地址變量的值),如果一致則更新為C。但如果值從原來是A,變成B,又變成了A,
那么cas進行檢查時不會發現它的值發生變化,而實際已經發生了變化(1A-2B-3A),即cas的"A-B-A問題"。
解決方式即使用版本號或者時間戳,發現不一致即更新失敗。例如:AtomicStampedReference引入stamp時間戳解決"A-B-A問題":

/** * An {@code AtomicStampedReference} maintains an object reference * along with an integer "stamp", that can be updated atomically. * * <p>Implementation note: This implementation maintains stamped * references by creating internal objects representing "boxed" * [reference, integer] pairs. * * @since 1.5 * @author Doug Lea * @param <V> The type of object referred to by this reference */ public class AtomicStampedReference<V> { private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } } private volatile Pair<V> pair; // ...... public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe(); private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class); private boolean casPair(Pair<V> cmp, Pair<V> val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); } static long objectFieldOffset(sun.misc.Unsafe UNSAFE, String field, Class<?> klazz) { try { return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field)); } catch (NoSuchFieldException e) { // Convert Exception to corresponding Error NoSuchFieldError error = new NoSuchFieldError(field); error.initCause(e); throw error; } } }
3、synchronized和cas二者區別 |
- synchronized為鎖機制隱含着獲鎖和解鎖的操作。利用操作系統提供支持實現訪問競爭資源的順序性,早期JDK版本未對synchronized優化並發效率低。后期引入輕量級鎖、偏向鎖等概念,大大優化性能。優勢在於使用簡單,維護成本低。
- cas實現了lock-free的概念,不引入鎖也可解決並發場景問題。如果單純使用cas會引起多重副作用,高級並發包下配合AQS一起可提升並發效率。
- 如果單獨使用,二者都偏向並發量不高或者業務簡單的場景。例如:單體web應用單調計數場景。