單例模式

 單例模式(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 要擴展單例對象,只有修改代碼,沒有其餘途徑

相關文章
相關標籤/搜索