首页 技术 正文
技术 2022年11月21日
0 收藏 745 点赞 2,134 浏览 3165 个字

单例模式的使用

jdk和Spring都有实现单例模式,这里举的例子是JDK中Runtime这个类

Runtime的使用

通过Runtime类可以获取JVM堆内存的信息,还可以调用它的方法进行GC。

public class Test {
public static void main(String[] args) throws Exception { Runtime runtime = Runtime.getRuntime();
runtime.gc();
//jvm的堆内存总量
System.out.println("堆内存总量" + runtime.totalMemory()/1024/1024 + "MB");
//jvm视图使用的最大堆内存
System.out.println("最大堆内存" + runtime.maxMemory()/1024/1024 + "MB");
//jvm剩余可用的内存
System.out.println("可用的内存" +runtime.freeMemory()/1024/1024 + "MB"); Runtime runtime1 = Runtime.getRuntime(); System.out.println(runtime == runtime1);
}
}

这里创建了两个对象,通过等于号判断,两个引用来自同一个对象,确实是单例模式

Runtime的定义

这个类是介绍是:每一个Java应用有一个Runtime的实例,可以获取应用运行时的环境属性,当前的实例通过

getRuntime方法获取 。应用程序不能创建这个类的实例。

这差不多包含了单例类的定义,然后看一下这个类的内部实现

很明显是一个标准的单例模式的(饿汉)实现,首先使用static修饰实例对象,所以类加载的时候就会创建实例,然后调用方法返回这个实例,使用private修饰构造函数。

反射破坏单例模式

Runtime类将构造函数私有化,就是不想让人创建它的实例,但是我们却可以使用反射来创建对象

public class Test {
public static void main(String[] args) throws Exception { Class<?> clazz = Runtime.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object o1 = constructor.newInstance();
Object o2 = Runtime.getRuntime();
System.out.println(o1.getClass().getSimpleName());
System.out.println(o2.getClass().getSimpleName());
System.out.println(o1 == o2); }
}

通过运行结果可以看到,已经成功的创建了两个Runtime对象

至于破坏Runtime类的单例有什么坏处我也不知道,毕竟我是不会用反射去破坏它的,总之应该是有坏处的,下面看一下不能被反射破坏的单例模式实现

单例模式的实现

枚举类实现

使用枚举实现是因为JDK底层保护我们的枚举类不被反射,就解决了单例被反射破坏的问题

EnumSingleton.java

在枚举类中放了一个内部类(其实不放内部类也行)

public enum EnumSingleton {    INSTANCE;
class MyRuntime{ public void hello(){
System.out.println("hello");
}
} private MyRuntime myRuntime; EnumSingleton(){
myRuntime = new MyRuntime();
} public MyRuntime getData(){
return myRuntime;
} public static EnumSingleton getInstance(){
return INSTANCE;
}
}

下面测试一下这个单例

public class Test {
public static void main(String[] args) throws Exception { EnumSingleton.MyRuntime myRuntime = EnumSingleton.INSTANCE.getData();
myRuntime.hello();
EnumSingleton.MyRuntime myRuntime1 = EnumSingleton.getInstance().getData();
System.out.println(myRuntime == myRuntime1);
}
}

结果显而易见,单例模式已经成功实现

至于使用反射测试枚举类,可以直接看一下JDK对枚举类的一个保护

使用反射创建对象,即调用Construct类的newInstance方法,这个方法里面已经定义了枚举对象不能被创建

使用枚举实现单例的坏处有

  • 因为很少使用枚举类,所以用枚举创建单例感觉挺奇怪的。
  • 虽然它可以防止被反射破坏,但是它确实复杂。

像上面Runtime类那样的单例实现就差不多了,有一个缺点是,Runtime在类加载的时候就创建对象了

如果有很多类似的单例实现,在类加载时就创建了很多不需要的对象,会很占用资源

下面写一个懒汉式静态内部类单例实现(调用时才创建对象)

public class LazyInnerClassSingleton {    static {
System.out.println("加载静态代码块");
} private LazyInnerClassSingleton(){ System.out.println("创建对象成功"); } public static void hello(){
System.out.println("hello");
}
/*
在调用getInstance方法时InnerLazy类被加载的才会初始化对象
*/
public static LazyInnerClassSingleton getInstance(){
return InnerLazy.LAZY;
} private static class InnerLazy{ private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); }
}

这种实现的要点在与

  • 外部类构造方法私有化,无法创建外部类
  • 内部类的静态变量LAZY一直到调用外部类的getInstance方法时才会被加载,然后LAZY对象才会被创建,实现了懒加载
  • 注意内部类只是提供实例的一个工具,这里的单例对象是外部类

测试一下是不是真的

public class Test {    public static void main(String[] args) throws Exception {        LazyInnerClassSingleton.hello();
System.out.println("开始创建对象实例");
LazyInnerClassSingleton.getInstance();
}
}

由运行结果看到,它只有在调用getInstance方法时才会创建对象,在加载外部类时是不会加载内部类的

为了让它不被反射破坏,在构造方法上多加一个判断

无论是使用new关键字还是反射,都会调用类的构造方法,所以外部类使用这两种方式字创建实例,不然就会把异常抛出
因为if语句永远为true,虽然在执行if语句之前,InnerLazy.LAZY为null,但是只要使用了这个变量,就会去加载内部类
加载完内部类,InnerLazy.LAZY就不为null,于是抛出异常

因为我没有过破坏单例模式的经历,所以也不知道为什么要搞这么复杂,只能说是很神奇。

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,116
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,588
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,433
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,204
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,840
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,925