Java深入探索線程安全和線程通信的特性
一、線程安全(重點)
1、線程安全概念
在多線程的情況下,需要考慮多個線程并行并發(fā)執(zhí)行:此時多個線程之間的代碼是隨機執(zhí)行的。如果多線程環(huán)境下代碼的運行結果是符合我們的預期的,即在單線程情況下應該的結果,則這個程序是線程安全的。
2、產生線程不安全的情況
多個線程共享變量的操作:
- 都是讀操作時,使用值進行判斷,打印等操作(不存在線程安全問題)
- 存在線程進行了寫操作(存在線程安全問題)
3、線程不安全的原因
(1)原子性:多行指令是最小的執(zhí)行單位(不可拆分),就具有原子性。如果不滿足原子性,則存在線程安全問題。
一條java語句不一定是原子的:
- 如n++、n--,是有三步組成:從內存讀取到cpu寄存器、修改數據、再寫回到cpu;
- Object o=new Object(),也涉及到三個步驟:申請內存、初始化對象、賦值。
以上列舉的雖然只是一條java語句,但是其不具有原子性。
(2)可見性:一個線程對共享變量的修改,能夠及時的被其他線程看見。多個線程并發(fā)并行執(zhí)行,使用各自的工作內存,互相之間不可見(不具有可見性)。
Java內存模型(JMM):
目的是屏蔽掉各種硬件和操作系統(tǒng)的內存訪問差異,以實現Java程序在各種平臺下都能達到一致的并發(fā)效果。
- 線程之間的共享變量存在主內存;
- 每一個線程都有自己的工作內存;
- 當程序要讀取一個共享變量時,會先將變量從主內存拷貝到工作內存,再從工作內存讀取數據;
- 當程序要修改一個共享變量時,會先修改工作內存中的副本,再同步到主內存。
(3)代碼順序性/有序性:
代碼重排序:
比如,以下代碼:
1、去前臺取U盤,
2、回教室寫一會作業(yè),
3、去前臺取快遞
在單線程的情況下,JVM、cpu會對其進行優(yōu)化,1→3→2,這樣會提高效率,這樣就叫做代碼重排序。
代碼重排序的前提是:保證邏輯不發(fā)生改變。
4、如何解決線程不安全問題
設計多線程代碼原則:滿足線程安全的前提下,盡可能地提高效率
(1)對共享變量的寫操作,可以加鎖來保證線程安全:
Java中加鎖的兩種方式:
- synchronized關鍵字:申請對給定的Java對象,對象頭加鎖;
- Lock:是一個鎖的接口,它的實現類提供了鎖這樣的對象,可以調用方法來加鎖/釋放鎖。
對共享變量的寫操作,不依賴任何共享變量,也可以使用volatile關鍵字來保證線程安全。
(2)對共享變量的讀操作,使用volatile關鍵字就可以保證線程安全
volatile關鍵字:修飾變量,變量的讀操作,本身就保證了原子性,volatile的作用是保證可見性和有序性,這樣就可以保證線程安全。
二、synchronized關鍵字
synchronized本質上是修飾指定對象的對象頭去。使用角度來看,synchronized必須搭配一個具體的對象來使用。
1、使用
(1)修飾普通方法:鎖TestDemo對象
//方法一 public class TestDemo { public synchronized void methond() { } } //方法二 public class TestDemo { public void methond() { synchronized(this){ } } }
(2)修飾靜態(tài)方法:鎖TestDemo對象
//方法一 public class TestDemo { public synchronized static void method() { } } //方法二 public class TestDemo { public static void method() { synchronized(TestDemo.class){ } } }
2、特性
(1)互斥
synchronized會起到同步互斥的作用,某個線程執(zhí)行到某個對象的synchronized中時,如果其他線程也執(zhí)行到同一個對象synchronized時會阻塞等待。
- 進入synchronized修飾的代碼塊,相當于加鎖;
- 退出synchronized代碼塊,相當于釋放鎖。
互斥可以滿足原子性,
(2)刷新內存
synchronized結束釋放鎖,會把工作內存中的數據刷新到主存中;其他線程申請鎖時,獲取的始終是最新的數據。(滿足可見性)。
(3)有序性
某個線程執(zhí)行一段同步代碼,不管如何重排序,過程中不可能有其他線程執(zhí)行的指令,這樣多個線程執(zhí)行同步代碼,就滿足一定的順序。
(4)可重入
同一個線程,可以多次申請同一個對象鎖(可重入)
三、volatile關鍵字
修飾某個變量(實例變量,靜態(tài)變量)
1、保證可見性
代碼在寫入volatilt修飾的變量時:
- 改變線程工作內存中volatile變量副本的值
- 將改變后的副本的值從工作內存中刷新到主存中
代碼在讀取volatile修飾的變量時:
- 從主存中讀取volatile變量的最新值到工作內存中
- 從工作內存中讀取副本值
2、禁止指令重排序
建立內存屏障,保證代碼有序性。
3、不保證原子性
synchronized和volatile有著本質區(qū)別。synchronized可以保證原子性,volatile保證的是內存的可見性。
只能在共享變量的讀操作以及常量賦值操作時使用(這些操作本身就具有原子性)
四、wait和notify(線程間的通信)
線程通信:線程間通信,就是一個線程以通知的方式,喚醒某些等待的線程(或者讓當前線程等待),這樣就可以讓線程通過通信的方式具有一定的順序性。
1、wait()方法
wait做的事情:
- 使當前執(zhí)行代碼的線程進入等待狀態(tài)(把線程放到等待隊列中)
- 釋放當前的鎖
- 滿足一定條件時,重新嘗試獲取這個鎖
wait結束等待的條件:
- 其他線程調用該對象的notify方法
- wait等待時間超時(wait提供了一個帶一個參數的方法,可以指定等待時間)
- 其他線程調用該等待線程的interrupted方法,導致wait拋出InterruptedException異常
wait要搭配synchronized來使用。脫離synchronized使用wait會直接拋異常。
如下:
Object object = new Object(); synchronized (object) { object.wait(); } //這種情況下線程會一直等待下去,這個時候需要使用notify來喚醒
2、notify()和notifyAll()方法
notify方法只是喚醒某一個等待的線程,使用notifyAll方法可以一次性喚醒所有的等待線程。
- notify()也是在同步方法中調用,用來通知其他等待的線程,對其發(fā)出通知notify,并使它們重新獲取該對象的對象鎖;
- 如果有多個線程在等待,則有線程調度器隨機挑選出一個處于等待狀態(tài)的線程;
- notify()方法執(zhí)行后,當前線程不會立馬釋放該對象鎖,要等到當前線程將程序執(zhí)行完,退出同步代碼塊后才會釋放對象鎖。
【注】
雖然notifyAll()同時喚醒所有處于等待狀態(tài)的線程,但是這些線程需要競爭鎖。所以并不是同時執(zhí)行,仍然是有先后順序的執(zhí)行。
3、wait和sleep的對比
一個是用于線程之間的通信,一個是讓線程阻塞一段時間。
- wait需要搭配synchrionzed使用,sleep不用
- wait是Object的方法,sleep是Thread的靜態(tài)方法
五、線程和進程的比較
1、線程的優(yōu)點
- 創(chuàng)建線程的代價比創(chuàng)建進程小得多
- 與進程切換相比,線程切換需要操作系統(tǒng)做的事少得多
- 線程占用的資源比進程少
- 能充分利用多個處理器,提高效率
- 在等待I/O操作結束的同時,程序可執(zhí)行其他的計算任務
- I/O密集型操作,為了提高性能,將I/O操作重疊。線程可以同時等待不同的I/O操作
- 計算密集型應用,為了能在多處理器系統(tǒng)上運行,將計算分解到多個線程中實現
2、線程和進程的區(qū)別
- 進程是進行資源分配的最小單位,線程是程序執(zhí)行的最小單位
- 進程有自己的內存地址空間,線程只獨享指令流執(zhí)行的必要資源,如寄存器和棧
- 同一個進程的各線程之間共享內存和文件資源,可以不通過內核進行直接通信
- 線程的創(chuàng)建、切換、銷毀效率更高
到此這篇關于Java深入探索線程安全和線程通信的特性的文章就介紹到這了,更多相關Java線程安全內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Jvisualvm監(jiān)控遠程SpringBoot項目的過程詳解
這篇文章主要介紹了Jvisualvm監(jiān)控遠程SpringBoot項目,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-04-04解決mybatis使用char類型字段查詢oracle數據庫時結果返回null問題
這篇文章主要介紹了mybatis使用char類型字段查詢oracle數據庫時結果返回null問題的解決方法,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2018-06-06Spring?Security放行的接口Knife4j靜態(tài)資源的問題小結
這篇文章主要介紹了Spring?Security使用Knife4j靜態(tài)資源的問題小結,項目中使用?Spring?Security?做身份認證和授權,使用?Knife4j?做接口調試,需要?Spring?Security?放行的接口記錄在?RequestMatcherConstant?類中,感興趣的朋友跟隨小編一起看看吧2024-02-02詳解Spring Boot中使用@Scheduled創(chuàng)建定時任務
本篇文章中主要介紹了Spring Boot中使用@Scheduled創(chuàng)建定時任務,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-03-03