個人博客網:https://wushaopei.github.io/ (你想要這里多有)
一、JUC是什么
1、JUC定義
JUC,即java.util.concurrent 在並發編程中使用的工具類
2、進程、線程的定義
2.1 進程、線程是什么?
進程:進程是一個具有一定獨立功能的程序關於某個數據集合的一次運行活動。它是操作系統動態執行的基本單元,在傳統的操作系統中,進程既是基本的分配單元,也是基本的執行單元。
線程:通常在一個進程中可以包含若干個線程,當然一個進程中至少有一個線程,不然沒有存在的意義。線程可以利用進程所擁有的資源,在引入線程的操作系統中,通常都是把進程作為分配資源的基本單位,而把線程作為獨立運行和獨立調度的基本單位,由於線程比進程更小,基本上不擁有系統資源,故對它的調度所付出的開銷就會小得多,能更高效的提高系統多個程序間並發執行的程度。
2.2 進程、線程例子
- 使用QQ,查看進程一定有一個QQ.exe的進程,我可以用qq和A文字聊天,和B視頻聊天,給C傳文件,給D發一段語言,QQ支持錄入信息的搜索。
- 大四的時候寫論文,用word寫論文,同時用QQ音樂放音樂,同時用QQ聊天,多個進程。
- word如沒有保存,停電關機,再通電后打開word可以恢復之前未保存的文檔,word也會檢查你的拼寫,兩個線程:容災備份,語法檢查
二、Lock 接口
1、Lock 接口定義
Lock :Lock 是juc 下的一個接口類,用於對多線程的同步實現。
2、Lock接口的實現ReentrantLock可重入鎖
2.1 Lock 的使用示例:
3、Thread 類 創建線程的方式
3.1 繼承Thread 類
問題:java是單繼承,資源寶貴,要用接口方式
如果使用繼承Thread 類的方式來創建線程,對資源是很大的浪費。所以實際開發中不建議這么寫。
3.2 new Thread()
這里通過創建線程實例的方式來創建線程,但在實際開發中,這樣是不好的,對資源的浪費依舊居高不下。
3.3 通過接口實現類為形參的方式注入創建 Thread 線程
這里是通過 Tread 有參構造的方式創建一個新的線程對象。
4、實現現成的三種方法
4.1 新建類實現 runnable接口
這種方法會新增類,有更新更好的方法
4.2 匿名內部類
這種方法不需要創建新的類,可以 new 接口
4.3 lambda 表達式
這種方法代碼更簡潔精煉
5、案例代碼:
三、java 8 新特性
1、lambda 表達式
1.1 查看例子: LambdaDemo
1.2 要求:接口只有一個方法
寫法分析: 拷貝小括號(),寫死右箭頭->,落地大括號{...}
1.3 函數式接口
lambda表達式,必須是函數式接口,必須只有一個方法;如果接口只有一個方法java默認它為函數式接口。
為了正確使用Lambda表達式,需要給接口加個注解:
如有兩個方法,立刻報錯
Runnable接口為什么可以用lambda表達式?
2、接口里是否能有實現方法?
2.1 default 方法
接口里在java8后容許有接口的實現,default方法默認實現
接口里default方法可以有幾個?
2.2 靜態方法實現
靜態方法實現:接口新增
可以有幾個?
注意靜態的叫類方法,能用foo去調嗎?要改成Foo
3、代碼
四、Callalbe 接口
1、Callable接口
1.1 是什么?
定義:Callable 是用來獲得多線程的方法。
注意:(1)繼承thread類(2)runnable接口
如果只回答這兩個你連被問到juc的機會都沒有
2、與Runnable 對比
創建新類MyThread實現runnable接口
新類MyThread2實現callable接口
面試題: callable接口與runnable接口的區別?
答:(1)是否有返回值
(2)是否拋異常
(3)落地方法不一樣,一個是run,一個是call
3、Callable 怎么用?
不能直接替換Runnable 來使用,原因: thread類的構造方法根本沒有Callable
解決辦法:java 多態,一個類可以實現多個接口
從上圖可以知道,Runnable 實現類FutureTask 接口;Callable 的實例可以作為Future Task 的構造參數。
如以上的函數一樣,實現多線程的創建。
關於 運行成功后如何獲得返回值?
4、Future Task
4.1 Future Task 是什么?
在項目中,用它在執行線程時,做異步調用,類似於使用 main 將一個個方法串起來,從而解決: 正常調用掛起堵塞問題。
4.2 原理
在主線程中需要執行比較耗時的操作時,但又不想阻塞主線程時,可以把這些作業交給Future對象在后台完成,當主線程將來需要時,就可以通過Future對象獲得后台作業的計算結果或者執行狀態。
一般FutureTask多用於耗時的計算,主線程可以在完成自己的任務后,再去獲取結果。
僅在計算完成時才能檢索結果;如果計算尚未完成,則阻塞 get 方法。一旦計算完成,就不能再重新開始或取消計算。get方法而獲取結果只有在計算完成時獲取,否則會一直阻塞直到任務轉入完成狀態,然后會返回結果或者拋出異常。
- 只計算一次
- get方法放到最后
4.3 代碼
五、線程間通信
1、面試題:兩個線程打印
兩個線程,一個線程打印1-52,另一個打印字母A-Z打印順序為12A34B...5152Z,
要求用線程間通信
2、例子:NotifyWaitDemo
3、線程間通信:1、生產者+消費者2、通知等待喚醒機制
4、多線程編程模板下
- 判斷
- 干活
- 通知
5、synchronized實現
5.1 代碼
5.2 如果換成4個線程執行呢?
如果換成4個線程會導致錯誤,虛假喚醒
- 原因:在java多線程判斷時,不能用if,程序出事出在了判斷上面
- 突然有一天加的線程進到if了,突然中斷了交出控制權
- 沒有進行驗證,而是直接走下去了,加了兩次,甚至多次
5.3 解決辦法
解決虛假喚醒:查看API,java.lang.Object
中斷和虛假喚醒是可能產生的,所以要用loop循環,if只判斷一次,while是只要喚醒就要拉回來再判斷一次。if換成while
(1)代碼
(2)原理圖:
6、java8新版實現
6.1 對標實現
6.2 Condition
代碼:
六、線程間定制化調用通信
1、例子:ThreadOrderAccess
2、線程-調用-資源類
3、判斷-干活-通知
- 有順序通知,需要有標識位
- 有一個鎖Lock,3把鑰匙Condition
- 判斷標志位
- 輸出線程名+第幾次+第幾輪
- 修改標志位,通知下一個
4、代碼
七、多線程鎖
1、例子:ThreadOrderAccess
2、鎖的 8 個問題
- 標准訪問,先打印短信還是郵件
- 停4秒在短信方法內,先打印短信還是郵件
- 普通的hello方法,是先打短信還是hello
- 現在有兩部手機,先打印短信還是郵件
- 兩個靜態同步方法,1部手機,先打印短信還是郵件
- 兩個靜態同步方法,2部手機,先打印短信還是郵件
- 1個靜態同步方法,1個普通同步方法,1部手機,先打印短信還是郵件
- 1個靜態同步方法,1個普通同步方法,2部手機,先打印短信還是郵件
3、8 鎖分析
A 一個對象里面如果有多個synchronized方法,某一個時刻內,只要一個線程去調用其中的一個synchronized方法了,
其它的線程都只能等待,換句話說,某一個時刻內,只能有唯一一個線程去訪問這些synchronized方法
鎖的是當前對象this,被鎖定后,其它的線程都不能進入到當前對象的其它的synchronized方法
- 加個普通方法后發現和同步鎖無關
- 換成兩個對象后,不是同一把鎖了,情況立刻變化。
synchronized實現同步的基礎:Java中的每一個對象都可以作為鎖。
具體表現為以下3種形式:
- 對於普通同步方法,鎖是當前實例對象。
- 對於靜態同步方法,鎖是當前類的Class對象。
- 對於同步方法塊,鎖是Synchonized括號里配置的對象
也就是說如果一個實例對象的非靜態同步方法獲取鎖后,該實例對象的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖后才能獲取鎖,可是別的實例對象的非靜態同步方法因為跟該實例對象的非靜態同步方法用的是不同的鎖,所以毋須等待該實例對象已獲取鎖的非靜態同步方法釋放鎖就可以獲取他們自己的鎖。
所有的靜態同步方法用的也是同一把鎖——類對象本身,這兩把鎖是兩個不同的對象,所以靜態同步方法與非靜態同步方法之間是不會有競態條件的。
但是一旦一個靜態同步方法獲取鎖后,其他的靜態同步方法都必須等待該方法釋放鎖后才能獲取鎖,而不管是同一個實例對象的靜態同步方法之間,還是不同的實例對象的靜態同步方法之間,只要它們同一個類的實例對象!
4、代碼
八、JUC強大的輔助類講解
1、ReentrantReadWriteLock 讀寫鎖
該類用於解決 寫寫、讀寫互斥的場景下,當進行寫操作時,不進行讀操作和其他的寫操作;進行讀操作時不進行寫操作;確保數據 的一致性和梯次讀取到數據的一致性。
(1)例子:ReadWriteLockDemo
(2)類似軟件: 紅蜘蛛
(3)代碼:
2、CountDownLatch
(1)例子:CountDownLatchDemo
(2)原理:
- CountDownLatch主要有兩個方法,當一個或多個線程調用await方法時,這些線程會阻塞。
- 其它線程調用countDown方法會將計數器減1(調用countDown方法的線程不會阻塞),
- 當計數器的值變為0時,因await方法阻塞的線程會被喚醒,繼續執行。
(3)代碼:
3、CyclicBarrier 循環柵欄
(1)例子: CountDownLatchDemo
(2)原理:
- CyclicBarrier
- 的字面意思是可循環(Cyclic)使用的屏障(Barrier)。它要做的事情是,
- 讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,
- 直到最后一個線程到達屏障時,屏障才會開門,所有
- 被屏障攔截的線程才會繼續干活。
- 線程進入屏障通過CyclicBarrier的await()方法。
(3)代碼
4、Semaphore 信號燈
(1)例子:CountDownLatchDemo
(2)原理:
在信號量上我們定義兩種操作:
- acquire(獲取) 當一個線程調用acquire操作時,它要么通過成功獲取信號量(信號量減1),
- 要么一直等下去,直到有線程釋放信號量,或超時。
- release(釋放)實際上會將信號量的值加1,然后喚醒等待的線程。
- 信號量主要用於兩個目的,一個是用於多個共享資源的互斥使用,另一個用於並發線程數的控制。
(3)代碼