设计模式——单例

单例设计模式

单例模式(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;
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340