设计模式4 - 单例模式 Singleton Pattern

Posted hlkawa

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式4 - 单例模式 Singleton Pattern相关的知识,希望对你有一定的参考价值。

单例 :单例模式确保一个类只有一个实例,并提供全局访问点,实现单例模式的方法是私有化构造函数,通过getInstance()方法实例化对象,并返回这个实例,并保证在JVM中只有一个实例

单例模式优缺点

优点

1、单例类只有一个实例,不会频繁创建对象

2、共享资源,全局使用,访问速度比较快(只有一个实例服用,不用频繁的创建)

3、节省创建时间,提高性能

缺点

1、因为只有一个实例对象,多线程情况下,会出现线程安全问题

2、单例实例化的对象,存放在方法区(java8 叫元空间)不会被回收,大量单例容易出现内存溢出问题(比如配置文件过大,实例化出来的类导致内存溢出)

单例模式应用场景

配置文件,Spring IOC容器默认单例,线程池和数据库连接池(复用机制),Servlet(线程不安全),JVM内置缓存Oscache,Ehcache (HashMap + 淘汰策略),枚举类,常量,计数器

单例的七钟写法

饿汉、懒汉(非线程安全)、懒汉(线程安全)、双重校验锁、静态内部类、枚举和容器类管理

饿汉式

 * 饿汉式单例  就初始化一次
 * 优点;先天线程安全 => 当类被加载是就创建了实例
 * 缺点:static修饰的占用方法区,定义太多即使不使用也会占用内存,项目启动也会变慢
 **/
public class Singleton饿汉式1 implements Serializable {

    private static Singleton饿汉式1 singleton饿汉式 = new Singleton饿汉式1();

    //无参构造函数 private
    private Singleton饿汉式1() {
    }

    public static Singleton饿汉式1 getSingleton饿汉式() {
        return singleton饿汉式;
    }

    public static void main(String[] args) {
        Singleton饿汉式1 singleton饿汉式1 = Singleton饿汉式1.getSingleton饿汉式();
        Singleton饿汉式1 singleton饿汉式2 = Singleton饿汉式1.getSingleton饿汉式();
        System.out.println(singleton饿汉式1 == singleton饿汉式2);
    }

懒汉式(线程安全)

如果不加锁就是线程不安全

 * 在被调用的时候才创建
 * 缺点: 线程不安全
 *  加锁synchronized 可以保证线程安全,但一般加synchronized锁意味着有线程等待
 **/
public class Singleton懒汉式2 {

    private static Singleton懒汉式2 singleton懒汉式;


    //构造函数私有化
    private Singleton懒汉式2() {
        //防止java反射技术破解
        if(singleton懒汉式 != null){
            try {
                throw new Exception("该对象已经被实例化。。。。");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    public synchronized static Singleton懒汉式2 getSingleton懒汉式() {

        if(singleton懒汉式 == null){
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton懒汉式 = new Singleton懒汉式2();
            return singleton懒汉式;
        }
        return singleton懒汉式;
    }

    public static void main(String[] args) {
       /* Singleton懒汉式 singleton懒汉式1 = Singleton懒汉式.getSingleton懒汉式();
        Singleton懒汉式 singleton懒汉式2 = Singleton懒汉式.getSingleton懒汉式();
        System.out.println(singleton懒汉式1 == singleton懒汉式2);*/

        for (int i = 0; i < 20; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Singleton懒汉式2 singleton懒汉式1 = Singleton懒汉式2.getSingleton懒汉式();
                    System.out.println(Thread.currentThread().getName() + singleton懒汉式1);
                }
            }).start();

        }
    }
}

双重检验锁(DCL) 

 * 双重检验锁相比懒汉式,在实例化类的时候才加锁在读的时候不加锁,相比懒汉式减少了线程在读时候等待的问题
 * 但是在实例化的过程加了锁还是存在线程等待的问题
 *
 **/
public class Singleton双重检验锁3 {
    private volatile static Singleton双重检验锁3 singleton双重检验锁3;


    //构造函数私有化
    private Singleton双重检验锁3() {
    }


    public  static Singleton双重检验锁3 getSingleton双重检验锁3() {

        if(singleton双重检验锁3 == null){
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (Singleton双重检验锁3.class){
                if(singleton双重检验锁3 == null){
                    singleton双重检验锁3 = new Singleton双重检验锁3();
                }
            }
        }
        return singleton双重检验锁3;
    }

    public static void main(String[] args) {

        for (int i = 0; i < 20; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Singleton双重检验锁3 双重检验锁 = Singleton双重检验锁3.getSingleton双重检验锁3();

                    System.out.println(Thread.currentThread().getName() + 双重检验锁);
                }
            }).start();

        }
    }

}

静态内部内形式

 *  继承了懒汉式和饿汉式的优点
 *          内部类在被调用的时候才会初始化对象
 **/
public class Singleton静态内部类5 {

    private Singleton静态内部类5() {
        System.out.println("构造函数初始化。。。。");
    }

    public static class  SingletonUtil{
        private  static Singleton静态内部类5 singleton静态内部类 = new Singleton静态内部类5();
    }


    public static void main(String[] args) {
        System.out.println("main方法启动。。。。");
        Singleton静态内部类5 singleton静态内部类1 = SingletonUtil.singleton静态内部类;
        Singleton静态内部类5 singleton静态内部类2 = SingletonUtil.singleton静态内部类;
        System.out.println(singleton静态内部类1 == singleton静态内部类2);
    }
}

枚举形式

枚举形式能够先天性,防止反射和序列化破解单例。

public enum Singleton枚举单例8 {
    SINGLETON_枚举单例;

    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        System.out.println("----getName----: " +name);
        return name;
    }

    public static void main(String[] args) throws Exception {
        Singleton枚举单例8 枚举单例1 = Singleton枚举单例8.SINGLETON_枚举单例;
        Singleton枚举单例8 枚举单例2 = Singleton枚举单例8.SINGLETON_枚举单例;
        枚举单例1.setName("kawa");
        枚举单例2.getName();
        System.out.println(枚举单例1 == 枚举单例2);


        //序列化破解发现获取的是同一个对象
        //序列化
        Singleton枚举单例8 枚举单例4 = Singleton枚举单例8.SINGLETON_枚举单例;
        FileOutputStream fos = new FileOutputStream(System.getProperty("user.dir")+"\Singleton2.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(枚举单例4);
        oos.flush();
        oos.close();

        //反序列化
        FileInputStream fis = new FileInputStream(System.getProperty("user.dir")+"\Singleton2.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Singleton枚举单例8 枚举单例5 = (Singleton枚举单例8) ois.readObject();
        System.out.println(枚举单例4==枚举单例5);



        //反射机制破解 会直接抛出异常 NoSuchMethodException
        Constructor<Singleton枚举单例8> constructor = Singleton枚举单例8.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton枚举单例8 枚举单例3 = constructor.newInstance();
        System.out.println(枚举单例3 == 枚举单例2);


    }

使用容器管理 

public class SingletonManager {
    private static Map<String, Object> objMap = new HashMap<String, Object>();
    public static void registerService(String key, Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }
    public static Object getService(String key) {
        {
            return objMap.get(key);
        }
    }
}

如何防止破坏单例这种使用SingletonManager 将多种单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

最后额外补充下,对象创建的方式:

1. 通过 new 创建

2. 通过反射 (如:Class.forName() )创建

3. 通过序列化创建

4. 通过clone()方式创建

以上是关于设计模式4 - 单例模式 Singleton Pattern的主要内容,如果未能解决你的问题,请参考以下文章

4.单例模式

单例模式(Singleton Pattern)

[01]Go设计模式:单例模式(Singleton)

Java设计模式之单例设计模式(Singleton)

单例模式

Java单体应用 - 架构模式 - 03.设计模式-03.单例模式