1. 程式人生 > >初見Java多執行緒(三、執行緒的阻塞狀態)

初見Java多執行緒(三、執行緒的阻塞狀態)

1. 阻止執行緒執行

對於執行緒的阻塞狀態,考慮一下三個方面,不考慮IO阻塞的情況:
睡眠;
等待;
因為需要一個物件的鎖定而被阻塞。

2. 睡眠

Thread.sleep(long millis);
Thread.sleep(long millis, int nanos);

該靜態方法強制當前正在執行的執行緒休眠(暫停執行),以減慢執行緒。當執行緒睡眠時,它睡在某個地方,在它甦醒之前不會返回到可執行狀態。當睡眠時間到了,則該執行緒返回到可執行狀態。

執行緒睡眠的原因:執行緒執行太快,或者需要強制進入下一輪,因為Java規範不保證合理的輪換。

睡眠的位置:為了讓其他執行緒有機會執行,可以將Thread.sleep()的呼叫放執行緒run()之內。這樣才能保證該執行緒執行過程中會睡眠。

看下面的例子:

public class SleepDemo {

    public static void main(String[] args) {

        Thread thread = new Thread(new My_thread());
        thread.setName("my_thread");
        thread.start();

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-------->"
+ i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } class My_thread implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "-------->"
+ i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }

輸出結果:

my_thread-------->0
main-------->0
main-------->1
my_thread-------->1
main-------->2
my_thread-------->2
main-------->3
my_thread-------->3
main-------->4
my_thread-------->4
main-------->5
my_thread-------->5
main-------->6
my_thread-------->6
my_thread-------->7
main-------->7
main-------->8
my_thread-------->8
main-------->9
my_thread-------->9
main-------->0
my_thread-------->0
main-------->1
my_thread-------->1
main-------->2
my_thread-------->2
main-------->3
my_thread-------->3
main-------->4
my_thread-------->4
my_thread-------->5
main-------->5
main-------->6
my_thread-------->6
my_thread-------->7
main-------->7
main-------->8
my_thread-------->8
main-------->9
my_thread-------->9

這樣在每次執行過程中,總會睡眠,睡眠了,其他執行緒就會有執行機會了。

注意:
1、執行緒睡眠是幫助所有執行緒獲得執行機會的最好方法。
2、執行緒睡眠到期自動甦醒,並返回到可執行狀態,不是執行狀態。因此,sleep()方法不能保證該執行緒睡眠到期後就開始執行。
3、sleep()是靜態方法,只能控制當前正在執行的執行緒。

3. 執行緒優先順序和執行緒讓步yield()

執行緒的讓步是通過Thread.yield()來實現的。yield()方法的作用是:暫停當前執行的執行緒,並執行其他執行緒。

yield()應該實現的是讓當前執行執行緒回到可執行狀態,然後允許其他具有相同優先順序的執行緒獲得執行機會。所以使用yield()的目的是讓相同優先順序的執行緒之間的輪轉執行。但是在實際中無法保證yield()達到讓步的目的,因為讓步的執行緒還有可能再次被CPU選中。

結論:yield()從未導致執行緒轉到等待/睡眠/阻塞狀態。在大多數情況下,yield()將導致執行緒從執行狀態轉到可執行狀態,但有可能沒有效果。

以下實現程式碼:

public class JoinDemo implements Runnable {

    @Override
    public void run() {
        // TODO Auto-generated method stub
        for(int i=0; i<5; i++){
            Thread.yield();
            System.out.println(Thread.currentThread().getName()+"-------->"+i);
        }
    }

    public static void main(String[] args) {

        JoinDemo demo1 = new JoinDemo();
        JoinDemo demo2 = new JoinDemo();
        Thread thread1 = new Thread(demo1, "A");
        Thread thread2 = new Thread(demo2, "B");
        thread1.start();
        thread2.start();

    }
}

執行結果:

A-------->0
A-------->1
B-------->0
B-------->1
B-------->2
B-------->3
B-------->4
A-------->2
A-------->3
A-------->4

4. join()方法

Thread的非靜態方法join()讓一個執行緒B“加入”到另外一個執行緒A的尾部。在A執行完畢之前,B不能工作。

另外,join()方法還有帶超時限制的過載版本。 例如t.join(5000);則讓執行緒等待5000毫秒,如果超過這個時間,則停止等待,變為可執行狀態。

例如:

public class JoinDemo implements Runnable {
    @Override
    public void run() {
        // TODO Auto-generated method stub
        for(int i=0; i<10; i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-------->"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {

        JoinDemo demo1 = new JoinDemo();
        Thread thread1 = new Thread(demo1, "A");
        thread1.start();
        thread1.join();

        for(int i=0; i<10; i++){
            System.out.println(Thread.currentThread().getName()+"-------->"+i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

執行結果:

A-------->0
A-------->1
A-------->2
A-------->3
A-------->4
A-------->5
A-------->6
A-------->7
A-------->8
A-------->9
main-------->0
main-------->1
main-------->2
main-------->3
main-------->4
main-------->5
main-------->6
main-------->7
main-------->8
main-------->9

5. 小結

  • 到目前位置,介紹了執行緒離開執行狀態的3種方法:
    1、呼叫Thread.sleep():使當前執行緒睡眠至少多少毫秒(儘管它可能在指定的時間之前被中斷)。
    2、呼叫Thread.yield():不能保障太多事情,儘管通常它會讓當前執行執行緒回到可執行性狀態,使得有相同優先順序的執行緒有機會執行。
    3、呼叫join()方法:保證當前執行緒停止執行,直到該執行緒所加入的執行緒完成為止。然而,如果它加入的執行緒沒有存活,則當前執行緒不需要停止。

  • 除了以上三種方式外,還有下面幾種特殊情況可能使執行緒離開執行狀態:
    1、執行緒的run()方法完成。
    2、在物件上呼叫wait()方法(不是線上程上呼叫)。
    3、執行緒不能在物件上獲得鎖定,它正試圖執行該物件的方法程式碼。
    4、執行緒排程程式可以決定將當前執行狀態移動到可執行狀態,以便讓另一個執行緒獲得執行機會,而不需要任何理由。