1. 程式人生 > 實用技巧 >設計模式--單例模式

設計模式--單例模式

模式定義;保證一個類只有一個例項,並且提供一個全域性訪問點。

應用場景:重量級的物件,不需要多個例項,如執行緒池,資料庫連線池。就是被複用的。。

懶漢模式,餓漢模式,靜態內部類,反射攻擊例項,列舉,序列化

懶漢:延遲載入,

public class LazySingletonTest {
public static void main(String[] args) {
//單執行緒
// LazySingleton instance=LazySingleton.getInstance();
// LazySingleton instance1=LazySingleton.getInstance();
// System.out.println(instance==instance1);
//兩個執行緒
new Thread(()->{
LazySingleton instance=LazySingleton.getInstance();
System.out.println("instance = " + instance);
}).start();
new Thread(()->{
LazySingleton instance=LazySingleton.getInstance();
System.out.println("instance = " + instance);
}).start();
}
/**instance = danli.LazySingleton@437d73bc
instance = danli.LazySingleton@2ea7cb0b
這是加synchronize之前,加上之後,只會出現相同的結果*/
}
class LazySingleton {
private volatile static LazySingleton instance;
private LazySingleton(){

}
//提供全域性的訪問點
//當呼叫instance的時候,才會例項化
public synchronized static LazySingleton getInstance() {
if (instance == null){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}

instance=new LazySingleton();
}
return instance;
}
}
加鎖的作用,就i是為了保證只new 一個例項,但是加syn就是不管有沒有初始化都會加鎖,但是當instance!=null的時候
是不需要加鎖的,直接返回

if (instance == null){
//但是當兩個執行緒都沒有例項的時候,就會都進去例項化,
// 所以就是兩個執行緒都會例項化,所以要再判斷一下
synchronized (LazySingleton.class){
if (instance == null) {
instance=new LazySingleton();
}
}
}
return instance;
new 會在堆空間,開闢一塊空間。CPU會有即時編碼,有指令重拍。1、分配空間2、初始化3、引用賦值。2,3可以倒,但是不影響執行
volatile會保證Java命令順序執行,不會出現指令重排現象。
private volatile static LazySingleton instance;

延遲載入,在使用的時候,進行載入。要注意的情況:

1)多執行緒情況下,執行緒安全問題,可以syn解決一些

2)double check 加鎖進行優化

3)還要防止Java的即時編碼(JIT),CPU有可能出現的指令重排,導致使用到沒有初始化的例項,加volatile關鍵字。

餓漢模式---

public class HungrySingletonTest {
public static void main(String[] args) {
//new HungrySingleton();這樣不能保證單例
HungrySingleton instance=HungrySingleton.getInstance();
HungrySingleton instance1=HungrySingleton.getInstance();
System.out.println(instance==instance1);
}
}
//餓漢模式
// 主要是通過類載入機制,
/**1、載入二進位制資料到記憶體,生成對應的class資料結構
* 2、連線:a.驗證b.準備(給類的靜態成員變數賦預設值)c.解析
* 3、初始化:給類的靜態變數賦初值
* 在類載入的時候,是在類被呼叫的時候,*/
class HungrySingleton{

private static HungrySingleton instance=new HungrySingleton();
//私有建構函式,保證在外部不能例項化,只要是為了保證單例
private HungrySingleton() {
}
//提供公開的方法,以便訪問
public static HungrySingleton getInstance(){
return instance;
}
}
靜態內部類:
public class InnerClassSingletonTest {
public static void main(String[] args) {
// InnerClassSingleton instance=InnerClassSingleton.getInstance();
// InnerClassSingleton instance1=InnerClassSingleton.getInstance();
// System.out.println(instance==instance1);
//多執行緒
new Thread(()->{
InnerClassSingleton instance=InnerClassSingleton.getInstance();
System.out.println("instance = " + instance);
}).start();
new Thread(()->{
InnerClassSingleton instance=InnerClassSingleton.getInstance();
System.out.println("instance = " + instance);
}).start();
}
}
class InnerClassSingleton{
private static class InnerClassHolder {
/**在靜態內部類進行屬性的初始化,也算是懶載入,不使用的時候,不會例項化*/
private static InnerClassSingleton instance=new InnerClassSingleton();
}
/**提供私有的建構函式,就能保證別人不能再外部訪問*/
private InnerClassSingleton(){
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
原理:
1)本質上是利用類的載入機制來保證執行緒安全
2)只有在實際中使用的時候,才會觸發類的初始化,所以也是一種懶載入。
反射攻擊例項:
 public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//反射,,,靜態,還有懶漢模式
//這是拿到建構函式
Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
//這裡是拿到訪問權,就算是private修飾也可以取到
declaredConstructor.setAccessible(true);
//然後進行例項化
InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();

InnerClassSingleton instance = InnerClassSingleton.getInstance();
//這裡的結果是false。
System.out.println(instance==innerClassSingleton);
}
}
靜態和餓漢都可以被反射進行攻擊,所以要加防護,,,,懶漢不能保證
/**提供私有的建構函式,就能保證別人不能再外部訪問*/
private InnerClassSingleton(){
if (InnerClassHolder.instance!=null){
throw new RuntimeException("報錯,instance已經初始化,單例不允許多個例項");
}
}
保證其他地方不能用反射的方式進行多例的例項化。


列舉舉例:
1)天然不支援反射建立對應的例項,且有自己的反序列化機制
2)利用類載入機制保證執行緒安全
public enum  EnumSingleton {
INSTANCE;
public void print(){
System.out.println(this.hashCode());
}
}
class EnumTest{
public static void main(String[] args) {
EnumSingleton instance=EnumSingleton.INSTANCE;
EnumSingleton instance1=EnumSingleton.INSTANCE;
//結果為true
System.out.println(instance== instance1);
}
}

//可以設定引數,一個string型別,一個int型別
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);
//這裡進行訪問權設定,private修飾的都可以
declaredConstructor.setAccessible(true);
//為什麼設定這兩個引數,底層要繼承Enum抽象類(裡面的引數為name,int),
EnumSingleton instance = declaredConstructor.newInstance("INSTANCE", 0);
這個方法報錯,不支援enum型別的物件建立。所以用enum是可以進行單例的建立,不會被反射使用,也是執行緒安全的


序列化:
implement Serializable,,
當用流進行時,writeOutFile和ObijectInputStream得到的結果並不是一樣的。可以加版本號,然後就可以控制內容保持一致。