併發編程之對象的發佈和逸出


1、對象的發佈和逸出
發佈(publish)對象意味着其做用域以外的代碼能夠訪問操做此對象。例如將對象的引用保存到其餘代碼能夠訪問的地方,或者在非私有的方法中返回對象的引用,或者將對象的引用傳遞給其餘類的方法。
爲了保證對象的線程安全性,不少時候咱們要避免發佈對象,可是有時候咱們又須要使用同步來安全的發佈某些對象。
逸出即爲發佈了本不應發佈的對象。
使用靜態變量引用對象是發佈對象最直觀和最簡單的方式。例如如下代碼示例,在示例中咱們也看到,因爲任何代碼均可以遍歷咱們發佈persons集合,致使咱們間接的發佈了Person實例,天然也就形成能夠肆意的訪問操做集合中的Person元素。java

package com.codeartist;

import java.util.HashSet;

public class ObjectPublish {

    public static HashSet<Person> persons ;
    public void init()
    {
        persons = new HashSet<Person>();
    }
    
}



在非私有的方法內返回一個私有變量的引用會致使私有變量的逸出,例如如下代碼安全

package com.codeartist;

import java.util.HashSet;

public class ObjectPublish {    
    private  HashSet<Person> persons=  new HashSet<Person>();
    public HashSet<Person>  getPersons()
    {
        return this.persons;
    }
    
}

 



發佈一個對象也會致使此對象的全部非私有的字段對象的發佈,其中也包括方法調用返回的對象。
在構造函數中使用直接初始化或者調用可改寫的實例方法都會致使隱式的this逸出也是常常發生的事情,例如如下代碼,在EventListener的實例中也經過this隱含的發佈了還沒有構造完成的ConstructorEscape實例,可能會形成沒法預知的結果。多線程

package com.codeartist;

public class ConstructorEscape {
    public ConstructorEscape(EventSource eventSource)
    {
        eventSource.registerListener(
                    new EventListener(){
                        public void OnEvent(Event e)
                        {
                            doSomeThing(e);
                        }
                    }
                );
    }
}


咱們可使用工廠方法防止隱式的this逸出問題,例如如下代碼併發

package com.codeartist;

public class ConstructorEscape {
    private final EventListener listener;
    
    private  ConstructorEscape()
    {
        this.listener=    new EventListener(){
            public void OnEvent(Event e)
            {
                doSomeThing(e);
            }
        };        
    }
    
    public static ConstructorEscape getInstance(EventSource eventSource)
    {
        ConstructorEscape  instance = new ConstructorEscape();
        eventSource.registerListener(instance.listener);
        return instance;
    }    
}

 



2、避免對象發佈之線程封閉
線程封閉可使數據的訪問限制在單個線程以內,相對鎖定同步來講,其實實現線程安全比較簡單的方式。
java提供了ThreadLocal類來實現線程封閉,其可使針對每一個線程存有共享狀態的獨立副本。其一般用於防止對可變的單實例變量和全局變量進行共享,例如每一個請求做爲一個邏輯事務須要初始化本身的事務上下文,這個事務上下文應該使用ThreadLocal來實現線程封閉。
棧封閉是線程封閉的特例,即數據做爲局部變量封閉在執行線程中,對於值類型的局部變量不存在逸出的問題,若是是引用類型的局部變量,開發人員須要確保其不要做爲返回值或者其餘的關聯引用等而被逸出。

3、避免對象發佈之不變性
某個對象建立以後就不能修改其狀態,那麼咱們就說這個對象是不可變對象。
因爲多線程操做可變狀態會致使原子性、可見性一系列問題,因此線程安全性是不可變對象與生俱來的特性。
不可變對象由構造函數初始化狀態,並能夠安全的傳遞給任何不可信代碼使用。
全部字段標記爲final的對象,因爲引用字段的對象可能能夠直接修改,因此其並不必定是不可變對象,其須要知足如下條件
對象的全部字段都用final標記
對象建立以後任何狀態都不能修改
對象不存在this隱式構造函數逸出函數

package com.codeartist;

import java.util.HashSet;

public class ObjectPublish {    
    private  HashSet<Person> persons=  new HashSet<Person>();
    
    public  ObjectPublish()
    {        
        persons.add(new Person("wufengtinghai"));
        persons.add(new Person("codeartist"));
    }
    
    public Person  getPerson(String name)
    {
        Person result = null;
        for(Person p  : this.persons)
        {
            if(p.name == name)
            {
                result = p.Clone();
            }
        }    
        return result;
    }    
}



4、對象的安全發佈
不少時候咱們是但願在多線程之間共享數據的,此時咱們就必須確保安全的發佈共享對象。
要安全的發佈一個對象,對象的引用以及對象的狀態對其餘線程都是可見的,一個正確構造的對象能夠經過如下方式安全的發佈
在靜態構造函數中初始化對象引用
使用volatile和AtomicReferance限定對象引用
使用final限定對象引用
將對象引用保存到有鎖保護的字段中
1.不可變對象
任何線程均可以在不須要同步的狀況下安全的訪問不可變對象及其final字段,若是字段的引用能夠改變則須要進行同步。
不可變對象在確保初始化安全的前提,能夠自由的發佈
    有時爲了確保不可變對象對於多個線程呈現一致的狀態,須要使用同步不可變對象的初始化。
    須要具有以上說的三個不變性限制條件。
2.隱式約定不可變對象
實際能夠改變對象在發佈後不會再改變狀態,則此對象成爲隱式約定不可變對象。
雖然任何線程均可以無需同步便可安全的訪問隱式約定不可變對象,可是因爲其自己仍是可變的,因此其須要以安全方式進行發佈。
3.可變對象
須要使用同步來確保發佈和共享的安全性。
5、對象的安全共享
爲了確保使用共享對象的安全性,咱們須要遵循其既定的規則(例如是不是不可變對象)來肯定咱們訪問對象的方式
不可變對象和隱式約定不可變對象能夠直接由多線程併發訪問。
線程封閉對象只能由建立線程持有並修改。
本身內部實現安全性的線程安全對象也能夠直接由多線程訪問。
通常的可變對象只能經過持有鎖進行同步來實現安全共享。

this

相關文章
相關標籤/搜索