Java多線程編程,多線程看這一篇就夠了

Java給多線程編程提供了內置的支持。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。

多線程是多任務的一種特別的形式,但多線程使用了更小的資源開銷。

這裏定義和線程相關的另一個術語—進程:一個進程包括由操作系統分配的內存空間,包含一個或者多個線程。一個線程不能獨立的存在,它必須是進程的一部分。一個進程一直運行,直到所有的非守護線程都結束運行後才能結束。

多線程能滿足程序員編寫高效率的程序來達到充分利用CPU的目的。


public enum State {
        //線程被new出來,還沒開始運行就是這狀態,對應枚舉下標0
        NEW,
        //線程調用start()方法後就會處在這個狀態,不管有沒有CPU資源分配給它,對應枚舉下標1
        RUNNABLE,
        //線程等待獲取鎖的時候就是這個狀態,對應枚舉下標2
        BLOCKED,
        //調用了wait(),join()等方法,卻沒有時間限制,除非有其他線程喚醒或者是中斷,否則線程就處於這個狀態,對應枚舉下標3
        WAITING,
        //調用了sleep(),wait(),join()等方法,不過設置了時間限制,所以會在指定的時間後自行返回,在這段時間裏線程處於這個狀態,對應枚舉下標4
        TIMED_WAITING,
        //線程執行完畢,就會變成這狀態,對應枚舉下標爲5
        TERMINATED;
    }

 

一個線程的生命週期

線程是一個動態執行的過程,它也有一個從產生到死亡的過程。

下圖顯示了一個線程完整的生命週期。

對於進程來說,從運行狀態轉向就緒狀態(時間片用完)。

  •  新建狀態:

使用new關鍵字和Thread類及其子類建立一個線程對象後,該對象就處於新建狀態。它保持這個狀態直到程序start()這個線程。

  • 就緒狀態:

當線程對象調用了start()方法之後,該線程就處於就緒狀態。就緒狀態的線程處於就緒隊列中,要等待JVM裏線程調度器的調度。

  • 運行狀態:

如果就緒狀態的線程獲取CPU資源,就可以執行run(),此時線程便處於運行狀態。處於運行狀態的線程最爲複雜,它可以變爲阻塞狀態、就緒狀態和死亡狀態(完成狀態)。

  • 阻塞狀態:

如果一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所佔用資源之後,該線程就從運行狀態進入阻塞狀態。在睡眠時間已到或者獲得設備資源之後可以重新進入就緒狀態。可以分爲三種:

  • 等待阻塞:運行狀態中的線程執行wait()方法,使線程進入到等待阻塞狀態。
  • 同步阻塞:線程在獲取synchronized同步鎖失敗(因爲同步鎖被其他線程佔用)。
  • 其他阻塞:通過調用線程的sleep()或者join()發出I/O請求時,線程就會進入到阻塞狀態。當sleep()狀態超時,join()等待線程終止或者超時,或者I/O處理完畢,線程重新轉入就緒狀態。
  • 死亡狀態:

一個運行狀態的線程完成任務或者其他終止條件發生時,該線程就會切換到終止狀態。


 

線程的優先級

每一個Java線程都有一個優先級,這樣有助於操作系統確定線程的調度順序。

Java線程的優先級是一個整數,其取值範圍是1(Thread.MIN_PRIORITY)-10(Thread.MAX_PRIORITY)。

默認情況下,每一個線程都會分配一個優先級NORM_PRIORITY(5)。

具有較高優先級的線程對程序更重要,並且應該在低優先級的線程之前分配處理器資源。但是,線程優先級不能保證線程執行的順序,而且非常依賴於平臺。


 

創建一個線程

Java提供了三種創建線程的方法:

  • 通過實現Runnable接口;
  • 通過繼承Thread類本身;
  • 通過Callable和Future創建線程。

 

通過實現Runnable接口來創建線程

創建一個線程,最簡單的方法是創建一個實現Runnable接口的類。

爲了實現Runnable,一個類只需要執行一個方法調用run(),聲明如下:

1

public void run()

 

你可以重寫該方法,重要的是理解run()可以調用其他方法,使用其他類,並聲明變量,就像主線程一樣。

在創建一個實現Runnable接口的類之後,你可以在類中實例化一個線程對象。

Thread定義了幾個構造方法,下面的這個是我們經常使用的:

1

Thread(Runnable threadOb,String threadName);

  

這裏,threadOb是一個實現Runnable接口的類的實例,並且threadName指定新線程的名字。

新線程創建之後,你調用它的start()方法它纔會運行。

1

void start();

  


 

通過繼承Thread來創建線程

創建一個線程的第二種方法是創建一個新的類,該類繼承Thread類,然後創建一個該類的實例。

繼承類必須重寫run()方法,該方法是新線程的入口點。它也必須調用start()方法才能執行。

該方法儘管被列爲一種多線程實現方式,但是本質上也是實現了Runnable接口的一個實例。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

package pkg;

 

class ThreadDemo extends Thread {

 

    private Thread t;

    private String threadName;

 

    ThreadDemo(String name) {

        threadName = name;

        System.out.println("Creating " + threadName);

    }

 

    public void run() {

        System.out.println("Running " + threadName);

        try {

            for (int i = 4; i > 0; i--) {

                System.out.println("Thread: " + threadName + ", " + i);

                // 讓線程睡眠一會

                Thread.sleep(50);

            }

        catch (InterruptedException e) {

            System.out.println("Thread " + threadName + " interrupted.");

        }

        System.out.println("Thread " + threadName + " exiting.");

    }

 

    public void start() {

        System.out.println("Starting " + threadName);

        if (t == null) {

            t = new Thread(this, threadName);

            t.start();

        }

    }

}

 

public class TestThread {

 

    public static void main(String args[]) {

        ThreadDemo T1 = new ThreadDemo("Thread-1");

        T1.start();

 

        ThreadDemo T2 = new ThreadDemo("Thread-2");

        T2.start();

    }

}

  


 

Thread方法  

下表列出了Thread類的一些重要方法:

測試線程是否處於活動狀態。上述方法是被Thread對象調用的。下面的方法是Thread類的靜態方法。

實例

如下的ThreadClassDemo程序演示了Thread類的一些方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

package pkg;

 

/**

 *

 * @author yl

 */

public class DisplayMessage implements Runnable {

 

    private String message;

 

    public DisplayMessage(String message) {

        this.message = message;

    }

 

    public void run() {

        while (true) {

            System.out.println(message);

        }

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

package pkg;

 

/**

 *

 * @author yl

 */

public class GuessANumber extends Thread {

 

    private int number;

 

    public GuessANumber(int number) {

        this.number = number;

    }

 

    public void run() {

        int counter = 0;

        int guess = 0;

        do {

            guess = (int) (Math.random() * 100 1);

            System.out.println(this.getName() + "guess " + guess);

            counter++;

        while (guess != number);

        System.out.println("** Correct!" this.getName() + "in" + counter + "guess.***");

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

package pkg;

 

/**

 *

 * @author yl

 */

public class ThreadClassDemo {

 

    public static void main(String[] args) {

        Runnable hello = new DisplayMessage("Hello");

        Thread thread1 = new Thread(hello);

        thread1.setDaemon(true);

        thread1.setName("hello");

        System.out.println("Starting hello thread...");

        thread1.start();

 

        Runnable bye = new DisplayMessage("Goodbye");

        Thread thread2 = new Thread(bye);

        thread2.setPriority(Thread.MIN_PRIORITY);

        thread2.setDaemon(true);

        System.out.println("Starting goodbye thread...");

        thread2.start();

 

        System.out.println("Starting thread3...");

        Thread thread3 = new GuessANumber(27);

        thread3.start();

        try {

            thread3.join();

        catch (InterruptedException e) {

            System.out.println("Thread interrupted.");

        }

        System.out.println("Starting thread4...");

        Thread thread4 = new GuessANumber(75);

        thread4.start();

        System.out.println("main() id ending...");

    }

 

}


 

通過Callable和Future創建線程

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

package pkg;

 

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.FutureTask;

 

public class CallableThreadTest implements Callable<Integer> {

 

    public static void main(String[] args) {

        CallableThreadTest ctt = new CallableThreadTest();

        FutureTask<Integer> ft = new FutureTask<>(ctt);

        for (int i = 0; i < 100; i++) {

            System.out.println(Thread.currentThread().getName() + " 的循環變量i的值" + i);

            if (i == 20) {

                new Thread(ft, "有返回值的線程").start();

            }

        }

        try {

            System.out.println("子線程的返回值:" + ft.get());

        catch (InterruptedException e) {

            e.printStackTrace();

        catch (ExecutionException e) {

            e.printStackTrace();

        }

 

    }

 

    @Override

    public Integer call() throws Exception {

        int i = 0;

        for (; i < 100; i++) {

            System.out.println(Thread.currentThread().getName() + " " + i);

        }

        return i;

    }

}


 

創建線程的三種方式的對比

採用實現Runnable、Callable接口的方式創建多線程時,線程類只是實現了Runnable接口或者Callable接口,還可以繼承其他類。

使用繼承Thread類的方式創建多線程時,編寫簡單,如果需要訪問當前線程,則無需使用Thread.currentThread()方法,直接使用this即可獲得當前線程。


線程的幾個主要概念

  • 線程同步
  • 線程間通信
  • 線程死鎖
  • 線程控制:掛起、停止和恢復  

 

多線程的使用

有效利用多線程的關鍵是理解程序是併發執行而不是串行執行的,例如:程序中有兩個子程序需要併發執行,這時候就需要利用多線程編程。

通過對多線程的使用,可以編寫出非常高效的程序。不過請注意,如果你創建太多的線程,程序執行的效率實際上是降低了,而不是提升了。

請記住,上下文的切換開銷也很重要,如果你創建了太多的線程,CPU花費在上下文的切換的時間將多於執行程序的時間。 

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