本文首發於一世流雲的專欄: https://segmentfault.com/blog...
LockSupport類,是JUC包中的一個工具類,是用來建立鎖和其餘同步類的基本線程阻塞原語。(Basic thread blocking primitives for creating locks and other synchronization classes)java
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,則調用線程進入阻塞狀態。
來看一個例子:
假設如今須要實現一種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
park
方法的調用通常要方法一個循環判斷體裏面。
如上述示例中的:ide
while (waiters.peek() != current || !locked.compareAndSet(false, true)) { LockSupport.park(this); }
之因此這樣作,是爲了防止線程被喚醒後,不進行判斷而意外繼續向下執行,這實際上是一種Guarded Suspension的多線程設計模式。工具
park
方法是會響應中斷的,可是不會拋出異常。(也就是說若是當前調用線程被中斷,則會當即返回但不會拋出中斷異常)park(Object blocker)
,會傳入一個blocker對象,所謂Blocker對象,其實就是當前線程調用時所在調用對象(如上述示例中的FIFOMutex對象)。該對象通常供監視、診斷工具肯定線程受阻塞的緣由時使用。類聲明:測試
方法聲明:this