java 单例模板_Java设计模式001 --- 单例模式(示例代码)

前言

什么是单例模式?就是在一个应用程序中,一个类的实例有且仅有一个;这个类负责创建该类的实例;

一般来说单例是有状态的对象,比如全局设置、数据库dao实例、全局资源等,并且可以根据需求延迟加载或者即时加载;

即时加载单例模式

1、静态域单例(我不习惯别人说的饿汉、懒汉)

public classSingleton1 {private static Singleton1 instance = newSingleton1();//是有构造器, 防止被实例化

privateSingleton1() {

}public staticSingleton1 getInstance() {returninstance;

}public voiddoWhatever() {

}

}

使用方法:SingleTon1.getInstance()

特点:在类加载的时候就初始化好了,无线程安全问题;

即时加载,但是存在单例被破坏的风险,如使用反射、序列化

反射方式:

public static void main(String[] args) throwsNoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

Class clazz = Singleton1.class;

Constructor constructor =clazz.getDeclaredConstructor();

constructor.setAccessible(true);

Singleton1 singleton1=constructor.newInstance();

System.out.println(singleton1==Singleton1.getInstance());

}

20200516205942935010.png

序列化方式(前提是单例类实现了Serializale):

public static voidmain(String[] args) {

Singleton1 singleton=Singleton1.getInstance();

File file= new File("srcmainesourcessingleton.txt");try (FileOutputStream fos = newFileOutputStream(file);

ObjectOutputStream oos= newObjectOutputStream(fos)) {

oos.writeObject(singleton);

}catch(IOException ex) {

ex.printStackTrace();

}try (FileInputStream fis = newFileInputStream(file);

ObjectInputStream ois= newObjectInputStream(fis)) {

Singleton1 singleton1=(Singleton1) ois.readObject();

System.out.println(singleton==singleton1);

}catch (IOException |ClassNotFoundException e) {

e.printStackTrace();

}

}

20200516205943210383.png

2、上述问题的解决方案

为了解决反射对单例造成的破坏,可以做如下修改:在私有构造方法中判断实例是否为null,否则抛运行时异常

//是有构造器, 防止被实例化

privateSingleton1() {if (instance != null) {throw newRuntimeException();

}

}

20200516205943307056.png

上述方法对序列化不起作用,这也从侧面验证了反序列化创建的对象不依赖类的构造器,而是由JVM创建的;

为了解决序列化对单例造成的破坏,可做如下修改:在序列化类中添加私有readResolve方法

privateObject readResolve() {returninstance;

}

20200516205943773823.png

虽然有以上方法可以解决上述问题,但是有更简便的单例模式 --- 枚举单例

3、枚举单例模式

枚举单例有如下几个有点:线程安全、能够防止反射和序列化带来的破坏、实现简单

public enumSingletonEnum {

INSTANCE;public voiddoWhatever() {

System.out.println("Single Enum.");

}

}

使用方法:SingletonEnum.INSTANCE.doWhatever()

验证下反射和序列化场景是否会破坏单例:

public static void main(String[] args) throwsNoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

SingletonEnum instance=SingletonEnum.INSTANCE;//验证两次获取的是否是同一个对象

System.out.println(instance ==SingletonEnum.INSTANCE);//序列化场景

File file = new File("srcmainesourcessingleton.txt");try (FileOutputStream fos = newFileOutputStream(file);

ObjectOutputStream oos= newObjectOutputStream(fos)) {

oos.writeObject(instance);

}catch(IOException ex) {

ex.printStackTrace();

}try (FileInputStream fis = newFileInputStream(file);

ObjectInputStream ois= newObjectInputStream(fis)) {

SingletonEnum instance2=(SingletonEnum) ois.readObject();

System.out.println(instance==instance2);

}catch (IOException |ClassNotFoundException e) {

e.printStackTrace();

}//反射场景

Class clazz = SingletonEnum.class;

Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);

constructor.setAccessible(true);

SingletonEnum instance1=constructor.newInstance();

System.out.println(instance==instance1);

}

运行结果:

20200516205943846084.png

为啥使用反射的时候会抛异常呢?这是因为反射的源码是这样写的:

20200516205943932016.png

那么又为啥枚举单例能够保证单例不被序列化破坏呢?

这是因为Java中规定,每个枚举变量在JVM中都是唯一的,并且Java还规定,枚举类在反序列化时使用枚举类的valueOf方法

枚举类反编译结果如下:

20200516205944019901.png

线程安全又是为什么?从反编译结果就能看出,INSTANCE是静态的,并且初始化时就创建了实例

未完待续。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值