1. 程式人生 > >java 併發(三)---Thread 執行緒

java 併發(三)---Thread 執行緒

Thread 的狀態

      執行緒共有五種狀態.分別是: (1)新建 (2)就緒 (3)執行 (4)阻塞 (5)死亡 ,下面列列舉的狀態需要結合狀態示意圖更好理解.

 

  •  新建狀態(New): 新建立了一個執行緒物件。
  • 就緒狀態(Runnable): 執行緒物件建立後,其他執行緒呼叫了該物件的start()方法。該狀態的執行緒位於“可執行執行緒池”中,變得可執行,只等待獲取CPU的使用權。即在就緒狀態的程序除CPU之外,其它的執行所需資源都已全部獲得(包括我們所說的鎖)。
  • 執行狀態(Running): 就緒狀態的執行緒獲取了CPU,執行程式程式碼。
  • 阻塞狀態(Blocked): 阻塞狀態是執行緒因為某種原因放棄CPU使用權,暫時停止執行。直到執行緒進入就緒狀態,才有機會轉到執行狀態
    • 等待阻塞 :  執行的執行緒執行wait()方法,該執行緒會釋放佔用的所有資源,JVM會把該執行緒放入“等待池”中。進入這個狀態後,是不能自動喚醒的,必須依靠其他執行緒呼叫notify()或notifyAll()方法才能被喚醒.
    • 同步阻塞 :  執行的執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒佔用,則JVM會把該執行緒放入“鎖池”中,言外之意就是鎖被其他執行緒拿了,自己只能等待。
    • 其他阻塞 :  執行的執行緒執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該執行緒置為阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。
    • 阻塞這個狀態可以這樣總結: 執行緒存在且沒死亡,那麼執行和就緒以外的狀態就是阻塞,不管是否獲得鎖或是進入鎖池. 
  • 死亡狀態(Dead):執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。

 


     下面為執行緒的狀態示意圖:

Thread

     

Thread 方法 和 Object兩個方法

      後面兩個Object 方法

  • sleep 方法 :  “sleep”—“睡覺”,意思就是休眠一段時間,時間過後繼續執行,不釋放鎖和其他資源,進入阻塞狀態
  • join   方法 :   原始碼實現在下方,可以看到只要thread存活的情況下就會某個時間內迴圈一個等待的方法 (注意這個方法和下面的

                        wait 方法不是一回事,下面的wait方法會一直阻塞在那裡), 使用場景是例如某個操作執行前需要執行一個載入資源的

                        任務,那麼執行的這個操作就要一直等待載入的操作完成以後才可以執行(join(0)).

  • yield 方法 :     讓步於其他執行緒執行.
  • wait  方法 :     等待,釋放鎖和其他資源,進入等待佇列,這個等待佇列裡邊存放的物件都是等待獲取鎖的物件,另外一點, wait 是物件

                         object 中的方法,而不是執行緒中的方法,同時呼叫 XX.wait(); 時必須要在同步語句中,或是該物件已經被加鎖的情況

                         下,試想一下,wait 方法本身就是某個物件加鎖後釋放鎖,不可能沒加鎖的情況下可以釋放鎖.wait 不能自動喚醒,需要

                         notify / notifyAll 方法

  • notify/notifyAll 方法 :  喚醒

 

 

   join的原始碼實現

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() – base;
            }
        }
    }

       但是有個問題一直沒搞明白,就是原始碼中的wait方法,要是Object.wait方法,那麼它會釋放鎖,然後其他執行緒可以獲取鎖,執行其他執行緒的動作,但是在join的原始碼實現中即使是一直迴圈,按道理是會釋放鎖的,其他執行緒可以執行的,但是事實卻不是,如下程式碼,至今未懂.

Thread thread1 = new Thread(() -> {
            System.out.println("t1 開始執行" + new Date());
            synchronized (obj) {
                try {
                    Thread.currentThread().join(0);
                   //obj.wait();
                    System.out.println("執行緒1 繼續執行,執行完畢");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });


        Thread thread2 = new Thread(() -> {
            try {
                synchronized (obj) {

                    System.out.println("執行緒2 開始執行 " + new Date());
                    Thread.sleep(2 * 1000);
                    System.out.println("執行緒2 執行結束 " + new Date());
                  //  obj.notifyAll();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        thread1.start();

        Thread.sleep(4*1000);
        thread2.start();

       執行後會發現執行緒一呼叫join()方法後,執行緒2沒能獲取物件執行,而是等待執行緒1執行完成後,執行緒2才會執行.

 

       我們來看看在 Java 7 Concurrency Cookbook 中相關的描述(很清楚地說明了 join() 的作用):

Waiting for the finalization of a thread

In some situations, we will have to wait for the finalization of a thread. For example, we may have a program that will begin initializing the resources it needs before proceeding with the rest of the execution. We can run the initialization tasks as threads and wait for its finalization before continuing with the rest of the program. For this purpose, we can use the join() method of the Thread class. When we call this method using a thread object, it suspends the execution of the calling thread until the object called finishes its execution.

       當我們呼叫某個執行緒的這個方法時,這個方法會掛起呼叫執行緒,直到被呼叫執行緒結束執行,呼叫執行緒才會繼續執行。

 


 

方法使用 

wait 和 notify 方法

       可以看到 notify 方法的使用,是在同步方法內,並且同樣獲取同樣的鎖物件, wait 和 notify 方法的運用常常被用來做生產者-消費者的實現.

//以下程式碼來自參考文章,見參考資料
public class Test {
    public static Object object = new Object();
    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();
         
        thread1.start();
         
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
         
        thread2.start();
    }
     
    static class Thread1 extends Thread{
        @Override
        public void run() {
            synchronized (object) {
                try {
                    object.wait();
                } catch (InterruptedException e) {
                }
                System.out.println("執行緒"+Thread.currentThread().getName()+"獲取到了鎖");
            }
        }
    }
     
    static class Thread2 extends Thread{
        @Override
        public void run() {
            synchronized (object) {
                object.notify();
                System.out.println("執行緒"+Thread.currentThread().getName()+"呼叫了object.notify()");
            }
            System.out.println("執行緒"+Thread.currentThread().getName()+"釋放了鎖");
        }
    }
}

 

join 方法

       下面程式碼中parent執行緒會等待child執行緒執行完成後再繼續執行.

// 父執行緒
public class Parent extends Thread {
    public void run() {
        Child child = new Child();
        child.start();
        child.join();
        // ...
    }
}
// 子執行緒
public class Child extends Thread {
    public void run() {
        // ...
    }
}

 

yield

public class YieldExcemple {

    public static void main(String[] args) {
        Thread threada = new ThreadA();
        Thread threadb = new ThreadB();
        // 設定優先順序:MIN_PRIORITY最低優先順序1;NORM_PRIORITY普通優先順序5;MAX_PRIORITY最高優先順序10
        threada.setPriority(Thread.MIN_PRIORITY);
        threadb.setPriority(Thread.MAX_PRIORITY);

        threada.start();
        threadb.start();
    }
}

class ThreadA extends Thread {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("ThreadA--" + i);
            Thread.yield();
        }
    }
}

class ThreadB extends Thread {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("ThreadB--" + i);
            Thread.yield();
        }
    }
}

          以下總結來自參考文章.

 

       Java執行緒中的Thread.yield( )方法,譯為執行緒讓步。顧名思義,就是說當一個執行緒使用了這個方法之後,它就會把自己CPU執行的時間讓掉,讓自己或者其它的執行緒執行,注意是讓自己或者其他執行緒執行,並不是單純的讓給其他執行緒。

        yield()的作用是讓步。它能讓當前執行緒由“執行狀態”進入到“就緒狀態”,從而讓其它具有相同優先順序的等待執行緒獲取執行權;但是,並不能保證在當前執行緒呼叫yield()之後,其它具有相同優先順序的執行緒就一定能獲得執行權;也有可能是當前執行緒又進入到“執行狀態”繼續執行!

      舉個例子:一幫朋友在排隊上公交車,輪到Yield的時候,他突然說:我不想先上去了,咱們大家來競賽上公交車。然後所有人就一塊衝向公交車,

有可能是其他人先上車了,也有可能是Yield先上車了。

     但是執行緒是有優先順序的,優先順序越高的人,就一定能第一個上車嗎?這是不一定的,優先順序高的人僅僅只是第一個上車的概率大了一點而已,

     最終第一個上車的,也有可能是優先順序最低的人。並且所謂的優先順序執行,是在大量執行次數中才能體現出來的。

 

參考資料: