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

1、前言html

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

2、LockSupport源碼分析api

  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型的表示實例對象相應字段在內存中的偏移地址,能夠經過該偏移地址獲取或者設置該字段的值。dom

  2.2 類的構造函數 ide

// 私有構造函數,沒法被實例化
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);

  說明:對兩個函數的說明以下this

  ① 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、示例說明

  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同樣。

4、總結

  LockSupport用來建立鎖和其餘同步類的基本線程阻塞原語。簡而言之,當調用LockSupport.park時,表示當前線程將會等待,直至得到許可,當調用LockSupport.unpark時,必須把等待得到許可的線程做爲參數進行傳遞,好讓此線程繼續運行。

  通過研究LockSupport源碼,對LockSupport的工做機制有了詳細的瞭解,閱讀源碼受益不淺,謝謝各位園友觀看~

相關文章
相關標籤/搜索