设计模式_单例模式
创建型设计模式
单例模式
简介: 令某一个类在程序中只存在一个实例
实例化方法
饿汉式:基本方式
在载入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;
}
}

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

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);
}
}

浙公网安备 33010602011771号