单例模式介绍和使用

一、概念

单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例设计模式分类两种:

  • ​ 饿汉式:类加载就会导致该单实例对象被创建
  • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

二、饿汉式

类加载就会导致该单实例对象被创建

1.静态变量

instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

public class Single {
    //私有构造方法,不予外部人员使用
    private Single(){
    }
    //创建实例
    private static Single instance=new Single();
    //返回实例
    public static Single getInstance(){
        return instance;
    }
}

2.静态代码块方式

在成员位置声明静态变量,而对象的创建是在静态代码块中,也是随着类的加载而创建。同上一方法,当然该方式也存在内存浪费问题

public class Single {
    //私有构造方法,不予外部人员使用
    private Single(){}
    //声明对象名
    private static Single instance;
    //在静态代码块中分配内存和指向对象
    static {
        instance=new Single();
    }
    //返回实例
    public static Single getInstance(){
        return instance;
    }
}

三、懒汉式

类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

方式一(线程不安全)

public class Single {
    //私有构造方法,不予外部人员使用
    private Single(){}
    //创建实例对象
    private static Single instance=null;
    //返回实例
    public static Single getInstance(){
       if(instance==null){
           return new Single();
       }
       return instance;
    }
}

线程不安全:当线程1在 instance==null 判断进入后,线程2执行 instance ==null 判断也进入到下一步就会导致创建多个对象

方式二(线程安全)

在上一方式的基础上给getInstance()加锁,但是这样会严重影响性能

public class Single {
    //私有构造方法,不予外部人员使用
    private Single(){}
    //创建实例对象
    private static Single instance=null;
    //返回实例
    public static synchronized Single getInstance(){
       if(instance==null){
           return new Single();
       }
       return instance;
    }
}

方式三(双重检查锁)

版本1

public class Single {
    //私有构造方法,不予外部人员使用
    private Single(){}
    //声明静态变量
    private static Single instance=null;
    //返回实例
    public static Single getInstance(){
        //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
        if (instance==null){
            synchronized(Single.class){
                //抢到锁之后再次判断是否为null
                if (instance==null){
                    return instance=new Single();
                }
            }
        }
        return instance;
    }
}

双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性有序性

instance=new Single(); //不是一个原子性操作

创建对象的三个步骤
在这里插入图片描述

正常走123
指令重排可能执行过程为132
这个时候线程1走完13时,线程2进行第一层if (instance==null)判断,此时是可以通过了,instance已经指向空间了,直接返回return instance,但是此时instance并未初始化,还是一个空指针

版本2

volatile 禁止指令重排

public class Single {
    //私有构造方法,不予外部人员使用
    private Single(){}
    //声明静态变量
    private static volatile Single instance=null;
    //返回实例
    public static Single getInstance(){
        //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
        if (instance==null){
            synchronized(Single.class){
                //抢到锁之后再次判断是否为null
                if (instance==null){
                    return instance=new Single();
                }
            }
        }
        return instance;
    }
}

方式四(静态内部类方式)

静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。

public class Single {
    //私有构造方法,不予外部人员使用
    private Single(){}
    //创建实例
    private static class SingleInstance{
        private static final Single Instance=new Single();
    }
    //返回实例
    public static Single getInstance(){
        return SingleInstance.Instance;
    }
}

​ 第一次加载Single类时不会去初始化Instance,只有第一次调用getInstance,虚拟机加载并初始化Instance,这样不仅能确保线程安全,也能保证 Single 类的唯一性。
​静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

四、枚举方式(饿汉式方式)

自带单例,反射也不能破坏单例

public enum Single {
    INSTANCE;
}

五、存在的问题和解决方式

序列化和反射可以破坏单例

1.序列化方式

//可序列化
public class Single implements Serializable {
    //私有构造方法,不予外部人员使用
    private Single(){}
    //创建实例
    private static class SingleInstance{
        private static final Single Instance=new Single();
    }
    //返回实例
    public static Single getInstance(){
        return SingleInstance.Instance;
    }
}

序列化破坏单列模式

public class test {
    public static void main(String[] args) throws Exception {
        //将Single的实例以流的形式写进test.txt文本中
        writeObjectToFile();
        //读取test.txt文本字节流并序列化为Single对象
        Single single1 = readObjectFromFile();
        Single single2 = readObjectFromFile();
        System.out.println(single1==single2);//false 可见生成两个不同的对象
    }
    //将Single的实例以流的形式写进文本中
    public static void writeObjectToFile() throws Exception {
        Single single=Single.getInstance();
        //创建对象输出流
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E:\\IDEA\\Design\\Single\\src\\demo5\\test.txt"));
        //将single写进test.txt中
        objectOutputStream.writeObject(single);
    }
    //读取文本中的字节流序列化成对象
    private static Single readObjectFromFile() throws Exception {
        //创建对象输入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\IDEA\\Design\\Single\\src\\demo5\\test.txt"));
        //第一个读取字节流并序列化为Single对象
        Single instance = (Single) ois.readObject();
        return instance;
    }
}

解决方案

在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。

public class Single implements Serializable {
    //私有构造方法,不予外部人员使用
    private Single(){}
    //创建实例
    private static class SingleInstance{
        private static final Single Instance=new Single();
    }
    //返回实例
    public static Single getInstance(){
        return SingleInstance.Instance;
    }

    //使用序列化的方式创建对象会调用该方法,限制为单例
    private Object readResolve() {
        return SingleInstance.Instance;
    }
}

2.反射方式

public class Single {
    //私有构造方法,不予外部人员使用
    private Single(){}
    //声明静态变量
    private static volatile Single instance=null;
    //返回实例
    public static Single getInstance(){
        //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
        if (instance==null){
            synchronized(Single.class){
                //抢到锁之后再次判断是否为null
                if (instance==null){
                    return instance=new Single();
                }
            }
        }
        return instance;
    }
}

反射破坏单例

public class test {
    public static void main(String[] args) throws Exception {
        //获取Singleton类的字节码对象
        Class c = Single.class;
        //获取Single类的私有无参构造方法对象
        Constructor constructor = c.getDeclaredConstructor();
        //取消访问检查
        constructor.setAccessible(true);

        Single s1 = (Single) constructor.newInstance();
        Single s2 = (Single) constructor.newInstance();

        //判断通过反射创建的两个对象是否是同一个对象
        System.out.println(s1 == s2);
    }
}

解决方案

  1. 版本1
public class Single {
    //私有构造方法,不予外部人员使用
    private Single(){
       //反射调用时报错,禁止反射调用
       synchronized(Single.class){
       		if(instance != null) {
	            throw new RuntimeException();
	        }
       }
    }
    //声明静态变量
    private static volatile Single instance=null;
    //返回实例
    public static Single getInstance(){
        //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
        if (instance==null){
            synchronized(Single.class){
                //抢到锁之后再次判断是否为null
                if (instance==null){
                    return instance=new Single();
                }
            }
        }
        return instance;
    }
}

上面的还是可以被破坏

构造方法中使用instance != null进行判断,一开都不使用getInstance方法,都是用反射获取对象就破坏了

  1. 版本2

限制反射只能获取一次对象

public class Single {
    //私有构造方法,不予外部人员使用
    private static boolean flag=false;
    private Single(){
        synchronized (Single.class){
            if(flag===false){
                flag=true;
            }{
               throw new RuntimeException("不要试图反射破坏单例");
            }
        }
    }
    //创建实例
    private static Single instance=null;
    //返回实例
    public static Single getInstance(){
        //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
        if (instance==null){
            synchronized(Single.class){
                //抢到锁之后再次判断是否为null
                if (instance==null){
                    return instance=new Single();
                }
            }
        }
        return instance;
    }
}

还是可以破坏

public class test {
    public static void main(String[] args) throws Exception {
        //获取Single类的私有无参构造方法对象
        Constructor constructor = Single.class.getDeclaredConstructor();
        //取消访问检查
        constructor.setAccessible(true);
        Single s1 = (Single) constructor.newInstance();
        Field flag = Single.class.getDeclaredField("flag");
        flag.setAccessible(true);
        //创建一个对象后重新设置flag的值为true
        flag.set(s1,false);

        Single s2 = (Single) constructor.newInstance();

        //判断通过反射创建的两个对象是否是同一个对象
        System.out.println(s1);
        System.out.println(s2);
    }
}

反射不能破坏枚举的单例

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值