设计模式系列之单例模式
单例模式的实现
在java中实现单例模式的方法有很多,比如饿汉式、懒汉式、静态内部类、枚举类型。
单例模式的核心就是只产生一个单例,外部无法通过new来创造新的类对象,也无法重复获得不同的类对象,只可根据该类暴露的一个getinstance()方法来获取初始化以后的单例对象。
以下为单例模式的具体实现:
1、饿汉式
public class HungrySingletonTest {
public static void main(String[] args) {
HungrySingleton instance1 = HungrySingleton.getInstance();
HungrySingleton instance2 = HungrySingleton.getInstance();
System.out.println(instance1==instance2);
}
}
class HungrySingleton{
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return instance;
}
}
标准饿汉式实现,通过private私有的无参构造来防止外部来new这个对象,饿汉式会在一开始就在静态代码块初始化一个instance类对象,需要的时候调用getInstance()方法取出instance对象即可。
2、懒汉式
public class LazySingletonTest {
public static void main(String[] args) {
new Thread(() -> {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
}).start();
new Thread(() -> {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
}).start();
}
}
class LazySingleton {
private volatile static LazySingleton instance;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
这种方法为标准的DCL懒汉式,一开始不初始化类对象,当使用的时候临时创建类对象,为防止重复创建需对对象进行加锁,为了防止JVM的底层指令重排机制造成多线程对对象重复进行创建,使用volatile关键字来防止指令重排。
3、静态内部类
public class InnerClassSingletonTest {
public static void main(String[] args){
InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
InnerClassSingleton instance2 = InnerClassSingleton.getInstance();
System.out.println(instance1==instance2);
}
}
class InnerClassSingleton {
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton(){
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
这种方法通过类中的私有内部类进行创建,只对外提供了一个公共方法getInstance,在初始化的时候调用私有类,获取属性时创建单例。
4、枚举
public enum EnumSingleton {
INSTANCE;
public void print(){
System.out.println(this.hashCode());
}
}
class EnumTest{
public static void main(String[] args){
EnumSingleton.INSTANCE.print();
}
}
利用反射破坏单例模式
其实除了枚举方式实现以外,其他类型都可以通过反射来破坏单例。
下面以静态内部类进行举例:
测试反射破坏单例
public class InnerClassSingletonTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
InnerClassSingleton instance = InnerClassSingleton.getInstance();
System.out.println(innerClassSingleton==instance);
}
}
因此可以通过在初始化构造函数中判断单例是否存在,存在则抛出异常进行处理。
class InnerClassSingleton{
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton(){
if (InnerClassHolder.instance!=null){
throw new RuntimeException("单例不允许多个实例");
}
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
解决单例模式的序列化问题
还是以静态内部类举例,此时我们希望将类序列化保存在文件中。
class InnerClassSingleton implements Serializable {
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton(){
if (InnerClassHolder.instance!=null){
throw new RuntimeException("单例不允许多个实例");
}
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
测试序列化然后反序列化以后是否还是原来的单例。
public class InnerClassSingletonTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
InnerClassSingleton instance = InnerClassSingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerializableTest"));
oos.writeObject(instance);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("SerializableTest"));
InnerClassSingleton innerClassSingleton = (InnerClassSingleton)ois.readObject();
ois.close();
System.out.println(innerClassSingleton==instance);
}
}
此时可得两者是不同的单例,通过查看Serializable类寻找到解决方法
通过在类中实现Object readResolve() throws ObjectStreamException
来解决序列化问题。此时增加以下代码:
Object readResolve() throws ObjectStreamException{
return InnerClassHolder.instance;
}
再次测试,若出现如下报错
则需要添加序列化的一个版本号,同样查看Serializable类,可以发现
如不添加版本号会根据类中的数据自动生成,如果对类进行了修改,则存进的和取出的版本号不相同,则报错,如果自己添加则会根据添加的版本号直接获取数据。
static final long serialVersionUID = 42L;
最终单例类如下:
class InnerClassSingleton implements Serializable {
static final long serialVersionUID = 42L;
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton(){
if (InnerClassHolder.instance!=null){
throw new RuntimeException("单例不允许多个实例");
}
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
Object readResolve() throws ObjectStreamException{
return InnerClassHolder.instance;
}
}
再次测试
public class InnerClassSingletonTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
InnerClassSingleton instance = InnerClassSingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerializableTest"));
oos.writeObject(instance);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("SerializableTest"));
InnerClassSingleton innerClassSingleton = (InnerClassSingleton)ois.readObject();
ois.close();
System.out.println(innerClassSingleton==instance);
}
}
验证枚举的安全性
我们来试图尝试用反射破坏枚举
public enum EnumSingleton {
INSTANCE;
public void print(){
System.out.println(this.hashCode());
}
}
class EnumTest{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
declaredConstructor.newInstance("INSTANCE",0);
}
}
由此可以验证反射无法对枚举类型进行修改,查看反射的newInstance方法
如图可以得到相同的结论。
由此可知枚举类型是相对安全的一种实现单例的方式。
本文地址:https://blog.csdn.net/qq_40359381/article/details/112236367
下一篇: RESTful API的理解