1. 程式人生 > >聊聊執行緒阻塞與恢復 LockSupport類

聊聊執行緒阻塞與恢復 LockSupport類

執行緒阻塞的實現方式

最近在學習Java併發包裡面各種鎖的實現以及AQS框架時,瞭解到執行緒阻塞都是通過LockSupport這樣一個類來實現的。查閱了相關資料發現早期的執行緒阻塞方式是直接藉助Thread類的suspend方法來完成。然而,現在使用suspend以及resume方法都會被提醒方法已過期,且容易導致死鎖。下面就裡面的原因進行簡單的探究。

一個Suspend方法導致死鎖的經典案例

public class SuspendTest {

    public static void main(String args[]) throws InterruptedException 
{ Thread t = new Thread(new MyTask()); t.start(); Thread.sleep(100); t.suspend(); System.out.println("resuming"); t.resume(); Thread.sleep(100); t.interrupt(); } public static class MyTask implements Runnable { @Override public
void run()
{ int count = 0; while (!Thread.currentThread().isInterrupted()) { count++; System.out.println("looping: " + count); } } } 複製程式碼

上述程式碼在執行的過程中很有可能會在System.out.println("resuming")這句語句處進入死鎖狀態。這是因為println方法是一個同步方法,在執行t.suspend()

時,執行緒t很有可能已經獲取out物件的鎖,進入了println方法。從而導致主執行緒獲取不到阻塞的t執行緒佔據的鎖,進而發生死鎖。

使用suspend方法帶來的不確定性正是因為執行緒t的阻塞是由外界控制的,也就意味t阻塞的時候執行的程式碼位置、資料狀態、鎖的資訊都是不能確定的。

public static class MyTask implements Runnable {

    @Override
    public void run() {
        boolean state = true;
        int count = 0;
        while (state && !Thread.currentThread().isInterrupted()) {
            count++;
            System.out.println("looping: " + count);
            try {
                Thread.sleep(10);
            } catch (InterruptedException ex) {
                System.out.println(ex);
                state = false;
            }
        }
    }
}
複製程式碼

我們給任務執行緒t的println方法後面加上一段睡眠時間,這樣t被阻塞時可能正在列印內容,也有可能在睡眠。主執行緒的println方法就有可能獲取到鎖,從而順利執行下去。改變睡眠的時間,程式是否會發生死鎖的概率也會改變。

LockSupport類

我們可以使用LockSupport類來實現執行緒的阻塞與恢復,相關的方法是parkunpark

public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}
複製程式碼

park方法可以接收一個阻塞塊物件(許可),並關聯到阻塞的執行緒上。unpark的引數為需要解除阻塞的執行緒。我們可以看到LockSupport只能夠阻塞當前執行緒,也就意味著執行緒在何處進入阻塞狀態是由自己決定的。下面通過這種方式實現前面的列印程式。

public class SuspendTest {

    public static void main(String args[]) throws InterruptedException {
        Thread t = new Thread(new MyTask());
        t.start();
        Thread.sleep(100);
        System.out.println("resuming");
        LockSupport.unlock(t);
        Thread.sleep(100);
        t.interrupt();
    }

    public static class MyTask implements Runnable {

        @Override
        public void run() {
            int count = 0;
            while (!Thread.currentThread().isInterrupted()) {
                count++;
                System.out.println("looping: " + count);
                LockSupport.lock(this);
        }
    }
}
複製程式碼

上述程式碼沒有死鎖問題,t執行緒進入阻塞狀態是在println執行之後。相比suspend方式缺少一定的靈活性,但是執行緒狀態更加具有確定性。執行緒沒有模糊狀態,那麼死鎖的發生就可能是由程式本身的設計帶來的,比如我們線上程釋放相關鎖之前執行LockSupport.lock()進入阻塞狀態,如下所示,那麼死鎖就一定會發生。

public class SuspendTest {

    public static ReentrantLock lock = new ReentrantLock();

    public static void main(String args[]) throws InterruptedException {
        Thread t = new Thread(new MyTask());
        t.start();
        Thread.sleep(100);
        lock.lock();
        System.out.println("resuming");
        lock.unlock();
        LockSupport.unlock(t);
        Thread.sleep(100);
        t.interrupt();
    }

    public static class MyTask implements Runnable {

        @Override
        public void run() {
            int count = 0;
            while (!Thread.currentThread().isInterrupted()) {
                count++;
                lock.lock();
                System.out.println("looping: " + count);
                LockSupport.lock(this);
                lock.unlock();
        }
    }
}
複製程式碼