Java必知必會之(四)---多線程全揭祕(上)

本文旨在用最通俗的語言講述最枯燥的基本知識。java

全文提綱:
1.線程是什麼?(上)
2.線程和進程的區別和聯繫(上)
3.建立多線程的方法(上)
4.線程的生命週期(上)
5.線程的控制(上)
6.線程同步(下)
7.線程池(下)
8.ThreadLocal的基本用法(下)
9.線程安全(下)編程


1.線程是什麼

線程是進程中的一個執行流程,是被系統獨立調度和分派的基本單位。

線程是什麼?進程是什麼?
這麼說可能有點懵逼,舉個栗子吧:安全

A工廠是一個生產汽車的工廠,今天員工張三去送貨,員工李四去進貨。這裏的多線程

  1. A工廠是一個進程(固然荒廢的沒有生命跡象的工廠不能算進程了)
  2. 員工張三去送貨 是一個線程
  3. 員工李四去進貨 也是一個線程

從例子能夠看出
進程是指運行中的程序(沒運行的程序,系統是不會爲之分配資源的),每一個進程都有本身獨立的內存空間,當一個程序進入內存運行時,程序內部可能包含多個程序執行流,這個程序執行流就是一個線程。併發

幾乎全部操做系統都支持多線程併發,就像咱們平時上班用的電腦,咱們可能習慣打開eclipse寫代碼,同時打開網易雲音樂聽課,並且還要打開有道翻譯時刻準備着把咱們的中文轉成英文…
可見咱們的電腦能夠支持多個應用程序同時執行的,但實際上,而對於每一個CPU來講,它在一個時間點內,只能執行一個程序,也就是一個進程,那爲何咱們同時打開這麼多程序運行沒問題呢?
那是由於現代電腦都不止一個CPU啦。固然這個是一個緣由。
最主要的是由於在程序運行過程當中,CPU在不一樣程序之間高速的來回切換執行,所以所謂的「併發執行」實際上並非多個程序在同時執行,而是系統對程序的執行作了調度,讓視覺上看起來是同時執行了。
因此線程中的併發:
是指多個進程被CPU快速的輪換執行,而不是同時執行app

2. 線程和進程的區別

經過上面的原理講述已經能看出區別了,最主要有2點eclipse

  1. 線程做爲調度和分配的基本單位,進程做爲擁有資源的基本單位
  2. 線程是進程中的一個執行流程,一個進程能夠包含多個線程

3. 多線程的建立

1. 繼承Thread類建立線程:

 1public class ThreadTest  extends Thread {
2    @Override
3    public void run() {
4        // 業務邏輯
5        super.run();
6    }
7
8    public static void main(String[] args) {
9        new ThreadTest().run();
10        new ThreadTest().run();
11    }
12}
複製代碼

2. 實現Runnable接口

 1public class ThreadTest  implements Runnable {
2    @Override
3    public void run() {
4         //業務邏輯
5    }
6
7    public static void main(String[] args) {
8        new ThreadTest().run();
9        new ThreadTest().run();
10    }
11}
複製代碼

3. 使用Callable和Future建立

Callable接口是jdk5以後的新接口,它提供了一個call方法做爲線程執行體,和thread的run方法相似,可是它的功能更強大:jvm

  • 它能夠有返回值
  • 它能夠聲明拋出異常
    所以也能夠像Runnable同樣,建立一個Callable對象做爲Thread的target,而實現它的call方法做爲執行體.
    同時jdk5提供了Future接口來表明Callable接口裏的call方法返回值,併爲Future接口提供了一個FutureTask實現類,該實現類實現了Future接口,並實現了Runable接口,它有如下幾個方法:
  1. boolean cancal(boolean mayInterruptRunning):試圖取消該Future裏關聯的Callable任務
  2. V get():返回Callable任務裏call()方法的返回值,調用該方法將致使程序阻塞,必須等到子線程結束後纔會獲得返回值
  3. V get(long timeout,TimeUnit unit):
  4. boolean isCancel():若是在Callable任務正常完成前被取消,則返回true
  5. boolean isDone():若是Callable任務已完成,則返回true
 1public static void main(String[] args) {
2
3        //1)建立一個Callable實現類,並實現call方法
4        //2)用FutrueTask來包裝類的實例
5     FutureTask ft=new FutureTask<>(new Callable<Integer>() {
6        @Override
7        public Integer call() throws Exception {
8            System.out.println("執行了");
9            try {
10                Thread.sleep(1000*5);
11            } catch (InterruptedException e1) {
12                // TODO Auto-generated catch block
13                e1.printStackTrace();
14            }
15
16            return 12;
17        }
18    });
19     //使用FutureTask對象做爲target來建立而且啓動線程
20     new Thread(ft).start();
21
22     //阻塞方式獲取線程返回值
23     try {
24        System.out.println("返回值:"+ft.get());
25    } catch (InterruptedException e) {
26        // TODO Auto-generated catch block
27        e.printStackTrace();
28    } catch (ExecutionException e) {
29        // TODO Auto-generated catch block
30        e.printStackTrace();
31    }
32     //帶有超時方式獲取線程返回值
33     try {
34        System.out.println("返回值:"+ft.get(2,TimeUnit.SECONDS));
35    } catch (InterruptedException | ExecutionException | TimeoutException e) {
36        // TODO Auto-generated catch block
37        e.printStackTrace();
38    }
39
40    }
複製代碼

4. 線程的生命週期

線程建立以後,不會當即處於運行狀態,根據前面對併發的定義理解:即便他啓動了也不會永遠都處於運行狀態,若是它一直處於運行狀態,就會一直佔據着CPU資源,線程之間的切換也就無從談起了。所以,線程是有生命週期的,他的生命週期包括如下幾種狀態:ide

  1. 新建(NEW)
  2. 就緒(Runnable)
  3. 運行(Running)
  4. 阻塞(Blocked)
  5. 死亡(Dead)
1. 新建狀態

當在程序中用new建立一個線程以後,它就處於新建狀態,此時它和程序中其它對象同樣處於初始化狀態(分配內存、初始化成員變量)。測試

2.就緒狀態

當程序調用了start方法以後,程序就處於就緒狀態,jvm會爲它建立方法調用棧和程序計數器,此時的線程狀態爲可運行狀態,並無運行,而是須要線程調度器的調度決定什麼時候運行。

3. 運行狀態

當就緒的線程得到CPU以後,就會執行線程執行體(run方法),這時候線程就處於了運行狀態。

4.阻塞狀態

處於運行的狀態的線程,除非執行時間很是很是很是短,不然它會由於系統對資源的調度而被中斷進入阻塞狀態。操做系統大多采用的是搶佔式調度策略,在線程得到CPU以後,系統給線程一段時間來處理任務,當到時間以後,系統會強制性剝奪線程所佔資源,而分配別的線程,至於分配給誰,這個取決於線程的優先級。

5.死亡狀態

處於運行狀態的線程,當它主動或者被動結束,線程就處於死亡狀態。至於結束的形式,一般有如下幾種:

  1. 線程執行完成,線程正常結束
  2. 線程執行過程當中出現異常或者錯誤,被動結束
  3. 線程主動調用stop方法結束線程

5.線程的控制

Java提供了線程在其生命週期中的一些方法,便於開發者對線程有更好的控制。
主要有如下方法:

  1. 等 待:join()
  2. 後 臺:setDeamon()
  3. 睡 眠:sleep()
  4. 讓 步:yield()
  5. 優先級:setPriority()

1.線程等待

當某個線程執行流中調用其餘線程的join()方法時,調用線程將被阻塞,知道被join()方法加入的join()線程執行完成爲止。

乍一看,怎麼也理解不了,這句話,咱們來寫一個程序測試一下:

 1public class ThreadTest  extends Thread {
2    @Override
3    public void run() {
4        System.out.println(getName()+"運行...");
5        for(int i=0;i<5;i++){
6            System.out.println(getName()+"執行:"+i);
7        }
8    }
9    public ThreadTest(String name){
10        super(name);
11    }
12    public static void main(String[] args) {
13        //main方法--主線程
14        //線程1
15        new ThreadTest("子線程1").start();
16        //線程2
17        ThreadTest t2=new ThreadTest("子線程2");
18        t2.start();
19
20        try {
21          t2.join(1000);
22        } catch (InterruptedException e) {
23          e.printStackTrace();
24        }
25        //線程3
26        new ThreadTest("子線程3").start();
27    }
28}
複製代碼

看輸出結果:

 1子線程1運行...
2子線程2運行...
3子線程2執行:0
4子線程2執行:1
5子線程2執行:2
6子線程2執行:3
7子線程1執行:0
8子線程2執行:4
9子線程1執行:1
10子線程1執行:2
11子線程1執行:3
12子線程1執行:4
13子線程3運行...
14子線程3執行:0
15子線程3執行:1
16子線程3執行:2
17子線程3執行:3
18子線程3執行:4
複製代碼

能夠看到,線程1和2在併發執行着,而線程3則在他們都執行完以後纔開始。
由此可知:
join()方法調用以後,後面的線程必須等待前面執行完以後才能執行,而不是併發執行

2.線程轉入後臺

當線程調用了setDaemon(true)以後,它就轉入爲後臺線程,爲前臺線程提供服務,而當前臺全部線程死亡時,後臺線程也會接受到JVM的通知而自動死亡。

1ThreadTest t2=new ThreadTest("子線程2");
2//這是爲後臺線程,但必須在start前設置,由於前臺線程死亡JVM會通知
3//後臺線程死亡,但接受指令到響應須要時間。所以要自愛start前就設置
4        t2.setDaemon(true);
5        t2.start();
複製代碼

3. 線程睡眠

當須要某個處於運行狀態的線程暫停執行而且進入阻塞狀態時,調用Thread.sleep既可。

4.線程讓步

當須要某個處於運行狀態的線程暫停執行而且進入就緒狀態,調用
Thread.yield()便可

5.線程優先級

前面說到,系統分配CPU給哪一個線程的執行,取決於線程的優先級,所以每一個線程都有必定的優先級,優先級高的線程會得到更多的執行機會,默認狀況下,每一個線程的默認優先級都與建立它的父線程優先級一致。
當咱們須要某個線程或者更多的執行機會時,調用
Thread.currentThread().setPriority(int newPriority);
方法便可,newPriority的範圍在1~10。

關於多線程的揭祕,上集都講到這裏,更高級的使用多線程,盡在下集 請關注我哦。


以爲本文對你有幫助?請分享給更多人

關注「編程無界」,提高裝逼技能

相關文章
相關標籤/搜索