单例模式(含Java代码样例)


什么是单例模式?

单例模式(Singleton Pattern)是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点以供外部代码使用。在java中表示在jvm中该对象只有一个实例的存在。


单例模式的应用场景

  • 1.项目中定义的配置文件
  • 2.Servlet对象默认就是单例
  • 3.线程池、数据库连接池
  • 4.Spring中Bean对象 和 ApplicationContext默认就是单例
  • 5.实现网站计数器
  • 6.Jvm内置缓存框架

单例优缺点

1.优点

能够节约当前堆内存空间,不需要频繁New对象,能够快速访问

2.缺点

当多个线程访问同一个单例对象的时候可能会存在线程安全问题


单例的七种写法

1.懒汉式(线程不安全)

  • 懒汉式基本概念:当真正需要获取到该对象时,才会创建该对象,该写法存在线程安全性问题。
  • 懒汉式(懒加载):当我们真正需要该对象时,才会创建该对象。
  • 优点:节约内存
  • 缺点:使用该单例对象时需要保证,会线程安全性问题
public class Singleton01 {
    //当真正需要使用该对象时才会创建。
    private static Singleton01 singleton01;

    private Singleton01() throws Exception {
//        if (singleton01 != null) {
//            throw new Exception("该对象已经创建,该类为单例模式");
//        }
//        System.out.println("无参构造函数");
    }
    public static Singleton01 getSingleton() throws Exception {
        if(singleton01 == null){
            singleton01 = new Singleton01();
        }
        return singleton01;
    }

    public static void main(String[] args) throws Exception {
        Singleton01 singleton1 = Singleton01.getSingleton();
        Singleton01 singleton2 = Singleton01.getSingleton();
        System.out.println(singleton1 == singleton2);//true
    }
}

2.懒汉式(线程安全)

  • 在做写操作情况下需要保证线程安全性问题
  • 懒汉式在第一次new出该对象已经赋值singleton,后面的所有线程直接获取该singleton对象 不需要重复new
public class Singleton02 {
    //实例化的变量引用私有化
    private static Singleton02 singleton = null;

    /**
     * 私有化构造函数
     */
    private Singleton02() {

    }

    //  创建和读取对象都需要获取Singleton02 锁
    public static synchronized Singleton02 getSingleton() {
        if (singleton == null) {
            singleton = new Singleton02();
        }
        return singleton;
    }

    public static void main(String[] args) {
        Singleton02 singleton1 = Singleton02.getSingleton();
        Singleton02 singleton2 = Singleton02.getSingleton();
        System.out.println(singleton1 == singleton2);//true
    }
}

3.懒汉式(双重检验锁)

  • 懒汉式线程安全—双重检验锁
  • (DCL,即 double-checked locking)
  • 能够保证线程安全,只会创建该单例对象的时候上锁,获取该单例对象不会上锁,效率比较高。
public class Singleton03 {
    //注意:加volatile关键字避免重排序
    private static volatile Singleton03 singleton03;

    private Singleton03(){

    }

    public static Singleton03 getSingleton(){
        if(singleton03 == null){
            //  第一个判断new,该单例对象为NULL时才会获取锁
            synchronized (Singleton03.class){
                if(singleton03 == null){
                    // 第二个判断是如果之前获取锁的线程创建对象成功,则不会在继续重复创建对象
                    //new的操作的时候在并发场景下可能会发生重排序(有可能让另外一个线程拿到不完整的对象)
                    // --- 使用volatile禁止重排序,内存屏障
                    singleton03 = new Singleton03();
                }
            }
        }
        return singleton03;
    }

    public static void main(String[] args) {
        Singleton03 singleton1 = Singleton03.getSingleton();
        Singleton03 singleton2 = Singleton03.getSingleton();
        System.out.println(singleton1 == singleton2);//true
    }
}

4.饿汉式

  • 提前创建单例对象
  • 优点:先天性线程安全
  • 缺点:如果没有使用该对象的话,该对象提前创建会占用堆内存
public class Singleton04 {
    //类加载器--当我们的class文件被加载时提前创建该对象,就会提前创建singleton对象
    private static Singleton04 singleton04 = new Singleton04();
    private Singleton04(){}
    public static Singleton04 getSingleton(){
        return singleton04;
    }

    public static void main(String[] args) {
        Singleton04 singleton01 = Singleton04.getSingleton();
        Singleton04 singleton02 = Singleton04.getSingleton();
        System.out.println(singleton01==singleton02);
    }
}

5.静态代码块

  • 当我们class被加载时,就会提前创建singleton对象
public class Singleton05 {

    private static Singleton05 singleton05 = null;
    private Singleton05() {
    }
    static {
        //只会执行一次Singleton03
        singleton05 = new Singleton05();
    }

    public static Singleton05 getSingleton() {
        return singleton05;
    }

    public static void main(String[] args) {
        Singleton05 singleton01 = Singleton05.getSingleton();
        Singleton05 singleton02 = Singleton05.getSingleton();
        System.out.println(singleton01 == singleton02);
    }
}

6.静态内部类

  • 静态内部类实现单例
  • 使用静态内部类到达懒汉式效果。也可以先天性保证线程安全的问题
  • spring框架源码中经常会发现使用静态内部类单例
public class Singleton06 {
    private Singleton06(){
        System.out.println("无参构造函数");
    }
    //当没有调用就静态内部类时,不会提前创建单例对象
    private static class SingletonHolder{
        private static Singleton06 singleton06 = new Singleton06();
    }

    public static Singleton06 getSingleton(){
        return SingletonHolder.singleton06;
    }

    public static void main(String[] args) {
        Singleton06 singleton01 = Singleton06.getSingleton();
        Singleton06 singleton02 = Singleton06.getSingleton();
        System.out.println(singleton01 == singleton02);
    }
}

7.枚举实现单例

public enum Singleton07 {
    TEST,MARK,SIGN;

    public void getInstance() {
        System.out.println("<<<getInstance>>>");
    }

    public static void main(String[] args) throws Exception {
        Singleton07 test1 = Singleton07.TEST;
        Singleton07 test2 = Singleton07.TEST;
        System.out.println(test1==test2);//true
        
        Singleton07 test3 = Singleton07.valueOf("TEST");
        System.out.println(test1==test3);//true
        
        Singleton07 mark = Singleton07.MARK;
        Singleton07 mark2 = Singleton07.MARK;
        System.out.println(mark == test1);//false
        System.out.println(mark==mark2);//true

        //反射攻击枚举
        //枚举底层实际上基于类封装的 没有无参构造函数 所有根据无参构造函数反射 会报错
        Class<?> c = Class.forName("com.test.Singleton07");
        Singleton07 instance3 = (Singleton07) c.newInstance();
        System.out.println(test1 == instance3);
        //枚举不能够被反射 ,反射底层代码有判断处理
    }
}

控制台信息

true
true
false
true
Exception in thread "main" java.lang.InstantiationException: com.test.Singleton07
	at java.lang.Class.newInstance(Class.java:427)
	at com.test.Singleton07.main(Singleton07.java:27)
Caused by: java.lang.NoSuchMethodException: com.test.Singleton07.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.newInstance(Class.java:412)
	... 1 more

如何破解单例模式

一、反射破解单例

反射如何破解单例

public class Singleton01 {
    //当真正需要使用该对象时才会创建。
    private static Singleton01 singleton01;
    // 写单例 特点;在我们当前jvm中只能够存在一个实例
    // 对无参构造函数私有化

    private Singleton01() throws Exception {
//        if (singleton01 != null) {
//            throw new Exception("该对象已经创建,该类为单例模式");
//        }
//        System.out.println("无参构造函数");
    }
    public static Singleton01 getSingleton() throws Exception {
        if(singleton01 == null){
            singleton01 = new Singleton01();
        }
        return singleton01;
    }

    public static void main(String[] args) throws Exception {
        // 使用反射破解单例
        Singleton01 singleton01 = Singleton01.getSingleton();
        Singleton01 singleton02 = Singleton01.getSingleton();
        Class<?> aClass = Class.forName("com.test.Singleton01");
        Singleton01 singleton03 = (Singleton01) aClass.newInstance();
        System.out.println(singleton02 == singleton03);//false
    }
}

如何防止反射单例被破解

将无参构造函数中注解打开

    private Singleton01() throws Exception {
        if (singleton01 != null) {
            throw new Exception("该对象已经创建,该类为单例模式");
        }
        System.out.println("无参构造函数");
    }

控制台信息

Exception in thread "main" java.lang.Exception: 该对象已经创建,该类为单例模式
	at com.test.Singleton01.<init>(Singleton01.java:18)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at java.lang.Class.newInstance(Class.java:442)
	at com.test.Singleton01.main(Singleton01.java:38)

二、序列化破解单例

序列化如何破解单例

    private static Singleton08 singleton = new Singleton08();


    public static Singleton08 getSingleton() {
        return singleton;
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Singleton08 singleton1 = Singleton08.getSingleton();

        // 1.将对象序列化存入到本地文件中
        FileOutputStream fos = new FileOutputStream("d:/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(singleton1);
        oos.close();
        fos.close();
        System.out.println("----------从硬盘中反序列化对象到内存中------------");
        //2.从硬盘中反序列化对象到内存中
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
        // 从新获取一个新的对象
        Singleton08 singleton2 = (Singleton08) ois.readObject();
        System.out.println(singleton1 == singleton2);
    }
}

如何防止序列化单例被破解

  • 重写readResolve方法,返回原来对象即可
    private Object readResolve() throws ObjectStreamException {
        return singleton;
    }

参考链接:菜鸟教程 | 单例模式

  • 23
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值