線程共享對象是引起線程安全的原因痒谴,所以怎么發(fā)布對象至關(guān)重要悴侵。
package com.accat.concurrency.example.publish;
import com.accat.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
@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()));
}
}
這里通過
getter
獲取對象后是整,任何其他對象或線程都可以直接操作states
肖揣,所以是不安全的。
package com.accat.concurrency.example.publish;
import com.accat.concurrency.annoations.NotRecommend;
import com.accat.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@NotThreadSafe
@NotRecommend
public class Escape {
private int thisCanBeEscape = 0;
public Escape () {
new InnerClass();
}
private class InnerClass {
public InnerClass() {
log.info("{}", Escape.this.thisCanBeEscape);
}
}
public static void main(String[] args) {
new Escape();
}
}
調(diào)用
Escape()
構(gòu)造Escape
類贰盗,而構(gòu)造過程包含一個內(nèi)部類InnerClass
许饿,其構(gòu)造函數(shù)使用Escape.this.thisCanBeEscape
,這個過程中有可能Escape還沒有構(gòu)造完成舵盈,如果在多線程程序中有可能線程訪問快于類本身的構(gòu)造陋率,從而引起線程安全問題。
package com.accat.concurrency.example.singleton;
import com.accat.concurrency.annoations.NotThreadSafe;
/**
* 懶漢模式
* 單例實例在第一次使用時進行創(chuàng)建
*/
@NotThreadSafe
public class SingletonExample1 {
// 私有構(gòu)造函數(shù)
private SingletonExample1() {
}
// 單例對象
private static SingletonExample1 instance = null;
// 靜態(tài)的工廠方法
public static SingletonExample1 getInstance() {
if (instance == null) {
instance = new SingletonExample1();
}
return instance;
}
}
懶漢模式
線程不安全秽晚,因為多線程的情況下瓦糟,有可能多個線程同時調(diào)用getInstance()
, 從而導(dǎo)致進行多次初始化操作赴蝇。
package com.accat.concurrency.example.singleton;
import com.accat.concurrency.annoations.ThreadSafe;
/**
* 餓漢模式
* 單例實例在類裝載時進行創(chuàng)建
*/
@ThreadSafe
public class SingletonExample2 {
// 私有構(gòu)造函數(shù)
private SingletonExample2() {
}
// 單例對象
private static SingletonExample2 instance = new SingletonExample2();
// 靜態(tài)的工廠方法
public static SingletonExample2 getInstance() {
return instance;
}
}
餓漢模式
解決了懶漢模式的線程安全問題菩浙,它的不足在于如果初始化的操作過多,有可能導(dǎo)致啟動時間更長句伶。
package com.accat.concurrency.example.singleton;
import com.accat.concurrency.annoations.NotThreadSafe;
/**
* 懶漢模式 -》 雙重同步鎖單例模式
* 單例實例在第一次使用時進行創(chuàng)建
*/
@NotThreadSafe
public class SingletonExample4 {
// 私有構(gòu)造函數(shù)
private SingletonExample4() {
}
// 1劲蜻、memory = allocate() 分配對象的內(nèi)存空間
// 2、ctorInstance() 初始化對象
// 3考余、instance = memory 設(shè)置instance指向剛分配的內(nèi)存
// JVM和cpu優(yōu)化先嬉,發(fā)生了指令重排
// 1、memory = allocate() 分配對象的內(nèi)存空間
// 3楚堤、instance = memory 設(shè)置instance指向剛分配的內(nèi)存
// 2疫蔓、ctorInstance() 初始化對象
// 單例對象
private static SingletonExample4 instance = null;
// 靜態(tài)的工廠方法
public static SingletonExample4 getInstance() {
if (instance == null) { // 雙重檢測機制 // B
synchronized (SingletonExample4.class) { // 同步鎖
if (instance == null) {
instance = new SingletonExample4(); // A - 3
}
}
}
return instance;
}
}
雙重檢測機制
從懶漢模式改造過來含懊,規(guī)避線程不安全的問題,但是這里存在另外一個問題衅胀。
instance = new SingletonExample4()
指令有可能被JVM重排岔乔。
然后導(dǎo)致多個線程執(zhí)行多次instance = new SingletonExample4()
。
// 單例對象 volatile + 雙重檢測機制 -> 禁止指令重排
private volatile static SingletonExample5 instance = null;
解決的方法是加上
volatile
關(guān)鍵字滚躯,禁止指令重排(Volatile適用場景二)
推薦寫法
package com.accat.concurrency.example.singleton;
import com.accat.concurrency.annoations.Recommend;
import com.accat.concurrency.annoations.ThreadSafe;
/**
* 枚舉模式:最安全
*/
@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保證這個方法絕對只調(diào)用一次
Singleton() {
singleton = new SingletonExample7();
}
public SingletonExample7 getInstance() {
return singleton;
}
}
}
兼并懶漢模式和餓漢模式的優(yōu)點雏门,只有在對象要被使用時才創(chuàng)建,又保證了線程安全性哀九。