Java併發核心淺談

耐心看完的你或多或少會有收穫!html

Java併發的核心就是 java.util.concurrent 包,而 j.u.c 的核心是AbstractQueuedSynchronizer抽象隊列同步器,簡稱 AQS,一些鎖啊!信號量啊!循環屏障啊!都是基於AQS。而 AQS 又是基於Unsafe的一系列compareAndSwap,因此理解了這塊,併發再也不是問題!java

何爲 CAS

先解釋下何爲compareAndSwap,就拿AtomicInteger來舉例了:c++

// 實際操做的值
	private volatile int value;
	// value 的偏移量 由於 int 是32位,知道首部地址就能夠了
	private static final long valueOffset;
	// 靜態初始化塊,經過虛擬機提供的接口,得到 valueOffset = 12
	// 不論你實例化多少個 AtomicInteger 對象,這些對象儘管指向不一樣的堆內存,可是結構都是同樣的
	// 因此初始化一次就行了
	static {
	     try {
	         valueOffset = unsafe.objectFieldOffset
	             (AtomicInteger.class.getDeclaredField("value"));
	     } catch (Exception ex) { throw new Error(ex); }
	 }
	 
	// 只是個封裝方法,起做用的代碼並不在這
	// 值得注意的是顯式的 this 和第三個參數 1
	public final int getAndIncrement() {
	        return unsafe.getAndAddInt(this, valueOffset, 1);
	}
	
	// 如下是 Unsafe 類 能夠直接訪問內存地址,相似指針,因此不安全
	// o 就是 getAndIncrement()傳入的 this,也就是 AtomicInteger 實例對象
	// offset 內存首部偏移量
	// delta 就是那個 1
	// 應該是希臘字母 δ /'deltə/ delta 變化量,化學反應中的加熱,屈光度,一元二次方程中的判別式
	// 佩服

	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;
	}
	
	// 從堆內存獲取最新的 value
	// 若是不明白,能夠先了解下 JMM 和 volatile
	public native int getIntVolatile(Object o, long offset);
	
	// expected 就是這個 v = getIntVolatile(o, offset);
	// 意思就是,我給你這個最新的 value,它要是如今 在內存中仍是這個值 那你就返回 true,而且把這塊內存上值更新爲 x 
	// 否則的話,我就一直 while (!compareAndSwapInt(o, offset, v, v + delta));
	// 至關於自旋鎖 活鎖,不要被高大上的術語嚇到 就是活的循環,不會像死鎖那樣線程 hang 住
	public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
複製代碼

何爲 Lock

知道了CAS就能夠進一步的說說ReentrantLock, 若是未曾瞭解Java對象結構,建議先了解下 Java 對象的內存結構安全

AQS的幾個子類,因爲方法和功能不一,在同步處理的細節上可能不同。可是,原理都是同樣的,離不開上述的CAS多線程

AQS

// 我先簡單解釋一下 synchronized 工做原理 首先它含有 monitorenter 和 monitorexit 兩條指令
	// Java 中的每一個對象都有本身的 Monitor(由HotSpot c++ 實現)
	// 當某個線程進入加鎖的代碼(實際上應該是拿到被加鎖的對象在內存的引用地址)
	// 會執行 monitorenter 而後將 monitor 置爲1,當其它線程訪問該內存時,發現 monitor 不爲 0
	// 因此其它線程沒法得到 monitor,直到佔有 monitor 的線程執行 monitorexit 退出將 monitor 減 1
	// 若是佔有 monitor 的線程重複進入,monitor 是能夠一直累加的(可重入鎖,例如經過遞歸或方法互調)
	// 瞭解了 synchronized 基本的工做原理,就會明白爲何會有諸如 nonfairTryAcquire(1) release(1) 的方法
	
	// 這是 AbstractQueuedSynchronizer 類中的字段
	// 由於 ReentrantLock 中的內部類 Sync 繼承於 AQS
	// The synchronization state
	private volatile int state;
	
	// tryLock why ?
	// 由於不一樣於 synchronized 的悲觀(我才無論你是否是併發,多線程,聲明瞭,我就加鎖)
	// 因此 ReentrantLock 我先 try 一 try 吧!萬一不是多線程併發呢!🤣
	public boolean tryLock() {
	        // 加鎖 加 1
	        return sync.nonfairTryAcquire(1);
	}
	
	public void unlock() {
	    // 釋放鎖 減 1
        sync.release(1);
	}

	final boolean nonfairTryAcquire(int acquires) {
           final Thread current = Thread.currentThread();
           int c = getState();
           // 0 的話 說明沒有線程佔有
           // 能夠得到鎖
           if (c == 0) {
               // 這個和上面的 AtomicInteger 同樣
               if (compareAndSetState(0, acquires)) {
               	   // 設置當前佔有鎖的線程
                   setExclusiveOwnerThread(current);
                   return true;
               }
           }
           else if (current == getExclusiveOwnerThread()) {
               int nextc = c + acquires;
               // 看見沒,鎖的計數有可能會有問題
               // 由於鎖的可重入,一直累加,指不定就加到 int 上限轉負數了
               if (nextc < 0) // overflow
                   throw new Error("Maximum lock count exceeded");
               // 不小於 0 更新 state
               setState(nextc);
               return true;
           }
           return false;
	  }
	  
	// AbstractOwnableSynchronizer 類
 	// The current owner of exclusive mode synchronization.
 	// 排它鎖 獨佔鎖 寫鎖 都一個意思 鎖的當前持有線程
 	private transient Thread exclusiveOwnerThread;
複製代碼

ObjectMonitor

Unsafe monitor 的相關方法,已棄用併發

/** Lock the object. It must get unlocked via {@link #monitorExit}. */
    @Deprecated
    public native void monitorEnter(Object o);

    /** * Unlock the object. It must have been locked via {@link * #monitorEnter}. */
    @Deprecated
    public native void monitorExit(Object o);

    /** * Tries to lock the object. Returns true or false to indicate * whether the lock succeeded. If it did, the object must be * unlocked via {@link #monitorExit}. */
    @Deprecated
    public native boolean tryMonitorEnter(Object o);
複製代碼

如下爲 hotspot c++ 源碼的一部分代碼,想追根溯源的能夠了解下 hotspot 源碼(頁面左側提供下載和瀏覽的連接)ui

// Enter support
void ObjectMonitor::enter(TRAPS) {
  // The following code is ordered to check the most common cases first
  // and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
  Thread * const Self = THREAD;

  void * cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL);
  if (cur == NULL) {
    // Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
    assert(_recursions == 0, "invariant");
    assert(_owner == Self, "invariant");
    return;
  }

  if (cur == Self) {
    // TODO-FIXME: check for integer overflow! BUGID 6557169. // 看來出現過 count overflow bug
    _recursions++; // recursion 遞歸說明了可重入
    return;
  }
  // 省略
}

void ObjectMonitor::exit(bool not_suspended, TRAPS) {
  Thread * const Self = THREAD;
  if (THREAD != _owner) {
    if (THREAD->is_lock_owned((address) _owner)) {
      // Transmute _owner from a BasicLock pointer to a Thread address.
      // We don't need to hold _mutex for this transition.
      // Non-null to Non-null is safe as long as all readers can
      // tolerate either flavor.
      assert(_recursions == 0, "invariant");
      _owner = THREAD;
      _recursions = 0;
    } else {
      // Apparent unbalanced locking ...
      // Naively we'd like to throw IllegalMonitorStateException.
      // As a practical matter we can neither allocate nor throw an
      // exception as ::exit() can be called from leaf routines.
      // see x86_32.ad Fast_Unlock() and the I1 and I2 properties.
      // Upon deeper reflection, however, in a properly run JVM the only
      // way we should encounter this situation is in the presence of
      // unbalanced JNI locking. TODO: CheckJNICalls.
      // See also: CR4414101
      TEVENT(Exit - Throw IMSX);
      assert(false, "Non-balanced monitor enter/exit! Likely JNI locking");
      return;
    }
  }

  if (_recursions != 0) {
    _recursions--;        // this is simple recursive enter
    TEVENT(Inflated exit - recursive);
    return;
  }
  // 省略
}
複製代碼

總結一下(感概)

Doug Lea 真正的大師,從他的代碼中能夠看出對於細節的處理與把控,以及對於我等代碼閱讀者的友好this

大道至簡,誰能想到 Java 的併發支持是基於一些加 1 減 1 的運算spa

synchronized的 Monitor 和各類鎖的 tryAcquire(1) tryRelease(1)是否是很像🤣.net

若是有幫助你理解併發,那麼你也能夠再結合源碼好好感覺下,請注意tryAcquire(1) tryRelease(1)這兩種方法(方法名或參數可能略有不一樣)

相關文章
相關標籤/搜索