Java多線程(一) —— 線程的狀態詳解

1、多線程概述 

一、 進程html

  • 是一個正在執行的程序。是程序在計算機上的一次運行活動。
  • 每個進程執行都有一個執行順序。該順序是一個執行路徑,或者叫一個控制單元。
  • 系統以進程爲基本單位進行系統資源的調度和分配。程序要運行,系統就在內存中爲該程序分配一塊獨立的內存空間,載入程序代碼和資源進行執行。
  • 程序運行期間該內存空間不能被其餘進程直接訪問。

 

二、線程java

  • 進程中的一個獨立的控制單元。線程在控制着進程的執行。只要進程中有一個線程在執行,進程就不會結束。
  • 線程是進程內一次具體的執行任務。程序的執行具體是經過線程來完成的,因此一個進程中至少有一個線程。
  • 線程是CPU調度的基本單位。
  • 一個進程能夠包含多個線程,這些線程共享數據空間和資源,但又分別擁有各自的執行堆棧和程序計數器。

  其實HelloWrold 程序中main方法的執行,就是Java虛擬機開啓的一個名爲「main」的線程來執行程序代碼。多線程

 

三、多線程ide

        在java虛擬機啓動的時候會有一個java.exe的執行程序,也就是一個進程。該進程中至少有一個線程負責java程序的執行。並且這個線程運行的代碼存在於main方法中。該線程稱之爲主線程。JVM啓動除了執行一個主線程,還有負責垃圾回收機制的線程。像種在一個進程中有多個線程執行的方式,就叫作多線程。this

 

四、多線程存在的意義spa

        多線程的出現能讓程序產生同時運行效果。能夠提升程序執行效率。線程

         例如:在java.exe進程執行主線程時,若是程序代碼特別多,在堆內存中產生了不少對象,而同時對象調用完後,就成了垃圾。若是垃圾過多就有多是堆內存出現內存不足的現象,只是若是隻有一個線程工做的話,程序的執行將會很低效。而若是有另外一個線程幫助處理的話,如垃圾回收機制線程來幫助回收垃圾的話,程序的運行將變得更有效率。3d

 

五、計算機CPU的運行原理code

         咱們電腦上有不少的程序在同時進行,就好像cpu在同時處理這因此程序同樣。可是,在一個時刻,單核的cpu只能運行一個程序。而咱們看到的同時運行效果,只是cpu在多個進程間作着快速切換動做。htm

         而cpu執行哪一個程序,是毫無規律性的。這也是多線程的一個特性:隨機性。哪一個線程被cpu執行,或者說搶到了cpu的執行權,哪一個線程就執行。而cpu不會只執行一個,當執行一個一會後,又會去執行另外一個,或者說另外一個搶走了cpu的執行權。至於到底是怎麼樣執行的,只能由cpu決定。

 

2、線程的生命週期及五種基本狀態

 

Java線程具備五中基本狀態

新建狀態(New):當線程對象對建立後,即進入了新建狀態,如:Thread t = new MyThread();

就緒狀態(Runnable):當調用線程對象的start()方法(t.start();),線程即進入就緒狀態。處於就緒狀態的線程,只是說明此線程已經作好了準備,隨時等待CPU調度執行,並非說執行了t.start()此線程當即就會執行;

運行狀態(Running):當CPU開始調度處於就緒狀態的線程時,此時線程才得以真正執行,即進入到運行狀態。注:就緒狀態是進入到運行狀態的惟一入口,也就是說,線程要想進入運行狀態執行,首先必須處於就緒狀態中;

阻塞狀態(Blocked):處於運行狀態中的線程因爲某種緣由,暫時放棄對CPU的使用權,中止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU調用以進入到運行狀態。根據阻塞產生的緣由不一樣,阻塞狀態又能夠分爲三種:

  • 等待阻塞 -- 運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態;
  • 同步阻塞 -- 線程在獲取synchronized同步鎖失敗(由於鎖被其它線程所佔用),它會進入同步阻塞狀態;
  • 其餘阻塞 -- 經過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入就緒狀態。

死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。

 

3、Java多線程的建立及啓動

  • 繼承Thread類,重寫該類的run()方法;
  • 實現Runnable接口,並重寫該接口的run()方法;
  • 使用Callable和Future接口建立線程。

 

1.繼承Thread類,重寫該類的run()方法。

class MyThread extends Thread {
    private int i = 0;

    @Override
    public void run() {
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Thread myThread1 = new MyThread();     // 建立一個新的線程  myThread1  此線程進入新建狀態
                Thread myThread2 = new MyThread();     // 建立一個新的線程 myThread2 此線程進入新建狀態
                myThread1.start();                     // 調用start()方法使得線程進入就緒狀態
                myThread2.start();                     // 調用start()方法使得線程進入就緒狀態
            }
        }
    }
}

  如上所示,繼承Thread類,經過重寫run()方法定義了一個新的線程類MyThread,其中run()方法的方法體表明瞭線程須要完成的任務,稱之爲線程執行體。當建立此線程類對象時一個新的線程得以建立,並進入到線程新建狀態。經過調用線程對象引用的start()方法,使得該線程進入到就緒狀態,此時此線程並不必定會立刻得以執行,這取決於CPU調度時機。

 

2.實現Runnable接口,並重寫該接口的run()方法,該run()方法一樣是線程執行體,建立Runnable實現類的實例,並以此實例做爲Thread類的target來建立Thread對象,該Thread對象纔是真正的線程對象。

class MyRunnable implements Runnable {
    private int i = 0;

    @Override
    public void run() {
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Runnable myRunnable = new MyRunnable(); // 建立一個Runnable實現類的對象
                Thread thread1 = new Thread(myRunnable); // 將myRunnable做爲Thread target建立新的線程
                Thread thread2 = new Thread(myRunnable);
                thread1.start(); // 調用start()方法使得線程進入就緒狀態
                thread2.start();
            }
        }
    }
}

 

Thread和Runnable之間究竟是什麼關係呢?

兩種方法建立線程的方法相似,不一樣的地方在於:

Thread thread = new MyThread(myRunnable);

那麼這種方式能夠順利建立出一個新的線程麼?答案是確定的。那麼此時的線程執行體究竟是MyRunnable接口中的run()方法仍是MyThread類中的run()方法呢?線程執行體是MyThread類中的run()方法。其實緣由很簡單,由於Thread類自己也是實現了Runnable接口,而run()方法最早是在Runnable接口中定義的方法。因此執行時優先執行MyThread類中的run()方法。

Runnable接口中最早定義的run()方法:

public interface Runnable {
     public abstract void run();  
}

Thread類中對於Runnable接口中run()方法的實現:

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

  也就是說,當執行到Thread類中的run()方法時,會首先判斷target是否存在,存在則執行target中的run()方法,也就是實現了Runnable接口並重寫了run()方法的類中的run()方法。可是上述給到的列子中,因爲多態的存在,根本就沒有執行到Thread類中的run()方法,而是直接先執行了運行時類型即MyThread類中的run()方法。

 

3.使用Callable和Future接口建立線程。具體是建立Callable接口的實現類,並實現clall()方法。並使用FutureTask類來包裝Callable實現類的對象,且以此FutureTask對象做爲Thread對象的target來建立線程。

看着好像有點複雜,直接來看一個例子就清晰了。

public class ThreadTest {
    public static void main(String[] args) {
        Callable<Integer> myCallable = new MyCallable();    // 建立MyCallable對象
        FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask來包裝MyCallable對象
for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); if (i == 30) { Thread thread = new Thread(ft); //FutureTask對象做爲Thread對象的target建立新的線程 thread.start(); //線程進入到就緒狀態 } } System.out.println("主線程for循環執行完畢.."); try { int sum = ft.get(); //取得新建立的新線程中的call()方法返回的結果 System.out.println("sum = " + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } class MyCallable implements Callable<Integer> { private int i = 0; // 與run()方法不一樣的是,call()方法具備返回值 @Override public Integer call() { int sum = 0; for (; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); sum += i; } return sum; } }

在實現Callable接口中,此時再也不是run()方法了,而是call()方法,此call()方法做爲線程執行體,同時還具備返回值!在建立新的線程時,是經過FutureTask來包裝MyCallable對象,同時做爲了Thread對象的target。

咱們發現FutureTask類其實是同時實現了Runnable和Future接口,由此才使得其具備Future和Runnable雙重特性。經過Runnable特性,能夠做爲Thread對象的target,而Future特性,使得其能夠取得新建立線程中的call()方法的返回值。

執行下此程序,咱們發現sum = 4950永遠都是最後輸出的。而「主線程for循環執行完畢..」則極可能是在子線程循環中間輸出。由CPU的線程調度機制,咱們知道,「主線程for循環執行完畢..」的輸出時機是沒有任何問題的,那麼爲何sum =4950會永遠最後輸出呢?緣由在於經過ft.get()方法獲取子線程call()方法的返回值時,當子線程此方法還未執行完畢,ft.get()方法會一直阻塞,直到call()方法執行完畢才能取到返回值。

上述主要講解了三種常見的線程建立方式,對於線程的啓動而言,都是調用線程對象的start()方法,須要特別注意的是:不能對同一線程對象兩次調用start()方法。

 

4、Java多線程的就緒、運行和死亡狀態

就緒狀態轉換爲運行狀態:當此線程獲得處理器資源;

運行狀態轉換爲就緒狀態:當此線程主動調用yield()方法或在運行過程當中失去處理器資源。

運行狀態轉換爲死亡狀態:當此線程線程執行體執行完畢或發生了異常。

此處須要特別注意的是:當調用線程的yield()方法時,線程從運行狀態轉換爲就緒狀態,但接下來CPU調度就緒狀態中的哪一個線程具備必定的隨機性,所以,可能會出現A線程調用了yield()方法後,接下來CPU仍然調度了A線程的狀況。

因爲實際的業務須要,經常會遇到須要在特定時機終止某一線程的運行,使其進入到死亡狀態。目前最通用的作法是設置一boolean型的變量,當條件知足時,使線程執行體快速執行完畢。如:

public class ThreadTest {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
      
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                thread.start();
            }
            if(i == 40){
                myRunnable.stopThread();
            }
        }
    }
}

class MyRunnable implements Runnable {
    private boolean stop;

    @Override
    public void run() {
        for (int i = 0; i < 100 && !stop; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }

    public void stopThread() {
        this.stop = true;
    }
}

 

5、Java多線程的阻塞狀態與線程控制

引發Java線程阻塞的主要方法:

1.join()

join —— 讓一個線程等待另外一個線程完成才繼續執行。如A線程線程執行體中調用B線程的join()方法,則A線程被阻塞,直到B線程執行完爲止,A才能得以繼續執行。

爲何要用join()方法

在不少狀況下,主線程生成並啓動了子線程,若是子線程裏要進行大量的耗時的運算,主線程每每將於子線程以前結束,可是若是主線程處理完其餘的事務後,須要用到子線程的處理結果,也就是主線程須要等待子線程執行完成以後再結束,這個時候就要用到join()方法了。

2.sleep()

sleep —— 讓當前的正在執行的線程暫停指定的時間,並進入阻塞狀態。在其睡眠的時間段內,該線程因爲不是處於就緒狀態,所以不會獲得執行的機會。即便此時系統中沒有任何其餘可執行的線程,出於sleep()中的線程也不會執行。所以sleep()方法經常使用來暫停線程執行。

前面有講到,當調用了新建的線程的start()方法後,線程進入到就緒狀態,可能會在接下來的某個時間獲取CPU時間片得以執行,若是但願這個新線程必然性的當即執行,直接調用原來線程的sleep(1)便可。

注:睡一個毫秒級夠了,由於CPU不會空閒,會切換到新建的線程。

 

3.後臺線程(Daemon Thread)

概念/目的:後臺線程主要是爲其餘線程(相對能夠稱之爲前臺線程)提供服務,或「守護線程」。如JVM中的垃圾回收線程。

生命週期:後臺線程的生命週期與前臺線程生命週期有必定關聯。主要體如今:當全部的前臺線程都進入死亡狀態時,後臺線程會自動死亡(其實這個也很好理解,由於後臺線程存在的目的在於爲前臺線程服務的,既然全部的前臺線程都死亡了,那它本身還留着有什麼用...偉大啊 ! !)。

設置後臺線程:調用Thread對象的setDaemon(true)方法能夠將指定的線程設置爲後臺線程

判斷線程是不是後臺線程:調用thread對象的isDeamon()方法。

注:main線程默認是前臺線程,前臺線程建立中建立的子線程默認是前臺線程,後臺線程中建立的線程默認是後臺線程。調用setDeamon(true)方法將前臺線程設置爲後臺線程時,須要在start()方法調用以前。前天線程都死亡後,JVM通知後臺線程死亡,但從接收指令到做出響應,須要必定的時間。

 

4.改變線程的優先級/setPriority():

每一個線程在執行時都具備必定的優先級,優先級高的線程具備較多的執行機會。每一個線程默認的優先級都與建立它的線程的優先級相同。main線程默認具備普通優先級。

設置線程優先級:setPriority(int priorityLevel)。參數priorityLevel範圍在1-10之間,經常使用的有以下三個靜態常量值:

MAX_PRIORITY:10

MIN_PRIORITY:1

NORM_PRIORITY:5

獲取線程優先級:getPriority()。

注:具備較高線程優先級的線程對象僅表示此線程具備較多的執行機會,而非優先執行。

 

5.yield()線程讓步

yield()的基本做用:暫停當前正在執行的線程對象讓出CPU資源,將當前線程從運行狀態轉換到就緒狀態並執行其餘線程。同時,yield()方法還與線程優先級有關,當某個線程調用yiled()方法從運行狀態轉換到就緒狀態後,CPU從就緒狀態線程隊列中只會選擇與該線程優先級相同或優先級更高的線程去執行。使用yield()的目的是讓相同優先級的線程之間能適當的輪轉執行。可是,實際中沒法保證yield()達到讓步目的,由於讓步的線程還有可能被線程調度程序再次選中。

注意:yield()從未致使線程轉到等待/睡眠/阻塞狀態。在大多數狀況下,yield()將致使線程從運行狀態轉到可運行(就緒)狀態,但有可能沒有效果。

 

join()實例:

public class ThreadTest {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                thread.start();
                try {
                    thread.join();    // main線程須要等待thread線程執行完後才能繼續執行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

 

sleep()實例:

public class ThreadTest {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                thread.start();
                try {
                    Thread.sleep(1);   // 使得thread必然可以立刻得以執行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

 

設置後臺進程實例:

public class ThreadTest {
    public static void main(String[] args) {
        Thread myThread = new MyThread();
        for (int i = 0; i < 100; i++) {
            System.out.println("main thread i = " + i);
            if (i == 20) {
                myThread.setDaemon(true);   //設置爲後臺守護線程
                myThread.start();
            }
        }
    }
}

class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("i = " + i);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

setPriority()實例:

public class ThreadTest {
    public static void main(String[] args) {
        Thread myThread = new MyThread();
        for (int i = 0; i < 100; i++) {
            System.out.println("main thread i = " + i);
            if (i == 20) {
                myThread.setPriority(Thread.MAX_PRIORITY);
                myThread.start();
            }
        }
    }
}

class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("i = " + i);
        }
    }
}

 

yeild()實例:

public class ThreadTest {
    public static void main(String[] args) {
        Thread myThread1 = new MyThread1();
        Thread myThread2 = new MyThread2();
        myThread1.setPriority(Thread.MAX_PRIORITY);
        myThread2.setPriority(Thread.MIN_PRIORITY);
        for (int i = 0; i < 100; i++) {
            System.out.println("main thread i = " + i);
            if (i == 20) {
                myThread1.start();
                myThread2.start();
                Thread.yield(); 
            }
        }
    }
}

class MyThread1 extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("myThread 1 --  i = " + i);
        }
    }
}

class MyThread2 extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("myThread 2 --  i = " + i);
        }
    }
}

 

參考連接:

http://www.cnblogs.com/lwbqqyumidi/p/3804883.html

http://www.cnblogs.com/lwbqqyumidi/p/3817517.html

http://www.cnblogs.com/lwbqqyumidi/p/3821389.html

相關文章
相關標籤/搜索