逸出的方式
上邊關(guān)于逸出的概念講述的很是模糊值朋,下面列舉幾個(gè)逸出的示例。
- 通過(guò)靜態(tài)變量引用逸出
public static Set<Secret> knownSecrets;
public void initialize() {
knowsSecrets = new HashSet<Secret>();
}
上邊代碼示例中巩搏,調(diào)用initialize方法昨登,發(fā)布了knowSecrets對(duì)象。當(dāng)你向knowSecrets中添加一個(gè)Secret時(shí)贯底,會(huì)同時(shí)將Secret對(duì)象發(fā)布出去篙骡,原因是可以通過(guò)遍歷knowSecrets獲取到Secret對(duì)象的引用,然后進(jìn)行修改丈甸。
- 通過(guò)非靜態(tài)(私有)方法
class UnsafeStates {
private String[] states = new String[]{"AK", "AL"};
public String[] getStates() {
return states;
}
}
以這種方式發(fā)布的states會(huì)出問(wèn)題糯俗,任何一個(gè)調(diào)用者都能修改它的內(nèi)容。數(shù)組states已經(jīng)逸出了它所屬的范圍睦擂,這個(gè)本應(yīng)該私有的數(shù)據(jù)得湘,事實(shí)上已經(jīng)變成共有的了。
- this逸出
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
}
在上邊代碼中顿仇,當(dāng)我們實(shí)例化ThisEscape對(duì)象時(shí)淘正,會(huì)調(diào)用source的registerListener方法時(shí),便啟動(dòng)了一個(gè)線程臼闻,而且這個(gè)線程持有了ThisEscape對(duì)象(調(diào)用了對(duì)象的doSomething方法)鸿吆,但此時(shí)ThisEscape對(duì)象卻沒(méi)有實(shí)例化完成(還沒(méi)有返回一個(gè)引用),所以我們說(shuō)述呐,此時(shí)造成了一個(gè)this引用逸出惩淳,即還沒(méi)有完成的實(shí)例化ThisEscape對(duì)象的動(dòng)作,卻已經(jīng)暴露了對(duì)象的引用乓搬,使其他線程可以訪問(wèn)還沒(méi)有構(gòu)造好的對(duì)象思犁,可能會(huì)造成意料不到的問(wèn)題。
通過(guò)上述示例进肯,個(gè)人理解激蹲,對(duì)逸出的概念應(yīng)該定義為:
一個(gè)對(duì)象,超出了它原本的作用域江掩,而可以被其它對(duì)象進(jìn)行修改学辱,而這種修改及修改的結(jié)果是無(wú)法預(yù)測(cè)的。換句話說(shuō):一個(gè)對(duì)象發(fā)布后环形,它的狀態(tài)應(yīng)該是穩(wěn)定的策泣,修改是可被檢測(cè)到的。如果在其它線程修改(或做其它操作)一個(gè)對(duì)象后導(dǎo)致對(duì)象的狀態(tài)未知斟赚,就可以說(shuō)這個(gè)對(duì)象逸出了着降。
總之差油,一個(gè)對(duì)象逸出后拗军,不論其它線程或?qū)ο笫欠袷褂眠@個(gè)逸出的對(duì)象都不重要任洞,重要的是,被誤用及被誤用后的未知結(jié)果的風(fēng)險(xiǎn)總是存在的发侵。
PS 書(shū)中給出了避免this逸出的方法:
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;
}
}
在這個(gè)構(gòu)造中交掏,我們看到的最大的一個(gè)區(qū)別就是:當(dāng)構(gòu)造好了SafeListener對(duì)象之后,我們才啟動(dòng)了監(jiān)聽(tīng)線程刃鳄,也就確保了SafeListener對(duì)象是構(gòu)造完成之后在使用的SafeListener對(duì)象盅弛。
對(duì)于這樣的技術(shù),書(shū)里面也有這樣的注釋?zhuān)?br> 具體來(lái)說(shuō)叔锐,只有當(dāng)構(gòu)造函數(shù)返回時(shí)挪鹏,this引用才應(yīng)該從線程中逸出。構(gòu)造函數(shù)可以將this引用保存到某個(gè)地方愉烙,只要其他線程不會(huì)在構(gòu)造函數(shù)完成之前使用它讨盒。
安全發(fā)布對(duì)象
- 在靜態(tài)初始化函數(shù)中初始化一個(gè)對(duì)象引用
- 將對(duì)象的應(yīng)用保存到volatile類(lèi)型的域或者AtomicReferance對(duì)象中
- 將對(duì)象的引用保存到某個(gè)正確構(gòu)造對(duì)象的final類(lèi)型域中
- 將對(duì)象的引用保存到一個(gè)由鎖保護(hù)的域中。
/**
* 懶漢模式(線程不安全)
* 單例實(shí)例在第一次使用時(shí)進(jìn)行創(chuàng)建
*/
@NotThreadSafe
public class SingletonExample1 {
// 私有構(gòu)造函數(shù)
private SingletonExample1() {
}
// 單例對(duì)象
private static SingletonExample1 instance = null;
// 靜態(tài)的工廠方法
public static SingletonExample1 getInstance() {
// 這里同時(shí)有兩個(gè)線程進(jìn)入就可能同時(shí)初始化兩個(gè)對(duì)象
if (instance == null) {
instance = new SingletonExample1();
}
return instance;
}
}
懶漢模式本身是線程不安全的步责,如果想要實(shí)現(xiàn)線程安全可以通過(guò)synchronized關(guān)鍵字實(shí)現(xiàn):
/**
* 懶漢模式
* 單例實(shí)例在第一次使用時(shí)進(jìn)行創(chuàng)建
*/
@ThreadSafe
@NotRecommend
public class SingletonExample3 {
// 私有構(gòu)造函數(shù)
private SingletonExample3() {
}
// 單例對(duì)象
private static SingletonExample3 instance = null;
// 靜態(tài)的工廠方法
public static synchronized SingletonExample3 getInstance() {
if (instance == null) {
instance = new SingletonExample3();
}
return instance;
}
}
但此中方式不推薦使用返顺,應(yīng)該它通過(guò)同一時(shí)間內(nèi)只允許一個(gè)線程來(lái)訪問(wèn)的方式實(shí)現(xiàn)線程安全,但是卻帶來(lái)了性能上面的開(kāi)銷(xiāo)蔓肯。
我們可以通過(guò)以下方式來(lái)實(shí)現(xiàn)線程安全:
懶漢模式 -》 volatile + 雙重同步鎖單例模式
/**
* 懶漢模式 -》 雙重同步鎖單例模式
* 單例實(shí)例在第一次使用時(shí)進(jìn)行創(chuàng)建
*/
@ThreadSafe
public class SingletonExample4 {
// 私有構(gòu)造函數(shù)
private SingletonExample4() {
}
// 1遂鹊、memory = allocate() 分配對(duì)象的內(nèi)存空間
// 2、ctorInstance() 初始化對(duì)象
// 3蔗包、instance = memory 設(shè)置instance指向剛分配的內(nèi)存
// JVM和cpu優(yōu)化秉扑,發(fā)生了指令重排(多線程 )
// 1、memory = allocate() 分配對(duì)象的內(nèi)存空間
// 3调限、instance = memory 設(shè)置instance指向剛分配的內(nèi)存
// 2邻储、ctorInstance() 初始化對(duì)象
// 單例對(duì)象 volatile + 雙重檢測(cè)機(jī)制 -> 禁止指令重排
private volatile static SingletonExample4 instance = null;
public static SingletonExample4 getInstance() {
if (instance == null) { // 雙重檢測(cè)機(jī)制 // B
synchronized (SingletonExample4.class) { // 同步鎖
if (instance == null) {
instance = new SingletonExample4(); // A - 3
}
}
}
return instance;
}
}
/**
* 餓漢模式
* 單例實(shí)例在類(lèi)裝載時(shí)進(jìn)行創(chuàng)建
*/
@ThreadSafe
public class SingletonExample2 {
// 私有構(gòu)造函數(shù)
private SingletonExample2() {
}
// 單例對(duì)象
private static SingletonExample2 instance = new SingletonExample2();
// 靜態(tài)的工廠方法
public static SingletonExample2 getInstance() {
return instance;
}
}
餓漢模式不會(huì)有線程問(wèn)題,但是在類(lèi)加載時(shí)實(shí)例化對(duì)象旧噪。使用時(shí)要考慮兩點(diǎn):
- 私有構(gòu)造函數(shù)在使用時(shí)沒(méi)有過(guò)多的邏輯處理(銷(xiāo)毀性能吨娜,慢)
- 這個(gè)對(duì)象一定會(huì)被使用(浪費(fèi)資源)
在靜態(tài)代碼塊中實(shí)例化一個(gè)對(duì)象:
/**
* 餓漢模式
* 單例實(shí)例在類(lèi)裝載時(shí)進(jìn)行創(chuàng)建
*/
@ThreadSafe
public class SingletonExample6 {
// 私有構(gòu)造函數(shù)
private SingletonExample6() {
}
// 單例對(duì)象
private static SingletonExample6 instance = null;
static {
instance = new SingletonExample6();
}
// 靜態(tài)的工廠方法
public static SingletonExample6 getInstance() {
return instance;
}
public static void main(String[] args) {
System.out.println(getInstance().hashCode());
System.out.println(getInstance().hashCode());
}
}
枚舉模式:
/**
* 枚舉模式:最安全
*/
@ThreadSafe
@Recommend
public class SingletonExample7 {
// 私有構(gòu)造函數(shù)
private SingletonExample7() {
}
public static SingletonExample7 getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE;
private SingletonExample7 singleton;
// JVM保證這個(gè)方法絕對(duì)只調(diào)用一次
Singleton() {
singleton = new SingletonExample7();
}
public SingletonExample7 getInstance() {
return singleton;
}
}
}