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

1、例子

  • 下面來一個例子加深對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)方法,也不會中斷。

2、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命令能夠對線程堆棧進行查看,該線程內部是含有的什麼對象

3、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對象多用於線程阻塞的時候用來分析緣由用的
 }
  • 基本都寫在了方法的解釋以後

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

  • 其實就是多了一個能夠設置的超時時間

5、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);
 }

6、下面再看一個例子

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)),可是不表明其餘線程對該標誌不感興趣,咱們還須要恢復一下。

7、源碼:

相關文章
相關標籤/搜索