併發編程實戰讀書筆記(二)對象的共享


併發編程實戰讀書筆記(二)對象的共享
本章內容:
1、內存可見性
2、線程封閉
3、不變性
4、安全發佈

一、內存可見性
1、線程可見性引入
下面代碼 中number值可能打印42,也有可能是0 重排序時!
public class Novisibility {

    private static boolean ready;
    private static int number;
    
    private static class ReadThread extends Thread{
        @Override
        public void run() {
            while(!ready){
                Thread.yield();
            }
            System.out.println(number);
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 20; i++) {
            new ReadThread().start();
        }
        ready = true;
        number = 42;
    }
}
還有一個例子 雙重檢測鎖實現單例模式 時有時會出現意外(實際其他線程已經創建,但是個別線程讀到的還是null 重複創建破壞了單例) 這也是爲什麼要加volitile修飾的原因
線程可見性可以解決此類問題:比如使用Volitile、比如get、set方法都使用synchronizedx修飾

2、加鎖與可見性

Volitile是Java的一種稍弱的同步機制,用來確保變量的更新通知到其他線程。
然而不建議過度依賴Volitile提供的可見性,因爲它比鎖更脆弱,也更難以理解。
volitile一般使用方式包括確保自身狀態的可見性,確保所引用對象的可見性,以及標識一些重要的生命週期事件的發生。
使用示例:其他線程通過改變flag可以控制這個線程的運行

public class RuntimeThread extends Thread{

    public static volatile AtomicBoolean flag = new AtomicBoolean(true);;
    @Override
    public synchronized void start() {
        
        while(flag.get()){
            
            System.out.println("do something");
        }
    }
    
}
侷限:Volitile只能保證線程可見。做不到原子性

二、線程封閉
就是將對象封閉在線程內部來做到線程安全
1、Ad-hoc線程封閉
2、棧封閉:局部變量
3、ThreadLocal:創建變量副本來保證線程安全。理解:Map<Thread,T> 線程終止時值會回收。
 

三、不變性。

不可變對象是線程安全的。使用不可變對象Immutable Object。
滿足以下幾點對象纔是不可變的:對象創建以後狀態就不能修改、對象所有域爲final、對象是正確創建的(創建過程中this沒有逸出)
例:
public class ImmutableClass {

    private final Set<String> sets = Sets.newHashSet();
    
    public ImmutableClass(){
        sets.add("back");
        sets.add("back1");
        sets.add("back2");
    }
    public boolean contains(String name){
        return sets.contains(name);
    }
}
注:Final域代表不能修改的意思。Final域所引用的對象是可變的,那麼這些被引用的對象是可以修改的。

Volite 修飾不可變對象可以做到線程安全

四、安全發佈
對象發佈的線程安全,例子:對象在A線程初始化,在B線程讀取可能讀不到正確的對象的狀態
解決方式(安全發佈對象):
1)即使發佈不可變對象沒有使用同步也可以安全的訪問對象
2)靜態初始化函數中初始化一個對象引用
3)對象引用保存在Volatile或AtomicReferance中
4)對象引用保存在鎖保護的域中(雙重檢測鎖)

在線程安全容器內部同步代表比如A線程初始化後將容器放入容器比如Vector,B線程讀取容器中的元素。那麼可以確保B讀到對象的狀態,即便代碼中沒有包含顯示的同步。
Hashtable,synchronizedMap,ConcurrentMap、Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、BlockingQueue、ConcurrentLinkedQueue這些都可以。
還有一些其他數據傳遞機制Future、Exchanger也能實現安全發佈

5)事實不可變對象
首先安全發佈只能保證對象狀態的一致,不能保證對象應用的線程安全。
當一個可變對象在發佈後不被修改,那麼這個對象就是事實上的不可變對象。安全發佈這種對象後就直接使用就能保證線程安全。

6)可變對象
如果對象在構造後可以修改。安全發佈只能確保"發佈當時" 的狀態可見。對於可變對象,不僅要保證安全發佈,還要同步來保證後續修改操作的可見性。

此時對象發佈取決於可變性:
不可變對象可以通過任意機制來發布
事實不可變對象必須通過安全的方式來發布
可變對象必須安全的方式發佈,並且必須是線程安全的或者某個鎖保護起來

7)安全的共享對象。
併發程序中使用和共享對象時,可以使用一些實用的策略:
線程封閉:線程封閉對象只有當前線程持有,且只有當前線程修改。
只讀共享:在沒有額外同步的情況下,共享只讀對象可以由多個線程併發訪問,但任何線程都不能修改它。共享的只讀對象包括不可變對象和事實不可變對象
線程安全共享:線程安全的對象在內部實現同步,因此多個線程可以通過對象的共有接口訪問,而不需要進一步同步。
保護對象:被保護的對象只能通過持有特定的鎖來訪問。保護對象也包括封裝在其他線程安全對象中的對象。以及已經發布並且由某個特定的鎖保護的對象

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章