本文旨在用最通俗的語言講述最枯燥的基本知識。java
全文提綱:
1.線程是什麼?(上)
2.線程和進程的區別和聯繫(上)
3.建立多線程的方法(上)
4.線程的生命週期(上)
5.線程的控制(上)
6.線程同步(下)
7.線程池(下)
8.ThreadLocal的基本用法(下)
9.線程安全(下)編程
線程是什麼?進程是什麼?
這麼說可能有點懵逼,舉個栗子吧:安全
A工廠是一個生產汽車的工廠,今天員工張三去送貨,員工李四去進貨。這裏的多線程
- A工廠是一個進程(固然荒廢的沒有生命跡象的工廠不能算進程了)
- 員工張三去送貨 是一個線程
- 員工李四去進貨 也是一個線程
從例子能夠看出
進程是指運行中的程序(沒運行的程序,系統是不會爲之分配資源的),每一個進程都有本身獨立的內存空間,當一個程序進入內存運行時,程序內部可能包含多個程序執行流,這個程序執行流就是一個線程。併發
幾乎全部操做系統都支持多線程併發,就像咱們平時上班用的電腦,咱們可能習慣打開eclipse寫代碼,同時打開網易雲音樂聽課,並且還要打開有道翻譯時刻準備着把咱們的中文轉成英文…
可見咱們的電腦能夠支持多個應用程序同時執行的,但實際上,而對於每一個CPU來講,它在一個時間點內,只能執行一個程序,也就是一個進程,那爲何咱們同時打開這麼多程序運行沒問題呢?
那是由於現代電腦都不止一個CPU啦。固然這個是一個緣由。
最主要的是由於在程序運行過程當中,CPU在不一樣程序之間高速的來回切換執行,所以所謂的「併發執行」實際上並非多個程序在同時執行,而是系統對程序的執行作了調度,讓視覺上看起來是同時執行了。
因此線程中的併發:
是指多個進程被CPU快速的輪換執行,而不是同時執行app
經過上面的原理講述已經能看出區別了,最主要有2點eclipse
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}
複製代碼
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}
複製代碼
Callable接口是jdk5以後的新接口,它提供了一個call方法做爲線程執行體,和thread的run方法相似,可是它的功能更強大:jvm
- 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 }
複製代碼
線程建立以後,不會當即處於運行狀態,根據前面對併發的定義理解:即便他啓動了也不會永遠都處於運行狀態,若是它一直處於運行狀態,就會一直佔據着CPU資源,線程之間的切換也就無從談起了。所以,線程是有生命週期的,他的生命週期包括如下幾種狀態:ide
- 新建(NEW)
- 就緒(Runnable)
- 運行(Running)
- 阻塞(Blocked)
- 死亡(Dead)
當在程序中用new建立一個線程以後,它就處於新建狀態,此時它和程序中其它對象同樣處於初始化狀態(分配內存、初始化成員變量)。測試
當程序調用了start方法以後,程序就處於就緒狀態,jvm會爲它建立方法調用棧和程序計數器,此時的線程狀態爲可運行狀態,並無運行,而是須要線程調度器的調度決定什麼時候運行。
當就緒的線程得到CPU以後,就會執行線程執行體(run方法),這時候線程就處於了運行狀態。
處於運行的狀態的線程,除非執行時間很是很是很是短,不然它會由於系統對資源的調度而被中斷進入阻塞狀態。操做系統大多采用的是搶佔式調度策略,在線程得到CPU以後,系統給線程一段時間來處理任務,當到時間以後,系統會強制性剝奪線程所佔資源,而分配別的線程,至於分配給誰,這個取決於線程的優先級。
處於運行狀態的線程,當它主動或者被動結束,線程就處於死亡狀態。至於結束的形式,一般有如下幾種:
- 線程執行完成,線程正常結束
- 線程執行過程當中出現異常或者錯誤,被動結束
- 線程主動調用stop方法結束線程
Java提供了線程在其生命週期中的一些方法,便於開發者對線程有更好的控制。
主要有如下方法:
- 等 待:join()
- 後 臺:setDeamon()
- 睡 眠:sleep()
- 讓 步:yield()
- 優先級:setPriority()
當某個線程執行流中調用其餘線程的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()方法調用以後,後面的線程必須等待前面執行完以後才能執行,而不是併發執行
當線程調用了setDaemon(true)以後,它就轉入爲後臺線程,爲前臺線程提供服務,而當前臺全部線程死亡時,後臺線程也會接受到JVM的通知而自動死亡。
1ThreadTest t2=new ThreadTest("子線程2");
2//這是爲後臺線程,但必須在start前設置,由於前臺線程死亡JVM會通知
3//後臺線程死亡,但接受指令到響應須要時間。所以要自愛start前就設置
4 t2.setDaemon(true);
5 t2.start();
複製代碼
當須要某個處於運行狀態的線程暫停執行而且進入阻塞狀態時,調用Thread.sleep既可。
當須要某個處於運行狀態的線程暫停執行而且進入就緒狀態,調用
Thread.yield()便可
前面說到,系統分配CPU給哪一個線程的執行,取決於線程的優先級,所以每一個線程都有必定的優先級,優先級高的線程會得到更多的執行機會,默認狀況下,每一個線程的默認優先級都與建立它的父線程優先級一致。
當咱們須要某個線程或者更多的執行機會時,調用
Thread.currentThread().setPriority(int newPriority);
方法便可,newPriority的範圍在1~10。
關於多線程的揭祕,上集都講到這裏,更高級的使用多線程,盡在下集 請關注我哦。
以爲本文對你有幫助?請分享給更多人
關注「編程無界」,提高裝逼技能