并发编程之多线程线程安全(下)
1、什么是 Volatile?
volatile 是一個類型修飾符,具有可見性,也就是說一旦某個線程修改了該被 volatile 修飾的變量,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,可以立即獲取修改之后的值。
在 java 中為了加快程序的運行效率,對一些變量的操作通常是在該線程的寄存器或是 CPU 緩存上進行的,之后才會同步到主存中,而加了 volatile 修飾符的變量則是直接讀寫主存。
[可以搜索了解一下 java 中的內存模型]
看下面一段代碼:
class?ThreadVolatileDemo?extends?Thread?{????public?boolean?flag?=?true;
????
????public?void?run()?{
????????System.out.println("開始執行子線程....");
????????while?(flag)?{
????????}
????????System.out.println("線程停止");
????}
????public?void?setRuning(boolean?flag)?{
????????this.flag?=?flag;
????}
}
public?class?ThreadVolatile?{
????public?static?void?main(String[]?args)?throws?InterruptedException?{
????????ThreadVolatileDemo?threadVolatileDemo?=?new?ThreadVolatileDemo();
????????threadVolatileDemo.start();
????????Thread.sleep(3000);
????????threadVolatileDemo.setRuning(false);
????????System.out.println("flag?已經設置成false");
????????Thread.sleep(1000);
????????System.out.println(threadVolatileDemo.flag);
????}
}
運行結果:
開始執行子線程....flag?已經設置成false
false
已經將結果設置為 fasle 為什么?還一直在運行呢。
原因:線程之間是不可見的,讀取的是副本,沒有及時讀取到主內存結果。
解決辦法使用 volatile 關鍵字將解決線程之間可見性, 強制線程每次讀取該值的時候都去“主內存”中取值。
2、Volatile 與 Synchronize 的區別?
上篇以及本篇多次提及原子性,借此重新了解一下多線程中的三大特性。
原子性、可見性、有序性。
2.1、什么是原子性?
如果有了解過事務的小伙伴,這一塊就比較好理解了,所謂的原子性即一個或多個操作,要么全部執行完成,要么就都不執行,如果只執行了一部分,對不起,你得撤銷已經完成的操作。
舉個例子:
賬戶 A 向賬戶 B 轉賬 1000 元,那么必然包括 2 個操作:從賬戶 A 減去 1000,向賬戶 B 加上 1000,這兩個操作必須要具備原子性才能保證不出現一些意外的問題發生。
總結:所謂的原子性其實就是保證數據一致、線程安全的一部分。
2.2、什么是可見性?
當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程就能夠立即看到修改的值。
若兩個線程在不同的 cpu,有個變量 i ,線程 1 改變了 i 的值還沒有刷新到主存,線程 2 又使用了 i,那么這個 i 值肯定還是之前的,線程 1 對變量的修改,線程2 沒有看到,這就是可見性問題了。
2.3、什么是有序性?
即程序執行時按照代碼書寫的先后順序執行。在Java內存模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程程序的執行,卻會影響到多線程并發執行的正確性。(本文不對指令重排作介紹,但不代表它不重要,它是理解 java 并發原理時非常重要的一個概念)。
重排序文章留空,后面補充。
3、多線程之間的通訊
什么是多線程之間的通訊?
多線程之間通訊,其實就是多個線程在操作同一個資源,但是操作的動作不同。畫圖演示:
多線程之間的通訊需求:
第一個線程寫入(input)用戶,另一個線程讀取(out)用戶,實現讀一個寫一個操作。
代碼實現:
共享資源實習類 Res
class?Res2{????public?String?userName;
????public?String?userSex;
}
class?InputThread?extends?Thread{
????private?Res2?mRes;
????public?InputThread(Res2?mRes){
????????this.mRes?=?mRes;
????}
????
????public?void?run()?{
????????int?count?=?0;
????????while?(true)?{
????????????synchronized?(mRes){
????????????????if?(count?==?0)?{
????????????????????mRes.userName?=?"余勝軍";
????????????????????mRes.userSex?=?"男";
????????????????}?else?{
????????????????????mRes.userName?=?"小紅";
????????????????????mRes.userSex?=?"女";
????????????????}
????????????????count?=?(count?+?1)?%?2;
????????????}
????????}
????}
}
class?OutThread?extends?Thread{
????private?Res2?mRes;
????public?OutThread(Res2?mRes){
????????this.mRes?=?mRes;
????}
????
????public?void?run()?{
????????while?(true){
????????????System.out.println(mRes.userName?+?"--"?+?mRes.userSex);
????????}
????}
}
public?class?Demo9?{
????public?static?void?main(String[]?args){
????????Res2?res?=?new?Res2();
????????InputThread?intThrad?=?new?InputThread(res);
????????OutThread?outThread?=?new?OutThread(res);
????????intThrad.start();
????????outThread.start();
????}
}
打印結果:
...余勝軍--男
小紅--女
小紅--女
余勝軍--男
小紅--女
在代碼中我們用到了 synchronized 關鍵字解決線程線程安全問題,所以實現了正確打印,如果不使用 synchronized 的話,可能會出現如下打印的臟數據:
余勝軍--女小紅--女
小紅--男
余勝軍--男
小紅--女
wait()、 notify() 方法。
關于該方法的介紹:
了解了 wait、notify 方法后,我們來改造一下上邊的代碼:
class?Res2{????public?String?userName;
????public?String?userSex;
????public?boolean?flag?=?false;/*線程通訊標識*/
}
class?InputThread?extends?Thread{
????private?Res2?mRes;
????public?InputThread(Res2?mRes){
????????this.mRes?=?mRes;
????}
????
????public?void?run()?{
????????int?count?=?0;
????????while?(true)?{
????????????synchronized?(mRes){
???????????????if(mRes.flag){
???????????????????try?{
???????????????????????mRes.wait();
???????????????????}catch?(Exception?e){
???????????????????}
???????????????}
???????????????if?(count?==?0)?{
????????????????????mRes.userName?=?"余勝軍";
????????????????????mRes.userSex?=?"男";
????????????????}?else?{
????????????????????mRes.userName?=?"小紅";
????????????????????mRes.userSex?=?"女";
????????????????}
????????????????count?=?(count?+?1)?%?2;
????????????????mRes.flag?=?true;
????????????????mRes.notify();
????????????}
????????}
????}
}
class?OutThread?extends?Thread{
????private?Res2?mRes;
????public?OutThread(Res2?mRes){
????????this.mRes?=?mRes;
????}
????
????public?void?run()?{
????????while?(true){
????????????synchronized?(mRes){
????????????????if(!mRes.flag){
????????????????????try?{
????????????????????????mRes.wait();
????????????????????}?catch?(Exception?e)?{}
????????????????}
????????????????System.out.println(mRes.userName?+?"--"?+?mRes.userSex);
????????????????mRes.flag?=?false;
????????????????mRes.notify();
????????????}
????????}
????}
}
public?class?Demo9?{
????public?static?void?main(String[]?args){
????????Res2?res?=?new?Res2();
????????InputThread?intThrad?=?new?InputThread(res);
????????OutThread?outThread?=?new?OutThread(res);
????????intThrad.start();
????????outThread.start();
????}
}
wait() 與 sleep() 區別?
sleep() 方法時屬于 Thread 類的,而 wait() 方法是屬于 Object 類的。
sleep() 方法導致了程序暫停執行指定的時間,讓出 cpu 給其他線程,但是它的監控狀態依然保持著,當指定的時間到了又會自動恢復運行狀態。
在調用 sleep() 方法的過程中,線程不會釋放對象鎖。
而當調用 wait() 方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用 notify() 方法后本線程才進入對象鎖池準備,獲取對象鎖進入運行狀態。
Lock鎖(顯示鎖)
lock接口提供了與 synchronized 關鍵字類似的同步功能,但需要在使用時需要手動獲取鎖和釋放鎖。
代碼示例:
Lock?lock??=?new?ReentrantLock();lock.lock();
try{
????/*可能會出現線程安全的操作*/
}finally{
????/*一定在finally中釋放鎖*/
????/*也不能把獲取鎖在try中進行,因為有可能在獲取鎖的時候拋出異常*/
????lock.ublock();
}
Condition用法—Lock中的wait()、notify()
Condition 的功能類似于在傳統的線程技術中的 Object.wait() 和 Object.notify() 的功能。
代碼:
Condition?condition?=?lock.newCondition();res.?condition.await();??類似wait
res.?Condition.?Signal()?類似notify
synchronized 與 lock 的區別
synchronized 重量級,lock 輕量級鎖,synchronized 不可控制,lock 可控制。
lock 相對 synchronized 比較靈活。
我創建了一個java相關的公眾號,用來記錄自己的學習之路,感興趣的小伙伴可以關注一下微信公眾號哈:niceyoo
總結
以上是生活随笔為你收集整理的并发编程之多线程线程安全(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GUI阅读字号和触点面积设计 (可用性设
- 下一篇: UEditor 插入图片大于2M提示文件