讀《java并發(fā)編程》零星筆記

java并發(fā)編程
書中代碼

線程安全

“當(dāng)多個(gè)線程訪問某個(gè)類時(shí)膘掰,不管運(yùn)行時(shí)環(huán)境采用何種調(diào)度方式章姓,或者這些線程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同识埋,這個(gè)類都能表現(xiàn)出正確的行為凡伊,那么就稱這個(gè)類是線程安全的”。

  • 對象的狀態(tài)是指存儲在狀態(tài)變量(實(shí)例或者靜態(tài)域)中的數(shù)據(jù)窒舟。

  • “共享”意味著變量可以有多個(gè)線程同時(shí)訪問系忙,而“可變”意味著變量的值在其生命周期內(nèi)可以變化。

  • 編寫線程安全的代碼的核心在于:要對狀態(tài)訪問操作進(jìn)行管理辜纲,特別時(shí)對共享的(shared)和可變(Mutable)的狀態(tài)的訪問笨觅。

  • 一個(gè)對象是否需要是線程安全的,取決于它是否需要被多個(gè)線程訪問耕腾,不需要被多線程訪問见剩,則不談它的安全性。要使對象是線程安全的扫俺,需要采用同步機(jī)制來協(xié)同對象可變狀態(tài)的訪問苍苞,如果無法實(shí)現(xiàn)協(xié)同,則可能導(dǎo)致數(shù)據(jù)被破壞以及錯(cuò)誤結(jié)果狼纬。

  • 無狀態(tài)對象一定時(shí)線程安全的羹呵,如Servlet,就沒有定義任何的屬性疗琉。所以多個(gè)線程調(diào)用它的方法總能得到正確的結(jié)果冈欢。但是自定義的Servlet子類中如果定義了一些狀態(tài),那么共享對象需要同步機(jī)制來保證對象可變狀態(tài)的訪問盈简。

內(nèi)存可見性

  • 做到內(nèi)存可見性的原則:對一個(gè)變量執(zhí)行unlock操作之前凑耻,必須先把此變量同步回主內(nèi)存中(執(zhí)行stroe太示、write操作)
    必看-內(nèi)存可見性視頻
  • 多個(gè)線程對共享變量進(jìn)行讀寫操作時(shí),如果線程A修改了共享變量香浩,其它線程應(yīng)該能夠知道這個(gè)修改类缤。實(shí)現(xiàn)方法有:synchronized、final邻吭、Volatile變量餐弱。
  • 枷鎖來實(shí)現(xiàn)內(nèi)存可見,內(nèi)置鎖可以用于確保某個(gè)線程以一種可預(yù)測的方式來查看另一個(gè)線程的執(zhí)行結(jié)果囱晴。

深入理解Java虛擬機(jī)筆記---原子性膏蚓、可見性、有序性

重排序

  • JVM為了能夠充分利用多核處理器的強(qiáng)大性能速缆,在缺乏同步的情況下降允,Java內(nèi)存模型允許編譯器對操作順序進(jìn)行重排序,并將數(shù)值寄存在寄存器中艺糜。此外剧董,他還允許CPU對操作順序進(jìn)行重排序,并將計(jì)算值緩存在處理器特定的緩存中破停。(如果沒有重排序存在翅楼,在編寫并發(fā)代碼時(shí)可以省去一些事,但是這是存在的所以需要做一些事情來防止對關(guān)鍵代碼的重排序
  • 以下代碼中主線程中的代碼可能存在重排序真慢,即在缺少同步情況下毅臊,JVM允許編譯器和CPU對操作順序進(jìn)行重排序,那么最后 number = 42; ready = true;語句的執(zhí)行順序就會顛倒變?yōu)? ready = true;number = 42; 黑界。這對ReaderThread線程來說是個(gè)悲劇管嬉,因?yàn)樗赡芟茸x到ready=true,在執(zhí)行還沒等主線程為number設(shè)置42朗鸠,就執(zhí)行了輸出number操作蚯撩,結(jié)果就為0。那么這種結(jié)果不是我期望的42結(jié)果烛占。這就是由于多個(gè)線程之間對內(nèi)存寫入操作的不可見導(dǎo)致的結(jié)果胎挎。實(shí)現(xiàn)內(nèi)存可見性如上面所示。
public class NoVisibility {
    private static boolean ready;
    private static int number;

    private static class ReaderThread extends Thread {
        public void run() {
            while (!ready)
                Thread.yield();
            System.out.println(number);
        }
    }

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

深入理解Java內(nèi)存模型(二)——重排序

  • java提供了volatile和synchronized兩個(gè)關(guān)鍵字來保證線程之間操作的有序性

正確性

某個(gè)類的行為與其規(guī)范完全一致忆家。在良好的規(guī)范中通常會定義各種不變性條件(Invariant)來約束對象的狀態(tài)s犹菇,以及定義各種后驗(yàn)條件(Postcondition)來描述對象操作的結(jié)果。

  • 補(bǔ)充:不變性條件可能涉及對象的多個(gè)狀態(tài)芽卿,比如揭芍,對象的a狀態(tài)變化時(shí)b也要變化,如果這個(gè)不變性在多線程中會被破壞了則該類不是線程安全的卸例,因此當(dāng)不變性臺條件涉及多個(gè)變量時(shí)沼沈,當(dāng)更新某一個(gè)變量時(shí)流酬,需要在同一個(gè)原子操作中對其它變量同時(shí)進(jìn)行更新币厕×辛恚可用鎖實(shí)現(xiàn)。

非原子性的64位操作

內(nèi)存可見性和原子性:Synchronized和Volatile的比較

Java內(nèi)存模型要求旦装,變量的讀取操作和寫入操作必須時(shí)原子操作页衙,但對于非volatile類型的long和double變量,JVM允許將64位的讀操作和寫操作分解為兩個(gè)32位的操作阴绢,當(dāng)讀取一個(gè)非volatile類型的long變量時(shí)店乐,如果對該變量的讀操作和寫操作在不同的線程中執(zhí)行,那么很可能會讀取到某個(gè)值的高32位和另一個(gè)值的低32位呻袭,因此在多線程中使用共享且可變(某個(gè)線程會對該變量執(zhí)行寫操作)的long和double等類型的bain了也是不安全的眨八,除非使用volatile來聲明他們或者使用鎖保護(hù)起來。(也許以后的處理器就都可以提供64位數(shù)值的原子操作)

volatile變量

  • java語言提供一種稍弱的同步機(jī)制左电,即volatile變量廉侧,用來確保將變量的更新操作通知到其它線程。當(dāng)把變量聲明位volatile類型后篓足,編譯器運(yùn)行時(shí)都會注意到這個(gè)變量是共享的段誊,因此不會將該變量上的操作與其他內(nèi)存操作一起重排。voldatilte變量不會被緩存到寄存器或者對其他處理器不可見的地方栈拖,因此在讀取volatile類型變量時(shí)總會返回最新寫入的值连舍。

  • 什么叫將變量的更新操作通知到其它線程?首先該變量是一個(gè)共享變量涩哟,可以被多個(gè)線程訪問索赏,就是上面講的內(nèi)存可見性

  • 不要過度使用volatile變量贴彼,僅當(dāng)volatile變量能簡化代碼的實(shí)現(xiàn)以及對同步策略的驗(yàn)證時(shí)潜腻,才應(yīng)該使用他們,如果在驗(yàn)證正確性時(shí)(某個(gè)類的行為與其規(guī)范完全一致)需要對可見性進(jìn)行復(fù)雜的判斷锻弓,那么就不要使用volatile變量砾赔。

  • volatile變量的一種典型用法:檢查某個(gè)狀態(tài)標(biāo)記以判斷是否退出循環(huán)。下面示例中青灼,線程通過數(shù)綿羊的方法進(jìn)入休眠暴心。為了使這個(gè)示例能正確執(zhí)行,asleep必須為volatile變量杂拨。否則當(dāng)asleep被另一個(gè)線程修改時(shí)专普,執(zhí)行判斷的線程卻發(fā)現(xiàn)不了。為什么發(fā)現(xiàn)不了弹沽?答:JVM在server模式中(另一個(gè)模式client做了相對較少的優(yōu)化)對代碼進(jìn)行了更多的優(yōu)化檀夹,其中就包括將循環(huán)中未被修改的變量提升到循環(huán)外部筋粗,對于該代碼中,asleep在while中沒有被修改,如果asleep不是volatile類型炸渡,那么JVM就會將asleep的判斷條件提升到循環(huán)體外部娜亿,這將導(dǎo)致一個(gè)無線循環(huán)。

public class CountingSheep {
    volatile boolean asleep;
    void tryToSleep() {
        while (!asleep)
            countSomeSheep();
    }
    void countSomeSheep() {
        // One, two, three...
    }
}
  • volatile變量只能確卑龆拢可見性买决,不能確保原子性,而加鎖機(jī)制兩種都可以吼畏。因?yàn)関olatile的語義不足以確保遞增操作(count++)的原子性督赤,除非你能確保只有一個(gè)線程對變量執(zhí)行寫操作。在訪問volatile變量時(shí)不會執(zhí)行加鎖操作泻蚊,因?yàn)橐簿筒粫?zhí)行線程阻塞躲舌。

  • 當(dāng)且僅當(dāng)滿足以下條件時(shí)才使用volatile變量:

    • 對變量的寫入操作不依賴變量的當(dāng)前值,或者你能確保只有單個(gè)線程更新變量的值性雄。
    • 該變量不會與其它狀態(tài)變量一起納入不變性條件中没卸。(在良好的規(guī)范中通常會定義各種不變性條件Invariant來約束對象的狀態(tài)s,以及定義各種后驗(yàn)條件Postcondition來描述對象操作的結(jié)果毅贮,)
    • 在訪問變量時(shí)不需要枷鎖办悟。
  • 補(bǔ)充不變性條件:不變性條件可能涉及對象的多個(gè)狀態(tài),比如滩褥,對象的a狀態(tài)變化時(shí)b也要變化病蛉,如果這個(gè)不變性在多線程中會被破壞了則該類不是線程安全的,因此當(dāng)不變性臺條件涉及多個(gè)變量時(shí)瑰煎,當(dāng)更新某一個(gè)變量時(shí)铺然,需要在同一個(gè)原子操作中對其它變量同時(shí)進(jìn)行更新【频椋可用鎖實(shí)現(xiàn)魄健。

    • 在LinkedList集合中存在多個(gè)不變性條件,其中一條如下:鏈表的第一個(gè)節(jié)點(diǎn)指針first和最后一個(gè)節(jié)點(diǎn)指針last的不變性關(guān)系插勤。
/**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

細(xì)說Java多線程之內(nèi)存可見性-視頻

發(fā)布與逸出

  • 發(fā)布一個(gè)對象:是對象能能在當(dāng)前作用域之外的代碼中使用沽瘦。當(dāng)發(fā)布一個(gè)對象可能會間接地發(fā)布其他對象。當(dāng)發(fā)布一個(gè)對象時(shí)农尖,在該對象的非私有域中引用的所有對象同樣會被發(fā)布析恋。
  • 逸出:當(dāng)某個(gè)不應(yīng)該被發(fā)布的對象被發(fā)布時(shí),這種情況稱為逸出盛卡。
  • 發(fā)布對象方法:
  • 將對象的引用保持到一個(gè)公有靜態(tài)變量中助隧。
  • 指向該對象的應(yīng)用保持到其它代碼可以訪問的地方
  • 在一個(gè)非私有方法中返回對象的引用。
  • 發(fā)布一個(gè)內(nèi)部的類實(shí)例滑沧。如下代碼:ThisEscape 發(fā)布EventListener時(shí)并村,也隱含的發(fā)布了ThisEscape實(shí)例本身巍实,因?yàn)檫@個(gè)內(nèi)部類實(shí)例中保護(hù)了對EventListener實(shí)例的隱含引用。這種非期望的發(fā)布就造成了ThisEscape對象的逸出哩牍。
public class ThisEscape {
    public ThisEscape(EventSource source) {
        source.registerListener(new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        });
    }
    void doSomething(Event e) {
    }
    interface EventSource {
        void registerListener(EventListener e);
    }
    interface EventListener {
        void onEvent(Event e);
    }
    interface Event {
    }
}
  • 防止逸出:
    不要在構(gòu)造過程中使用this引用棚潦。
    如果想著構(gòu)造函數(shù)中注冊一個(gè)事件監(jiān)聽器或者啟動線程,那么可以使用一個(gè)私有的構(gòu)造函數(shù)和一個(gè)公共的工廠方法姐叁。如下:
public class SafeListener {
    private final EventListener listener;

    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        };
    }
    public static SafeListener newInstance(EventSource source) {
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }
    void doSomething(Event e) {
    }
    interface EventSource {
        void registerListener(EventListener e);
    }
    interface EventListener {
        void onEvent(Event e);
    }
    interface Event {
    }
}

競態(tài)條件與復(fù)合操作

  • 當(dāng)某個(gè)計(jì)算的正確性取決于多個(gè)線程的交替執(zhí)行時(shí)序時(shí)瓦盛,那么就會發(fā)生競態(tài)條件。出現(xiàn)競態(tài)條件外潜,就可能會造成線程不安全。
    下面代碼就顯示了延遲初始化中的競態(tài)條件挠唆,它破壞了這個(gè)類的正確性处窥。
public class LazyInitRace {
    private ExpensiveObject instance = null;

    public ExpensiveObject getInstance() {
        if (instance == null)
            instance = new ExpensiveObject();
        return instance;
    }
}

以下代碼存在競態(tài)條件,count++包含了”讀取-修改-寫入”三個(gè)操作玄组。

public class UnsafeCountingFactorizer extends GenericServlet implements Servlet {
    private long count = 0;

    public long getCount() {
        return count;
    }

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        ++count;
        encodeIntoResponse(resp, factors);
    }
....
}
  • 常見競態(tài)條件:先檢查后執(zhí)行滔驾、讀取-修改-寫入。
  • 避免競態(tài)條件產(chǎn)生的線程不安全問題俄讹,這些操作應(yīng)該是原子性的哆致。即為了確保線程安全性,把”先檢查后執(zhí)行“患膛、”讀取-修改-寫入“等操作統(tǒng)稱為復(fù)合操作:包含了一組必須以原子方式執(zhí)行的操作摊阀。
  • 實(shí)現(xiàn)復(fù)合操作的原子性:枷鎖機(jī)制(可實(shí)現(xiàn)多個(gè)狀態(tài)的原子操作)、原子變量類(針對只有一個(gè)狀態(tài))
  • 為了實(shí)現(xiàn)這種復(fù)合操作的原子性可以使用加鎖機(jī)制踪蹬。對于一些只包含一個(gè)狀態(tài)的復(fù)合操作可以使用java.until.concurrent.atomoc包中包含的一些原子變量類來解決胞此。
  • java.until.concurrent.atomoc包中包含的一些原子變量類,用于實(shí)現(xiàn)在數(shù)值和對象引用上的原子狀態(tài)轉(zhuǎn)換。

如下代碼:使用AtomicLong來代替long類型的計(jì)數(shù)器跃捣,能夠確保所有對計(jì)數(shù)器狀態(tài)的訪問都是原子的漱牵,

public class CountingFactorizer extends GenericServlet implements Servlet {
    private final AtomicLong count = new AtomicLong(0);

    public long getCount() { return count.get(); }
    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        count.incrementAndGet();
        encodeIntoResponse(resp, factors);
    }
}

線程封閉

  • 定義:當(dāng)訪問共享的可變數(shù)據(jù)時(shí),通常需要使用同步疚漆。一種避免使用同步的方式就是不共享數(shù)據(jù)酣胀。如果僅在單線程內(nèi)訪問數(shù)據(jù),就不需要同步娶聘,這種技術(shù)被稱為線程封閉技術(shù)(Thread Confinement)闻镶,它是實(shí)現(xiàn)現(xiàn)在安全性的最簡單方式之一。

  • 案例:JDBC的Connection對象就使用了線程封閉技術(shù)趴荸,JDBC規(guī)范并不要求Connection對象必須是線程安全的儒溉。在典型的服務(wù)器應(yīng)用程序中,線程從連接池中獲得一個(gè)Connection對象发钝,并且用該對象來處理請求顿涣,使用完后再將對象返還給連接池波闹。由于大多數(shù)請求(如Servlet)都是有單個(gè)線程采用同步技術(shù)的方式來處理,并且再Connection對象返回之前涛碑,連接池不會再將他分配給其它線程精堕,因此這中連接管理模式再處理請求時(shí)隱含的將Connection對象封裝再線程中。
    Servlet的多線程和線程安全

  • 注:應(yīng)用程序服務(wù)器提供的連接池是線程安全的連接池通常會由多個(gè)線程訪問蒲障,因此非線程安全的連接池是毫無意義的歹篓。

  • 實(shí)現(xiàn)線程封閉性的技術(shù):Java沒有強(qiáng)制規(guī)定某個(gè)變量必須有鎖來保護(hù),同樣也無法強(qiáng)制將對象封裝再某個(gè)線程中揉阎。線程封閉是再程序設(shè)計(jì)中考慮的一個(gè)因素庄撮。但是Java語言及其核心類庫提供了一些機(jī)制來幫助維持線程封閉性,例如棧封閉和ThreadLocal類毙籽,還有一種是Ad-hoc線程封閉洞斯,即便如此程序員也需要負(fù)責(zé)確保封閉性在線程中的對象不會從線程中逸出。

Ad-hoc線程封閉(脆弱坑赡,不推薦)

維護(hù)線程封閉性的職責(zé)完全有程序?qū)崿F(xiàn)來承擔(dān)烙如,很脆弱不建議使用。
如下代碼通過Map實(shí)現(xiàn)線程封閉性:
其中static類型的data是線程間共享的毅否,但是為了實(shí)現(xiàn)數(shù)據(jù)和線程綁定亚铁,所以通過map來存放不同線程操作的數(shù)據(jù)data,data的獲取和線程也是綁定的螟加,這樣就實(shí)現(xiàn)了data數(shù)據(jù)在單線程中訪問了,不會與其它線程共享徘溢,注map是和其它線程共享的, 這樣每個(gè)線程中操作的A仰迁、B類都是共享和本線程綁定的那個(gè)data甸昏,從而不會沖突和出錯(cuò)。

Ad-hoc線程封閉

棧封閉性

  • 棧封閉性是線程封閉性的一種特例徐许,再棧封閉中施蜜,只能通過局部變量才能訪問對象。
  • 局部變量固有屬性之一就是封閉在執(zhí)行線程中雌隅。它們位于執(zhí)行線程的棧中翻默,其他線程無法訪問這各棧。棧封閉(也被稱為線程內(nèi)部使用或者線程局部局部使用恰起,不要與核心類庫中的ThreadLocal混淆)比Ad-hoc線程封閉更易維護(hù)修械。
  • 如下:對于loadTheArk方法的局部變量numPairs,無論如何也不會破環(huán)棧的封閉性检盼。因?yàn)槿魏畏椒ǘ紵o法獲得對基本類型的引用肯污,因此java語言的這種語義就確保了基本類型的局部變量始終封閉在線程內(nèi)。
  • 在維護(hù)對象引用的棧封閉性時(shí),程序員需要確保被引用的對象不會逸出蹦渣。loadTheArk方法中animals引用指向了一個(gè)SortedSet對象哄芜,此時(shí)只有一個(gè)引用指向了集合animals,這個(gè)引用被封閉在了局部變量中柬唯,因此也被封閉在執(zhí)行線程中认臊。但是,如果發(fā)布了對集合animals的引用锄奢,那么封閉性也被破壞失晴,并導(dǎo)致對象animals逸出。
public int loadTheArk(Collection<Animal> candidates) {
        SortedSet<Animal> animals;
        int numPairs = 0;
        Animal candidate = null;

        // animals confined to method, don't let them escape!
        animals = new TreeSet<Animal>(new SpeciesGenderComparator());
        animals.addAll(candidates);
        for (Animal a : animals) {
            if (candidate == null || !candidate.isPotentialMate(a))
                candidate = a;
            else {
                ark.load(new AnimalPair(candidate, a));
                ++numPairs;
                candidate = null;
            }
        }
        return numPairs;
    }

ThreadLocal類(重點(diǎn))

  • 這個(gè)類能使線程中的某個(gè)值與保存該值的對象關(guān)聯(lián)起來拘央。ThreadLocal提供類get與set等訪問接口或方法涂屁,這些方法為每個(gè)使用該變量的線程都存有一份獨(dú)立的副本,因此get總是返回由當(dāng)前執(zhí)行線程在調(diào)用set時(shí)設(shè)置的最新值堪滨。
  • ThreadLocal對象通常用于防止對可變的單實(shí)例變量(Singleton)或全局變量進(jìn)行共享胯陋。如:單線程應(yīng)用中可能會位置一個(gè)全局的數(shù)據(jù)庫連接,并在程序啟動時(shí)初始化這個(gè)連接對象袱箱,從而避免在調(diào)用每個(gè)方法時(shí)都需要傳遞一個(gè)Connection對象(實(shí)現(xiàn)線程內(nèi)數(shù)據(jù)共享)。
  • 如下代碼义矛,通過將JDBC的連接保存到ThreadLocal對象中发笔,每個(gè)線程都會擁有屬于自己的連接。
public class ConnectionDispenser {
    static String DB_URL = "jdbc:mysql://localhost/mydatabase";

    private ThreadLocal<Connection> connectionHolder
            = new ThreadLocal<Connection>() {
                public Connection initialValue() {
                    try {
                        return DriverManager.getConnection(DB_URL);
                    } catch (SQLException e) {
                        throw new RuntimeException("Unable to acquire Connection, e");
                    }
                };
            };

    public Connection getConnection() {
        return connectionHolder.get();
    }
}
  • 更多案例:1.如果需要將一個(gè)單線程應(yīng)用移植到多線程環(huán)境中凉翻,通過將共享的全局變量轉(zhuǎn)換為ThreadLocal(如果全局變量的語義允許)了讨,可以維持線程安全性。2.在EJB調(diào)用期間制轰,J2EE容器需要將一個(gè)事務(wù)上下文(Transaction Context)與某個(gè)執(zhí)行中的線程關(guān)聯(lián)起來前计。通過將Transaction Context保存在靜態(tài)的ThreadLocal對象中,可以很容易實(shí)現(xiàn)這個(gè)功能垃杖。

  • 實(shí)現(xiàn)機(jī)制:可以將ThreadLocal<T>視為包含了Map<Thread男杈,T>對象,其中保存了特定于該線程的值调俘,但ThreadLocal的實(shí)現(xiàn)并非如此伶棒,這些特定于線程的值保存在Thread對象中,當(dāng)線程終止后彩库,這些值會作為垃圾回收肤无。

  • 注意:不要濫用ThreadLocal,例如將所有全局變量都作為ThreadLocal變量骇钦,或者作為“隱藏”方法參數(shù)的手段(設(shè)為全局就不需要通過參數(shù)傳遞過來)宛渐。ThreadLocal變量類似全局變量,它降低了代碼的可重用性,并在類之間引入隱含的耦合性(一個(gè)線程中或涉及操作多個(gè)類窥翩,這些類中有的方法就有可能依賴ThreadLocal變量)业岁,因此要格外小心。

線程封閉性鳍烁、線程內(nèi)數(shù)據(jù)共享

  • 一個(gè)線程T1內(nèi)操作多個(gè)對象A叨襟、B時(shí),A幔荒、B中操作的數(shù)據(jù)都屬于該線程范圍內(nèi)的糊闽。

  • 比如:javaWeb中存錢操作,會操作數(shù)據(jù)庫爹梁。

  • 張三開啟T1線程獲取連接connection右犹,然后T1內(nèi)操作取錢類A取錢,操作記錄類B記錄日志姚垃,然后進(jìn)行conn提交念链。

  • 李四開啟T2線程獲取連接connection,然后T1內(nèi)操作取錢類A取錢积糯,操作記錄類B記錄日志掂墓,然后進(jìn)行conn提交。

  • 線程間獨(dú)立:以上兩個(gè)線程獲取的connection應(yīng)該是獨(dú)立的看成,只屬于該線程君编,如果T1和T2共享一個(gè)connection,那么如果張三轉(zhuǎn)入錢后還沒來的急轉(zhuǎn)出川慌,就被李四提前轉(zhuǎn)出了吃嘿,那么就會出錯(cuò)。 (即實(shí)現(xiàn)線程封閉性)

  • 線程內(nèi)共享:每個(gè)線程中的connection對象是對該線程中所有被操作對象都是共享的梦重。

Paste_Image.png

不變性

  • 滿足同步需求的另一種方法是使用不可變對象(Immutable Object).

  • 當(dāng)滿足以下條件兑燥,對象才是不可變的:

  • 對象創(chuàng)建以后其狀態(tài)就不能修改。(比如:可通過關(guān)鍵字-》簡單類型狀態(tài)琴拧、程序控制實(shí)現(xiàn)-》引用類型狀態(tài))

  • 對象的所有域都是final類型(有例外)降瞳。

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

  • 不可變性并不等于將對象中所有的域都聲明為final類型艾蓝,即使都為final類型力崇,這個(gè)對象也仍然是可變的,因?yàn)閒inal域可以保存可變對象的引用赢织。

  • 如下代碼:在不可變對象基礎(chǔ)上構(gòu)建不可變類亮靴,盡管Set對象是可變的,但從ThreeStooges設(shè)計(jì)中可以看到于置,在Set對象構(gòu)造完成后茧吊,無法對其進(jìn)行修改贞岭。(程序控制)

public final class ThreeStooges {
    private final Set<String> stooges = new HashSet<String>();

    public ThreeStooges() {
        stooges.add("Moe");
        stooges.add("Larry");
        stooges.add("Curly");
    }
    public boolean isStooge(String name) {
        return stooges.contains(name);
    }
    public String getStoogeNames() {
        List<String> stooges = new Vector<String>();
        stooges.add("Moe");
        stooges.add("Larry");
        stooges.add("Curly");
        return stooges.toString();
    }
}
  • 這時(shí)候你可能會郁悶,沒有不將域聲明為final也可以啊搓侄,為什么要設(shè)為final瞄桨。答:1.(自己理解)final域能確保初始化過程的安全性。2.其次通過將域聲明為final類型讶踪,也相當(dāng)于告訴維護(hù)人員這些域是不會變化的芯侥。3.良好的編程習(xí)慣。

Final域

  • fianl類型的域是不能修改的乳讥,但是如果final域所引用的對象是可變的柱查,那么這些被引用的對象是可以修改的。
  • 在JMM中云石,final域能確保初始化過程的安全性

安全發(fā)布

不正確的發(fā)布:正確的對象被破壞

final域的內(nèi)存語義

不可變對象與初始化安全性

安全發(fā)布的常用模式

詳解Java中的clone方法 -- 原型模式
string 在clone()中的特殊性 (轉(zhuǎn)載)

--------待更新

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末唉工,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子汹忠,更是在濱河造成了極大的恐慌淋硝,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宽菜,死亡現(xiàn)場離奇詭異谣膳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)铅乡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門参歹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事熙兔∮镉” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵态辛,是天一觀的道長。 經(jīng)常有香客問我,道長咬腕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任葬荷,我火速辦了婚禮涨共,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宠漩。我一直安慰自己举反,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布扒吁。 她就那樣靜靜地躺著火鼻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上魁索,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天融撞,我揣著相機(jī)與錄音,去河邊找鬼粗蔚。 笑死尝偎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鹏控。 我是一名探鬼主播致扯,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼牧挣!你這毒婦竟也來了急前?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤瀑构,失蹤者是張志新(化名)和其女友劉穎裆针,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寺晌,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡世吨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呻征。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耘婚。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖陆赋,靈堂內(nèi)的尸體忽然破棺而出沐祷,到底是詐尸還是另有隱情,我是刑警寧澤攒岛,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布赖临,位于F島的核電站,受9級特大地震影響灾锯,放射性物質(zhì)發(fā)生泄漏兢榨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一顺饮、第九天 我趴在偏房一處隱蔽的房頂上張望吵聪。 院中可真熱鬧,春花似錦兼雄、人聲如沸吟逝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽澎办。三九已至嘲碱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間局蚀,已是汗流浹背麦锯。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留琅绅,地道東北人扶欣。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像千扶,于是被迫代替她去往敵國和親料祠。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

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