首頁  >  文章  >  Java  >  Java 線程狀態之 BLOCKED

Java 線程狀態之 BLOCKED

高洛峰
高洛峰原創
2016-11-22 16:20:272209瀏覽

BLOCKED 狀態的定義

前面已經說過 BLOCKED(阻塞) 的簡單定義為:

一個正在阻塞等待一個監視器鎖的執行緒處於這一狀態。 (A thread that is blocked waiting for a monitor lock is in this state.)

更詳細的定義可以參考Thread.State 中的javadoc:

/**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

這句話很長,可以拆成兩個簡單句來理解。

A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method。

一個處於 blocked 狀態的執行緒正在等待一個監視器鎖定以進入一個同步的區塊或方法。

A thread in the blocked state is waiting for a monitor lock to reenter a synchronized block/method after calling Object.wait。

一個處於 blocked 狀態的執行緒正在等待一個監視器鎖,在其呼叫 Object.wait 方法之後,以再次進入一個同步的區塊或方法。

進入(enter)同步塊時阻塞

先說第一句,這個比較好理解。

監視器鎖用於同步訪問,以達到多執行緒間的互斥。所以一旦一個執行緒取得鎖進入同步區塊,在其出來之前,如果其它執行緒想進入,就會因為取得不到鎖而阻塞在同步區塊之外,這時的狀態就是 BLOCKED。

註:此狀態的進入及解除都不受我們控制,當鎖可用時,執行緒即從阻塞狀態中恢復。

我們可以用一些程式碼來示範這個過程:

@Test
public void testBlocked() throws Exception {
    class Counter {
        int counter;
        public synchronized void increase() {
            counter++;
            try {
                Thread.sleep(30000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    Counter c = new Counter();
    
    Thread t1 = new Thread(new Runnable() {
        public void run() {
            c.increase();
        }
    }, "t1线程");
    t1.start();
    
    Thread t2 = new Thread(new Runnable() {
        public void run() {
            c.increase();
        }
    }, "t2线程");
    t2.start();
    
    Thread.sleep(100); // 确保 t2 run已经得到执行
    assertThat(t2.getState()).isEqualTo(Thread.State.BLOCKED);
}

以上定義了一個存取計數器 counter,有一個同步的 increase 方法。 t1 執行緒先進入,然後在同步區塊裡面睡覺,導致鎖遲遲無法釋放,t2 嘗試執行同步方法時就因無法取得鎖而被阻塞了。

VisualVM 監控顯示了 t2 執行緒的狀態:

Java 線程狀態之 BLOCKED

圖上的「監視(monitor)」狀態即為 BLOCKED 狀態。可以看到在t1睡眠期間t2處於 BLOCKED 狀態。

BLOCKED 狀態可以視為一種特殊的 WAITING,特別指等待鎖。

wait 之後重進入(reenter)同步區塊時阻塞

現在再來看第二句:

2. A thread in the blocked state is waiting for a monitor lock to reenter a synchronized block/methodized block/meth wait。

一個處於 blocked 狀態的執行緒正在等待一個監視器鎖,在其呼叫 Object.wait 方法之後,以再次進入一個同步的區塊或方法。

這句話有點繞,也不好翻譯成一句簡潔的中文。如果沒有對 wait 的相關背景有較好的理解,則不容易理解這句話。我們在此把它稍微展開講一下。既然是 reenter,說明有兩次 enter,這個過程是這樣的:

呼叫 wait 方法必須在同步區塊中,即是要先取得鎖定並進入同步區塊,這是第一次 enter。

而呼叫 wait 之後則會釋放該鎖,並進入此鎖的等待佇列(wait set)。

當收到其它線程的notify 或notifyAll 通知之後,等待線程並不能立即恢復執行,因為停止的地方是在同步塊內,而鎖已經釋放了,所以它要重新獲取鎖才能再次進入(reenter)同步區塊,然後從上次wait 的地方恢復執行。這是第二次 enter,所以叫 reenter。

但鎖並不會優先給它,該線程還是要與其它線程去競爭鎖,這一過程跟 enter 的過程其實是一樣的,因此也可能因為鎖已經被其它線程據有而導致 BLOCKED。

這個過程就是所謂的 reenter a synchronized block/method after calling Object.wait。

我們也用一段程式碼來示範這個過程:

@Test
public void testReenterBlocked() throws Exception {
    class Account {
        int amount = 100; // 账户初始100元
        public synchronized void deposit(int cash) { // 存钱
            amount += cash;
            notify();
            try {
                Thread.sleep(30000); // 通知后却暂时不退出
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        public synchronized void withdraw(int cash) { // 取钱
            while (cash > amount) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            amount -= cash;
        }
    }
    Account account = new Account();
    
    Thread withdrawThread = new Thread(new Runnable() {
        public void run() {
            account.withdraw(200);
        }
    }, "取钱线程");
    withdrawThread.start();
    
    Thread.sleep(100); // 确保取钱线程已经得到执行
    
    assertThat(withdrawThread.getState()).isEqualTo(Thread.State.WAITING);
    
    Thread depositThread = new Thread(new Runnable() {
        public void run() {
            account.deposit(100);
        }
    }, "存钱线程");
    Thread.sleep(10000); // 让取钱线程等待一段时间
    depositThread.start();

    Thread.sleep(300); // 确保取钱线程已经被存钱线程所通知到

    assertThat(withdrawThread.getState()).isEqualTo(Thread.State.BLOCKED);
}

簡單介紹一下以上程式碼場景:

有一個帳戶對象,有存錢(deposit)和取錢(withdraw)方法,初始金額100元。

取錢執行緒先啟動,並進入(enter)同步區塊,試圖取200元,發現錢不夠,呼叫 wait,鎖釋放,執行緒掛起(WAITING 狀態)。

10秒後存錢線程啟動,存入錢並通知(notify)取錢線程,但之後繼續在同步塊中睡眠,導致鎖沒有釋放。

取錢執行緒收到通知後,退出WA​​ITING 狀態,但已經不持有鎖,當試圖重新進入(reenter)同步區塊以恢復執行時,因鎖尚未被存錢執行緒釋放,於是被阻塞(BLOCKED 狀態)。

監控的顯示:

Java 線程狀態之 BLOCKED

如圖,取錢線程先是 WAITING,在收到通知因無法取得鎖而阻塞(BLOCKED)。

總結

綜合來看這兩句話,兩層意思,其實還是一個意思,簡單地講,就是enter,reenter 也還是enter,概括地講:

當因為獲取不到鎖而無法進入同步區塊時,執行緒處於BLOCKED 狀態。

如果有線程長時間處於 BLOCKED 狀態,要考慮是否發生了死鎖(deadlock)的狀況。

BLOCKED 狀態可以視作為一種特殊的waiting,是傳統waiting 狀態的一個細分:

Java 線程狀態之 BLOCKED

由於還沒有講到WAITING 狀態,而這裡有涉及到了wait 方法,所以上面對wait 也稍微稍微對wait做了一些分析,在下一章,會更詳細的分析WAITING 和TIMED_WAITING 這兩個狀態。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn