欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 游戏 > 单例设计模式

单例设计模式

2025/4/28 10:53:26 来源:https://blog.csdn.net/2302_79904362/article/details/147566757  浏览:    关键词:单例设计模式

概述

  单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

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

饿汉式

静态变量方式

public class Singleton {// 1.私有构造方法private Singleton() {}// 2.创建本类对象private static Singleton instance = new Singleton();// 3.提供公共的访问方式,让外界获取该对象public static Singleton getInstance() {return instance;}
}

  该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

public class Client {public static void main(String[] args) {//创建Singleton类的对象Singleton instance = Singleton.getInstance();Singleton instance1 = Singleton.getInstance();//判断获取的对象是否是同一个System.out.println(instance == instance1);}
}

静态代码块方式

public class Singleton {// 1. 构造器私有化private Singleton() {}// 2. 本类内部创建对象实例private static Singleton instance;//null// 3.在静态代码块中复制static {instance = new Singleton();}// 4. 提供一个公有的静态方法,返回实例对象public static Singleton getInstance() {return instance;}
}

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

懒汉式

线程不安全

public class Singleton {//私有构造方法private Singleton() {}//声明Singleton类型的变量instanceprivate static Singleton instance;//只是声明一个该类型的变量,并没有进行赋值//对外提供访问方式public static Singleton getInstance() {//判断instance是否为null,如果为null,则创建对象并赋值给instance,// 如果不为null,则直接返回instanceif (instance == null) {instance = new Singleton();}return instance;}
}

  从上面代码可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。

线程安全

public class Singleton {//私有构造方法private Singleton() {}//声明Singleton类型的变量instanceprivate static Singleton instance;//只是声明一个该类型的变量,并没有进行赋值//对外提供访问方式public static synchronized Singleton getInstance() {//判断instance是否为null,如果为null,则创建对象并赋值给instance,// 如果不为null,则直接返回instanceif (instance == null) {instance = new Singleton();}return instance;}
}

双重检查锁

public class Singleton { //私有构造方法private Singleton() {}private static Singleton instance; //对外提供静态方法获取该对象public static Singleton getInstance() {//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例if (instance == null) {synchronized (Singleton.class) {//抢到锁之后再次判断是否为nullif (instance == null) {instance = new Singleton();}}}return instance;}
}

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

public class Singleton { //私有构造方法private Singleton() {}private static volatile Singleton instance; //对外提供静态方法获取该对象public static Singleton getInstance() {//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例if (instance == null) {synchronized (Singleton.class) {//抢到锁之后再次判断是否为nullif (instance == null) {instance = new Singleton();}}}return instance;}
}

  添加 volatile 关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。

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

public class Singleton {//私有构造方法private Singleton() {}//定义一个静态内部类private static class SingletonHolder {//在内部类中声明并初始化外部类的对象private static final Singleton INSTANCE = new Singleton();}//对外提供静态方法获取该对象public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

  第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder,并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。

  静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任
何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

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

public enum Singleton {INSTANCE;
}

存在的问题

破坏单例模式:

  • 序列化反序列化
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 class Client {public static void main(String[] args) throws IOException, ClassNotFoundException {
//        writeDataToFile();readDataFromFile();readDataFromFile();}//从文件读取数据(对象)public static void readDataFromFile() throws IOException, ClassNotFoundException {//1.创建对象输入流对象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\javacode\\design-patterns\\src\\main\\resources\\a.txt"));//2.读取对象Singleton instance = (Singleton) ois.readObject();System.out.println(instance);//3.释放资源ois.close();}//向文件中写数据(对象)public static void writeDataToFile() throws IOException {// 1.获取Singleton类的对象Singleton instance = Singleton.getInstance();//2.创建对象输出流对象ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\javacode\\design-patterns\\src\\main\\resources\\a.txt"));//3.写对象oos.writeObject(instance);//4.释放资源oos.close();}
}

上面代码运行结果是 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;}//当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回private Object readResolve() {return SingletonHolder.INSTANCE;}
}
  • 反射
public class Singleton implements Serializable {private static boolean flag = false;//私有构造方法private Singleton() {synchronized (Singleton.class){if(flag){throw new RuntimeException("不能创建多个对象");}flag = true;}}//定义一个静态内部类private static class SingletonHolder {//在内部类中声明并初始化外部类的对象private static final Singleton INSTANCE = new Singleton();}
}
public class Client {public static void main(String[] args) throws Exception {//获取Singleton类的字节码对象Class clazz = Singleton.class;//获取Singleton类的私有无参构造方法对象Constructor constructor = clazz.getDeclaredConstructor();//取消访问检查constructor.setAccessible(true);//创建Singleton类的对象s1Singleton s1 = (Singleton) constructor.newInstance();//创建Singleton类的对象s2Singleton s2 = (Singleton) constructor.newInstance();//判断通过反射创建的两个Singleton对象是否是同一个对象System.out.println(s1 == s2);}
}

上面代码运行结果是 false ,表明序列化和反序列化已经破坏了单例设计模式
注意:枚举方式不会出现这两个问题。

解决方案
  当通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此中操作。

public class Singleton implements Serializable {private static boolean flag = false;//私有构造方法private Singleton() {synchronized (Singleton.class){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;}
}

举例

  Runtime类就是使用的单例设计模式。

package pattern.singleton.demo9;public class Runtime {private static Runtime currentRuntime = new Runtime();/*** Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object.** @return the <code>Runtime</code> object associated with the current* Java application. */public static Runtime getRuntime() {return currentRuntime;}/*** Don't let anyone else instantiate this class*/private Runtime() {} ...
}

  从上面源代码中可以看出Runtime类使用的是恶汉式(静态属性)方式来实现单例模式的。

public class RuntimeDemo {public static void main(String[] args) throws IOException {//获取Runtime类对象Runtime runtime = Runtime.getRuntime();//返回 Java 虚拟机中的内存总量。System.out.println(runtime.totalMemory());//返回 Java 虚拟机试图使用的最大内存量。System.out.println(runtime.maxMemory());//创建一个新的进程执行指定的字符串命令,返回进程对象Process process = runtime.exec("ipconfig");//获取命令执行后的结果,通过输入流获取InputStream inputStream = process.getInputStream();byte[] arr = new byte[1024 * 1024 * 100];int b = inputStream.read(arr);System.out.println(new String(arr, 0, b, "gbk"));}
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词