1. 程式人生 > >java 並發(三)---Thread 線程

java 並發(三)---Thread 線程

tom font test strong pla 一個 依靠 some ++

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 方法 和 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先上車了。

但是線程是有優先級的,優先級越高的人,就一定能第一個上車嗎?這是不一定的,優先級高的人僅僅只是第一個上車的概率大了一點而已,

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

參考資料:

  • https://www.cnblogs.com/jijijiefang/articles/7222955.html
  • https://www.cnblogs.com/huangzejun/p/7908898.html
  • https://www.cnblogs.com/java-spring/p/8309931.html

java 並發(三)---Thread 線程