Java高併發20-併發包中鎖原理解析(二)

一、例子

  • 下面來一個例子加深對park和unpark的理解
package com.ruigege.LockSourceAnalysis6;

import java.util.concurrent.locks.LockSupport;

public class TestParkAndUnpark {
 public static void main(String[] args) throws InterruptedException {
  Thread thread = new Thread(new Runnable() {
   @Override
   public void run() {
    System.out.println("child thread begin park");
    //調用park方法,掛起自己
    LockSupport.park();
    System.out.println("child thread end park");
    System.out.println("今天又學了一個快捷鍵,sysout + alt +/ 是控制檯" + 
    "輸出的一個快捷鍵");
   }
  });
  
  // 啓動子線程
  thread.start();
  Thread.sleep(1000); // 主線程休眠一秒鐘,目的是能夠讓子線程及時使用
  System.out.println("main thread begin unpark");
  LockSupport.unpark(thread); // 調用unpark方法,能夠讓子線程thread持有許可證,
  // 然後park方法返回
 }
}
  • 下面來解釋一下這個類的主要功效
  • 首先建立了一個子線程,然後調用park方法,由於默認情況下,子線程沒有持有許可證,因此它會把自己掛起;在主線程中執行了unpark方法,參數爲子線程,這樣做的目的就是讓子線程能夠持有許可證,然後子線程調用的park方法就會返回
  • 注意點:park方法不會告訴我們是因爲哪種原因返回的,因此調用者需要根據之前調用park方法的原因,再次檢查條件是否滿足,如果不滿足的話,還需再次調用park方法
  • 例如:根據調用前後的中斷狀態的對比可以判斷是不是因爲被中斷才返回的。
  • 下面爲了說明調用park方法後的線程是因爲被中斷才返回的,我們修改代碼
package com.ruigege.LockSourceAnalysis6;

import java.util.concurrent.locks.LockSupport;

public class TestParkAndUnpark {
 public static void main(String[] args) throws InterruptedException {
  Thread thread = new Thread(new Runnable() {
   @Override
   public void run() {
    System.out.println("child thread begin park");
    //調用park方法,掛起自己
//    LockSupport.park();
    while(!Thread.currentThread().isInterrupted()) {
     LockSupport.park();
    }
    System.out.println("child thread end park");
    System.out.println("今天又學了一個快捷鍵,sysout + alt +/ 是控制檯" + 
    "輸出的一個快捷鍵");
   }
  });
  
  // 啓動子線程
  thread.start();
  Thread.sleep(1000); // 主線程休眠一秒鐘,目的是能夠讓子線程及時使用
  System.out.println("main thread begin unpark");
//  LockSupport.unpark(thread); // 調用unpark方法,能夠讓子線程thread持有許可證,
  // 然後park方法返回
  
  thread.interrupt();
 }
}
20.1
20.1
  • 我們可以從中看出,如果只有中斷了子線程,子線程纔會運行結束,如果子線程不中斷的話,即使調用了LockSupport(thread)方法,也不會中斷。

二、void parkNanos(long nanos)方法

  • 與park方法相類似,如果該線程沒有拿到許可證,那麼調用parkNanos(long nanos)方法該線程會立即停止阻塞,並返回;如果有許可證,那麼nanos毫秒之後,該線程纔會返回。
  • 先舉個例子
package com.ruigege.LockSourceAnalysis6;

import java.util.concurrent.locks.LockSupport;

public class TestPark {
 public void testPark() {
  LockSupport.park();
 }
 public static void main(String[] args) {
  System.out.println("開始park方法");
  TestPark testPark = new TestPark();
  testPark.testPark();
 }

}
20.2
20.2
  • 下面是我們使用parkNanos方法來代替LockSupport.park()方法
  LockSupport.park(this);
  • 使用這個帶參數的park(Object blocker)方法,當線程在沒有持有許可證的時候,調用park方法,會被阻塞起來,這個blocker對象會被記錄到該線程的內部。
  • 使用jstack pid命令可以對線程堆棧進行查看,該線程內部是含有的什麼對象

三、park(Object blocker)源碼解析

 public static void park2(Object blocker) {
  // 獲取當前線程
  Thread t = Thread.currentThread();
  // Thread對象中有一個volatile Object blocker
  // 這裏調用setter方法,把這個blocker記錄到該線程的blocker對象中
  setBlocker(t,blocker);
  // 調用park方法對該線程進行阻塞
  UNSAFE.park(false,0L); // UNSAFE其實是該線程的Unsafe變量,我們
  // 這裏省略前面的定義,直接拿來解釋
  setBlocker(t,null);
  // 最後我們又把blocker對象置爲空,這是因爲已經停止阻塞了
  // 這個blocker對象多用於線程阻塞的時候用來分析原因用的
 }
  • 基本都寫在了方法的解釋之後

四、void parkNanos(Object blocker,long nanos)方法

  • 其實就是多了一個可以設置的超時時間

五、void parkUtil(Object blocker,long deadline)方法

  • 這個方法和parkNanos不同的就是超時時間的算法,parkNanos的超時時間是從線程阻塞開始算起的,而parkUtil方法的超時時間是從1970年開始算起,到某一個時間點的毫秒數
 public static void parkUtile(Object blocker,long deadline) {
  Thread t = Thread.currentThread();
  setBlocker(t,blocker);
  UNSAFE.park(false,deadline);
  setBlocker(t,null);
 }

六、下面再看一個例子

package com.ruigege.LockSourceAnalysis6;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;

public class FIFOMutex {
 private final AtomicBoolean locked = new AtomicBoolean(false); // 一個boolean類的鎖
 private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>(); // 一個高併發隊列
 
 public void lock() {
  boolean wasInterrupted = false// 中斷的標誌
  Thread current = Thread.currentThread();
  waiters.add(current); // 隊列中添加這個線程
  // (1)
  while(waiters.peek() != current || !locked.compareAndSet(false,true)) {
   // 複習compareAndSet方法,第一個參數是期盼的值,第二個就是如果就是期盼的值,那麼就
   // 設置爲第二個參數,然後返回true
   LockeSupport.park(this);
   if(Thread.interrupted()) { // (2)
    wasInterrupted = true;
   } 
  }
  waiters.remove();
  if(wasInterrupted) { // (3)
   current.interrupt();
  }
 }
 public void unlock() {
  locked.set(false);
  LockSupport.unpark(waiters.peek());
 }
}
  • 這是一個先進先出的鎖,也就是隻有隊列的首元素可以獲取鎖,在代碼(1)如果當前線程不是隊首或者當前鎖已經被其他線程獲取,那麼調用park方法掛起自己。
  • 然後再代碼(2)處做判斷,如果park方法是因爲被中斷而返回的,則忽略中斷,並且重置中斷標誌,複習該方法去
  • 在代碼(3)中,判斷標記,如果標記爲true那麼中斷該線程
  • 總結:其實就是其他線程中斷了該線程,雖然我對中斷信號不感興趣,忽略它(也就是代碼(2)),但是不代表其他線程對該標誌不感興趣,我們還需要恢復一下。

七、源碼:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章