摘要:對象的共享上一章介紹了如何通過同步來避免多個(gè)線程在同一時(shí)刻訪問相同的數(shù)據(jù),而本章將介紹如何共享和發(fā)布對象,從而使它們能夠安全地由多個(gè)線程同時(shí)訪問。為了確保多個(gè)線程的之間對內(nèi)存寫入操作的可見性,必須使用同步機(jī)制。
對象的共享
上一章介紹了如何通過同步來避免多個(gè)線程在同一時(shí)刻訪問相同的數(shù)據(jù),而本章將介紹如何共享和發(fā)布對象,從而使它們能夠安全地由多個(gè)線程同時(shí)訪問。
列同步代碼塊和同步方法可以確保以原子的方式執(zhí)行操作,但一種常見的誤解是,認(rèn)為關(guān)鍵字synchronized只能用于實(shí)現(xiàn)原子性或者確定“臨界區(qū)”。同步還有另一重要的方面;內(nèi)存可見性。
我們不僅希望防止某個(gè)線程正在使用對象狀態(tài)而另一個(gè)線程在同時(shí)修改該狀態(tài),而希望確保當(dāng)一個(gè)線程修改了對象狀態(tài)之后,其他線程能夠看到發(fā)生的狀態(tài)變化。
如果沒有同步,那么這種情況就無法實(shí)現(xiàn)。
3.1 可見性</>復(fù)制代碼
通常,我們無法確保執(zhí)行讀操作的線程能適時(shí)地看到其他線程寫入的值。有時(shí)甚至是不可能的事情。為了確保多個(gè)線程的之間對內(nèi)存寫入操作的可見性,必須使用同步機(jī)制。
</>復(fù)制代碼
/**
* NoVisibility
*
* Sharing variables without synchronization
*
* @author Brian Goetz and Tim Peierls
*/
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;
}
}
主線程啟動讀線程,然后將number設(shè)為42,并將ready設(shè)為true。讀線程一直循環(huán)知道發(fā)現(xiàn)ready的值為true,然后輸出number的值。雖然NoVisibility看起來會輸出42,但事實(shí)上很可能輸出0,或者根本無法終止。這是因?yàn)樵诖a中沒有使用足夠的同步機(jī)制,因此無法保證主線程寫入的ready值和number值對于讀線程來說是可見的。
3.1.1 失效數(shù)據(jù)查看變臉時(shí),可能會得到一個(gè)已經(jīng)失效的值。
下方代碼中,如果某線程調(diào)用了set,那么另一個(gè)正在調(diào)用get的線程可能會看到更新后的value值,也可能看不到。
</>復(fù)制代碼
/**
* MutableInteger
*
* Non-thread-safe mutable integer holder
*
* @author Brian Goetz and Tim Peierls
*/
@NotThreadSafe
public class MutableInteger {
private int value;
public int get() {
return value;
}
public void set(int value) {
this.value = value;
}
}
下面的SynchronizedInteger 中,通過對get和set等方法進(jìn)行同步,可以使 MutableInteger 成為一個(gè)線程安全的類。
</>復(fù)制代碼
/**
* SynchronizedInteger
*
* Thread-safe mutable integer holder
*
* @author Brian Goetz and Tim Peierls
*/
@ThreadSafe
public class SynchronizedInteger {
@GuardedBy("this") private int value;
public synchronized int get() {
return value;
}
public synchronized void set(int value) {
this.value = value;
}
}
3.1.2 非原子的64位操作
</>復(fù)制代碼
- 最低安全性:當(dāng)線程在沒有同步的情況下讀取變量時(shí),可能會得到一個(gè)失效值,但至少這個(gè)值是由之前的某個(gè)線程設(shè)置的值,而不是一個(gè)隨機(jī)值。這種安全性保證也被稱之為最低安全性。
- 最低安全性適用于絕大多數(shù)變量,但是存在一個(gè)例外:非volatile類型的64位數(shù)值變量(double和long),JVM允許將64位的度操作或?qū)懖俜纸鉃?兩個(gè)32位操作。
3.1.3 加鎖和可見性
3.1.4 Volatile變量</>復(fù)制代碼
加鎖的含義不僅僅局限于互斥行為,還包括內(nèi)存可見性。為了確保所有的線程都能看到共享變量的最新值,所有執(zhí)行讀操作或者寫操作的線程都必須在同一個(gè)鎖上同步。
Java語言提供了一種稍弱的同步機(jī)制,即volatile變量,用來確保將變量的更新操作通知到其它線程。
當(dāng)把變量聲明為volatile類型后,編譯器于運(yùn)行時(shí)都會注意到這個(gè)變量是共享的,因此不會講該變量上的操作與其他內(nèi)存操作一起重排序。volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型的變量時(shí)總會返回最新寫入的值。
volatile不會加鎖,所以非常輕量級。
volatile 不能使用在 驗(yàn)證正確性時(shí)需要對可見性進(jìn)行復(fù)雜的判斷,那么就不需要使用volatile變量。
volatile 的正確使用方式 ,1,確保它們自身狀態(tài)的可見性。2,確保它們所引用對象狀態(tài)的可見性,3,以及標(biāo)識一些重要的程序生命周期事件的發(fā)生。
下面給出volatile 的典型用法。
</>復(fù)制代碼
/**
* CountingSheep
*
* Counting sheep
*
* @author Brian Goetz and Tim Peierls
*/
public class CountingSheep {
volatile boolean asleep;
void tryToSleep() {
while (!asleep)
countSomeSheep();
}
void countSomeSheep() {
// One, two, three...
}
}
當(dāng)且僅當(dāng)滿足以下所有條件是,才應(yīng)該使用volatile變量:
對變量的寫入操作不依賴變量的當(dāng)前值,或者你能確保只有單個(gè)線程更新變量的值。
該變量不會與其他狀態(tài)變量一起納入不變性條件中。
在訪問變量時(shí)不需要加鎖。
3.2 發(fā)布與逸出“發(fā)布”一個(gè)對象的意思是指,是對象能夠在當(dāng)前作用域之外的代碼中使用。
“逸出”當(dāng)某個(gè)不應(yīng)該發(fā)布的對象被發(fā)布時(shí),被稱之為逸出。
發(fā)布一個(gè)對象
</>復(fù)制代碼
/**
* Secrets
*
* Publishing an object
*
* @author Brian Goetz and Tim Peierls
*/
class Secrets {
public static Set knownSecrets;
public void initialize() {
knownSecrets = new HashSet();
}
}
class Secret {
}
使內(nèi)部的可變狀態(tài)逸出(不要這么做 )
</>復(fù)制代碼
/**
* UnsafeStates
*
* Allowing internal mutable state to escape
*
* @author Brian Goetz and Tim Peierls
*/
class UnsafeStates {
private String[] states = new String[]{
"AK", "AL" /*...*/
};
public String[] getStates() {
return states;
}
}
最后一種發(fā)布的方式, 就是發(fā)布一個(gè)內(nèi)部的類實(shí)例。
</>復(fù)制代碼
/**
隱式的使this引用逸出(不要這樣做)
* ThisEscape
*
* Implicitly allowing the this reference to escape
*
* @author Brian Goetz and Tim Peierls
*/
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 {
}
}
使用工廠方法來防止this引用在構(gòu)造函數(shù)中逸出
</>復(fù)制代碼
/**
* SafeListener
*
* Using a factory method to prevent the this reference from escaping during construction
*
* @author Brian Goetz and Tim Peierls
*/
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 {
}
}
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/74385.html
摘要:對象的組合介紹一些組合模式,這些模式能夠使一個(gè)類更容易成為線程安全的,并且維護(hù)這些類時(shí)不會無意破壞類的安全性保證。狀態(tài)變量的所有者將決定采用何種加鎖協(xié)議來維持變量狀態(tài)的完整性。所有權(quán)意味著控制權(quán)。 對象的組合 介紹一些組合模式,這些模式能夠使一個(gè)類更容易成為線程安全的,并且維護(hù)這些類時(shí)不會無意破壞類的安全性保證。 設(shè)計(jì)線程安全的類 在設(shè)計(jì)線程安全類的過程中,需要包含以下三個(gè)基本要素: ...
摘要:線程封閉當(dāng)訪問共享的可變數(shù)據(jù)時(shí),通常需要使用同步。如果僅在單線程內(nèi)訪問數(shù)據(jù),就不要同步。這種技術(shù)成為線程封閉。棧封閉棧封閉是線程封閉的一種特例,在棧封閉中,只能通過局部變量才能訪問對象。,對象是正確創(chuàng)建的。 線程封閉 當(dāng)訪問共享的可變數(shù)據(jù)時(shí),通常需要使用同步。一種避免使用同步的方式就是不共享數(shù)據(jù)。如果僅在單線程內(nèi)訪問數(shù)據(jù),就不要同步。這種技術(shù)成為線程封閉(Thread Confine...
摘要:無狀態(tài)的是線程安全的,當(dāng)無狀態(tài)變?yōu)橛袪顟B(tài)時(shí)就是不安全的破壞了線程的安全性,非原子性操作競態(tài)條件在并發(fā)編程中,由于不恰當(dāng)?shù)膱?zhí)行時(shí)序而出現(xiàn)的不正確結(jié)果是一種非常重要的情況,被稱之為競態(tài)條件。重入意味著獲取鎖的操作的粒度是線程,而不是調(diào)用。 這本書的內(nèi)容是什么? 本書提供了各種實(shí)用的設(shè)計(jì)規(guī)則,用于幫助開發(fā)人員創(chuàng)建安全的和高性能的并發(fā)類。 什么類是線程安全的? 當(dāng)多個(gè)線程訪問某...
摘要:上集算法實(shí)現(xiàn)的優(yōu)點(diǎn)當(dāng)一個(gè)線程執(zhí)行任務(wù)失敗不影響其他線程的進(jìn)行最大限度的利用資源能提高程序的伸縮性伸縮性不修改任何代碼升級硬件就能帶來性能上的提高升級硬件帶來的性能提高明顯就是伸縮性良好的缺點(diǎn)代碼復(fù)雜影響閱讀性剛開始看的時(shí)候沒有正確的思路理解 ConcurrentLinkedQueue(上集) 算法實(shí)現(xiàn) CAS CAS的優(yōu)點(diǎn) 當(dāng)一個(gè)線程執(zhí)行任務(wù)失敗不影響其他線程的進(jìn)行 最大限度的利用...
閱讀 2643·2021-11-22 15:24
閱讀 1377·2021-11-17 09:38
閱讀 2753·2021-10-09 09:57
閱讀 1202·2019-08-30 15:44
閱讀 2444·2019-08-30 14:00
閱讀 3546·2019-08-30 11:26
閱讀 2938·2019-08-29 16:28
閱讀 753·2019-08-29 13:56
极致性价比!云服务器续费无忧!
Tesla A100/A800、Tesla V100S等多种GPU云主机特惠2折起,不限台数,续费同价。
NVIDIA RTX 40系,高性价比推理显卡,满足AI应用场景需要。
乌兰察布+上海青浦,满足东推西训AI场景需要