發布對象
定義:使一個對象能夠被當前范圍之外的代碼所使用。
@Slf4j
@NotThreadSafe
public class UnsafePublish {
private String[] states = {"a", "b", "c"};
public String[] getStates() {
return states;
}
public static void main(String[] args) {
UnsafePublish unsafePublish = new UnsafePublish();
log.info("{}", Arrays.toString(unsafePublish.getStates()));
unsafePublish.getStates()[0] = "d";
log.info("{}", Arrays.toString(unsafePublish.getStates()));
}
}
此處UnsafePublish中使用public域來發布了一個方法來訪問私有域,但獲取狀態是線程不安全的,因為可能在獲取的同時會有其它線程對states做了修改。
對象逸出
一種錯誤的發布,當一個對象還沒有構造完成時,就使它被其它線程所見。
@Slf4j
@NotThreadSafe
@NotRecommend
public class Escape {
private int thisCanBeEscape = 0;
public Escape () {
new InnerClass();
}
private class InnerClass {
public InnerClass() {
//此處使用的this是Escape還沒有構造完成的對象
log.info("{}", Escape.this.thisCanBeEscape);
}
}
public static void main(String[] args) {
new Escape();
}
}
發布安全對象的方式
1. 懶漢模式的單例
/**
* 懶漢模式
* 單例實例在第一次使用時進行創建(這種實現是線程不安全的)
*/
@NotThreadSafe
public class SingletonExample1 {
// 私有構造函數
private SingletonExample1() {
}
// 單例對象
private static SingletonExample1 instance = null;
// 靜態的工廠方法
public static SingletonExample1 getInstance() {
if (instance == null) {
instance = new SingletonExample1();
}
return instance;
}
}
單例實例在第一次使用時進行創建(這種實現是線程不安全的),因為在多線程環境下可能會被創建出多個對象。這還不簡單麼,加個synchronized不就安全了
/**
* 懶漢模式
* 單例實例在第一次使用時進行創建
*/
@ThreadSafe
@NotRecommend
public class SingletonExample3 {
// 私有構造函數
private SingletonExample3() {
}
// 單例對象
private static SingletonExample3 instance = null;
// 靜態的工廠方法
public static synchronized SingletonExample3 getInstance() {
if (instance == null) {
instance = new SingletonExample3();
}
return instance;
}
}
此時我們將獲取對象方法變成同步方法,雖然保證了能獲取到唯一的對象實例,但是這種方式性能開銷太大,每一個線程都有一個試圖去獲取同步鎖的過程。而眾所周知,加鎖是很耗時的。能避免則避免。我們腦海中是不是閃現出了同步代碼塊
這一手段呢?也就是我們常說的雙重檢測單例模式
/**
* 懶漢模式 -》 雙重同步鎖單例模式
* 單例實例在第一次使用時進行創建
*/
@NotThreadSafe
public class SingletonExample4 {
// 私有構造函數
private SingletonExample4() {
}
// 1、memory = allocate() 分配對象的內存空間
// 2、ctorInstance() 初始化對象
// 3、instance = memory 設置instance指向剛分配的內存
// JVM和cpu優化,發生了指令重排
// 1、memory = allocate() 分配對象的內存空間
// 3、instance = memory 設置instance指向剛分配的內存
// 2、ctorInstance() 初始化對象
// 單例對象
//private static SingletonExample4 instance = null;
//單例對象 volatile + 雙重檢測機制 -> 禁止指令重排
private volatile static SingletonExample5 instance = null;
// 靜態的工廠方法
public static SingletonExample4 getInstance() {
if (instance == null) { // 雙重檢測機制 // B
synchronized (SingletonExample4.class) { // 同步鎖
if (instance == null) {
instance = new SingletonExample4(); // A - 3
}
}
}
return instance;
}
}
2. 餓漢模式單例
/**
* 餓漢模式
* 單例實例在類裝載時進行創建(線程安全的)
* 不足:
* 1.如果構造函數中有過多初始化處理,會造成性能的下降
* 2.如果沒有地方使用的話會造成資源的浪費
*
*/
@ThreadSafe
public class SingletonExample2 {
// 私有構造函數
private SingletonExample2() {
}
// 方法1:靜態域創建單例對象
private static SingletonExample2 instance = new SingletonExample2();
//方法2:靜態塊創建單例對象
/**
instance = null;
static{
instance = new SingletonExample2();
}
**/
// 靜態的工廠方法
public static SingletonExample2 getInstance() {
return instance;
}
}
3. 枚舉實現單例模式(最安全)
/**
* 枚舉模式:最安全
*/
@ThreadSafe
@Recommend
public class SingletonExample7 {
// 私有構造函數
private SingletonExample7() {
}
public static SingletonExample7 getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE;
private SingletonExample7 singleton;
// JVM保證這個方法絕對只調用一次
Singleton() {
singleton = new SingletonExample7();
}
public SingletonExample7 getInstance() {
return singleton;
}
}
}
安全發布對象的方式
- 在靜態初始化函數中初始化一個對象引用。
- 將對象的引用保存到volatile類型域或者AtomicReference對象中。
- 將對象的引用保存到某個正確構造對象的final類型域中。
- 將對象的引用保存到一個由鎖保護的域中。