Java多線程、同步異步及阻塞和非阻塞


1、進程和線程的概念

進程:運行中的應用程序稱為進程,擁有系統資源(cpu、內存)

線程:進程中的一段代碼,一個進程中可以有多段代碼。本身不擁有資源(共享所在進程的資源);

在java中,程序入口被自動創建為主線程,在主線程中可以創建多個子線程。

多進程: 在操作系統中能同時運行多個任務(程序)

多線程: 在同一應用程序中有多個功能流同時執行

已經有了進程,為什么還會需要線程呢?主要原因如下:

許多應用程序中,同時發生着多個活動。將這些應用程序分解成多個准並行的線程,程序設計的模型會變成更加簡單。
由於線程比進程進行更加輕量,創建和取消更加容易。
如果程序是IO密集型,那么多線程執行能夠加快程序的執行速度。(如果是CPU密集型,則沒有這個優勢)
在多CPU系統中,多線程是可以真正並行執行的。

2、線程的主要特點

①、不能以一個文件名的方式獨立存在在磁盤中;

②、不能單獨執行,只有在進程啟動后才可啟動;

③、線程可以共享進程相同的內存(代碼與數據)。

3、多線程原理

同一時間,CPU只能處理1條線程,只有1條線程在工作(執行)
多線程並發(同時)執行,其實是CPU快速地在多條線程之間調度(切換)
如果CPU調度線程的時間足夠快,就造成了多線程並發執行的假象

思考:如果線程非常非常多,會發生什么情況?

CPU會在N多線程之間調度,CPU會累死,消耗大量的CPU資源

每條線程被調度執行的頻次會降低(線程的執行效率降低)

4、線程的主要用途

①、利用它可以完成重復性的工作(如實現動畫、聲音等的播放)。

②、從事一次性較費時的初始化工作(如網絡連接、聲音數據文件的加載)。

③、並發執行的運行效果(一個進程多個線程)以實現更復雜的功能

5、多線程(多個線程同時運行)程序的優缺點

優點:

①、可以減輕系統性能方面的瓶頸,因為可以並行操作;

②、提高CPU的處理器的效率,在多線程中,通過優先級管理,可以使重要的程序優先操作,提高了任務管理的靈活性;

另一方面,在多CPU系統中,可以把不同的線程在不同的CPU中執行,真正做到同時處理多任務。

缺點:

1、開啟線程需要占用一定的內存空間(默認情況下,主線程占用1M,子線程占用512KB),如果開啟大量的線程,會占用大量的內存空間,降低程序的性能

2、線程越多,CPU在調度線程上的開銷就越大

3、程序設計更加復雜:比如線程之間的通信、多線程的數據共享

6、多線程的生命周期

線程狀態:

與人有生老病死一樣,線程也同樣要經歷新建、就緒、運行(活動)、阻塞和死亡五種不同的狀態。這五種狀態都可以通過Thread類中的方法進行控制。

創建並運行線程:

① 新建狀態(New Thread):在Java語言中使用new 操作符創建一個線程后,該線程僅僅是一個空對象,它具備類線程的一些特征,但此時系統沒有為其分配資源,這時的線程處於創建狀態。

線程處於創建狀態時,可通過Thread類的方法來設置各種屬性,如線程的優先級(setPriority)、線程名(setName)和線程的類型(setDaemon)等。

② 就緒狀態(Runnable):使用start()方法啟動一個線程后,系統為該線程分配了除CPU外的所需資源,使該線程處於就緒狀態。此外,如果某個線程執行了yield()方法,那么該線程會被暫時剝奪CPU資源,重新進入就緒狀態。

③ 運行狀態(Running):Java運行系統通過調度選中一個處於就緒狀態的線程,使其占有CPU並轉為運行狀態。此時,系統真正執行線程的run()方法。

a) 可以通過Thread類的isAlive方法來判斷線程是否處於就緒/運行狀態:當線程處於就緒/運行狀態時,isAlive返回true,當isAlive返回false時,可能線程處於阻塞狀態,也可能處於停止狀態。

④ 阻塞和喚醒線程

阻塞狀態(Blocked):一個正在運行的線程因某些原因不能繼續運行時,就進入阻塞 狀態。這些原因包括:

等待阻塞:當線程執行了某個對象的wait()方法時,線程會被置入該對象的等待集中,直到執行了該對象的notify()方法wait()/notify()方法的執行要求線程首先獲得該對象的鎖。
同步阻塞:當多個線程試圖進入某個同步區域(同步鎖)時,沒能進入該同步區域(同步鎖)的線程會被置入鎖定集(鎖池)中,直到獲得該同步區域的鎖,進入就緒狀態。
其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
⑤ 死亡狀態(Dead):線程在run()方法執行結束后進入死亡狀態。此外,如果線程執行了interrupt()或stop()方法,那么它也會以異常退出的方式進入死亡狀態。

7、終止線程的三種方法

① 使用退出標志,使線程正常退出,也就是當run方法完成后線程終止,推薦使用。

② 使用stop方法強制終止線程(這個方法不推薦使用,因為stop和suppend、resume一樣,也可能發生不可預料的結果)。

③ 使用interrupt方法中斷線程。

8、概念解釋

8.1 同步/異步, 它們是消息的通知機制

同步:
所謂同步,當前程序執行完才能執行后面的程序,程序執行時按照順序執行,需要等待。平時寫的代碼基本都是同步的;

異步:
異步的概念和同步相對。
程序沒有等到上一步程序執行完才執行下一步,而是直接往下執行,前提是下面的程序沒有用到異步操作的值,異步的實現方式基本上都是多線程(定時任務也可實現,但是情況少)。

8.2 阻塞/非阻塞, 它們是程序在等待消息(無所謂同步或者異步)時的狀態.

阻塞:
阻塞調用是指調用結果返回之前,當前線程會被掛起。函數只有在得到結果之后才會返回。
有人也許會把阻塞調用和同步調用等同起來,實際上他是不同的。
對於同步調用來說,很多時候當前線程還是激活的,只是從邏輯上當前函數沒有返回而已。

非阻塞:
非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函數不會阻塞當前線程,而會立刻返回。

簡單示例:老張燒水
老張愛喝茶,廢話不說,煮開水。
出場人物:老張,水壺兩把(普通水壺,簡稱水壺;會響的水壺,簡稱響水壺)。
1 老張把水壺放到火上,立等水開。(同步阻塞)

老張覺得自己有點傻
2 老張把水壺放到火上,去客廳看電視,時不時去廚房看看水開沒有。(同步非阻塞)

老張還是覺得自己有點傻,於是變高端了,買了把會響笛的那種水壺。水開之后,能大聲發出嘀~~~~的噪音。
3 老張把響水壺放到火上,立等水開。(異步阻塞)(本可以坐着等通知的卻非要立即等着,實際不大會出現這種情況,異步異步阻塞沒有實際意義)

老張覺得這樣傻等意義不大
4 老張把響水壺放到火上,去客廳看電視,水壺響之前不再去看它了,響了再去拿壺。(異步非阻塞)

所謂同步異步,只是對於水壺而言。
普通水壺,同步;響水壺,異步。
雖然都能干活,但響水壺可以在自己完工之后,提示老張水開了。這是普通水壺所不能及的。
同步只能讓調用者去輪詢自己(情況2中),造成老張效率的低下。
所謂阻塞非阻塞,僅僅對於老張而言。
立等的老張,阻塞;看電視的老張,非阻塞。
情況1和情況3中老張就是阻塞的,媳婦喊他都不知道。雖然3中響水壺是異步的,可對於立等的老張沒有太大的意義。所以一般異步是配合非阻塞使用的,這樣才能發揮異步的效用。

同步阻塞關系:
線程阻塞(祥見多線程介紹)除了程序主動調用休眠外常見的就是程序遇到同步代碼塊,同一時間不能並行執行,當有多個請求了出現線程等待的情況即為阻塞。

同步原因:
阻塞源於同步代碼塊,首先需要弄清楚何時需要同步,需要同步的地方是因為多個線程操作了同一個變量,導致在並行執行時變量值的混亂,故需要加同步鎖來實現同一時間只能有同一個線程執行同步代碼塊中的程序,如果不涉及多線程操作同一個變量的情況是不需要使用同步的,在多線程編程時盡量避免操作公共變量來避免阻塞。

9、Java同步機制有4種實現方式

ThreadLocal
synchronized( )
wait() 與 notify()
volatile
目的:都是為了解決多線程中的對同一變量的訪問沖突

9.1 ThreadLocal

ThreadLocal 保證不同線程擁有不同實例,相同線程一定擁有相同的實例,即為每一個使用該變量的線程提供一個該變量值的副本,每一個線程都可以獨立改變自己的副本,而不是與其它線程的副本沖突。

優勢:提供了線程安全的共享對象與其它同步機制的區別:同步機制是為了同步多個線程對相同資源的並發訪問,是為了多個線程之間進行通信;而ThreadLocal 是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源,這樣當然不需要多個線程進行同步了。

9.2 volatile

volatile 修飾的成員變量在每次被線程訪問時,都強迫從共享內存中重讀該成員變量的值。而且當成員變量發生變化時,強迫線程將變化值回寫到共享內存。

優勢:這樣在任何時刻,兩個不同的線程總是看到某個成員變量的同一個值。

緣由:Java 語言規范中指出,為了獲得最佳速度,允許線程保存共享成員變量的私有拷貝,而且只當線程進入或者離開同步代碼塊時才與共享成員變量的原始值對比。這樣當多個線程同時與某個對象交互時,就必須要注意到要讓線程及時的得到共享成員變量的變化。而 volatile 關鍵字就是提示 VM :對於這個成員變量不能保存它的私有拷貝,而應直接與共享成員變量交互。

使用技巧:在兩個或者更多的線程訪問的成員變量上使用 volatile 。當要訪問的變量已在synchronized 代碼塊中,或者為常量時,不必使用。

線程為了提高效率,將某成員變量(如A)拷貝了一份(如B),線程中對A的訪問其實訪問的是B。只在某些動作時才進行A和B的同步,因此存在A和B不一致的情況。volatile就是用來避免這種情況的。 volatile告訴jvm,它所修飾的變量不保留拷貝,直接訪問主內存中的(讀操作多時使用較好;線程間需要通信,本條做不到)

Volatile 變量具有 synchronized 的可見性特性,但是不具備原子特性。這就是說線程能夠自動發現 volatile 變量的最新值。Volatile 變量可用於提供線程安全,但是只能應用於非常有限的一組用例:多個變量之間或者某個變量的當前值與修改后值之間沒有約束。

只能在有限的一些情形下使用 volatile 變量替代鎖。要使 volatile 變量提供理想的線程安全,必須同時滿足下面兩個條件:

對變量的寫操作不依賴於當前值;
該變量沒有包含在具有其他變量的不變式中。

9.3 sleep() vs wait()

sleep是線程類(Thread)的方法,導致此線程暫停執行指定時間,把執行機會給其他線程,但是監控狀態依然保持,到時后會自動恢復。調用sleep不會釋放對象鎖。

wait() 是Object類的方法,對此對象調用wait方法導致本線程放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象發出notify方法(或notifyAll)后本線程才進入對象鎖定池准備獲得對象鎖進入運行狀態。
(如果變量被聲明為volatile,在每次訪問時都會和主存一致;如果變量在同步方法或者同步塊中被訪問,當在方法或者塊的入口處獲得鎖以及方法或者塊退出時釋放鎖時變量被同步。)
---------------------
作者:visant
來源:CSDN
原文:https://blog.csdn.net/visant/article/details/79721743
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM