Java併發:五種執行緒安全型別、執行緒安全的實現、列舉型別
1. Java中的執行緒安全
- Java執行緒安全:狹義地認為是多執行緒之間共享資料的訪問。
- Java語言中各種操作共享的資料有5種類型:不可變、絕對執行緒安全、相對執行緒安全、執行緒相容、執行緒獨立
① 不可變
- 不可變(Immutable) 的物件一定是執行緒安全的,不需要再採取任何的執行緒安全保障措施。
- 只要能正確構建一個不可變物件,該物件永遠不會在多個執行緒之間出現不一致的狀態。
- 多執行緒環境下,應當儘量使物件成為不可變,來滿足執行緒安全。
實現不可變=========》
- 如果共享資料是基本資料型別,使用final關鍵字對其進行修飾,就可以保證它是不可變的。
- 如果共享資料是一個物件,要保證物件的行為不會對其狀態產生任何影響。
- String是不可變的,對其進行substring()、replace()、concat()等操作,返回的是新的String物件,原始的String物件的值不受影響。而如果對StringBuffer或者StringBuilder物件進行substring()、replace()、append()等操作,直接對原物件的值進行改變。
- 要構建不可變物件,需要將內部狀態變數定義為final型別。如
java.lang.Integer
類中將value定義為final型別。=====》privatefinalintvalue;
常見的不可變的型別:
- final關鍵字修飾的基本資料型別
- 列舉型別、String型別
- 常見的包裝型別:Short、Integer、Long、Float、Double、Byte、Character等
- 大資料型別:BigInteger、BigDecimal
對於集合型別,可以使用Collections.unmodifiableXXX()
方法來獲取一個不可變的集合。
- 通過
Collections.unmodifiableMap(map)
獲的一個不可變的Map型別。 Collections.unmodifiableXXX()
先對原始的集合進行拷貝,需要對集合進行修改的方法都直接丟擲異常。
例如,如果獲得的不可變map物件進行put()、remove()、clear()操作,則會丟擲UnsupportedOperationException異常
② 絕對執行緒安全
絕對執行緒安全的實現,通常需要付出很大的、甚至不切實際的代價。
Java API中提供的執行緒安全,大多數都不是絕對執行緒安全。
例如,對於陣列集合Vector的操作,如get()、add()、remove()都是有synchronized關鍵字修飾。有時呼叫時也需要手動新增同步手段,保證多執行緒的安全。
下面的程式碼看似不需要同步,實際執行過程中會報錯。
importjava.util.Vector;
/**
*@Author:lucy
*@Version1.0
*/
publicclassVectorTest{
publicstaticvoidmain(String[]args){
Vector<Integer>vector=newVector<>();
while(true){
for(inti=0;i<10;i++){
vector.add(i);
}
newThread(newRunnable(){
@Override
publicvoidrun(){
for(inti=0;i<vector.size();i++){
System.out.println("獲取vector的第"+i+"個元素:"+vector.get(i));
}
}
}).start();
newThread(newRunnable(){
@Override
publicvoidrun(){
for(inti=0;i<vector.size();i++){
System.out.println("刪除vector中的第"+i+"個元素");
vector.remove(i);
}
}
}).start();
while(Thread.activeCount()>20)
return;
}
}
}
出現ArrayIndexOutOfBoundsException
異常,原因:某個執行緒恰好刪除了元素i,使得當前執行緒無法訪問元素i。
Exceptioninthread"Thread-1109"java.lang.ArrayIndexOutOfBoundsException:Arrayindexoutofrange:1
atjava.util.Vector.remove(Vector.java:831)
atVectorTest$2.run(VectorTest.java:28)
atjava.lang.Thread.run(Thread.java:745)
需要將對元素的get和remove構造成同步程式碼塊:
synchronized(vector){
for(inti=0;i<vector.size();i++){
System.out.println("獲取vector的第"+i+"個元素:"+vector.get(i));
}
}
synchronized(vector){
for(inti=0;i<vector.size();i++){
System.out.println("刪除vector中的第"+i+"個元素");
vector.remove(i);
}
}
③ 相對執行緒安全
- 相對執行緒安全需要保證對該物件的單個操作是執行緒安全的,在必要的時候可以使用同步措施實現執行緒安全。
- 大部分的執行緒安全類都屬於相對執行緒安全,如Java容器中的Vector、HashTable、通過
Collections.synchronizedXXX()
方法包裝的集合。
④ 執行緒相容
- Java中大部分的類都是執行緒相容的,通過新增同步措施,可以保證在多執行緒環境中安全使用這些類的物件。
- 如常見的ArrayList、HashTableMap都是執行緒相容的。
⑤ 執行緒對立
- 執行緒對立是指:無法通過新增同步措施,實現多執行緒中的安全使用。
- 執行緒對立的常見操作有:Thread類的suspend()和resume()(已經被JDK宣告廢除),
System.setIn()
和System.setOut()
等。
2. Java的列舉型別
通過enum關鍵字修飾的資料型別,叫列舉型別。
- 列舉型別的每個元素都有自己的序號,通常從0開始編號。
- 可以通過values()方法遍歷列舉型別,通過name()或者toString()獲取列舉型別的名稱
- 通過ordinal()方法獲取列舉型別中元素的序號
publicclassEnumData{
publicstaticvoidmain(String[]args){
for(Familyfamily:Family.values()){
System.out.println(family.name()+":"+family.ordinal());
}
}
}
enumFamily{
GRADMOTHER,GRANDFATHER,MOTHER,FATHER,DAUGHTER,SON;
}
3. Java執行緒安全的實現
① 互斥同步
互斥同步(Mutex Exclusion & Synchronization)是一種常見的併發正確性保障手段。
- 同步:多個執行緒併發訪問共享資料,保證共享資料同一時刻只被一個(或者一些,使用訊號量)執行緒使用。
- 互斥:互斥是實現同步的一種手段,主要的互斥實現方式:臨界區(Critical Section)、互斥量(Mutex)、訊號量(Semaphore)。
同步與互斥的關係:
- 互斥是原因,同步是結果。
- 同步是目的,互斥是方法。
Java中,最基本的實現互斥同步的手段是synchronized關鍵字,其次是JUC包中的ReentrantLock。