Java多線程進階(五)—— J.U.C之locks框架:LockSupport

835380f210f958dd402255a53f540091.jpg

本文首發於一世流雲的專欄: https://segmentfault.com/blog...

1、LockSupport類簡介

LockSupport類,是JUC包中的一個工具類,是用來建立鎖和其餘同步類的基本線程阻塞原語。(Basic thread blocking primitives for creating locks and other synchronization classesjava

LockSupport類的核心方法其實就兩個:park()unark(),其中park()方法用來阻塞當前調用線程,unpark()方法用於喚醒指定線程。
這其實和Object類的wait()和signial()方法有些相似,可是LockSupport的這兩種方法從語意上講比Object類的方法更清晰,並且能夠針對指定線程進行阻塞和喚醒。segmentfault

LockSupport類使用了一種名爲Permit(許可)的概念來作到阻塞和喚醒線程的功能,能夠把許可當作是一種(0,1)信號量(Semaphore),但與 Semaphore 不一樣的是,許可的累加上限是1。
初始時,permit爲0,當調用 unpark()方法時,線程的permit加1,當調用 park()方法時,若是permit爲0,則調用線程進入阻塞狀態。

1.1 使用示例

來看一個例子:
假設如今須要實現一種FIFO類型的獨佔鎖,能夠把這種鎖當作是ReentrantLock的公平鎖簡單版本,且是不可重入的,就是說當一個線程得到鎖後,其它等待線程以FIFO的調度方式等待獲取鎖。設計模式

public class FIFOMutex {
    private final AtomicBoolean locked = new AtomicBoolean(false);
    private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();
 
    public void lock() {
        Thread current = Thread.currentThread();
        waiters.add(current);
 
        // 若是當前線程不在隊首,或鎖已被佔用,則當前線程阻塞
        // NOTE:這個判斷的意圖其實就是:鎖必須由隊首元素拿到
        while (waiters.peek() != current || !locked.compareAndSet(false, true)) {
            LockSupport.park(this);
        }
        waiters.remove(); // 刪除隊首元素
    }
 
    public void unlock() {
        locked.set(false);
        LockSupport.unpark(waiters.peek());
    }
}

測試用例:api

public class Main {
    public static void main(String[] args) throws InterruptedException {
        FIFOMutex mutex = new FIFOMutex();
        MyThread a1 = new MyThread("a1", mutex);
        MyThread a2 = new MyThread("a2", mutex);
        MyThread a3 = new MyThread("a3", mutex);
 
        a1.start();
        a2.start();
        a3.start();
 
        a1.join();
        a2.join();
        a3.join();
 
        assert MyThread.count == 300;
        System.out.print("Finished");
    }
}
 
class MyThread extends Thread {
    private String name;
    private FIFOMutex mutex;
    public static int count;
 
    public MyThread(String name, FIFOMutex mutex) {
        this.name = name;
        this.mutex = mutex;
    }
 
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            mutex.lock();
            count++;
            System.out.println("name:" + name + "  count:" + count);
            mutex.unlock();
        }
    }
}

上述FIFOMutex 類的實現中,當判斷鎖已被佔用時,會調用LockSupport.park(this)方法,將當前調用線程阻塞;當使用完鎖時,會調用LockSupport.unpark(waiters.peek())方法將等待隊列中的隊首線程喚醒。多線程

經過LockSupport的這兩個方法,能夠很方便的阻塞和喚醒線程。可是LockSupport的使用過程當中還須要注意如下幾點:oracle

  1. park方法的調用通常要方法一個循環判斷體裏面。

    如上述示例中的:ide

    while (waiters.peek() != current || !locked.compareAndSet(false, true)) {
        LockSupport.park(this);
    }

    之因此這樣作,是爲了防止線程被喚醒後,不進行判斷而意外繼續向下執行,這實際上是一種Guarded Suspension的多線程設計模式。工具

  2. park方法是會響應中斷的,可是不會拋出異常。(也就是說若是當前調用線程被中斷,則會當即返回但不會拋出中斷異常)
  3. park的重載方法park(Object blocker),會傳入一個blocker對象,所謂Blocker對象,其實就是當前線程調用時所在調用對象(如上述示例中的FIFOMutex對象)。該對象通常供監視、診斷工具肯定線程受阻塞的緣由時使用。

2、LockSupport類/方法聲明

類聲明:
image.png測試

方法聲明:
image.pngthis

相關文章
相關標籤/搜索