摘要:發(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 AtomicReferencelastNumber = 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 Setcars; 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 ThreadLocalconnectionHolder = 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 Setstooges = 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ì)象不可變的條件
安全發(fā)布對(duì)象創(chuàng)建以后其狀態(tài)就不能修改。
對(duì)象的所有域都是final類(lèi)型。
對(duì)象是正確創(chuàng)建的(在對(duì)象的創(chuàng)建期間,this引用沒(méi)有逸出)
任何線(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
摘要:當(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)其為純粹...
摘要:之前,使用匿名類(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)化了很多代碼從而極大地提高了我們的...
摘要:利用前面所述的方法,這個(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...
摘要:上下文比如,接受它傳遞的方法的參數(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)型檢查、...
摘要:線(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ò)一些...
閱讀 1103·2021-11-24 09:39
閱讀 1338·2021-11-18 13:18
閱讀 2511·2021-11-15 11:38
閱讀 1854·2021-09-26 09:47
閱讀 1658·2021-09-22 15:09
閱讀 1653·2021-09-03 10:29
閱讀 1544·2019-08-29 17:28
閱讀 2976·2019-08-29 16:30