白話說編程之java線程
- 線程和進程:
- 進程:
- 線程:
- 線程和進程的區別:
- 詳解多線程:
- 并發
- 為什么使用并發
- 并發的執行原理
- 并行
- 線程的五種狀態:
- 創建狀態:
- 就緒狀態:
- 運行狀態:
- 阻塞狀態:
- 死亡狀態:
- 線程的創建方式:
- 繼承Thread
- 實現Runnable
- 匿名內部類方式
- 迷惑問題
- 繼承THread類方法和實現Runnable接口 這兩種方式選擇哪一種好呢?
- 繼器啟動線程是調用run方法還是start方法
- 線程的分類:
- 用戶線程(也叫前臺線程):
- 守護線程(也叫后臺線程):
- 分享一波:程序員賺外快-必看的巔峰干貨
線程和進程:
在說多線程之前,我們先來研究一下線程,說到線程,我們又不得不說到進程,因為很多初學者會把線程和進程分不清,搞混淆。
進程:
是操作系統系統運行的最小單元。怎么理解這句話,可以這樣去對比,相信大家都見過積木玩具,可以搭建成很多的大型成品(操作系統Windows/Linux等等),而每一個積木都是組成這個成品的一個組件(也就是進程單元)這是操作系統和進程的關系(操作系統的組成很復雜,不是像積木簡單的組合就能完成的,但是最底層原理我們可以這么想象是沒有問題的)。
線程:
線程是一組指令的集合,它可以獨立的運行在一個進程里。關于指令的集合:就是指我們編程里寫的一個類,里面用到的關鍵字、方法名、變量名等等都是指令(你們可以這樣去理解,完全沒有問題,這里聲明一下真正的指令是jvm編譯成.class文件后,.class中的二進制碼才是真正的指令,但是二進制碼對于人類來說太多太難記,所以就用關鍵字來代表二進制的含義)。在這里我們又可以把線程去當成一個積木,而進程是一個個積木所組成的成品。
線程和進程的區別:
通過上面的例子,不難理解進程和線程之間的關系,進程包含線程,線程是進程的一個單元。所以在這里我們要注意一下,在進程中至少包含一個線程(主線程)。(這樣講,一個積木都沒有還會有成品嗎)
總結:進程是所有線程的集合,每一個線程是進程中的一條執行路徑。
詳解多線程:
在上面的例子中,我們知道線程和進程的區別,對于多線程的具體作用我們在這里詳細解釋。
并發
并發:多個線程在一個單核cpu上進行資源搶占并運行,就是并發。
多線程在cpu中是并發運行的(下面會詳細解釋并發,還有和并行的區別)
首先我們要知道在cpu(這里指的是單核cpu)中在某一個時間片,只能有一個線程在cpu中運行。一個cpu在同一時間只能執行一個線程。
為什么使用并發
在一個進程中有多個線程需要執行,這些線程會搶占cpu的時間片來運行自身。有人會問,為什么不等一個線程執行完之后再去執行另一個線程呢?思考一下,我們在使用電腦的時候,可以同時看文檔,聽音樂。如果cpu設計成一個線程執行完之后再去執行另一個線程,你就只能聽完音樂,在去看文檔,在聽音樂的過程中你的鼠標都移動不了(鼠標也是一個單獨線程),你們覺得這樣的體驗好嗎,所以cpu肯定不能這樣設計。
并發的執行原理
操作系統是以搶占式的方法來調度線程的(也有其他方法,我們目前先學這一種)。就是線程自己去強cpu的資源,誰先搶到誰就在cpu上運行一個時間片(這個時間片很短很短)。這里的線程會有優先級的特點,優先級高的線程搶到cpu的概率大,概率大,概率大(是概率大,不代表一定能搶到)運行完成之后釋放cpu資源,回去重新搶占cpu。因為運行的時間很短,所以在人為感官上覺得這些線程始再同時運行的。
其實上面多個線程搶占cpu資源就是多線程的并發。
注意:這里是一個cpu。隨著需求的不斷增高,科技的不斷發展,單核cpu的性能已經不能滿足效率。于是就出現了多核cpu,也就出現了并行
并行
并行:在同一時間點,多個線程同時運行在多核cpu的多個核上,這叫并行。和并發不同的是:同一時間,在并行中,線程可以同時運行,而并發則只能有一個線程運行。
線程的五種狀態:
創建狀態:
當用new操作符創建一個線程時, 例如new Thread?,線程還沒有開始運行,此時線程處在新建狀態。 當一個線程處于新生狀態時,程序還沒有開始運行線程中的代碼
就緒狀態:
一個新創建的線程并不自動開始運行,要執行線程,必須調用線程的start()方法。當線程對象調用start()方法即啟動了線程,start()方法創建線程運行的系統資源,并調度線程運行run()方法。當start()方法返回后,線程就處于就緒狀態。
處于就緒狀態的線程并不一定立即運行run()方法,線程還必須同其他線程競爭CPU時間,只有獲得CPU時間才可以運行線程。因為在單CPU的計算機系統中,不可能同時運行多個線程,一個時刻僅有一個線程處于運行狀態。因此此時可能有多個線程處于就緒狀態。對多個處于就緒狀態的線程是由Java運行時系統的線程調度程序(thread scheduler)來調度的。
運行狀態:
當線程獲得CPU時間后,它才進入運行狀態,真正開始執行run()方法.
阻塞狀態:
線程運行過程中,可能由于各種原因進入阻塞狀態:
1>線程通過調用sleep方法進入睡眠狀態;
2>線程調用一個在I/O上被阻塞的操作,即該操作在輸入輸出操作完成之前不會返回到它的調用者;
3>線程試圖得到一個鎖,而該鎖正被其他線程持有;
4>線程在等待某個觸發條件;
死亡狀態:
有兩個原因會導致線程死亡:
1> run方法正常退出而自然死亡,
2> 一個未捕獲的異常終止了run方法而使線程猝死。
為了確定線程在當前是否存活著(就是要么是可運行的,要么是被阻塞了),需要使用isAlive方法。如果是可運行或被阻塞,這個方法返回true; 如果線程仍舊是new狀態且不是可運行的, 或者線程死亡了,則返回false.
如圖所示:
線程的創建方式:
繼承Thread
實現Runnable
匿名內部類方式
上面只要會繼承THread類方法和實現Runnable接口這兩種就行,還有其他的線程池創建等等,在后面的章節說
迷惑問題
繼承THread類方法和實現Runnable接口 這兩種方式選擇哪一種好呢?
在java語言中,有一個特點,就是單繼承,多實現。實現了接口還可以繼續繼承,繼承了類不能再繼承。所以一般情況下使用實現Runnable會有更好的拓展性。
繼器啟動線程是調用run方法還是start方法
Run方法僅僅只是線程所要執行的代碼塊部分,如果調用run方法,就和調用其他普通方法一樣,從上到下,順序執行。沒有了多線程的特點。所以使用start方法使線程進入就緒狀態。
線程的分類:
用戶線程(也叫前臺線程):
用戶自定義創建的線程,用戶線程的狀態不受其他線程的影響。別的線程掛了就掛了,影響不到他,因為他們是互相獨立的。
守護線程(也叫后臺線程):
守護線程受用戶線程的影響,當用戶線程銷毀后,守護線程也會跟著銷毀。比如GC