Java多線程之線程創建與啓動

一、線程與進程

操作系統中可以支持多個程序同時運行,每個程序就代表了一個進程,各個程序即進程間相互獨立工作,而每個程序中可能包含多個順序執行流,每個執行流就代表了一個線程,各個線程共享進程中的資源。

1.什麼是進程

進程是系統進行資源調度與分配的最小單位,一般而言,進程具有三個特點:

  • 獨立性:每個進程都有自己獨立的私有資源,不與其他進程共享,也就是說,其他進程不可以直接訪問其他進程的地址空間。
  • 動態性:進程具有自己的生命週期和狀態轉變。
  • 併發性:併發是指在同一時刻只能有一條指令執行,但多個進程可以被快速輪換執行,由於處理速度非常快,感覺上多個進程在同時進行。

2.什麼是線程

線程也被成爲輕量級進程,當進程中需要併發執行多個任務時,就需要使用線程的概念,線程是進程的執行單元,它們可以共享進程中的資源。

3.進程與線程的區別

  • 進程是資源的分配和調度的一個獨立單元,而線程是CPU調度的基本單元。
  • 同一個進程中可以包含多個線程,一個進程中至少包含一個主線程。
  • 同一進程中的多個線程共享代碼段(代碼和常量),數據段(全局變量和靜態變量),擴展段(堆存儲)。但是每個線程擁有自己的棧段,棧段又叫運行時段,用來存放所有局部變量和臨時變量。
  • 同一個進程中的線程可以併發執行。
  • 線程間的通信比進程間通信要方便的多,但是線程之間需要處理好同步與互斥的關係。

二、線程的創建與啓動

Java使用Thread類代表線程,所有的線程對象都必須是Thread類或其子類的實例,Java中創建多線程有三種方法:

  • 繼承Thread類重寫run方法
  • 實現Runnable接口重寫run方法
  • 使用Callable和Future創建線程

1.繼承Thread類創建線程類

(1)定義Thread類的子類,並重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務。
(2)創建Thread子類的實例,即創建了線程對象。
(3)調用線程對象的start()方法來啓動該線程。

代碼如下所示:

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

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ThreadTest thread1 = new ThreadTest();
        ThreadTest thread2 = new ThreadTest();
        for(int i=0; i<50;i++){
            System.out.println(Thread.currentThread().getName()+"  : "+i);  
            if(i==10)  
            {
                thread1.start();
                thread2.start();
            }
        }
    }

}

運行結果可能如下所示:

main  : 15
Thread-1 0
Thread-1 1
Thread-1 2
Thread-1 3
Thread-1 4
Thread-1 5
main  : 16
Thread-1 6
main  : 17
Thread-0 0
Thread-1 7
Thread-0 1
main  : 18
Thread-0 2
Thread-1 8
Thread-0 3
main  : 19
main  : 20
Thread-0 4

由測試結果可知,main進程執行至i=10時,開始新建兩個新線程,兩個新線程從i=0開始分別執行,Thread-0和Thread-1中的i值是不連續的,證明兩個線程中的i值不共享,因爲兩個新線程是ThreadTest創建的兩個實例,Thread-0和Thread-1不能共享實例變量。

2.實現Runnable接口創建線程類

(1)定義runnable接口的實現類,並重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執行體。
(2)創建 Runnable實現類的實例,並依此實例作爲Thread的target來創建Thread對象,該Thread對象纔是真正的線程對象。
(3)調用線程對象的start()方法來啓動該線程。

代碼如下所示:


public class RunnableTest implements Runnable{
    int i = 0;
    @Override
    public void run() {
        // TODO Auto-generated method stub
        for(;i<50;i++){
            System.out.println(Thread.currentThread().getName() + " " +i);
        }

    }
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        RunnableTest runnable = new RunnableTest();
        for(int i = 0;i < 100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" "+i);  
            if(i==20)  
            {  
                new Thread(runnable,"線程1").start();  
                new Thread(runnable,"線程2").start();  
            }  
        }  
    }

}

運行結果可能如下所示:

main 53
main 54
main 55
main 56
線程1 0
線程2 0
main 57
線程1 1
main 58
線程2 2
main 59
線程1 3
main 60
線程2 4
main 61

由測試結果可知,兩個子線程的i值是連續變化的,說明使用Runnable接口的方式創建的多個線程可以共享線程類的實例變量。

3.使用Callable和Future創建線程

(1)創建Callable接口的實現類,並實現call()方法,該call()方法將作爲線程執行體,並且有返回值,call()方法還可以拋出異常。
(2)創建Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。
(3)使用FutureTask對象作爲Thread對象的target創建並啓動新線程。
(4)調用FutureTask對象的get()方法來獲得子線程執行結束後的返回值

實現代碼如下所示:


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
 * 創建Callable接口的實現類,並實現call()方法,該call()方法將作爲線程執行體,並且有返回值
 * @author zhx
 *
 */
class CallableDemo implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        // 計算1-100的和
        int sum = 0;
        for (int i = 1; i <= 100; i++)
            sum += i;
        return sum;
    }
}

public class CallableTest {

    public static void main(String[] args) {
        CallableDemo cd = new CallableDemo();
        //創建Callable實現類的實例,使用FutureTask類來包裝Callable對象
        //該FutureTask對象封裝了該Callable對象的call()方法的返回值
        FutureTask<Integer> result = new FutureTask<>(cd);
        //使用FutureTask對象作爲Thread對象的target創建並啓動新線程。
        new Thread(result).start();
        int sum = 0;
        // 接收運算結果
        // 只有當該線程執行完畢後纔會獲取到運算結果,等同於閉鎖的效果
        try {
            //調用FutureTask對象的get()方法來獲得子線程執行結束後的返回值
            sum = result.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("sum is " + sum);
    }
}

三、三種創建方式的對比

1.採用實現Runnable、Callable接口的方式創見多線程

優勢:

線程類只是實現了Runnable接口或Callable接口,還可以繼承其他類。

在這種方式下,多個線程可以共享同一個target對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU、代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想。

劣勢:

編程稍微複雜,如果要訪問當前線程,則必須使用Thread.currentThread()方法。

2.使用繼承Thread類的方式創建多線程

優勢:

編寫簡單,如果需要訪問當前線程,則無需使用Thread.currentThread()方法,直接使用this即可獲得當前線程。

劣勢:

線程類已經繼承了Thread類,所以不能再繼承其他父類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章