1. 程式人生 > >001-多線程基礎-進程線程、線程狀態、優先級、用戶線程和守護線程

001-多線程基礎-進程線程、線程狀態、優先級、用戶線程和守護線程

我們 row 上進 最好 left 同一時間 set 關系 dos系統

一、進程與線程

1、DOS系統【單進程系統】

  最早的時候DOS有一個特點:只要電腦有病毒,那麽電腦就死機了。
  原因:傳統的DOS系統屬於單進程系統,即:在同一時間段內只允許有一個程序運行。

2、Windows系統【多進程多線程】

  電腦中毒也可以運行,但是會變慢
  原因:因為在一個cpu、一塊資源的情況下,程序利用一些輪轉算法,可以讓一個資源在一個時間段可以同時處理多個程序(進程),但是在一個時間點上只允許一個進程去執行。
  windows:任務管理器
  linux:ps
  在每一個進程上可以劃分出若幹個線程,那麽線程的操作一定是要比進程更快。多線程操作要超多進程操作。但是所有的線程都一定是要在進程的基礎上進行劃分。

  所以進程一旦消失,那麽線程一定消失
  線程依附於進程存在。

  進程:一個進程就是一個“執行中的程序”,是程序在計算機上的一次運行活動。程序要運行,系統就在內存中為該程序分配一塊獨立的內存空間,載入程序代碼和資源進行執行。程序運行期間該內存空間不能被其他進程直接訪問。系統以進程為基本單位進行系統資源的調度和分配。

  線程:程序的執行具體是通過線程來完成的,所以一個進程中至少有一個線程。回憶一下 HelloWrold 程序中main方法的執行,其實這時候,Java虛擬機會開啟一個名為“main”的線程來執行程序代碼。一個進程可以包含多個線程,這些線程共享數據空間和資源,但又分別擁有各自的執行堆棧和程序計數器。線程是CPU調度的基本單位。

二、java的線程介紹【線程狀態、優先級、用戶線程和守護線程】

1、java線程的生命周期【線程狀態】

  java線程在他的生命周期內有幾種不同的狀態:線程初始化,啟動,運行和死亡。

  技術分享

上圖所示的狀態解釋如下:【可通過thread dump查看】

  ● new 是指線程被初始化,但是還沒有調用其start方法,還沒有開始執行

    每一個線程,在堆內存中都有一個對應的Thread對象。Thread t = new Thread();當剛剛在堆內存中創建Thread對象,還沒有調用t.start()方法之前,線程就處在NEW狀態。在這個狀態上,線程與普通的java對象沒有什麽區別,就僅僅是一個堆內存中的對象。

  ● runnable 調用線程的start方法之後,線程開始執行其任務,這時候線程是運行狀態

    該狀態表示線程具備所有運行條件,在運行隊列中準備操作系統的調度,或者正在運行。 這個狀態的線程比較正常,但如果線程長時間停留在在這個狀態就不正常了,這說明線程運行的時間很長(存在性能問題),或者是線程一直得不得執行的機會(存在線程饑餓的問題)。

  ● waiting 有時候線程需要等待另外一個線程執行完畢之後再執行,這時候線程處於等待狀態,處於等待狀態的線程需要其他線程notify之後才能恢復到運行狀態

    處在該線程的狀態,正在等待某個事件的發生,只有特定的條件滿足,才能獲得執行機會。而產生這個特定的事件,通常都是另一個線程。也就是說,如果不發生特定的事件,那麽處在該狀態的線程一直等待,不能獲取執行的機會。比如說,A線程調用了obj對象的obj.wait()方法,如果沒有線程調用obj.notify或obj.notifyAll,那麽A線程就沒有辦法恢復運行;如果A線程調用了LockSupport.park(),沒有別的線程調用LockSupport.unpark(A),那麽A沒有辦法恢復運行。

  ● timed waiting 運行中的線程可以進入到定時等待的狀態,這時候線程間隔指定的時間間隔之後就會恢復到運行狀態

    J.U.C中很多與線程相關類,都提供了限時版本和不限時版本的API。TIMED_WAITING意味著線程調用了限時版本的API,正在等待時間流逝;當等待時間過去後,線程一樣可以恢復運行。如果線程進入了WAITING狀態,一定要特定的事件發生才能恢復運行;而處在TIMED_WAITING的線程,如果特定的事件發生或者是時間流逝完畢,都會恢復運行。

  ● terminated 當線程任務執行完畢或者被abort的時候線程處於終止狀態

    線程執行完畢,執行完run方法正常返回,或者拋出了運行時異常而結束,線程都會停留在這個狀態。這個時候線程只剩下Thread對象了,沒有什麽用了。

  線程的狀態在Thread.State這個枚舉類型中定義:

技術分享
    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * 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,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }
View Code

2、java線程的優先級  

  每一個java線程都有一個優先級,操作系統可以通過線程的優先級決定決定將cpu分配給哪個線程。優先級越高的線程越可能得到cpu資源。
  java線程優先級的值在1-10之間,1是常量MIN_PRIORITY,10是常量MAX_PRIORITY 。默認情況下java的線程的優先級是NORM_PRIORITY 即5.
  高優先級的線程通常更重要,更有可能獲得cpu時間資源,但是並不能保證絕對可以獲得cpu。

3、用戶線程和守護線程

  java中線程分為兩種類型:用戶線程和守護線程。通過Thread.setDaemon(false)設置為用戶線程;通過Thread.setDaemon(true)設置為守護線程。如果不設置此屬性,默認為用戶線程
  用戶線程和守護線程的區別:【用戶線程存活程序存活,其他則結束
    1. 主線程結束後用戶線程還會繼續運行,JVM存活;主線程結束後守護線程和JVM的狀態又下面第2條確定。
    2.如果沒有用戶線程,都是守護線程,那麽JVM結束(隨之而來的是所有的一切煙消雲散,包括所有的守護線程)。

  main方法說明

  1.Main線程是個非守護線程,不能設置成守護線程。
    main線程是由java虛擬機在啟動的時候創建的,進入程序的入口。main方法開始執行的時候,main用戶線程已經創建好並在運行了。對於運行中的線程,不可以調用Thread.setDaemon(),調用會拋出異常Exception in thread "main"
  2.Main線程結束,如果還有其他用戶線程則繼續運行,如果都是守護線程則直接結束
    主線程,只是個普通的非守護線程,用來啟動應用程序,不能設置成守護線程;除此之外,它跟其他非守護線程沒有什麽不同。主線程執行結束,其他用戶線程一樣可以正常執行。
    按照操作系統的理論,進程是資源分配的基本單位,線程是CPU調度的基本單位。對於CPU來說,其實並不存在java的主線程和子線程之分,都只是個普通的線程。進程的資源是線程共享的,只要進程還在,線程就可以正常執行,換句話說線程是強依賴於進程的。也就是說,線程其實並不存在互相依賴的關系,一個線程的死亡從理論上來說,不會對其他線程有什麽影響。
    java虛擬機(相當於進程)退出的時機是:虛擬機中所有存活的線程都是守護線程。只要還有存活的非守護線程虛擬機就不會退出,而是等待非守護線程執行完畢;反之,如果虛擬機中的線程都是守護線程,那麽不管這些線程的死活java虛擬機都會退出。

  補充說明:

    定義:守護線程--也稱“服務線程”,在沒有用戶線程可服務時會自動離開。
    優先級:守護線程的優先級比較低,用於為系統中的其它對象和線程提供服務。
    設置:通過setDaemon(true)來設置線程為“守護線程”;將一個用戶線程設置為守護線程的方式是在線程啟動用線程對象的setDaemon方法。
    示例: 垃圾回收線程就是一個經典的守護線程,當我們的程序中不再有任何運行的Thread,程序就不會再產生垃圾,垃圾回收器也就無事可做,所以當垃圾回收線程是JVM上僅剩的線程時,垃圾回收線程會自動離開。它始終在低級別的狀態中運行,用於實時監控和管理系統中的可回收資源。
    生命周期:守護進程(Daemon)是運行在後臺的一種特殊進程。它獨立於控制終端並且周期性地執行某種任務或等待處理某些發生的事件。也就是說守護線程不依賴於終端,但是依賴於系統,與系統“同生共死”。那Java的守護線程是什麽樣子的呢。當JVM中所有的線程都是守護線程的時候,JVM就可以退出了;如果還有一個或以上的非守護線程則JVM不會退出

    例子程序:

      thread = new Thread(this);
      thread.setDaemon(true);
      thread.start();
     當java虛擬機中沒有非守護線程在運行的時候,java虛擬機會關閉。當所有常規線程運行完畢以後,守護線程不管運行到哪裏,虛擬機都會退出運行。所以你的守護線程最好不要寫一些會影響程序的業務邏輯。否則無法預料程序到底 會出現什麽問題。

示例:

技術分享
    public static void main(String[] args) throws Exception {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                String daemon = Thread.currentThread().isDaemon() ? "daemon" : "not daemon";
                while (true) {
                    System.out.println("Im is running " + daemon + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        };
        Thread thread = new Thread(r);
        thread.setDaemon(true);
        thread.start();
        Thread.sleep(3000);
        System.out.println("main is over");
    }
View Code

  運行上面程序,輸出如下內容後程序就退出了。
    Im is running daemon14:14:48
    Im is running daemon14:14:49
    Im is running daemon14:14:50
    main is over

  可以看到在主線程退出之後,deamon線程也就被終止了,同時程序也就退出了。
  我們對上面程序稍作改動,將t.setDaemon(true)註釋掉,再看下運行結果。
    Im is running not daemon14:15:29
    Im is running not daemon14:15:30
    Im is running not daemon14:15:31
    main is over
    Im is running not daemon14:15:32
    Im is running not daemon14:15:33

    ……

  可以看到在主線程退出之後,t線程還在繼續執行,這是因為線程t默認情況下是非守護線程,盡管主線程退出了,他還是在繼續執行著。
  需要註意設置線程是否為守護線程必須在其執行之前進行設置,否則會拋出異常IllegalThreadStateException。這一點可以從Thread類的setDaemon(boolean)的源碼中得到求證。如下源碼:

    public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }

  可以看到如果線程在live狀態調用setDaemon會拋出異常。

3、java多線程實現的兩種方式

  1. 使用Runnable接口實現多線程

    使用Runnable接口實現多線程需要兩個步驟,首先實現Runnable接口類,然後聲明Thread實例,調用thread實例的start方法,開始執行。

  2. 從Thread類繼承實現java的多線程

    從java的Thread類繼承實現多線程,也是實現其run方法,然後聲明實例,並調用實例的start方法啟動線程。

4、java Thread類的主要方法介紹

Thread的實例方法:

方法定義

方法說明

public void start()

最常用的方法,顧名思義啟動線程,即開始執行線程的run方法

public void run()

如果線程重寫了run方法,那麽執行重寫的方法,否則執行線程的Runnable接口中定義的run方法

public final void setName(String)

設置線程的名稱

public final void setPriority(int)

設置線程的優先級(範圍在1-10包含1,10)

public final void setDeamon(boolean)

設置線程是否是後臺線程

public final void join(long)

在另外一個線程中調用當前線程的join方法,會導致當前線程阻塞,直到另一線程執行完畢,或者超過參數指定毫秒數

public void interrupt()

中斷線程

public final boolean isAlive()

線程是否處於存活狀態,線程在啟動和結束之前都處於存活狀態

Thread類的常用靜態方法:

方法定義

方法說明

public static void yield()

使當前運行線程相同優先級的線程獲得執行機會,類似sleep,但是只會將cpu讓給相同優先級的線程

public static void sleep(long)

使當前線程休眠指定毫秒的時間

public static boolean holdsLock(Object x)

判斷當前線程是否擁有對象的鎖

public static Thread currentThread()

獲得當前線程實例

public static void dumpStack()

打印當前線程的執行堆棧,這對多線程程序的調試很有幫助

001-多線程基礎-進程線程、線程狀態、優先級、用戶線程和守護線程