Java多線程

一:進程與線程

概述:幾乎任何的操做系統都支持運行多個任務,一般一個任務就是一個程序,而一個程序就是一個進程。當一個進程運行時,內部可能包括多個順序執行流,每一個順序執行流就是一個線程java

 

進程:進程是指處於運行過程當中的程序,而且具備必定的獨立功能。進程是系統進行資源分配和調度的一個單位。當程序進入內存運行時,即爲編程

 

進程的三個特色:多線程

1:獨立性:進程是系統中獨立存在的實體,它能夠獨立擁有資源,每個進程都有本身獨立的地址空間,沒有進程自己的運行,用戶進程不能夠直接訪問其餘進程的地址空間。併發

2:動態性:進程和程序的區別在於進程是動態的,進程中有時間的概念,進程具備本身的生命週期和各類不一樣的狀態ide

3:併發性:多個進程能夠在單個處理器上併發執行,互不影響測試

 併發性和並行性是不一樣的概念:並行是指同一時刻,多個命令在多個處理器上同時執行;併發是指在同一時刻,只有一條命令是在處理器上執行的,但多個進程命令被快速輪換執行,使得在宏觀上具備多個進程同時執行的效果spa

線程:線程是進程的組成部分,一個進程能夠擁有多個線程而一個線程必須擁有一個父進程線程能夠擁有本身的堆棧,本身的程序計數器和本身的局部變量,但不能擁有系統資源。它與父進程的其餘線程共享該進程的全部資源操作系統

 

線程的特色線程

線程能夠完成必定任務,能夠和其它線程共享父進程的共享變量和部分環境,相互協做來完成任務。code

線程是獨立運行的,其不知道進程中是否還有其餘線程存在。

線程的執行是搶佔式的,也就是說,當前執行的線程隨時可能被掛起,以便運行另外一個線程。

一個線程能夠建立或撤銷另外一個線程,一個進程中的多個線程能夠併發執行。

二:線程的建立及使用

java使用Thread類表明線程,全部的線程對象都必須是Thread或者其子類的實例,每一個線程的做用是完成必定任務,其實是就是執行一段程序流(一段順序執行的代碼)

方案一:繼承Thread類建立線程類

步驟:1.定義Thread類的子類 並重寫該類的Run方法,該run方法的方法體就表明了該線程須要完成的任務

      2.建立Thread類的實例,即建立了線程對象

      3.調用線程的start方法來啓動線程

 

package Thread01;

public class MyThread extends Thread {
    private int i;

    @Override
    public void run() {
       for (; i < 10; i++) {
         System.out.println(getName()+"\t"+i);
    }
    }
}

測試方法

package Thread01;

public class MyTest {
    public static void main(String[] args) {
        for (int i = 0; i <10; i++) {
             System.out.println(Thread.currentThread().getName()+"\t"+i);
             if(i==5){
                 MyThread mt=new MyThread();
                 MyThread mt2=new MyThread();
                 mt.start();
                 mt2.start();
             }
        }
    }
}

方案二:實現Runnable接口

1:定義Runnable接口的實現類,並重寫它的Run方法,run方法一樣是該線程的執行體

2:建立Runnable實現類的實例,並將此實例做爲Thread的target建立一個Thread對象,該Thread對象纔是真正的線程對象

3:調用start方法啓動該線程

 

package Thread02;

public class SecondThread implements Runnable{
    private int i;
    public void run() {
        for (; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20)
            {
                System.out.println(Thread.currentThread().getName()+"執行完畢");
            }
        }
    }
}

測試方法

package Thread02;

public class MyTest {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==5)
            {
                SecondThread s1=new SecondThread();
                Thread t1=new Thread(s1,"線程1");
                Thread t2=new Thread(s1,"線程2");
                t1.start();
                t2.start();
               
            }
        }
    }
}

結論:採用Ruunable接口的方式建立多個線程能夠共享線程類的實例變量,這是由於在這種方式下,程序建立的Runnable對象只是線程的target,而多個線程能夠共享一個target,因此多個線程能夠共享一個實例變量

 

經過Runnable實現多線程其實就是將run包裝成線程的執行體,可是目前java沒法將任意方法包裝成線程執行體

方案三:使用callable和future建立線程

 

Java5開始,Java提供 Callable接口,Callable接口提供了一個call()方法能夠做爲線程執行體,看起來和Runnable很像,但call()方法更強大——call()方法能夠有返回值、call()方法能夠拋出異常

 

Java5提供了Future接口來表明Callable接口的call()方法的返回值,並Future接口提供了一個FutureTask實現類,該實現類實現類Future接口,也實現了Runnable接口——能夠做爲Thread的target。

實現步驟:

1:建立Callable接口的實現類,並實現call方法,call方法會成爲線程執行體,且call方法具備返回值,在建立callable接口的實現類!

2:使用FutrueTask類來包裝Callable對象,FutrueTask封裝類Callable的call方法的返回值

3:使用FutrueTask對象做爲Thread的target建立並啓動新線程

4:使用FutrueTask的get方法獲取執行結束後的返回值

 

package Thread03;

import java.util.concurrent.Callable;

public class Target implements Callable<Integer> {
    int i=0;
    public Integer call() throws Exception {
        for (; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+""+i);
        }
        return i;
    }

}

測試方法

package Thread03;

import java.util.concurrent.FutureTask;

public class ThirdThread {
     public static void main(String[] args) {
            Target t1=new Target();
            FutureTask<Integer> ft=new FutureTask<Integer>(t1);
            Thread t2=new Thread(ft,"新線程");
            t2.start();
            try {
                System.out.println(ft.get());
            } catch (Exception e) {
            }
    }
}

結論:採起Runnable、Callable的優點在於——線程類只是實現了Runnable或Callable接口,還能夠繼承其它類;在這種方法下,多個線程能夠共享一個target對象,所以很是適合多個相同線程處理同一份資源的狀況,從而將CPU、代碼和數據分開,形參清晰的模型,體現了面對對象的編程思想。劣勢在於編程複雜度略高。

 

三:線程的狀態

當線程被建立並被啓動時,它既不是一啓動就進入了執行狀態,在線程的生命週期中,它要通過new(新建),就緒(Runnable),運行(Running),阻塞(Blocked),dead(死亡)。

當線程啓動以後,它不可能一直霸佔着cpu獨自運行,全部cpu須要在多條線程輪流切換,因而線程就也會屢次在運行.就緒之間切換

1:新建和就緒狀態

--新建狀態:

當程序使用new關鍵字建立了一個線程時,該線程就處於新建狀態。

此時的它和其它java對象同樣,僅有虛擬機分配內存,並初始化成員變量的值。此時的線程對象並無表現出線程的任何動態特徵,程序也不會執行線程的線程執行體

--就緒狀態:

當線程對象調用了start()方法後,該線程就處於就緒狀態java虛擬機會爲其建立方法調用棧和程序計數器,處於該狀態的線程並沒有開始執行,只是代表該線程能夠運行了,至於該線程什麼時候運行,取決於JVM的調度。

 

注意!!!

啓動線程要調用start方法,而不是run方法,永遠不要調用線程的run方法,若是調用run方法,系統會把線程對象看成普通的對象,會吧線程的執行體看成普通方法來調用!在調用了run方法以後,該線程就不在處於新建狀態,不要再調用該線程的start方法!java中只能對處於新建狀態的線程使用start方法,不然將會引起IllegalThreadStateException異常!

2:運行狀態和阻塞狀態

當發生以下的幾種狀況時,將會進入阻塞狀態

當線程調用sleep方法主動放棄所佔用的處理器資源

線程調用了一個阻塞時的IO方法,在該方法返回以前,線程會被阻塞

線程試圖得到一個同步監視器,但該同步監視器正被其餘線程鎖持有

線程正在等待某個通知(notify)

程序調用了線程的suspend方法將該線程掛起

 

當以上幾個狀況,當發生以下的狀況將會從新進入就緒狀態

調用sleep()方法過了指定時間

 

線程調用的阻塞時IO方法依舊返回

 

線程成功地得到了試圖得到的同步監視器

 

如今正在等待某個通知,而其它線程發出一個通知

 

處於掛起狀態的線程被調用了resume()方法

注意!!!

線程從阻塞狀態只能進入就緒狀態,沒法直接進入運行狀態。就緒和運行狀態之間的轉換一般不受程序控制,而是系統線程的調度決定的。

調用yield()方法可讓處於運行時的線程轉入就緒狀態。

3:線程死亡

線程會以如下三種方式結束,結束後處於死亡狀態

run或call方法執行完成,程序結束

線程拋出一個未捕獲的Exception或者Error

直接調用該線程的stop方法來結束線程

 

當主線程結束時,其它線程不受任何影響,並不會隨之結束。一旦子線程啓動起來後,他就會擁有和主線程相同的地位,它不會受主線程影響。

 

爲了測試某個線程是否死亡,能夠調用該線程的isAlive方法,當線程處於就緒,運行,阻塞三種狀態時,將返回true;當線程處於新建,死亡兩種狀態時返回爲false。

不要試圖對一個已經死亡的線程調用start方法讓它從新啓動,死亡後的線程沒法做爲線程使用。

 

若是處於非新建狀態的線程使用start方法,就會引起IllegalThreadStateException異常。

相關文章
相關標籤/搜索