【JUC】JDK1.8原始碼分析之LockSupport(一)

leesf發表於2016-04-02

一、前言

  最開始打算分析ReentrantLock,但是分析到最後,發現離不開LockSuport的支援,所以,索性就先開始分析LockSupport,因為它是鎖中的基礎,是一個提供鎖機制的工具類,所以先對其進行分析。

二、LockSupport原始碼分析

  2.1 類的屬性 

public class LockSupport {
    // Hotspot implementation via intrinsics API
    private static final sun.misc.Unsafe UNSAFE;
    // 表示記憶體偏移地址
    private static final long parkBlockerOffset;
    // 表示記憶體偏移地址
    private static final long SEED;
    // 表示記憶體偏移地址
    private static final long PROBE;
    // 表示記憶體偏移地址
    private static final long SECONDARY;
    
    static {
        try {
            // 獲取Unsafe例項
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            // 執行緒類型別
            Class<?> tk = Thread.class;
            // 獲取Thread的parkBlocker欄位的記憶體偏移地址
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
            // 獲取Thread的threadLocalRandomSeed欄位的記憶體偏移地址
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            // 獲取Thread的threadLocalRandomProbe欄位的記憶體偏移地址
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            // 獲取Thread的threadLocalRandomSecondarySeed欄位的記憶體偏移地址
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception ex) { throw new Error(ex); }
    }
}
View Code

  說明:UNSAFE欄位表示sun.misc.Unsafe類,檢視其原始碼,點選在這裡,一般程式中不允許直接呼叫,而long型的表示例項物件相應欄位在記憶體中的偏移地址,可以通過該偏移地址獲取或者設定該欄位的值。

  2.2 類的建構函式 

// 私有建構函式,無法被例項化
private LockSupport() {}

  說明:LockSupport只有一個私有建構函式,無法被例項化。

  2.3 核心函式分析

  在分析LockSupport函式之前,先引入sun.misc.Unsafe類中的park和unpark函式,因為LockSupport的核心函式都是基於Unsafe類中定義的park和unpark函式,下面給出兩個函式的定義。  

public native void park(boolean isAbsolute, long time);
public native void unpark(Thread thread);

  說明:對兩個函式的說明如下

  ① park函式,阻塞執行緒,並且該執行緒在下列情況發生之前都會被阻塞:① 呼叫unpark函式,釋放該執行緒的許可。② 該執行緒被中斷。③ 設定的時間到了。並且,當time為絕對時間時,isAbsolute為true,否則,isAbsolute為false。當time為0時,表示無限等待,直到unpark發生。

  ② unpark函式,釋放執行緒的許可,即啟用呼叫park後阻塞的執行緒。這個函式不是安全的,呼叫這個函式時要確保執行緒依舊存活。

  1. park函式 

  park函式有兩個過載版本,方法摘要如下  

public static void park();
public static void park(Object blocker);

  說明:兩個函式的區別在於park()函式沒有沒有blocker,即沒有設定執行緒的parkBlocker欄位。park(Object)型函式如下。

public static void park(Object blocker) {
        // 獲取當前執行緒
        Thread t = Thread.currentThread();
        // 設定Blocker
        setBlocker(t, blocker);
        // 獲取許可
        UNSAFE.park(false, 0L);
        // 重新可執行後再此設定Blocker
        setBlocker(t, null);
    }
View Code

  說明:呼叫park函式時,首先獲取當前執行緒,然後設定當前執行緒的parkBlocker欄位,即呼叫setBlocker函式,之後呼叫Unsafe類的park函式,之後再呼叫setBlocker函式。那麼問題來了,為什麼要在此park函式中要呼叫兩次setBlocker函式呢?原因其實很簡單,呼叫park函式時,當前執行緒首先設定好parkBlocker欄位,然後再呼叫Unsafe的park函式,此後,當前執行緒就已經阻塞了,等待該執行緒的unpark函式被呼叫,所以後面的一個setBlocker函式無法執行,unpark函式被呼叫,該執行緒獲得許可後,就可以繼續執行了,也就執行第二個setBlocker,把該執行緒的parkBlocker欄位設定為null,這樣就完成了整個park函式的邏輯。如果沒有第二個setBlocker,那麼之後沒有呼叫park(Object blocker),而直接呼叫getBlocker函式,得到的還是前一個park(Object blocker)設定的blocker,顯然是不符合邏輯的。總之,必須要保證在park(Object blocker)整個函式執行完後,該執行緒的parkBlocker欄位又恢復為null。所以,park(Object)型函式裡必須要呼叫setBlocker函式兩次。setBlocker方法如下。 

private static void setBlocker(Thread t, Object arg) {
        // 設定執行緒t的parkBlocker欄位的值為arg
        UNSAFE.putObject(t, parkBlockerOffset, arg);
    }
View Code

  說明:此方法用於設定執行緒t的parkBlocker欄位的值為arg。

  另外一個無參過載版本,park()函式如下。  

public static void park() {
    // 獲取許可,設定時間為無限長,直到可以獲取許可
        UNSAFE.park(false, 0L);
}
View Code

  說明:呼叫了park函式後,會禁用當前執行緒,除非許可可用。在以下三種情況之一發生之前,當前執行緒都將處於休眠狀態,即下列情況發生時,當前執行緒會獲取許可,可以繼續執行。

  ① 其他某個執行緒將當前執行緒作為目標呼叫 unpark。

  ② 其他某個執行緒中斷當前執行緒。

  ③ 該呼叫不合邏輯地(即毫無理由地)返回。

  2. parkNanos函式

  此函式表示在許可可用前禁用當前執行緒,並最多等待指定的等待時間。具體函式如下。

public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) { // 時間大於0
            // 獲取當前執行緒
            Thread t = Thread.currentThread();
            // 設定Blocker
            setBlocker(t, blocker);
            // 獲取許可,並設定了時間
            UNSAFE.park(false, nanos);
            // 設定許可
            setBlocker(t, null);
        }
    }
View Code

  說明:該函式也是呼叫了兩次setBlocker函式,nanos參數列示相對時間,表示等待多長時間。

  3. parkUntil函式

  此函式表示在指定的時限前禁用當前執行緒,除非許可可用。具體函式如下。  

public static void parkUntil(Object blocker, long deadline) {
        // 獲取當前執行緒
        Thread t = Thread.currentThread();
        // 設定Blocker
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        // 設定Blocker為null
        setBlocker(t, null);
    }
View Code

  說明:該函式也呼叫了兩次setBlocker函式,deadline參數列示絕對時間,表示指定的時間。

  4. unpark函式

  此函式表示如果給定執行緒的許可尚不可用,則使其可用。如果執行緒在 park 上受阻塞,則它將解除其阻塞狀態。否則,保證下一次呼叫 park 不會受阻塞。如果給定執行緒尚未啟動,則無法保證此操作有任何效果。具體函式如下。  

public static void unpark(Thread thread) {
        if (thread != null) // 執行緒為不空
            UNSAFE.unpark(thread); // 釋放該執行緒許可
    }
View Code

  說明:釋放許可,指定執行緒可以繼續執行。

三、示例說明

  3.1 實現兩執行緒同步

  1. 使用wait/notify實現  

package com.hust.grid.leesf.locksupport;

class MyThread extends Thread {
    
    public void run() {
        synchronized (this) {
            System.out.println("before notify");            
            notify();
            System.out.println("after notify");    
        }
    }
}

public class WaitAndNotifyDemo {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();            
        synchronized (myThread) {
            try {        
                myThread.start();
                // 主執行緒睡眠3s
                Thread.sleep(3000);
                System.out.println("before wait");
                // 阻塞主執行緒
                myThread.wait();
                System.out.println("after wait");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }            
        }        
    }
}
View Code

  執行結果 

before wait
before notify
after notify
after wait

  說明:具體的流程圖如下

  

  使用wait/notify實現同步時,必須先呼叫wait,後呼叫notify,如果先呼叫notify,再呼叫wait,將起不了作用。具體程式碼如下  

package com.hust.grid.leesf.locksupport;

class MyThread extends Thread {
    public void run() {
        synchronized (this) {
            System.out.println("before notify");            
            notify();
            System.out.println("after notify");    
        }
    }
}

public class WaitAndNotifyDemo {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();        
        myThread.start();
        // 主執行緒睡眠3s
        Thread.sleep(3000);
        synchronized (myThread) {
            try {        
                System.out.println("before wait");
                // 阻塞主執行緒
                myThread.wait();
                System.out.println("after wait");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }            
        }        
    }
}
View Code

  執行結果:  

before notify
after notify
before wait

  說明:由於先呼叫了notify,再呼叫的wait,此時主執行緒還是會一直阻塞。

  3.2 使用park/unpark實現 

package com.hust.grid.leesf.entry;

import java.util.concurrent.locks.LockSupport;

class MyThread extends Thread {
    private Object object;

    public MyThread(Object object) {
        this.object = object;
    }

    public void run() {
        System.out.println("before unpark");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 獲取blocker
        System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object));
        // 釋放許可
        LockSupport.unpark((Thread) object);
        // 休眠500ms,保證先執行park中的setBlocker(t, null);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 再次獲取blocker
        System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object));

        System.out.println("after unpark");
    }
}

public class test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread(Thread.currentThread());
        myThread.start();
        System.out.println("before park");
        // 獲取許可
        LockSupport.park("ParkAndUnparkDemo");
        System.out.println("after park");
    }
}
View Code

  執行結果:  

before park
before unpark
Blocker info ParkAndUnparkDemo
after park
Blocker info null
after unpark

  說明:本程式先執行park,然後在執行unpark,進行同步,並且在unpark的前後都呼叫了getBlocker,可以看到兩次的結果不一樣,並且第二次呼叫的結果為null,這是因為在呼叫unpark之後,執行了Lock.park(Object blocker)函式中的setBlocker(t, null)函式,所以第二次呼叫getBlocker時為null。

  上例是先呼叫park,然後呼叫unpark,現在修改程式,先呼叫unpark,然後呼叫park,看能不能正確同步。具體程式碼如下  

package com.hust.grid.leesf.locksupport;

import java.util.concurrent.locks.LockSupport;

class MyThread extends Thread {
    private Object object;

    public MyThread(Object object) {
        this.object = object;
    }

    public void run() {
        System.out.println("before unpark");        
        // 釋放許可
        LockSupport.unpark((Thread) object);
        System.out.println("after unpark");
    }
}

public class ParkAndUnparkDemo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread(Thread.currentThread());
        myThread.start();
        try {
            // 主執行緒睡眠3s
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("before park");
        // 獲取許可
        LockSupport.park("ParkAndUnparkDemo");
        System.out.println("after park");
    }
}
View Code

  執行結果:

before unpark
after unpark
before park
after park

  說明:可以看到,在先呼叫unpark,再呼叫park時,仍能夠正確實現同步,不會造成由wait/notify呼叫順序不當所引起的阻塞。因此park/unpark相比wait/notify更加的靈活。

  2. 中斷響應

  看下面示例  

package com.hust.grid.leesf.locksupport;

import java.util.concurrent.locks.LockSupport;

class MyThread extends Thread {
    private Object object;

    public MyThread(Object object) {
        this.object = object;
    }

    public void run() {
        System.out.println("before interrupt");        
        try {
            // 休眠3s
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }    
        Thread thread = (Thread) object;
        // 中斷執行緒
        thread.interrupt();
        System.out.println("after interrupt");
    }
}

public class InterruptDemo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread(Thread.currentThread());
        myThread.start();
        System.out.println("before park");
        // 獲取許可
        LockSupport.park("ParkAndUnparkDemo");
        System.out.println("after park");
    }
}
View Code

  執行結果: 

before park
before interrupt
after interrupt
after park

  說明:可以看到,在主執行緒呼叫park阻塞後,在myThread執行緒中發出了中斷訊號,此時主執行緒會繼續執行,也就是說明此時interrupt起到的作用與unpark一樣。

四、總結

  LockSupport用來建立鎖和其他同步類的基本執行緒阻塞原語。簡而言之,當呼叫LockSupport.park時,表示當前執行緒將會等待,直至獲得許可,當呼叫LockSupport.unpark時,必須把等待獲得許可的執行緒作為引數進行傳遞,好讓此執行緒繼續執行。

  經過研究LockSupport原始碼,對LockSupport的工作機制有了詳細的瞭解,閱讀原始碼受益匪淺,謝謝各位園友觀看~

相關文章