当前位置:   article > 正文

java常用设计模式 ----单例模式(简单易懂)_利用单例模式,管理文件中读取的数据,模拟程序的缓存设置。利用`get`方法获取其数

利用单例模式,管理文件中读取的数据,模拟程序的缓存设置。利用`get`方法获取其数

单例模式

一、什么是单例模式

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

二、单例模式应用场景

  • 项目中定义的配置文件
  • Servlet对象默认就是单例
  • 线程池、数据库连接池
  • Spring中Bean对象默认就是单例
  • 实现网站计数器
  • Jvm内置缓存框架(定义单例HashMap)
  • 定义枚举常量信息

三、单例模式优缺点

优点:能够节约当前堆内存,共享资源,全局使用,不需要频繁New对象,能够快速访问,提高性能。
缺点:当多个线程访问同一个单例对象的时候可能会存在线程安全问题。

四、单例的七种写法

分别是「饿汉」、「懒汉(非线程安全)」、「懒汉(线程安全)」、「双重校验锁」、「静态内部类」、「枚举」和「容器类管理」、「静态块初始化」
饿汉式

public class SingletonV1 {

    /**
     * 饿汉式 优点:先天性线程是安全的,当类初始化的 就会创建该对象 缺点:如果饿汉式使用过多,可能会影响项目启动的效率问题。
     */
    private static SingletonV1 singletonV1 = new SingletonV1();

    /**
     * 将构造函数私有化 禁止初始化
     */
    private SingletonV1() {

    }

    public static SingletonV1 getInstance() {
        return singletonV1;
    }

    public static void main(String[] args) {
        SingletonV1 instance1 = SingletonV1.getInstance();
        SingletonV1 instance2 = SingletonV1.getInstance();
        System.out.println(instance1 == instance2);

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

懒汉式(线程不安全)

public class SingletonV2 {

    /**
     * 懒汉式 (线程不安全)
     */
    private static SingletonV2 singletonV2;

    private SingletonV2() {

    }

    /**
     * 在真正需要创建对象的时候使用...
     *
     * @return
     */
    public static SingletonV2 getInstance() {
        if (singletonV2 == null) {
            try {
                Thread.sleep(2000);
            } catch (Exception e) {
            }
            singletonV2 = new SingletonV2();
        }
        return singletonV2;
    }

    public static void main(String[] args) {
        // 1.模拟线程不安全
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                public void run() {
                    SingletonV2 instance1 = SingletonV2.getInstance();
                    System.out.println(Thread.currentThread().getName() + "," + instance1);
                }
            }).start();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

懒汉式(线程安全)

public class SingletonV3 {
    /**
     * 懒汉式 线程安全
     */
    private static SingletonV3 singletonV3;

    private SingletonV3() {

    }

    /**
     * 能够解决线程安全问题,创建和获取实例时都上锁 ,效率非常低,所以推荐使用双重检验锁
     *
     * @return
     */
    public synchronized static SingletonV3 getInstance() {
        if (singletonV3 == null) {
            System.out.println("创建实例SingletonV3");
            singletonV3 = new SingletonV3();
        }
        System.out.println("获取SingletonV3实例");
        return singletonV3;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                public void run() {
                    SingletonV3 instance1 = SingletonV3.getInstance();
                    System.out.println(Thread.currentThread().getName() + "," + instance1);
                }
            }).start();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

双重检验锁(DCL)

public class SingletonV4 {
    /**
     * volatile 禁止重排序和 提高可见性
     */
    private volatile static SingletonV4 singletonV4;

    private SingletonV4() {

    }

    public static SingletonV4 getInstance() {
        if (singletonV4 == null) { // 第一次判断如果没有创建对象 开始上锁...
            synchronized (SingletonV4.class) {
                if (singletonV4 == null) { // 当用户抢到锁,判断初始化
                    System.out.println("第一次开始创建实例对象....获取锁啦...");
                    singletonV4 = new SingletonV4();
                }
            }
        }
        return singletonV4;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                public void run() {
                    SingletonV4 instance1 = SingletonV4.getInstance();
                    System.out.println(Thread.currentThread().getName() + "," + instance1);
                }
            }).start();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

静态内部类形式

public class SingletonV5 {

    private SingletonV5() {
        System.out.println("对象初始...");
    }

    public static SingletonV5 getInstance() {
        return SingletonV5Utils.singletonV5;
    }

    /**
     * 静态内部方式能够避免同步带来的效率问题和有能实现延迟加载
     */
    public static class SingletonV5Utils {
        private static SingletonV5 singletonV5 = new SingletonV5();
    }

    public static void main(String[] args) {
        System.out.println("项目启动成功");
        SingletonV5 instance1 = SingletonV5.getInstance();
        SingletonV5 instance2 = SingletonV5.getInstance();
        System.out.println(instance1 == instance2);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

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

public enum EnumSingleton {
    INSTANCE;

    // 枚举能够绝对有效的防止实例化多次,和防止反射和序列化破解
    public void add() {
        System.out.println("add方法...");
    }
}

public static void main(String[] args) throws Exception {
    EnumSingleton instance1 = EnumSingleton.INSTANCE;
    EnumSingleton instance2 = EnumSingleton.INSTANCE;
    System.out.println(instance1 == instance2);
    Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
    declaredConstructor.setAccessible(true);
    EnumSingleton v3 = declaredConstructor.newInstance();
    System.out.println(v3==instance1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

使用容器管理

public class SingletonManager {
    private static Map<String, Object> singletonMap = new HashMap<>();
    public static void registerService(String key, Object instance) {
        if (key.length()>0 && instance != null) {
            if (!singletonMap.containsKey(key)) {
                singletonMap.put(key, instance);
            }
        }
    }
    public static Object getService(String key) {
        return singletonMap.get(key);
    }
    public static void main(String[] args) {
        System.out.println("项目启动成功");
        SingletonManager.registerService("object",new Object());
        Object instance1 = SingletonManager.getService("object");
        Object instance2 = SingletonManager.getService("object");
        System.out.println(instance1 == instance2);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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

五、如何防止破坏单例

虽然单例通过私有构造函数,可以实现防止程序猿初始化对象,但是还可以通过反射和序列化技术破解单例。
使用反射技术破解单例

// 1. 使用懒汉式创建对象
SingletonV3 instance1 = SingletonV3.getInstance();
// 2. 使用Java反射技术初始化对象 执行无参构造函数
Constructor<SingletonV3> declaredConstructor = SingletonV3.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
SingletonV3 instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

如何防止被反射破解

private SingletonV3() throws Exception {
    synchronized (SingletonV3.class) {
        if (singletonV3 != null) {
            throw new Exception("该对象已经初始化..");
        }
        System.out.println("执行SingletonV3无参构造函数...");
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

使用序列化技术破解单例

public class Test002 {
    public static void main(String[] args) throws Exception {
        // 1.需要将该对象序列化到本地存放
        FileOutputStream fos =  new FileOutputStream("d:/code/user.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        Singleton01 instance1 = Singleton01.getInstance();
        oos.writeObject(instance1);
        oos.close();
        fos.close();
        //2.从硬盘中反序列化对象到内存中
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/code/user.txt"));
        Singleton01 instance2 = (Singleton01) ois.readObject();
        System.out.println(instance1==instance2);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

如何防止序列化破解
单例对象重写该方法 指定返回的对象 防止序列化破解

//返回序列化获取对象 ,保证为单例
public Object readResolve() {
    return singletonV3;
}
  • 1
  • 2
  • 3
  • 4

注意:如果该类是Serializable类型的 则调用它第一个非Serializable父类的无参构造函数初始化该对象该对象

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/157706
推荐阅读
相关标签
  

闽ICP备14008679号