單例模式(Singleton Pattern)是指確保一個類在任何狀況下都絕對只有一個實例,並提供一個全局訪問點 。緩存
特色:隱藏其全部的構造方法安全
屬於建立型模式性能優化
適用場景: 確保任何狀況下都絕對只有一個實例網絡
單例類:ServletContext、ServletConfig、ApplicationContext、DBPool多線程
單例模式的常見寫法:併發
1.餓漢式單例: 在單例類首次加載時就建立實例。ide
//優勢: 1 沒有加任何的鎖、執行效率比較高,在用戶體驗上來講,比懶漢式更好 2 絕對線程安全,在線程還沒出現之前就是實例化了,不可能存在訪問安全問題 //缺點:類加載的時候就初始化,無論用仍是不用,都佔着空間,浪費了內存 public class HungrySingleton { // 加final的緣由,若是不加能夠經過反射等方法進行覆蓋, 就不是單例對象了 private static final HungrySingleton hungrySingleton = new HungrySingleton(); private HungrySingleton(){} public static HungrySingleton getInstance(){ return hungrySingleton; } }
//餓漢式靜態塊單例 public class HungryStaticSingleton { private static final HungryStaticSingleton hungrySingleton; static { hungrySingleton = new HungryStaticSingleton(); } private HungryStaticSingleton(){} public static HungryStaticSingleton getInstance(){ return hungrySingleton; } }
2. 懶漢式單例: 被外部類調用時才建立實例性能
public class LazySimpleSingleton { private LazySimpleSingleton(){} //靜態塊,公共內存區域 private static LazySimpleSingleton lazy = null; public static LazySimpleSingleton getInstance(){ // 多線程狀況下會出現線程安全問題 if(lazy == null){ lazy = new LazySimpleSingleton(); } return lazy; } }
public class LazySimpleSingleton { private LazySimpleSingleton(){} //靜態塊,公共內存區域 private static LazySimpleSingleton lazy = null; // JDK 1.6以後對synchronized性能優化了很多, 不可避免地仍是存在必定的性能問題 // 在方法上而且用static修飾, 可能會形成死鎖 public synchronized static LazySimpleSingleton getInstance(){ if(lazy == null){ lazy = new LazySimpleSingleton(); } return lazy; } }
public class ExectorThread implements Runnable{ @Override public void run() { LazySimpleSingleton singleton = LazySimpleSingleton.getInstance(); System.out.println(Thread.currentThread().getName() + ":" + singleton); } }
public class LazySimpleSingletonTest { public static void main(String[] args) { Thread t1 = new Thread(new ExectorThread()); Thread t2 = new Thread(new ExectorThread()); t1.start(); t2.start(); System.out.println("End"); } }
public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton lazy = null; private LazyDoubleCheckSingleton(){} public static LazyDoubleCheckSingleton getInstance(){ if(lazy == null){ synchronized (LazyDoubleCheckSingleton.class){ if(lazy == null){ lazy = new LazyDoubleCheckSingleton(); // cpu執行時會轉化成JVM指令執行 // 指令重排序(線程執行搶時間, 可能先執行2再執行3, 也可能先執行3再執行2),在多線程或線程所得狀況下可能會發生 // 解決方法加 volatile 關鍵字 //1.分配內存給這個對象 //2.初始化對象 //3.設置lazy指向剛分配的內存地址 //4.初次訪問對象 } } } return lazy; } }
//懶漢式單例 //這種形式兼顧餓漢式的內存浪費,也兼顧synchronized性能問題 //完美地屏蔽了這兩個缺點 public class LazyInnerClassSingleton { //默認使用LazyInnerClassGeneral的時候,會先初始化內部類 //若是沒使用的話,內部類是不加載的 private LazyInnerClassSingleton(){ if(LazyHolder.LAZY != null){ throw new RuntimeException("不容許建立多個實例"); } } //每個關鍵字都不是多餘的 //static 是爲了使單例的空間共享 //保證這個方法不會被重寫,重載 public static final LazyInnerClassSingleton getInstance(){ //在返回結果之前,必定會先加載內部類 return LazyHolder.LAZY; } //默認不加載 private static class LazyHolder{ private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
3 反射破壞單例優化
public static void main(String[] args) { try{ //很無聊的狀況下,進行破壞 Class<?> clazz = LazyInnerClassSingleton.class; //經過反射拿到私有的構造方法 Constructor c = clazz.getDeclaredConstructor(null); //強制訪問 c.setAccessible(true); //暴力初始化 Object o1 = c.newInstance(); //調用了兩次構造方法,至關於new了兩次 //犯了原則性問題, Object o2 = c.newInstance(); System.out.println(o1 == o2); }catch (Exception e){ e.printStackTrace(); } }
4 序列化破壞單例ui
//反序列化時致使單例破壞 public class SeriableSingleton implements Serializable { //序列化就是說把內存中的狀態經過轉換成字節碼的形式 //從而轉換一個IO流,寫入到其餘地方(能夠是磁盤、網絡IO) //反序列化 //將已經持久化的字節碼內容,轉換爲IO流 //經過IO流的讀取,進而將讀取的內容轉換爲Java對象 //在轉換過程當中會從新建立對象new public final static SeriableSingleton INSTANCE = new SeriableSingleton(); private SeriableSingleton(){} public static SeriableSingleton getInstance(){ return INSTANCE; } }
public class SeriableSingletonTest { public static void main(String[] args) { SeriableSingleton s1 = null; SeriableSingleton s2 = SeriableSingleton.getInstance(); FileOutputStream fos = null; try { fos = new FileOutputStream("SeriableSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("SeriableSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); s1 = (SeriableSingleton)ois.readObject(); ois.close(); System.out.println(s1); System.out.println(s2); System.out.println(s1 == s2); } catch (Exception e) { e.printStackTrace(); } } }
System.out.println(s1 == s2) 打印結果爲false
解決辦法:
public class SeriableSingleton implements Serializable { //序列化就是說把內存中的狀態經過轉換成字節碼的形式 //從而轉換一個IO流,寫入到其餘地方(能夠是磁盤、網絡IO) //內存中狀態給永久保存下來了 //反序列化 //將已經持久化的字節碼內容,轉換爲IO流 //經過IO流的讀取,進而將讀取的內容轉換爲Java對象 //在轉換過程當中會從新建立對象new public final static SeriableSingleton INSTANCE = new SeriableSingleton(); private SeriableSingleton(){} public static SeriableSingleton getInstance(){ return INSTANCE; } // 重寫 readResolve()方法, 只不過是覆蓋了反序列化出來的對象 // 仍是建立了兩次, 發生在JVM 層面, 相對來講比較安全 // 以前反序列化出來的對象仍是會被JVM回收 private Object readResolve(){ return INSTANCE; } }
這時System.out.println(s1 == s2);打印結果爲true
ObjectInputStream..readObject()源碼中會去判斷這個類是否有readResolve()這個方法
5 註冊式單例: 將每個實例都緩存到統一的容器中,使用惟一標識獲取實例
分爲兩種: 枚舉式、容器式
枚舉式:
public enum EnumSingleton { INSTANCE; private Object data; public Object getData() { return data; } public void setData(Object data) { this.data = data; } public static EnumSingleton getInstance(){ return INSTANCE; } }
/* public static void main(String[] args) { try { EnumSingleton instance1 = null; EnumSingleton instance2 = EnumSingleton.getInstance(); instance2.setData(new Object()); FileOutputStream fos = new FileOutputStream("EnumSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(instance2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("EnumSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); instance1 = (EnumSingleton) ois.readObject(); ois.close(); System.out.println(instance1.getData()); System.out.println(instance2.getData()); System.out.println(instance1.getData() == instance2.getData()); // true }catch (Exception e){ e.printStackTrace(); } }*/ public static void main(String[] args) { try { Class clazz = EnumSingleton.class; Constructor c = clazz.getDeclaredConstructor(String.class,int.class); c.setAccessible(true); EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("BBA",112); // 從JDK層面就爲枚舉不被反序列化和反射作了限制 }catch (Exception e){ // IllegalArgumentException e.printStackTrace(); } }
容器式:
//Spring中的作法,就是用這種註冊式單例 public class ContainerSingleton { private ContainerSingleton(){} private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>(); public static Object getInstance(String className){ synchronized (ioc) { if (!ioc.containsKey(className)) { Object obj = null; try { obj = Class.forName(className).newInstance(); ioc.put(className, obj); } catch (Exception e) { e.printStackTrace(); } return obj; } else { return ioc.get(className); } } } }
public class ConcurrentExecutor { /** * @param runHandler * @param executeCount 發起請求總數 * @param concurrentCount 同時併發執行的線程數 * @throws Exception */ public static void execute(final RunHandler runHandler,int executeCount,int concurrentCount) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); //控制信號量,此處用於控制併發的線程數 final Semaphore semaphore = new Semaphore(concurrentCount); //閉鎖,可實現計數量遞減 final CountDownLatch countDownLatch = new CountDownLatch(executeCount); for (int i = 0; i < executeCount; i ++){ executorService.execute(new Runnable() { public void run() { try{ //執行此方法用於獲取執行許可,當總計未釋放的許可數不超過executeCount時, //則容許同性,不然線程阻塞等待,知道獲取到許可 semaphore.acquire(); runHandler.handler(); //釋放許可 semaphore.release(); }catch (Exception e){ e.printStackTrace(); } countDownLatch.countDown(); } }); } countDownLatch.await();//線程阻塞,直到閉鎖值爲0時,阻塞才釋放,繼續往下執行 executorService.shutdown(); } public interface RunHandler{ void handler(); } }
public class Pojo { }
public static void main(String[] args) { try { long start = System.currentTimeMillis(); ConcurrentExecutor.execute(new ConcurrentExecutor.RunHandler() { public void handler() { Object obj = ContainerSingleton.getInstance("com.pattern.singleton.Pojo"); System.out.println(System.currentTimeMillis() + ": " + obj); } }, 10,6); long end = System.currentTimeMillis(); System.out.println("總耗時:" + (end - start) + " ms."); }catch (Exception e){ e.printStackTrace(); } }
6 ThreadLocal線程單例: 保證線程內部的全局惟一,且天生線程安全
public class ThreadLocalSingleton { private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>(){ @Override protected ThreadLocalSingleton initialValue() { return new ThreadLocalSingleton(); } }; private ThreadLocalSingleton(){} public static ThreadLocalSingleton getInstance(){ return threadLocalInstance.get(); } }
public class ExectorThread implements Runnable{ @Override public void run() { ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance(); System.out.println(Thread.currentThread().getName() + ":" + singleton); } }
public class ThreadLocalSingletonTest { public static void main(String[] args) { System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); Thread t1 = new Thread(new ExectorThread()); Thread t2 = new Thread(new ExectorThread()); t1.start(); t2.start(); System.out.println("End"); } }
ThreadLocal 也是一個註冊式單例 ThreadLocal 僞線程安全 使用場景: 使用ThreadLocal來實現多數據源動態切換
單例模式的優勢: 1 在內存中只有一個實例,減小了內存開銷
2 能夠避免對資源的多重佔用
3 設置全局訪問點,嚴格控制訪問
缺點: 1 沒有接口,擴展困難
2 要擴展單例對象,只有修改代碼,沒有其餘途徑