單例簡介:
保證一個類隻有一個執行個體,并且提供一個通路該執行個體的全局通路點。
UML圖
常見應用場景:
1.全局計數器采用單例模式,不然不好同步。
2.應用程式的日志應用,共享日志檔案一直處于打開狀态,是以隻能有一個執行個體去操作,否則内容不好追加。
3.資料庫連接配接池的設計也用單例,否則浪費資源。
4.spring中的bean預設都是單例。
5.servlet是單例。
6.spring mvc/ structs1,控制器對象是單例。
7.項目中工具類,一般都用單例,沒必要浪費資源。
常見實作方式
1.餓漢模式(線程安全、調用效率高、不能延時加載)
/**
* 單例模式
* 餓漢模式
* */
public class SingletonDemo1 {
//私有靜态屬性 類初始化的時候就new了對象
private static /*final*/ SingletonDemo1 instance = new SingletonDemo1();
//私有化構造器
private SingletonDemo1(){}
//隻能從這裡取對象 方法沒有同步 調用效率高
public static /*synchronized*/ SingletonDemo1 getInstance(){
return instance;
}
}
static在類裝載的時候就初始化了(第一句 new),JVM保證隻會new一次SingletonDemo,是以getInstance方法不會有并發問題,得到的都是同一個對象,可以省略synchronized。
2.懶漢模式(線程安全、調用效率不高(getInstance方法用了synchronized同步方法)、可以延時加載)
/*
* 單例模式
* 懶漢模式
* 延遲加載
* */
public class SingletonDemo2 {
//這裡不用new 延遲加載 等到有人要用的時候調用getinstance方法時才new
private static SingletonDemo2 s;
//私有化構造器
private SingletonDemo2(){}
//需要加synchronized,同步化該方法,不然多線程的時候可能會new很多個對象
public static synchronized SingletonDemo2 getInstance(){
if(s == null){
s = new SingletonDemo2();
}
return s;
}
}
3.靜态内部類模式(線程安全、調用率高、可以延時加載)
/*
* 單例模式
* 靜态内部類模式
* 懶加載 線程安全 效率高
* */
public class SingletonDemo3 {
//靜态内部類 初始化SingletonDemo3的時候并不會立即初始化這個靜态内部類
private static class singletonClassInstance{
//在靜态内部類中定義單例對象
private static final SingletonDemo3 instance = new SingletonDemo3();
}
public static SingletonDemo3 getInstance(){
//調用的時候才初始化這個單例類 靜态内部類初始化的時候是天然線程安全的,jvm隻會初始化一次靜态内部類
return singletonClassInstance.instance;
}
//私有化構造器
private SingletonDemo3(){}
4.懶漢模式 -》 雙重同步鎖單例模式
public class SingletonExample5 {
// 私有構造函數
private SingletonExample5() {
}
// 1、memory = allocate() 配置設定對象的記憶體空間
// 2、ctorInstance() 初始化對象
// 3、instance = memory 設定instance指向剛配置設定的記憶體
// 單例對象 volatile + 雙重檢測機制 -> 禁止指令重排
private volatile static SingletonExample5 instance = null;
// 靜态的工廠方法
public static SingletonExample5 getInstance() {
if (instance == null) { // 雙重檢測機制 // B
synchronized (SingletonExample5.class) { // 同步鎖
if (instance == null) {
instance = new SingletonExample5(); // A - 3
}
}
}
return instance;
}
}
5.枚舉單例(線程安全、調用率高、不能延時加載、不會被反射反序列化生成多個執行個體)
/*
* 單例模式
*
* 枚舉模式
*
* 無延遲加載
* 避免通過反射和反序列化的漏洞建立新的對象
*/
//枚舉類天然單例
public enum SingletonDemo4 {
//定義一個枚舉元素,它就代表了SingletonDemo4的一個執行個體
INSTANCE;
//單例可以有自己的操作
public void singletonOperation(){
//功能處理
}
}
避免通過反射和反序列化的漏洞建立新的對象
枚舉類天然單例
無延遲加載
通過反射漏洞建立多個對象
import java.lang.reflect.Constructor;
/**
* @author liyijie
* @date 2018年8月13日下午2:41:23
* @email [email protected]
* @remark 通過反射、反序列化破解單例(枚舉除外)
* @version
*/
public class client2 {
public static void main(String[] args) throws Exception {
SingletonDemo3 s1 = SingletonDemo3.getInstance();
SingletonDemo3 s2 = SingletonDemo3.getInstance();
System.out.println(s1);
System.out.println(s2);
//得到class
Class<SingletonDemo6> class6 = (Class<SingletonDemo6>)Class.forName("com.sid.singleton.SingletonDemo6");
//得到構造器
Constructor<SingletonDemo6> c =class6.getDeclaredConstructor(null);
//跳過權限檢查,不然不能通路私有方法
c.setAccessible(true);
SingletonDemo6 s3 = c.newInstance();
SingletonDemo6 s4 = c.newInstance();
System.out.println(s3);
System.out.println(s4);
}
}
防止反射漏洞建立多個對象需要修改單例類的私有化構造器,第二次建立對象時報錯
import java.io.ObjectStreamException;
import java.io.Serializable;
/*
* 防止反射、反序列化破解單例
* implements Serializable是用來測試反序列化漏洞的,實際寫單例不需要實作這個接口
* */
public class SingletonDemo6 implements Serializable{
//這裡不用new 延遲加載 等到有人要用的時候調用getinstance方法時才new
private static SingletonDemo6 s;
//私有化構造器
private SingletonDemo6(){
//第二次建立這個對象報錯、防止利用反射漏洞
if(s!=null){
throw new RuntimeException();
}
}
//需要加synchronized,同步化該方法,不然多線程的時候可能會new很多個對象
public static synchronized SingletonDemo6 getInstance(){
if(s == null){
s = new SingletonDemo6();
}
return s;
}
//反序列化拿對象時傳回這個s對象,防止利用反序列化漏洞建立多個執行個體
private Object readResolve() throws ObjectStreamException{
return s;
}
}