设计模式——单例

单例设计模式

单例模式(singleton Pattern)是Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的结构

单例模式主要有以下角色:

  • 单例类。只能创建一个实例的类。
  • 访问类。使用单例类。

单例模式的实现

单例设计模式分类两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

①饿汉式方式一:静态变量方式

package pattern.singleton.demo1;

public class Singleton {
    //私有化构造器
    private Singleton(){}

    //在本类方法中创建本类对象
    private static Singleton instance = new Singleton();

    //提供一个公共的访问方式,让外界获取该对象
    public static Singleton getInstance(){
        return instance;
    }
}

②饿汉式方式二:静态代码块方式

package pattern.singleton.demo1;

public class Singleton {
    //私有化构造器
    private Singleton(){}

    //声明Singleton类型的变量
    private static Singleton instance;
    //在静态代码块中进行赋值
    static {
        instance = new Singleton();
    }

    //提供一个公共的访问方式,让外界获取该对象
    public static Singleton getInstance(){
        return instance;
    }
}

说明:
该方式在成员位置声明singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式l基本上—样,当然该方式也存在内存浪费问题。

③懒汉式:线程安全

package pattern.singleton.demo2;

public class Singleton {
    //构造器私有化
    private Singleton(){}

    //声明Singleton类型的变量instance
    private static Singleton instance;

    //对外提供访问方式
    public static synchronized Singleton getInstance(){
        //判断instance是否为null,如果为null,说明还没有创建singleton类的对象
        //如果没有,创建一个并返回,如果有,直接返回
        if(instance == null){
            return new Singleton();
        }
        return instance;
    }
}

④懒汉式(双重检查锁)
再来讨论一下懒汉模式中加锁的问题,对于getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式

public class Singleton {
    //构造器私有化
    private Singleton(){}

    //声明Singleton类型的变量instance
    private static Singleton instance;

    //对外提供访问方式
    public  synchronized Singleton getInstance(){
        //判断instance是否为null,如果为null,说明还没有创建singleton类的对象
        //如果没有,创建一个并返回,如果有,直接返回
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    return new Singleton();
                }
            }
        }
        return instance;
    }
}

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

private static volatile Singleton instance;

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

public class Singleton {
    //构造器私有化
    private Singleton(){}

    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
    
}

⑥枚举类(饿汉式)
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

public enum Singleton{
	INSTANCE;
}

序列化、反序列化会破坏单例模式

public class Client {
    public static void main(String[] args) throws Exception{
        writeObjectToFile();
        readObjectFromFile();
        readObjectFromFile();
    }

    public static void readObjectFromFile()throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a.txt"));
        Singleton instance = (Singleton)ois.readObject();
        System.out.println(instance);
        ois.close();
    }

    public static void writeObjectToFile() throws Exception{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));
        Singleton instance = Singleton.getInstance();
        oos.writeObject(instance);
        oos.close();
    }
}

从结果可以看到两次得到的对象地址不一样
在这里插入图片描述
通过反射也可以破坏单例模式

public class Client {
    public static void main(String[] args) throws Exception {
        Class clazz = Singleton.class;
        Constructor declaredConstructor = clazz.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        Singleton o = (Singleton) declaredConstructor.newInstance();
        Singleton o1 = (Singleton) declaredConstructor.newInstance();
        System.out.println(o == o1);
    }
 }

结果输出为false,说明两次输出的对象不是同一个

  • 序列化、反序列方式破坏单例模式的解决方法
    在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
public class Singleton implements Serializable {
    //构造器私有化
    private Singleton(){}

    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }

    //当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
    public Object readResolve(){
        return SingletonHolder.INSTANCE;
    }
}

运行结果显示两个两次对象地址相同
在这里插入图片描述

  • 反射方式破解单例的解决方法
public class Singleton{
    private static boolean flag = false;
    //构造器私有化
    private Singleton(){
        synchronized (Singleton.class){
            //判断flag是否为true,如果是true,说明非第一次访问,直接抛异常,否则可以直接创建对象
            if(flag){
                throw new  RuntimeException("不能创建多个对象");
            }
            flag = true;
        }
    }

    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值