设计模式_单例模式

创建型设计模式

单例模式

简介: 令某一个类在程序中只存在一个实例

实例化方法

饿汉式:基本方式

在载入JVM虚拟机时直接实例化

我们知道,类加载的方式是按需加载,且加载一次。因此,在下述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
public class Demo1 {
public static void main(String[] args) {
for (int i = 1; i <= 999; ++i) {
new Thread(new Runnable() {
@Override
public void run() {
Singleton instance = Singleton.getInstance();
System.out.println(instance); // 打印结果都一样
}
}).start();
}
}
}

懒汉式:基本方式

做到懒加载(懒汉式需要考虑线程安全问题)

优点:当需要时对对象进行实例化,节约了内存

缺点:存在线程同步问题,需要加锁,然而此种方法对getInstance()进行加锁,导致即使instance已经不为null,仅仅只单纯获取对象时依然受到锁的限制,效率过低。

class Singleton {
private static volatile Singleton instance; // volatile保证线程同步
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

懒汉式:双重校验锁

保留了基本方式的优点,也解决了基本方式的缺点

class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

懒汉式:静态内部类

静态内部类单例模式由内部类创建,由于JVM在加载外部类时,不会加载静态内部类,只要当静态内部类被访问时才会进行加载,并初始化静态变量,静态变量被static修饰保证只会被实例化一次,切严格保证实例化顺序。

class Singleton {
private Singleton() {}
private static class SingletonHolder {
private final static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

image

双重校验锁与静态内部类都是非常推荐的懒汉式单例模式

饿汉式:枚举

image

enum Singleton {
/**
* Singleton实例
*/
INSTANCE
}
public class Demo {
public static void main(String[] args) {
for (int i = 1; i <= 999; ++i) {
new Thread(new Runnable() {
@Override
public void run() {
Singleton instance = Singleton.INSTANCE;
System.out.println(instance.hashCode());
}
}).start();
}
}
}

总结

一般情况下,不建议使用懒汉式基本方式,建议使用饿汉式基本方式。只有在要明确实现 lazy loading 效果时,才会使用静态内部类方式。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用双重校验锁方式。

破坏单例模式

序列化与反序列化

序列化:对象➡字节串;是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

目的: 1、以某种存储形式使自定义对象持久化;2、将对象从一个地方传递到另一个地方;

反序列化:字节串➡对象;将存储的对象信息重新转换为对象;

目的:1、将持久化后的对象重新实例化;2、接收其他地方传递来的对象

class Singleton implements Serializable {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
public class Demo1 {
public static void main(String[] args) throws Exception {
writeSerializable();
Singleton singleton = readSerializable();
Singleton singleton1 = readSerializable();
System.out.println(singleton == singleton1);
}
public static void writeSerializable() throws Exception {
Singleton instance = Singleton.getInstance();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
new FileOutputStream("C:\\Users\\80946\\Desktop\\temp.txt"));
objectOutputStream.writeObject(instance);
objectOutputStream.close();
}
public static Singleton readSerializable() throws Exception {
ObjectInputStream inputStream = new ObjectInputStream(
new FileInputStream("C:\\Users\\80946\\Desktop\\temp.txt"));
Singleton singleton = (Singleton) inputStream.readObject();
inputStream.close();
return singleton;
}
}

输出结果:

false

由此可见,Singleton在本程序中出现了两个实例,违背的单例模式。序列化与反序列化破坏了单例模式。

解决方案

Singleton类中添加 readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new除的对象

此解决方案与inputStream.readObject()的内部实现有关。

class Singleton implements Serializable {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public Object readResolve() {
return INSTANCE;
}
public static Singleton getInstance() {
return INSTANCE;
}
}
public class Demo1 {
public static void main(String[] args) {
writeSerializable();
Singleton singleton = readSerializable();
Singleton singleton1 = readSerializable();
System.out.println(singleton == singleton1);
}
public static void writeSerializable() {
Singleton instance = Singleton.getInstance();
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(
new FileOutputStream("C:\\Users\\80946\\Desktop\\temp.txt"))) {
objectOutputStream.writeObject(instance);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Singleton readSerializable() {
Singleton singleton = null;
try (ObjectInputStream inputStream = new ObjectInputStream(
new FileInputStream("C:\\Users\\80946\\Desktop\\temp.txt"))) {
singleton = (Singleton) inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return singleton;
}
}

输出结果:

true

反序列化得到的时同一对象,遵守了单例模式

反射

Java反射:在运行状态中,程序可以拿到一个类或者一个对象的所有属性和方法,并对其进行修改。

class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
public class Demo1 {
public static void main(String[] args) throws Exception {
Class<Singleton> singletonClass = Singleton.class;
Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Singleton singleton = declaredConstructor.newInstance();
Singleton singleton1 = declaredConstructor.newInstance();
System.out.println(singleton == singleton1);
}
}

输出结果:

false

由此可见通过反射可以改变构造函数的可见性,导致可以通过构造函数直接

解决方案

饿汉式

class Singleton {
private final static Singleton INSTANCE = new Singleton();
/**
* 当单例对象已经被实现后,如果构造方法再次被调用,就会发生异常
*/
private Singleton() {
if (INSTANCE != null) {
throw new RuntimeException("不能创建多个对象");
}
}
public static Singleton getInstance() {
return INSTANCE;
}
}

懒汉式

懒汉式存在多线程问题,故处理方式和饿汉式不一样

// 静态内部类方式,其他的懒汉式也可以这样处理
class Singleton {
private volatile static boolean flag = false;
private Singleton() {
synchronized (Singleton.class) {
if (flag) {
throw new RuntimeException();
}
flag = true;
}
}
private static class SingletonHolder {
private final static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
public class Demo1 {
public static void main(String[] args) throws Exception {
for (int i = 0; i <= 99; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Class<Singleton> singletonClass = Singleton.class;
Singleton singleton = null;
Singleton singleton1 = null;
try {
Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
singleton = declaredConstructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(singleton);
}
}).start();
}
Singleton instance = Singleton.getInstance();
System.out.println(instance);
}
}

输出结果:

仅有一个输出不为null,故Singleton只实例化了一次

但经测试,此时类已被破坏,既即使不通过反射,通过正常方式也无法获取对象

原因分析

只能得到第一次反射创建的那个对象,但这个对象似乎无法存到SingletonHolder.INSTANCE中。。导致使用正常方式获取单例对象时,就会执行Singleton INSTANCE = new Singleton(); 结果就又异常了,其他的懒汉式也有这种问题。。。。(我不会解决)
饿汉式没有这种问题

碰巧的解决方法

// 仅限懒汉式:静态内部类法使用
// 其他方法使用会栈溢出
class Singleton {
private volatile static boolean flag = false;
private Singleton() {
synchronized (Singleton.class) {
Singleton instance = Singleton.getInstance();
if (flag) {
throw new RuntimeException();
}
flag = true;
}
}
private static class SingletonHolder {
private final static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
public class Demo1 {
public static void main(String[] args) throws Exception {
for (int i = 0; i <= 99; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Class<Singleton> singletonClass = Singleton.class;
Singleton singleton = null;
Singleton singleton1 = null;
try {
Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
singleton = declaredConstructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(singleton);
}
}).start();
}
Singleton instance = Singleton.getInstance();
System.out.println(instance);
}
}
posted @   Voca  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
点击右上角即可分享
微信分享提示