1. 程式人生 > >《java併發程式設計實戰》之 物件共享

《java併發程式設計實戰》之 物件共享

解決問題:
如何共享和釋出物件,從而使它們能夠安全地由多個執行緒同時訪問

寫多執行緒注意兩點

  1. 防止某個執行緒正在使用物件狀態時,而另一個執行緒同時在修改狀態。
  2. 確保當一個執行緒修改了物件狀態後,其他執行緒能夠看到狀態變化。(同步的記憶體可見性)

1.可見性

錯誤寫法

public class NoVisibity {
    private static boolean ready=false;
    private static int number=0;

    private static class ReadyThread extends Thread{
        public void run(){
            while(!ready){
                Thread.yield(); //去掉情況也是一樣。
                System.out.println(number);
            }
        }
    }

    public static void main(String[] args) {
        new ReadyThread().start();
        number = 42;
        ready = true;
    }
}

大部分情況不會輸出任何值。

原因:從程式碼邏輯上看到是設定了number=42, 應該輸出42,然後ready=true,停止輸出,但事實上我們不能保證執行緒中的操作按照指定的順序執行,當主執行緒在沒有同步的情況下,寫入了number,然後又寫入ready,那麼讀現車給你看到的順序可能與寫入的相反,所以沒有資料輸出。


多個執行緒之間資料共享時,同步很重要。
Java執行緒中的Thread.yield()方法,譯為執行緒讓步。顧名思義,就是說當一個執行緒使用了這個方法之後,它就會把自己CPU執行的時間讓掉,讓自己或者其它的執行緒執行,注意是讓自己或者其他執行緒執行,並不是單純的讓給其他執行緒。
    
yield()的作用是讓步。它能讓當前執行緒由“執行狀態”進入到“就緒狀態”,從而讓其它具有相同優先順序的等待執行緒獲取執行權;但是,並不能保證在當前執行緒呼叫yield()之後,其它具有相同優先順序的執行緒就一定能獲得執行權;也有可能是當前執行緒又進入到“執行狀態”繼續執行!

舉個例子:一幫朋友在排隊上公交車,輪到Yield的時候,他突然說:我不想先上去了,咱們大家來競賽上公交車。然後所有人就一塊衝向公交車,有可能是其他人先上車了,也有可能是Yield先上車了。但是執行緒是有優先順序的,優先順序越高的人,就一定能第一個上車嗎?這是不一定的,優先順序高的人僅僅只是第一個上車的概率大了一點而已,最終第一個上車的,也有可能是優先順序最低的人。並且所謂的優先順序執行,是在大量執行次數中才能體現出來的。

使用volatile原則:

  • 對變數的寫入不依賴變數當前值,如:count++ 這種騷操作不可以。除非保證單執行緒更新變數值
  • 訪問變數不需要加鎖,因為volatile只是保證可減刑,不保證原子性
  • 該變數不會與其他狀態變數一起納入不變性條件中

2.釋出和逸出

2.1釋出

釋出一個物件,也就是讓物件以外作用域的程式碼可以使用。
弊端:釋出物件內部狀態可能會破壞執行緒的安全性 如:未完成構造物件之前就進行釋出

public static Set<Secret> knowSecrets;    //靜態公共的容器,每個執行緒都可以訪問
public void initialize(){
    knowSecrets = new HashSet<Secret>();
}

2.2 逸出

釋出了不該釋出的物件就是逸出。
在構造方法中,內部類機制的釋出,存在把本類this逸出

正確的防止逸出

1.私有的構造方法 和 公共的工廠方法,避免了不正確的構造過程
例如

public class SafeListener{
    private final EventListener listener;
    public SafeListener(){
        listener = new EventListener(Event e){
            doSomtthing(e);
        };
    }
    
    public static SafeListener newInstance(EventSource source){
        SafeListener safe = new SafeListener();
        source.registerlistener(safe.listener);
        return safe;
    }
}




this隱式逸出,在構造方法中註冊方法,構造方法會獲得this,共享給其他執行緒是很危險的。
public class ThisEscape{
    public ThisEscape(EventSource source){
        source.registerListener(){
            new EventListener(){
                public void onEvent(Event e){
                    doSomething();
                }    
            }
        }
    }
}

3.執行緒封閉

僅在單執行緒內訪問可變資料,就是執行緒封閉

ThreadLocal類 為每個使用某個變數的執行緒儲存了一份獨立的副本,get() set()方法可以獲得這個設定副本的值。

private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>{
    public Connection initialValue(){
        return DriverManager.getConnection()DB_URL;
    }
}

public static Connection getConnection(){
    return connectionHolder.get();
}


執行緒通過呼叫getConnection(), 每個執行緒獲得屬於自己的一個connection副本,這些值是儲存線上程中的,執行緒終止後,就會垃圾回收。