Java设计模式之单例模式示例详解
目录
- 0.概述
- 1.饿汉式
- 1.1 饿汉式单例实现
- 1.2 破坏单例的几种情况
- 1.3 预防单例的破坏
- 2.枚举饿汉式
- 2.1 枚举单例实现
- 2.2 破坏单例
- 3.懒汉式
- 4.双检锁懒汉式
- 5.内部类懒汉式
- 6.JDK中单例的体现
0.概述 为什么要使用单例模式?
在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。因此这里需要用到单例模式
使用单例模式的好处?
对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销
由于new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间
【Java设计模式之单例模式示例详解】
1.饿汉式
1.1 饿汉式单例实现
实例会提前创建:
/*** 饿汉式** @author xppll* @date 2021/12/24 21:21*/public class Singleton1 implements Serializable {//构造私有private Singleton1() {System.out.println("private Singleton1()"); }//唯一实例private static final Singleton1 INSTANCE = new Singleton1(); //获得实例方法public static Singleton1 getINSTANCE() {return INSTANCE; }//其他方法public static void otherMethod() {System.out.println("otherMethod()"); }}
测试:
/** * @author xppll * @date 2021/12/24 21:28 */public class TestSingleton {public static void main(String[] args) {//触发Singleton1类的初始化,会为类的静态变量赋予正确的初始值,单例对象就会被创建!Singleton1.otherMethod(); System.out.println("-----------------------------------"); System.out.println(Singleton1.getINSTANCE()); System.out.println(Singleton1.getINSTANCE()); }}//输出:private Singleton1()otherMethod()-----------------------------------singleton.Singleton1@10bedb4singleton.Singleton1@10bedb4
1.2 破坏单例的几种情况
1.反射破坏单例
2.反序列化破坏单例
3.Unsafe破坏单例
演示:
/** * @author xppll * @date 2021/12/24 21:28 */public class TestSingleton {public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {//触发Singleton1类的初始化,会为类的静态变量赋予正确的初始值,单例对象就会被创建!Singleton1.otherMethod(); System.out.println("-----------------------------------"); System.out.println(Singleton1.getINSTANCE()); System.out.println(Singleton1.getINSTANCE()); //反射破坏单例reflection(Singleton1.class); //反序列化破坏单例serializable(Singleton1.getINSTANCE()); //Unsafe破坏单例unsafe(Singleton1.class); } //反射破坏单例private static void reflection(Class> clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {//得到无参Constructor> constructor = clazz.getDeclaredConstructor(); //将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性constructor.setAccessible(true); //创建实例System.out.println("反射创建实例:" + constructor.newInstance()); } //反序列化破坏单例private static void serializable(Object instance) throws IOException, ClassNotFoundException {ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); //序列化oos.writeObject(instance); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); //反序列化System.out.println("反序列化创建示例:" + ois.readObject()); } //Unsafe破坏单例private static void unsafe(Class> clazz) throws InstantiationException {Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz); System.out.println("Unsafe 创建实例:" + o); }}
结果:
文章图片
可以看出三种方式都会破坏单例!
1.3 预防单例的破坏
预防反射破坏单例
在构造方法中加个判断即可:
//构造私有private Singleton1() {//防止反射破坏单例if(INSTANCE!=null){throw new RuntimeException("单例对象不能重复创建"); }System.out.println("private Singleton1()"); }
预防反序列化破坏单例
在
Singleton1()
中重写readResolve
方法://重写这个方法,如果序列化了,就会返回这个,不会返回反序列化的对象public Object readResolve(){returnINSTANCE; }
Unsafe破坏单例无法预防
2.枚举饿汉式
2.1 枚举单例实现
枚举实现单例:
/** * 枚举实现单例 * * @author xppll * @date 2021/12/24 22:23 */public enum Singleton2 {INSTANCE; //枚举的构造方法默认是private的,可以不写Singleton2() {System.out.println("private Singleton2()"); }//重写toString方法@Overridepublic String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode()); }//获得实例方法(这个可以不要,枚举变量都是public的)public static Singleton2 getInstance() {return INSTANCE; }//其他方法public static void otherMethod() {System.out.println("otherMethod()"); }}
测试:
/** * @author xppll * @date 2021/12/24 21:28 */public class TestSingleton {public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {//触发Singleton2类的初始化,会为类的静态变量赋予正确的初始值,单例对象就会被创建!Singleton2.otherMethod(); System.out.println("-----------------------------------"); System.out.println(Singleton2.getInstance()); System.out.println(Singleton2.getInstance()); }}//输出:private Singleton2()otherMethod()-----------------------------------singleton.Singleton2@2de80csingleton.Singleton2@2de80c
可以看出当调用
otherMethod()
时,就会触发类的加载,枚举对象就会创建,所以枚举实现单例是饿汉式的2.2 破坏单例
枚举类实现单例的好处:
1.反序列化无法破坏枚举单例
2.反射无法破坏枚举单例
栗子:
需要先修改反射破坏代码,枚举需要有参构造
public class TestSingleton {public static void main(String[] args) throws Exception {Singleton5.otherMethod(); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); System.out.println(Singleton5.getInstance()); System.out.println(Singleton5.getInstance()); //反序列化破坏单例serializable(Singleton2.getInstance()); //Unsafe破坏单例unsafe(Singleton2.class); //反射破坏单例reflection(Singleton2.class); }private static void unsafe(Class> clazz) throws InstantiationException {Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz); System.out.println("Unsafe 创建实例:" + o); }private static void serializable(Object instance) throws IOException, ClassNotFoundException {ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(instance); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); System.out.println("反序列化创建实例:" + ois.readObject()); }private static void reflection(Class> clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {Constructor> constructor = clazz.getDeclaredConstructor(String.class,int.class); constructor.setAccessible(true); System.out.println("反射创建实例:" + constructor.newInstance()); }}
结果:
文章图片
可以看出
1.反射是无法创建枚举对象!无法破坏枚举单例
2.反序列化也不会破坏枚举单例!
3.Unsafe依然会破坏!
3.懒汉式 实现代码如下:
/** * 懒汉式 * * @author xppll * @date 2021/12/25 08:34 */public class Singleton3 implements Serializable {//构造私有private Singleton3() {System.out.println("private Singleton3()"); }//唯一实例private static Singleton3 INSTANCE = null; public static Singleton3 getInstance() {//第一次调用的时候才创建if (INSTANCE == null) {INSTANCE = new Singleton3(); }return INSTANCE; }//其他方法public static void otherMethod() {System.out.println("otherMethod()"); }}
测试:
/** * @author xppll * @date 2021/12/24 21:28 */public class TestSingleton {public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {Singleton3.otherMethod(); System.out.println("-----------------------------------"); System.out.println(Singleton3.getInstance()); System.out.println(Singleton3.getInstance()); }}
结果:
文章图片
可以看出只有在第一次调用
getInstance()
时才会创建唯一的单例对象,因此是懒汉式的。但是这种方式在多线程环境下是会有问题的,可能多个线程会同时执行
INSTANCE = new Singleton3();
。因此这里需要在getInstance()
方法上加上synchronized
关键字保证多线程下的正确性:public static synchronized Singleton3 getInstance() {//第一次调用的时候才创建if (INSTANCE == null) {INSTANCE = new Singleton3(); }return INSTANCE; }
但是这种方法是有问题的,第一次创建完对象后,以后的操作是不需要在加锁的,所以这种方式会影响性能!
我们的目标应该是第一次创建单例的时候给予保护,后续操作则不需要加锁保护!
4.双检锁懒汉式 针对上面的问题,这里给出第四种方法双检锁懒汉式进行优化:
/** * 双检锁懒汉式 * * @author xppll * @date 2021/12/25 08:53 */public class Singleton4 {//构造私有private Singleton4() {System.out.println("private Singleton4()"); }//唯一实例//这里volatile的作用是保证共享变量有序性!private static volatile Singleton4 INSTANCE = null; //双检锁优化public static synchronized Singleton4 getInstance() {//实例没创建,才会进入内部的 synchronized 代码块,提高性能,防止每次都加锁if (INSTANCE == null) {//可能第一个线程在synchronized 代码块还没创建完对象时,第二个线程已经到了这一步,所以里面还需要加上判断synchronized (Singleton4.class) {//也许有其他线程已经创建实例,所以再判断一次if (INSTANCE == null) {INSTANCE = new Singleton4(); }}}return INSTANCE; }//其他方法public static void otherMethod() {System.out.println("otherMethod()"); }}
5.内部类懒汉式 内部类懒汉式单例实现:
/** * 内部类懒汉式 * * @author xppll * @date 2021/12/25 09:24 */public class Singleton5 {//构造私有private Singleton5() {System.out.println("private Singleton5()"); }//静态内部类实现懒汉式单例,静态变量的创建会放在静态代码块里执行,jvm会保证其线程安全//只有第一次用到内部类时,才会初始化创建单例private static class Holder {static Singleton5 INSTANCE = new Singleton5(); }//获得实例方法public static Singleton5 getInstance() {return Holder.INSTANCE; }//其他方法public static void otherMethod() {System.out.println("otherMethod()"); }}
测试:
/** * @author xppll * @date 2021/12/24 21:28 */public class TestSingleton {public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {Singleton5.otherMethod(); System.out.println("-----------------------------------"); System.out.println(Singleton5.getInstance()); System.out.println(Singleton5.getInstance()); }}
结果:
文章图片
可以看出内部类实现单例也是懒汉式的!
6.JDK中单例的体现 Runtime 体现了饿汉式单例
文章图片
System类下的Console 体现了双检锁懒汉式单例
文章图片
Collections 中的 EmptyNavigableSet内部类懒汉式单例
文章图片
Collections 中的ReverseComparator.REVERSE_ORDER 内部类懒汉式单例
文章图片
Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例
文章图片
到此这篇关于Java设计模式之单例模式示例详解的文章就介绍到这了,更多相关Java单例模式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 事件代理
- Java|Java OpenCV图像处理之SIFT角点检测详解
- java中如何实现重建二叉树
- 数组常用方法一
- 【Hadoop踩雷】Mac下安装Hadoop3以及Java版本问题
- Java|Java基础——数组
- RxJava|RxJava 在Android项目中的使用(一)
- java之static、static|java之static、static final、final的区别与应用
- Java基础-高级特性-枚举实现状态机