多線程設(shè)計模式解讀—Immutable Object(不可變對象)模式

多線程設(shè)計模式解讀—Immutable Object(不可變對象)模式

前面講了Producer-Consumer模式利赋,它有許多變種搜立,我們以后會講席噩。我們將接著了解另外一個分支的設(shè)計模式蛾坯,前面所講的所有的模式丑搔,都是要用到鎖的,而鎖是會帶來一些額外的開銷和問題的高镐,那么能不能不通過鎖溉旋,實現(xiàn)多線程環(huán)境下的線程安全呢?其中一個思路就是通過Immutable Object(不可變對象)模式嫉髓。它使用對外可見的不可變對象观腊,天生具有線程安全的“基因”。因為與多線程的原子性算行、可見性相關(guān)的問題(如失效數(shù)據(jù)梧油、丟失更新操作、對象處于不一致狀態(tài)等)都與多線程試圖同時訪問同一個可變狀態(tài)相關(guān)州邢,若對象狀態(tài)不可變儡陨,那這些問題也就不存在了。

不可變對象的條件:

  • 對象創(chuàng)建以后其狀態(tài)就不能修改
  • 對象的所有域都是final類型

  • 對象是正確創(chuàng)建的(對象創(chuàng)建期間,this引用沒有逸出)

構(gòu)造不可變對象建議:

  • 類聲明為final類型迄委,字段可見性設(shè)置為private褐筛,這樣可以防止子類修改其字段值类少。
  • 字段聲明為final字段叙身,這樣字段被賦值一次,就不會再被賦值硫狞。同時保證了字段引用的對象的初始化安全信轿。
  • 不存在setter方法,且確保字段引用的實例未變化残吩。

示例代碼實例如下:

public final class ImmutableCustomer {
    private final String name;
    private final String address;
    public ImmutableCustomer(String name, String address) {
        this.name = name;
        this.address = address;
    }
    public String getName() {
        return name;
    }
    public String getAddress() {
        return address;
    }
    @Override
    public String toString() {
        return "[ ImmutableCustomer: name = " + name + ", address = " + address + " ]";
    }
}

public class OpeCustomerThread extends Thread {
    private ImmutableCustomer immutableCustomer;
    public OpeCustomerThread(ImmutableCustomer person) {
        this.immutableCustomer = person;
    }
    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + "  "
                + immutableCustomer);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        ImmutableCustomer alice = new        ImmutableCustomer("Alice", "Alaska");
        alice = new ImmutableCustomer("Ace", "Alaska");
        new OpeCustomerThread(alice).start();
        new OpeCustomerThread(alice).start();
        new OpeCustomerThread(alice).start();
    }
}

jdk中的CopyOnWriteArrayList也使用了該模式财忽,它是ArrayList的線程安全變體,其中所有變更操作(添加泣侮,設(shè)置等)都是通過創(chuàng)建底層數(shù)組的新副本來實現(xiàn)的(實際上即彪,array的元素是可以被替換的,這是一個事實不可變對象活尊,即對象從技術(shù)上而言未滿足不可變對象的嚴格定義隶校,是可變,但其狀態(tài)在安全發(fā)布后不會再改變了)蛹锰。這需要一定開銷深胳,但是當遍歷操作遠比變更頻繁時,它可能比其他方法更有效铜犬。它不需要加鎖就可以排除并發(fā)線程之間的干擾舞终。迭代器不會拋出ConcurrentModificationException。自迭代器創(chuàng)建后癣猾,迭代器無需考慮后期修改操作帶來的影響敛劝。

源碼片段如下:

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    /** 鎖保護所有的變更操作 */
    final transient ReentrantLock lock = new ReentrantLock();

    /** array,只能通過getArray/setArray訪問 */
    private transient volatile Object[] array;

    /**
     * 獲取array
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * 設(shè)置array.
     */
    final void setArray(Object[] a) {
        array = a;
    }
  
   /**
     * 添加特定元素到list
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
  
  
    /**
     * 移除特定位置的元素
     */
    public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
  
  
 /**
     * 返回的迭代器提供構(gòu)造迭代器時list狀態(tài)的快照纷宇。遍歷迭代器時不需要同步攘蔽。 迭代器不支持remove方法。
     */
    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }
  
}

從對以往CopyOnWriteArrayList使用呐粘,我們可以總結(jié)使用不可變對象模式需要注意的地方:

1满俗、當變更操作比較頻繁時,會在狀態(tài)變化時不斷創(chuàng)建替換新的不可變對象作岖,這會加重GC的負擔和系統(tǒng)開銷唆垃,應(yīng)該謹慎使用。

2痘儡、CopyOnWriteArrayList中array的元素是可以被替換的辕万,訪問其中的元素需要避免外部代碼修改其狀態(tài),這里的迭代器不支持remove方法。類似的情況渐尿,如我們返回HashMap類型的對象時醉途,需要做好防御性復(fù)制:

Collections.unmodifiableMap(deepCopy(map))

歡迎掃碼關(guān)注公眾號java達人:

drjava
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市砖茸,隨后出現(xiàn)的幾起案子隘擎,更是在濱河造成了極大的恐慌,老刑警劉巖凉夯,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件货葬,死亡現(xiàn)場離奇詭異,居然都是意外死亡劲够,警方通過查閱死者的電腦和手機震桶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來征绎,“玉大人蹲姐,你說我怎么就攤上這事∪耸粒” “怎么了柴墩?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長顷扩。 經(jīng)常有香客問我拐邪,道長,這世上最難降的妖魔是什么隘截? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任扎阶,我火速辦了婚禮,結(jié)果婚禮上婶芭,老公的妹妹穿的比我還像新娘东臀。我一直安慰自己,他們只是感情好犀农,可當我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布惰赋。 她就那樣靜靜地躺著,像睡著了一般呵哨。 火紅的嫁衣襯著肌膚如雪赁濒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天孟害,我揣著相機與錄音拒炎,去河邊找鬼。 笑死挨务,一個胖子當著我的面吹牛击你,可吹牛的內(nèi)容都是我干的玉组。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼丁侄,長吁一口氣:“原來是場噩夢啊……” “哼惯雳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鸿摇,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤石景,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后户辱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸵钝,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡糙臼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年庐镐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片变逃。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡必逆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出揽乱,到底是詐尸還是另有隱情名眉,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布凰棉,位于F島的核電站损拢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏撒犀。R本人自食惡果不足惜福压,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望或舞。 院中可真熱鬧荆姆,春花似錦、人聲如沸映凳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诈豌。三九已至仆救,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間矫渔,已是汗流浹背彤蔽。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蚌斩,地道東北人铆惑。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓范嘱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親员魏。 傳聞我的和親對象是個殘疾皇子丑蛤,可洞房花燭夜當晚...
    茶點故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 在一個方法內(nèi)部定義的變量都存儲在棧中,當這個函數(shù)運行結(jié)束后撕阎,其對應(yīng)的棧就會被回收受裹,此時,在其方法體中定義的變量將不...
    Y了個J閱讀 4,413評論 1 14
  • 不足的地方請大家多多指正虏束,如有其它沒有想到的常問面試題請大家多多評論棉饶,一起成長,感謝!~ String可以被繼承嗎...
    啟示錄是真的閱讀 2,925評論 3 3
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法镇匀,類相關(guān)的語法照藻,內(nèi)部類的語法,繼承相關(guān)的語法汗侵,異常的語法幸缕,線程的語...
    子非魚_t_閱讀 31,598評論 18 399
  • 有時心情也會低落,找不到理由地 復(fù)雜的情緒夾雜在一起晰韵,浮躁不安发乔,任何一點小事都會把我激怒,讓整個人都不好起來 以前...
    恩煦閱讀 171評論 2 1
  • 由于拖延癥的發(fā)作雪猪,硬是回來2周了栏尚,才想來細細寫寫去三亞的感覺。這次去主要是拍婚紗照只恨,可是我們順帶玩了幾天译仗。...
    Myself_048f閱讀 427評論 0 0