本文旨在用最通俗的語言講述最枯燥的基本知識。
全文提綱:
1.線程是什麼?(上)
2.線程和進程的區別和聯繫(上)
3.創建多線程的方法(上)
4.線程的生命週期(上)
5.線程的控制(上)
6.線程同步(下)
7.線程池(下)
8.ThreadLocal的基本用法(下)
9.線程安全(下)
1.線程是什麼
線程是進程中的一個執行流程,是被系統獨立調度和分派的基本單位。
線程是什麼?進程是什麼?
這麼說可能有點懵逼,舉個栗子吧:
A工廠是一個生產汽車的工廠,今天員工張三去送貨,員工李四去進貨。這裏的
A工廠是一個進程(當然荒廢的沒有生命跡象的工廠不能算進程了)
員工張三去送貨 是一個線程
員工李四去進貨 也是一個線程
從例子可以看出
進程是指運行中的程序(沒運行的程序,系統是不會爲之分配資源的),每個進程都有自己獨立的內存空間,當一個程序進入內存運行時,程序內部可能包含多個程序執行流,這個程序執行流就是一個線程。
幾乎所有操作系統都支持多線程併發,就像我們平時上班用的電腦,我們可能習慣打開eclipse寫代碼,同時打開網易雲音樂聽課,而且還要打開有道翻譯時刻準備着把我們的中文轉成英文…
可見我們的電腦可以支持多個應用程序同時執行的,但實際上,而對於每個CPU來說,它在一個時間點內,只能執行一個程序,也就是一個進程,那爲什麼我們同時打開這麼多程序運行沒問題呢?
那是因爲現代電腦都不止一個CPU啦。當然這個是一個原因。
最主要的是因爲在程序運行過程中,CPU在不同程序之間高速的來回切換執行,因此所謂的“併發執行”實際上並不是多個程序在同時執行,而是系統對程序的執行做了調度,讓視覺上看起來是同時執行了。
所以線程中的併發:
是指多個進程被CPU快速的輪換執行,而不是同時執行
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方法類似,但是它的功能更強大:
它可以有返回值
它可以聲明拋出異常
因此也可以像Runnable一樣,創建一個Callable對象作爲Thread的target,而實現它的call方法作爲執行體.
同時jdk5提供了Future接口來代表Callable接口裏的call方法返回值,併爲Future接口提供了一個FutureTask實現類,該實現類實現了Future接口,並實現了Runable接口,它有以下幾個方法:
boolean cancal(boolean mayInterruptRunning):試圖取消該Future裏關聯的Callable任務
V get():返回Callable任務裏call()方法的返回值,調用該方法將導致程序阻塞,必須等到子線程結束後纔會得到返回值
V get(long timeout,TimeUnit unit):
boolean isCancel():如果在Callable任務正常完成前被取消,則返回true
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資源,線程之間的切換也就無從談起了。因此,線程是有生命週期的,他的生命週期包括以下幾種狀態:
新建(NEW)
就緒(Runnable)
運行(Running)
阻塞(Blocked)
死亡(Dead)
1. 新建狀態
當在程序中用new創建一個線程之後,它就處於新建狀態,此時它和程序中其它對象一樣處於初始化狀態(分配內存、初始化成員變量)。
2.就緒狀態
當程序調用了start方法之後,程序就處於就緒狀態,jvm會爲它創建方法調用棧和程序計數器,此時的線程狀態爲可運行狀態,並沒有運行,而是需要線程調度器的調度決定何時運行。
3. 運行狀態
當就緒的線程獲得CPU之後,就會執行線程執行體(run方法),這時候線程就處於了運行狀態。
4.阻塞狀態
處於運行的狀態的線程,除非執行時間非常非常非常短,否則它會因爲系統對資源的調度而被中斷進入阻塞狀態。操作系統大多采用的是搶佔式調度策略,在線程獲得CPU之後,系統給線程一段時間來處理任務,當到時間之後,系統會強制性剝奪線程所佔資源,而分配別的線程,至於分配給誰,這個取決於線程的優先級。
5.死亡狀態
處於運行狀態的線程,當它主動或者被動結束,線程就處於死亡狀態。至於結束的形式,通常有以下幾種:
線程執行完成,線程正常結束
線程執行過程中出現異常或者錯誤,被動結束
線程主動調用stop方法結束線程
5.線程的控制
Java提供了線程在其生命週期中的一些方法,便於開發者對線程有更好的控制。
主要有以下方法:
等 待:join()
後 臺:setDeamon()
睡 眠:sleep()
讓 步:yield()
優先級: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。
關於多線程的揭祕,上集都講到這裏,更高級的使用多線程,盡在下集 請關注我哦。
強烈推薦:
覺得本文對你有幫助?請分享給更多人
關注「編程×××」,提升裝逼技能