摘要:發(fā)布的對(duì)象內(nèi)部狀態(tài)可能會(huì)破壞封裝性,使程序難以維持不變性條件。不變性線程安全性是不可變對(duì)象的固有屬性之一。可變對(duì)象必須通過安全方式來(lái)發(fā)布,并且必須是線程安全的或者有某個(gè)鎖保護(hù)起來(lái)。
線程的優(yōu)缺點(diǎn)
線程是系統(tǒng)調(diào)度的基本單位。線程如果使用得當(dāng),可以有效地降低程序的開發(fā)和維護(hù)等成本,同時(shí)提升復(fù)雜應(yīng)用程序的性能。多線程程序可以通過提高處理器資源的利用率來(lái)提升系統(tǒng)的吞吐率。與此同時(shí),在線程的使用開發(fā)過程中,也存在著諸多需要考慮的風(fēng)險(xiǎn)。
安全性:有合理的同步下,多線程的并發(fā)隨機(jī)執(zhí)行使線程安全性變得復(fù)雜,如++i。
活躍性:在多線程中,常因?yàn)槿鄙儋Y源而處于阻塞狀態(tài),當(dāng)某個(gè)操作不幸造成無(wú)限循環(huán),無(wú)法繼續(xù)執(zhí)行下去的時(shí)候,就會(huì)發(fā)生活躍性問題。
性能:線程總會(huì)帶來(lái)程序的運(yùn)行時(shí)開銷,多線程中,當(dāng)頻繁地出現(xiàn)上下文切換操作時(shí),將會(huì)帶來(lái)極大的開銷。
線程安全性線程安全的問題著重于解決如何對(duì)狀態(tài)訪問操作進(jìn)行管理,特別是對(duì)共享和可變的狀態(tài)。共享意味著可多個(gè)線程同時(shí)訪問;可變即在變量在其生命周期內(nèi)可以被改變;狀態(tài)就是由某個(gè)類中的成員變量(Field)。
一個(gè)無(wú)狀態(tài)的對(duì)象一定是線程安全的。因?yàn)樗鼪]有可被改變的東西。
public class LoginServlet implements Servlet { public void service(ServletRequest req, ServletResponse resp) { System.out.println("無(wú)狀態(tài)Servlet,安全的類,沒有字段可操作"); } }原子性
正如我們熟知的 ++i操作,它包含了三個(gè)獨(dú)立的“讀取-修改-寫入”操作序列,顯然是一個(gè)復(fù)合操作。為此java提供了原子變量來(lái)解決 ++i這類問題。當(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):
鎖是可以重入的。即子類的同步方法可以調(diào)用本類或父類的同步方法。
同一時(shí)刻,只有一個(gè)線程能夠訪問對(duì)象中的同步方法。
靜態(tà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è)線程修改對(duì)象狀態(tài)后,其他線程沒有看見修改后的狀態(tài),這種現(xiàn)象稱為“重排序”。
java內(nèi)存模型允許編譯器對(duì)操作順序進(jìn)行重排序,并將數(shù)據(jù)緩存在寄存器中。當(dāng)缺乏同步的情況下,每一個(gè)線程在獨(dú)立的緩存中使用緩存的數(shù)據(jù),并不知道主存中的數(shù)據(jù)已被更改。這就涉及到內(nèi)存可見性的問題。
可見性內(nèi)存可見性:同步的另一個(gè)重要的方面。我們不僅希望防止多個(gè)線程同時(shí)操作對(duì)象狀態(tài),而且還希望確保某一個(gè)線程修改了狀態(tài)后,能被其他線程看見變化。
volatile:使用 synchronized可以實(shí)現(xiàn)內(nèi)存可見,但java提供了一種稍弱的更輕量級(jí)得同步機(jī)制volatile變量。在訪問volatile變量時(shí)不會(huì)執(zhí)行加鎖操作,因此不會(huì)產(chǎn)生線程阻塞。即便如此還是不能過度使用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í),這種情況被稱為逸出。
// 正確發(fā)布:對(duì)象引用放置公有靜態(tài)域中,所有類和線程都可見 class CarFactory { public static Setcars; private CarFactory() { cars = new HashSet (); } // 私有,外部無(wú)法獲取 CarFactory的引用 public static Car void newInstance() { Car car = new Car("碰碰車"); 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)部值 }線程封閉
將可變的數(shù)據(jù)僅放置在單線程中操作的技術(shù),稱之為發(fā)線程封閉。
棧封閉:只能通過局部變量才能訪問對(duì)象。局部變量的固有屬性之一就是封裝在執(zhí)行線程中,它們位于執(zhí)行線程的棧中,其他線程無(wú)法訪問這個(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; // 基本類型沒有引用,對(duì)象創(chuàng)建和修改都沒有逸出本方法 }
ThreadLocal類:ThreadLocal能夠使線程中的某個(gè)值與保存值的對(duì)象關(guān)聯(lián)起來(lái)。ThreadLocal提供了 get、set等訪問接口的方法,這些方法為每一個(gè)使用該變量的線程都存有一份獨(dú)立的副本,因get總是返回由當(dāng)前執(zhí)行線程在調(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。不變性
線程安全性是不可變對(duì)象的固有屬性之一。不可變對(duì)象一定是線程安全的,它們的不變性條件是由構(gòu)造函數(shù)創(chuàng)建的,只要它們的狀態(tài)不可變。
// 在可變對(duì)象基礎(chǔ)上構(gòu)建不可變類 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); } }// 沒有提供可修改狀態(tài)的方式,盡管使用了Set可變集合,但被private final修飾著
對(duì)象不可變的條件
安全發(fā)布對(duì)象創(chuàng)建以后其狀態(tài)就不能修改。
對(duì)象的所有域都是final類型。
對(duì)象是正確創(chuàng)建的(在對(duì)象的創(chuàng)建期間,this引用沒有逸出)
任何線程都可以在不需要額外同步的情況下安全地訪問不可變對(duì)象,即使在發(fā)布這些對(duì)象時(shí)沒有使用同步。
// 安全的 Holder類 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ì)有線程在默認(rèn)初始化 與 構(gòu)造初始化中,獲取到 n 值為 0, 而不是 10;
要安全的發(fā)布一個(gè)對(duì)象,對(duì)象的引用以及對(duì)象的狀態(tài)必須同時(shí)對(duì)其他線程可見。一個(gè)正確構(gòu)造的對(duì)象可以通過以下方式安全發(fā)布:
在靜態(tài)初始化函數(shù)中初始化一個(gè)對(duì)象引用。
將對(duì)象的引用保存到 volatitle 類型的域或者 AtomicReferance 對(duì)象中。
將對(duì)象的引用保存到某個(gè)正確構(gòu)造對(duì)象的 final 類型域中。
將對(duì)象的引用保存到一個(gè)由鎖保護(hù)的域中。
在線程并發(fā)容器中的安全發(fā)布:
通過將一個(gè)鍵或者值放入 Hashtable、synchronizedMap 或者 ConsurrentMap中,可以安全地將它發(fā)布給任何從這些容器中訪問它的線程(無(wú)論是直接訪問還是通過迭代器訪問)。
通過將某個(gè)元素放入 Vector、 CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList 或 synchronizedSet中,可以將元素安全地發(fā)布到任何從這些容器中訪問該元素的線程。
通過將某個(gè)元素放入 BlockingQueue或者ConcurrentLinkedQueue中,可以將該元素安全地發(fā)布到任何從這些隊(duì)列中訪問該元素的線程。
通常,要發(fā)布一個(gè)靜態(tài)構(gòu)造的對(duì)象,最簡(jiǎn)單、安全的方式就是使用靜態(tài)的初始化器。如public static Holder holder = new Holder(10)。如果對(duì)象在發(fā)布后狀態(tài)不會(huì)被修改(則稱為事實(shí)不可變對(duì)象),那么在沒有額外的同步情況下,任何線程都可以安全地使用被安全發(fā)布的不可變對(duì)象。
對(duì)象的發(fā)布需求取決于它的可變性:
不可變對(duì)象可以通過任意機(jī)制來(lái)發(fā)布。
事實(shí)不可變對(duì)象必須通過安全方式來(lái)發(fā)布。
可變對(duì)象必須通過安全方式來(lái)發(fā)布,并且必須是線程安全的或者有某個(gè)鎖保護(hù)起來(lái)。
在并發(fā)程序中使用和共享對(duì)象時(shí)可采用的策略:
線程封閉。將對(duì)象封閉在線程中,如在方法中創(chuàng)建和修改局部對(duì)象。
只讀共享。
線程安全共享。對(duì)象內(nèi)部實(shí)現(xiàn)同步,使用公有接口來(lái)訪問。
保護(hù)對(duì)象。使用特定的鎖來(lái)保護(hù)對(duì)象。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/73777.html
摘要:當(dāng)我們希望能界定這二者之間的區(qū)別時(shí),我們將第一種稱為純粹的函數(shù)式編程,后者稱為函數(shù)式編程。函數(shù)式編程我們的準(zhǔn)則是,被稱為函數(shù)式的函數(shù)或方法都只能修改本地變量。另一種觀點(diǎn)支持引用透明的函數(shù)式編程,認(rèn)為方法不應(yīng)該有對(duì)外部可見的對(duì)象修改。 一、實(shí)現(xiàn)和維護(hù)系統(tǒng) 1.共享的可變數(shù)據(jù) 如果一個(gè)方法既不修改它內(nèi)嵌類的狀態(tài),也不修改其他對(duì)象的狀態(tài),使用return返回所有的計(jì)算結(jié)果,那么我們稱其為純粹...
摘要:之前,使用匿名類給蘋果排序的代碼是的,這段代碼看上去并不是那么的清晰明了,使用表達(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ū)Σ粩嘧兓男枨螅詈笪覀円彩褂玫搅薒ambda,通過表達(dá)式為我們簡(jiǎn)化了很多代碼從而極大地提高了我們的...
摘要:利用前面所述的方法,這個(gè)例子可以用方法引用改寫成下面的樣子構(gòu)造函數(shù)引用對(duì)于一個(gè)現(xiàn)有構(gòu)造函數(shù),你可以利用它的名稱和關(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á)式需要類型稱為目標(biāo)類型。但局部變量必須顯示的聲明,或?qū)嶋H上就算。換句話說,表達(dá)式只能捕獲指派給它們的局部變量一次。注捕獲實(shí)例變量可以被看作捕獲最終局部變量。 由于第三章的內(nèi)容比較多,而且為了讓大家更好的了解Lambda表達(dá)式的使用,也寫了一些相關(guān)的實(shí)例,可以在Github或者碼云上拉取讀書筆記的代碼進(jìn)行參考。 類型檢查、...
摘要:線程允許同一個(gè)進(jìn)程中同時(shí)存在多個(gè)程序控制流。線程也被稱為輕量級(jí)進(jìn)程。現(xiàn)代操作系統(tǒng)中,都是以線程為基本的調(diào)度單位,而不是進(jìn)程。 并發(fā)簡(jiǎn)史 在早期的計(jì)算機(jī)中不包含操作系統(tǒng),從頭至尾都只執(zhí)行一個(gè)程序,并且這個(gè)程序能訪問計(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)存、文件句柄、安全證書等。不同進(jìn)程之間通過一些...
閱讀 1074·2021-11-24 09:39
閱讀 1307·2021-11-18 13:18
閱讀 2425·2021-11-15 11:38
閱讀 1824·2021-09-26 09:47
閱讀 1625·2021-09-22 15:09
閱讀 1624·2021-09-03 10:29
閱讀 1510·2019-08-29 17:28
閱讀 2951·2019-08-29 16:30