来源:https://blog.csdn.net/ayunlong/article/details/130297205?spm=1001.2014.3001.5502
Java设计模式-单例模式(Singleton Pattern)
单例模式可以非常有效的节省系统开支
什么是单例模式?
单例就是单个实例,在进程所分配的内存中仅能存在唯一一个对象。
单例模式就是通过技术手段,来实现这个对象在内存中是唯一的。
为什么要使用单例模式?
实例的创建需要系统资源的开支,特别是类功能负责、读取IO、网络、数据库连接等操作更是非常大的开支,那么创建一个实例,所有的功能都使用该实例是不不是就可以节省系统开支。
什么是单例模式
单例模式的实现方式及线程安全问题
第一种:饿汉式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package org.singleton;
public class HungryPrinter { private static final HungryPrinter instance = new HungryPrinter(); private HungryPrinter(){} public static HungryPrinter getInstance(){ return instance; } }
|
饿汉式的有以下几个特点,这三个特点共同完成了单个实例的限制:
- 使用private私有化实例;final限制修改对象;static在类加载时初始化Printer对象
- 私有化构造函数,禁止主动new对象
- 只能通过getInstance获取实例
static关键字决定了,类加载时便会初始化Printer对象,哪怕没有使用这个对象,这是一种资源浪费。
第二种:懒汉式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package org.singleton;
public class LazyPrinter { private static LazyPrinter instance; private LazyPrinter(){}
public static LazyPrinter getInstance(){ if (instance == null){ instance = new LazyPrinter(); } return instance; } }
|
上面的代码是不是看来很完美,解决了饿汉式的资源浪费问题,但引入了新的问题,多线程安全!多么头疼的词语。什么情况下会引起多线程安全?
- 存在多线程同时访问一个资源(同时访问instace)
- 访问非方法内部的局部变量(instance就是非方法内部的局部变量)
刚写的懒汉式代码完全符合,假设T1与T2两个线程,同时调用了getInstance方法,T1运行到if (instance == null)处后CPU时间片用完了,这时候轮到T2,T2执行的时候T1还没有成功创建,所以成功创建了对象,当T1重新拿回执行权时,恰好也执行了new操作,又重新创建了对象, 这样就破坏了单例模式。
第三种:双锁检测(double check)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package org.singleton;
public class DoubleCheckPrinter { private static volatile DoubleCheckPrinter instance; private DoubleCheckPrinter(){}
public static DoubleCheckPrinter getInstance(){ if (instance == null){ synchronized (DoubleCheckPrinter.class){ if(instance == null){ instance = new DoubleCheckPrinter(); } } } return instance; } }
|
volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排它锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。
第四种:静态内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package org.singleton;
public class InnerClassPrinter { private InnerClassPrinter(){} public static InnerClassPrinter getInstance(){ return InnerClassHolder.INSTANCE; }
private static final class InnerClassHolder { private static final InnerClassPrinter INSTANCE = new InnerClassPrinter(); } }
|
感觉和饿汉模式很类似,就是把实例化的过程放到了内部类,解决了类加载时便会初始化实例的缺陷,同时也解决了懒汉式的线程安全问题。
第五种:枚举单例
枚举类实现单例模式,是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,而且枚举类型是所有单例实现中唯一一种不会被破坏的单例模式
1 2 3 4 5 6
|
public enum Singleton { INSTANCE; }
|
说明:枚举方式属于饿汉式的方法
反序列化破坏单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package org.singleton;
public class Test { public static void main(String[] args) throws Exception { DoubleCheckPrinter printer = DoubleCheckPrinter.getInstance(); FileOutputStream fileOutputStream = new FileOutputStream(new File("printer.txt")); ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream); oos.writeObject(printer);
FileInputStream inputStream = new FileInputStream("printer.txt"); ObjectInputStream oosInput = new ObjectInputStream(inputStream); System.out.println(printer == oosInput.readObject());
} }
false
|
解决办法:
在类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就会返回这个方法的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package org.singleton;
public class InnerClassPrinter implements Serializable { private InnerClassPrinter(){} public static InnerClassPrinter getInstance(){ return InnerClassHolder.INSTANCE; }
private static final class InnerClassHolder { private static final InnerClassPrinter INSTANCE = new InnerClassPrinter(); } public Object readResolve(){ return InnerClassPrinter.INSTANCE; } }
|
反射破坏单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package org.singleton;
import java.lang.reflect.Constructor;
public class Test { public static void main(String[] args) throws Exception { DoubleCheckPrinter printer = DoubleCheckPrinter.getInstance(); DoubleCheckPrinter printer1 = DoubleCheckPrinter.getInstance();
System.out.println("通过getInstance获取的对象是否为同一个对象:" + (printer == printer1));
Class<?> cls = DoubleCheckPrinter.getInstance().getClass(); Constructor constructor = cls.getDeclaredConstructor(); constructor.setAccessible(true); DoubleCheckPrinter printer2 = (DoubleCheckPrinter) constructor.newInstance(); System.out.println("通过反射获取的对象是否为同一个对象:" + (printer == printer2)); } }
通过getInstance获取的对象是否为同一个对象:true 通过反射获取的对象是否为同一个对象:false
|
解决方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package org.singleton;
public class InnerClassPrinter { private static boolean flag = false; private InnerClassPrinter(){
synchronized(InnerClassPrinter.class){ if (flag){ throw new RuntimeException("不能2创建多个对象"); }
flag = true ; } } public static InnerClassPrinter getInstance(){ return InnerClassHolder.instance; }
private static volatile InnerClassPrinter instance;
private static final class InnerClassHolder { if(instance != null){ return instance; } private static final InnerClassPrinter instance = new InnerClassPrinter(); } }
|
Struts2单例模式的应用