成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

【J2SE】java并發(fā)編程實(shí)戰(zhàn) 讀書(shū)筆記( 一、二、三章)

QLQ / 1671人閱讀

摘要:發(fā)布的對(duì)象內(nèi)部狀態(tài)可能會(huì)破壞封裝性,使程序難以維持不變性條件。不變性線(xiàn)程安全性是不可變對(duì)象的固有屬性之一??勺儗?duì)象必須通過(guò)安全方式來(lái)發(fā)布,并且必須是線(xiàn)程安全的或者有某個(gè)鎖保護(hù)起來(lái)。

線(xiàn)程的優(yōu)缺點(diǎn)

線(xiàn)程是系統(tǒng)調(diào)度的基本單位。線(xiàn)程如果使用得當(dāng),可以有效地降低程序的開(kāi)發(fā)和維護(hù)等成本,同時(shí)提升復(fù)雜應(yīng)用程序的性能。多線(xiàn)程程序可以通過(guò)提高處理器資源的利用率來(lái)提升系統(tǒng)的吞吐率。與此同時(shí),在線(xiàn)程的使用開(kāi)發(fā)過(guò)程中,也存在著諸多需要考慮的風(fēng)險(xiǎn)。

安全性:有合理的同步下,多線(xiàn)程的并發(fā)隨機(jī)執(zhí)行使線(xiàn)程安全性變得復(fù)雜,如++i。

活躍性:在多線(xiàn)程中,常因?yàn)槿鄙儋Y源而處于阻塞狀態(tài),當(dāng)某個(gè)操作不幸造成無(wú)限循環(huán),無(wú)法繼續(xù)執(zhí)行下去的時(shí)候,就會(huì)發(fā)生活躍性問(wèn)題。

性能:線(xiàn)程總會(huì)帶來(lái)程序的運(yùn)行時(shí)開(kāi)銷(xiāo),多線(xiàn)程中,當(dāng)頻繁地出現(xiàn)上下文切換操作時(shí),將會(huì)帶來(lái)極大的開(kāi)銷(xiāo)。

線(xiàn)程安全性

線(xiàn)程安全的問(wèn)題著重于解決如何對(duì)狀態(tài)訪(fǎng)問(wèn)操作進(jìn)行管理,特別是對(duì)共享和可變的狀態(tài)。共享意味著可多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn);可變即在變量在其生命周期內(nèi)可以被改變;狀態(tài)就是由某個(gè)類(lèi)中的成員變量(Field)。

一個(gè)無(wú)狀態(tài)的對(duì)象一定是線(xiàn)程安全的。因?yàn)樗鼪](méi)有可被改變的東西。
public class LoginServlet implements Servlet {
    public void service(ServletRequest req, ServletResponse resp) {
        System.out.println("無(wú)狀態(tài)Servlet,安全的類(lèi),沒(méi)有字段可操作");
    }
}
原子性

正如我們熟知的 ++i操作,它包含了三個(gè)獨(dú)立的“讀取-修改-寫(xiě)入”操作序列,顯然是一個(gè)復(fù)合操作。為此java提供了原子變量來(lái)解決 ++i這類(lèi)問(wèn)題。當(dāng)狀態(tài)只是一個(gè)的時(shí)候,完全可以勝任所有的情況,但當(dāng)一個(gè)對(duì)象擁有兩個(gè)及以上的狀態(tài)時(shí),仍然存在著需要思考的復(fù)合操作,盡管狀態(tài)都使用原子變量。如下:

public class UnsafeCachingFactorizer implements Servlet {
    private final AtomicReference lastNumber = 
        new AtomicReference();
    private final AtomicReference lastFactors = 
        new AtomicReference();

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        if (i.equals(lastNumber.get())) {
            encodeIntoResponse(resp, lastFactors.get());
        } else {
            BigInteger[] factors = factor(i);
            lastNumber.set(i);
            lastFactors.set(factors);
            encodeIntoResponse(resp, factors);
        }
    }
} // lastNumber lastFactors 雖然都是原子的,但是 if-else 是復(fù)合操作,屬“先驗(yàn)條件” 

既然是復(fù)合操作,最直接,簡(jiǎn)單的方式就是使用synchronized將這個(gè)方法同步起來(lái)。這種方式能到達(dá)預(yù)期效果,但效率十分低下。

既然提到synchronized加鎖同步,那么就必須知道 鎖的特點(diǎn):

鎖是可以重入的。即子類(lèi)的同步方法可以調(diào)用本類(lèi)或父類(lèi)的同步方法。

同一時(shí)刻,只有一個(gè)線(xiàn)程能夠訪(fǎng)問(wèn)對(duì)象中的同步方法。

靜態(tài)方法的鎖是 類(lèi);普通方法的鎖是 對(duì)象本身。

回顧上面的代碼,一個(gè)方法體中,只要涉及了多個(gè)狀態(tài)的時(shí)候,就一定需要同步整個(gè)方法嗎?答案是否定的,同步只是為了讓多步操作為原子性,即對(duì)復(fù)合操作同步即可,因此需要明確的便是哪些操作是復(fù)合操作。如下:

public class CachedFactorizer implements Servlet {
    private BigInteger lastNumber;
    private BigInteger[] lastFactors;
    private long hits;
    private long cacheHits;
    
    public synchronized long getHits() {
        return hits;
    }
    
    public synchronized double getCacheHitRatio() {
        return (double) cacheHits / (double) hits;
    }
    
    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = null;
        
        synchronized (this) {
            ++hits;
            if (i.equals(lastNumber)) {
                ++cacheHits;
                factors = lastFactors.clone();
            }
        }
        
        if (factors == null) {
            factors = factor(i);
            synchronized (this) {
                lastNumber = 1;
                lastFactors = factors.clone();
            }
        }
        
        encodeIntoResponse(reqsp, factors);
    }
}// 兩個(gè)synchronized分別同步獨(dú)立的復(fù)合操作。
對(duì)象共享

重排序:當(dāng)一個(gè)線(xiàn)程修改對(duì)象狀態(tài)后,其他線(xiàn)程沒(méi)有看見(jiàn)修改后的狀態(tài),這種現(xiàn)象稱(chēng)為“重排序”。

java內(nèi)存模型允許編譯器對(duì)操作順序進(jìn)行重排序,并將數(shù)據(jù)緩存在寄存器中。當(dāng)缺乏同步的情況下,每一個(gè)線(xiàn)程在獨(dú)立的緩存中使用緩存的數(shù)據(jù),并不知道主存中的數(shù)據(jù)已被更改。這就涉及到內(nèi)存可見(jiàn)性的問(wèn)題。

可見(jiàn)性

內(nèi)存可見(jiàn)性:同步的另一個(gè)重要的方面。我們不僅希望防止多個(gè)線(xiàn)程同時(shí)操作對(duì)象狀態(tài),而且還希望確保某一個(gè)線(xiàn)程修改了狀態(tài)后,能被其他線(xiàn)程看見(jiàn)變化。

volatile:使用 synchronized可以實(shí)現(xiàn)內(nèi)存可見(jiàn),但java提供了一種稍弱的更輕量級(jí)得同步機(jī)制volatile變量。在訪(fǎng)問(wèn)volatile變量時(shí)不會(huì)執(zhí)行加鎖操作,因此不會(huì)產(chǎn)生線(xiàn)程阻塞。即便如此還是不能過(guò)度使用volatile,當(dāng)且僅當(dāng)能簡(jiǎn)化代碼的實(shí)現(xiàn)以及對(duì)同步策略的驗(yàn)證時(shí),才考慮使用它。

發(fā)布與逸出

發(fā)布指:使對(duì)象能夠在當(dāng)前作用于之外的代碼中使用。即對(duì)象引用能被其他對(duì)象持有。發(fā)布的對(duì)象內(nèi)部狀態(tài)可能會(huì)破壞封裝性,使程序難以維持不變性條件。

逸出指:當(dāng)某個(gè)不應(yīng)該發(fā)布的對(duì)象被發(fā)布時(shí),這種情況被稱(chēng)為逸出。

// 正確發(fā)布:對(duì)象引用放置公有靜態(tài)域中,所有類(lèi)和線(xiàn)程都可見(jiàn)
class CarFactory {
    public static Set cars;
    
    private CarFactory() {
        cars = new HashSet();
    }    // 私有,外部無(wú)法獲取 CarFactory的引用
    
    public static Car void newInstance() {    
        Car car = new Car("碰碰車(chē)");
        cars.put(car);
        return car;
    }    // 使用方法來(lái)獲取 car
}
// 逸出
class Person {
    private String[] foods = new String[] {"土豆"};
    
    public Person(Event event) {
        person.registListener {
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            }
        }
    }// 隱式逸出了this,外界得到了Person的引用 并且 EventListener也獲取了Person的引用。
    
    public String[] getFoods() {
        return foods;
    }// 對(duì)發(fā)布的私有 foods,外界還是可以修改foods內(nèi)部值
}
線(xiàn)程封閉

將可變的數(shù)據(jù)僅放置在單線(xiàn)程中操作的技術(shù),稱(chēng)之為發(fā)線(xiàn)程封閉。

棧封閉:只能通過(guò)局部變量才能訪(fǎng)問(wèn)對(duì)象。局部變量的固有屬性之一就是封裝在執(zhí)行線(xiàn)程中,它們位于執(zhí)行線(xiàn)程的棧中,其他線(xiàn)程無(wú)法訪(fǎng)問(wèn)這個(gè)棧,即只在一個(gè)方法內(nèi)創(chuàng)建和使用對(duì)象。

public int test(Person p) {
    int num = 0;
    PersonHolder holder = new PersonHolder();
    
    Person newPerson = deepCopy(p);
    Person woman = holder.getLove(newPerson);
    newPerson.setWomen(person);
    num++;
    
    return num; // 基本類(lèi)型沒(méi)有引用,對(duì)象創(chuàng)建和修改都沒(méi)有逸出本方法
}

ThreadLocal類(lèi):ThreadLocal能夠使線(xiàn)程中的某個(gè)值與保存值的對(duì)象關(guān)聯(lián)起來(lái)。ThreadLocal提供了 get、set等訪(fǎng)問(wèn)接口的方法,這些方法為每一個(gè)使用該變量的線(xiàn)程都存有一份獨(dú)立的副本,因get總是返回由當(dāng)前執(zhí)行線(xiàn)程在調(diào)用set時(shí)設(shè)置的最新值。

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

public static Connection getConnection() {
    return connectionHolder.get();
}
當(dāng)某個(gè)頻繁執(zhí)行的操作需要一個(gè)臨時(shí)對(duì)象,例如一個(gè)緩沖區(qū),而同時(shí)又希望避免在每次執(zhí)行時(shí)都重新分配該臨時(shí)對(duì)象,就可以使用ThreadLocal。
不變性

線(xiàn)程安全性是不可變對(duì)象的固有屬性之一。不可變對(duì)象一定是線(xiàn)程安全的,它們的不變性條件是由構(gòu)造函數(shù)創(chuàng)建的,只要它們的狀態(tài)不可變。

//    在可變對(duì)象基礎(chǔ)上構(gòu)建不可變類(lèi)
public final class ThreadStooges {
    private final Set stooges = new HashSet();
    
    public ThreadStooges() {
        stooges.add("Moe");
        stooges.add("Larry");
    }
    
    public boolean isStooge(String name) {
        return stooges.contains(name);
    }
}// 沒(méi)有提供可修改狀態(tài)的方式,盡管使用了Set可變集合,但被private final修飾著

對(duì)象不可變的條件

對(duì)象創(chuàng)建以后其狀態(tài)就不能修改。

對(duì)象的所有域都是final類(lèi)型。

對(duì)象是正確創(chuàng)建的(在對(duì)象的創(chuàng)建期間,this引用沒(méi)有逸出)

安全發(fā)布
任何線(xiàn)程都可以在不需要額外同步的情況下安全地訪(fǎng)問(wèn)不可變對(duì)象,即使在發(fā)布這些對(duì)象時(shí)沒(méi)有使用同步。
// 安全的 Holder類(lèi)
class Holder {
    private int n;
    public Holder(int n) {
        this.n = n;
    }
}

public class SessionHolder {
    // 錯(cuò)誤的發(fā)布,導(dǎo)致 Holder不安全
    public Holder holder;
    
    public void init() {
        holder = new Holder(10);
    }
}// 當(dāng)初始化 holder的時(shí)候,holder.n會(huì)被先默認(rèn)初始化為 0,然后構(gòu)造函數(shù)才初始化為 10;在并發(fā)情況下,可能會(huì)有線(xiàn)程在默認(rèn)初始化 與 構(gòu)造初始化中,獲取到 n 值為 0, 而不是 10;

要安全的發(fā)布一個(gè)對(duì)象,對(duì)象的引用以及對(duì)象的狀態(tài)必須同時(shí)對(duì)其他線(xiàn)程可見(jiàn)。一個(gè)正確構(gòu)造的對(duì)象可以通過(guò)以下方式安全發(fā)布:

在靜態(tài)初始化函數(shù)中初始化一個(gè)對(duì)象引用。

將對(duì)象的引用保存到 volatitle 類(lèi)型的域或者 AtomicReferance 對(duì)象中。

將對(duì)象的引用保存到某個(gè)正確構(gòu)造對(duì)象的 final 類(lèi)型域中。

將對(duì)象的引用保存到一個(gè)由鎖保護(hù)的域中。

在線(xiàn)程并發(fā)容器中的安全發(fā)布:

通過(guò)將一個(gè)鍵或者值放入 Hashtable、synchronizedMap 或者 ConsurrentMap中,可以安全地將它發(fā)布給任何從這些容器中訪(fǎng)問(wèn)它的線(xiàn)程(無(wú)論是直接訪(fǎng)問(wèn)還是通過(guò)迭代器訪(fǎng)問(wèn))。

通過(guò)將某個(gè)元素放入 Vector、 CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList 或 synchronizedSet中,可以將元素安全地發(fā)布到任何從這些容器中訪(fǎng)問(wèn)該元素的線(xiàn)程。

通過(guò)將某個(gè)元素放入 BlockingQueue或者ConcurrentLinkedQueue中,可以將該元素安全地發(fā)布到任何從這些隊(duì)列中訪(fǎng)問(wèn)該元素的線(xiàn)程。

通常,要發(fā)布一個(gè)靜態(tài)構(gòu)造的對(duì)象,最簡(jiǎn)單、安全的方式就是使用靜態(tài)的初始化器。如public static Holder holder = new Holder(10)。如果對(duì)象在發(fā)布后狀態(tài)不會(huì)被修改(則稱(chēng)為事實(shí)不可變對(duì)象),那么在沒(méi)有額外的同步情況下,任何線(xiàn)程都可以安全地使用被安全發(fā)布的不可變對(duì)象。

對(duì)象的發(fā)布需求取決于它的可變性:

不可變對(duì)象可以通過(guò)任意機(jī)制來(lái)發(fā)布。

事實(shí)不可變對(duì)象必須通過(guò)安全方式來(lái)發(fā)布。

可變對(duì)象必須通過(guò)安全方式來(lái)發(fā)布,并且必須是線(xiàn)程安全的或者有某個(gè)鎖保護(hù)起來(lái)。

在并發(fā)程序中使用和共享對(duì)象時(shí)可采用的策略:

線(xiàn)程封閉。將對(duì)象封閉在線(xiàn)程中,如在方法中創(chuàng)建和修改局部對(duì)象。

只讀共享。

線(xiàn)程安全共享。對(duì)象內(nèi)部實(shí)現(xiàn)同步,使用公有接口來(lái)訪(fǎng)問(wèn)。

保護(hù)對(duì)象。使用特定的鎖來(lái)保護(hù)對(duì)象。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/73777.html

相關(guān)文章

  • java 8 實(shí)戰(zhàn)讀書(shū)筆記 -第十三章 函數(shù)式的思考

    摘要:當(dāng)我們希望能界定這二者之間的區(qū)別時(shí),我們將第一種稱(chēng)為純粹的函數(shù)式編程,后者稱(chēng)為函數(shù)式編程。函數(shù)式編程我們的準(zhǔn)則是,被稱(chēng)為函數(shù)式的函數(shù)或方法都只能修改本地變量。另一種觀點(diǎn)支持引用透明的函數(shù)式編程,認(rèn)為方法不應(yīng)該有對(duì)外部可見(jiàn)的對(duì)象修改。 一、實(shí)現(xiàn)和維護(hù)系統(tǒng) 1.共享的可變數(shù)據(jù) 如果一個(gè)方法既不修改它內(nèi)嵌類(lèi)的狀態(tài),也不修改其他對(duì)象的狀態(tài),使用return返回所有的計(jì)算結(jié)果,那么我們稱(chēng)其為純粹...

    Donne 評(píng)論0 收藏0
  • Java8實(shí)戰(zhàn)》-第三章讀書(shū)筆記(Lambda表達(dá)式-01)

    摘要:之前,使用匿名類(lèi)給蘋(píng)果排序的代碼是的,這段代碼看上去并不是那么的清晰明了,使用表達(dá)式改進(jìn)后或者是不得不承認(rèn),代碼看起來(lái)跟清晰了。這是由泛型接口內(nèi)部實(shí)現(xiàn)方式造成的。 # Lambda表達(dá)式在《Java8實(shí)戰(zhàn)》中第三章主要講的是Lambda表達(dá)式,在上一章節(jié)的筆記中我們利用了行為參數(shù)化來(lái)因?qū)Σ粩嘧兓男枨?,最后我們也使用到了Lambda,通過(guò)表達(dá)式為我們簡(jiǎn)化了很多代碼從而極大地提高了我們的...

    longshengwang 評(píng)論0 收藏0
  • java 8 實(shí)戰(zhàn)讀書(shū)筆記 -第三章 Lambda表達(dá)式

    摘要:利用前面所述的方法,這個(gè)例子可以用方法引用改寫(xiě)成下面的樣子構(gòu)造函數(shù)引用對(duì)于一個(gè)現(xiàn)有構(gòu)造函數(shù),你可以利用它的名稱(chēng)和關(guān)鍵字來(lái)創(chuàng)建它的一個(gè)引用。 第三章 Lambda表達(dá)式 函數(shù)式接口 函數(shù)式接口就是只定義一個(gè)抽象方法的接口,哪怕有很多默認(rèn)方法,只要接口只定義了一個(gè)抽象方法,它就仍然是一個(gè)函數(shù)式接口。 常用函數(shù)式接口 showImg(https://segmentfault.com/img...

    whinc 評(píng)論0 收藏0
  • Java8實(shí)戰(zhàn)》-第三章讀書(shū)筆記(Lambda表達(dá)式-02)

    摘要:上下文比如,接受它傳遞的方法的參數(shù),或者接受它的值得局部變量中表達(dá)式需要類(lèi)型稱(chēng)為目標(biāo)類(lèi)型。但局部變量必須顯示的聲明,或?qū)嶋H上就算。換句話(huà)說(shuō),表達(dá)式只能捕獲指派給它們的局部變量一次。注捕獲實(shí)例變量可以被看作捕獲最終局部變量。 由于第三章的內(nèi)容比較多,而且為了讓大家更好的了解Lambda表達(dá)式的使用,也寫(xiě)了一些相關(guān)的實(shí)例,可以在Github或者碼云上拉取讀書(shū)筆記的代碼進(jìn)行參考。 類(lèi)型檢查、...

    iflove 評(píng)論0 收藏0
  • Java并發(fā)編程實(shí)戰(zhàn)讀書(shū)筆記-第1章 簡(jiǎn)介

    摘要:線(xiàn)程允許同一個(gè)進(jìn)程中同時(shí)存在多個(gè)程序控制流。線(xiàn)程也被稱(chēng)為輕量級(jí)進(jìn)程?,F(xiàn)代操作系統(tǒng)中,都是以線(xiàn)程為基本的調(diào)度單位,而不是進(jìn)程。 并發(fā)簡(jiǎn)史 在早期的計(jì)算機(jī)中不包含操作系統(tǒng),從頭至尾都只執(zhí)行一個(gè)程序,并且這個(gè)程序能訪(fǎng)問(wèn)計(jì)算機(jī)所有資源。操作系統(tǒng)的出現(xiàn)使得計(jì)算機(jī)每次能運(yùn)行多個(gè)程序,并且不同的程序都在單獨(dú)的進(jìn)程中運(yùn)行:操作系統(tǒng)為各個(gè)獨(dú)立執(zhí)行的進(jìn)程分配內(nèi)存、文件句柄、安全證書(shū)等。不同進(jìn)程之間通過(guò)一些...

    zhoutk 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<