单例模式详解(包括反射破坏和序列化破坏)

单例模式详解

1. 饿汉模式(线程安全)

public class Singleton {

    private final static Singleton INSTANCE = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return INSTANCE;
    }
}

优点:写法简单,类加载就进行实例化,避免了线程安全的问题
缺点:没有达到lazyloading,内存造成了浪费

2. 懒汉式(线程安全)

public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

优点:lazyLoading,线程安全
缺点:效率太低,每次都要进行加排它锁,其实只要第一次创建对象时加锁,后面再调用直接return就好了

3. 第二种方式的改进(双重检查)

public class Singleton {

    private static volatile Singleton singleton;

    private Singleton() {}

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

优点:线程安全,效率较高,推荐使用

4. 静态内部类方式

public class Singleton {

    private Singleton() {}

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

优点:类似饿汉模式,在类加载的时候,并不会加载静态内部类,当需要用到的时候,再进行加载,在这个加载过程中,会创建Instance对象。(线程安全,效率较高,延迟加载)

5. 枚举类实现(强烈推荐)

public enum Singleton {
    INSTANCE;
    public void whateverMethod() {

    }
}

优点:线程安全,防止序列化破坏单例


单例模式的反射破坏

如果有如下代码:

public class Singleton {
    private static Singleton instance = new Singleton();  

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

测试类:

public class Test {
    public static void main(String[] args) throws Exception{
        Singleton s1 = Singleton.getInstance();

        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton s2 = constructor.newInstance();

        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());

    }
}

输出结果:

705927765
366712642

分析:从结果来看,创建了两个单例对象,这样就破坏了单例模式。破坏了的原因,是将构造方式的private的检查,通过constructor.setAccessible(true);进行了屏蔽。
解决办法:将构造方法的调用次数设置一个标志,来进行表示调用次数,超过一次之后,再进行调用构造方法直接抛出异常。
解决代码如下:
单例类:

public class Singleton {
     private static int count = 0;

        private static Singleton instance = null;

        private Singleton(){
            synchronized (Singleton.class) {
                if(count > 0){
                    throw new RuntimeException("创建了两个实例");
                }
                count++;
            }

        }

        public static Singleton getInstance() {
            if(instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
}

测试类:

import java.lang.reflect.Constructor;

public class Test {
    public static void main(String[] args) throws Exception {

        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton s1 = constructor.newInstance();
        Singleton s2 = constructor.newInstance();
    }
}

测试结果:

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
    at interview.Test.main(Test.java:11)
Caused by: java.lang.RuntimeException: 创建了两个实例
    at interview.Singleton.<init>(Singleton.java:11)
    ... 5 more

至此解决了反射破坏单例模式的问题。


序列化破坏单例模式

talk is cheap,show me the code

package com.serialize;  

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import org.junit.Test;  

public class SerSingleton implements Serializable  
{  
    private static final long serialVersionUID = 1L;  

    String name;  

    private SerSingleton()  
    {  
        System.out.println("Singleton is create");  
        name="SerSingleton";  
    }  

    private static SerSingleton instance = new SerSingleton();  

    public static SerSingleton getInstance()  
    {  
        return instance;  
    }  

    public static void createString()  
    {  
        System.out.println("createString in Singleton");  
    }  



public static void main(String[] args) throws IOException, ClassNotFoundException{
    SerSingleton s1= null;  
    SerSingleton s = SerSingleton.getInstance();  

    FileOutputStream fos = new FileOutputStream("SerSingleton.obj");  
    ObjectOutputStream oos = new ObjectOutputStream(fos);  
    oos.writeObject(s);  
    oos.flush();  
    oos.close();  

    FileInputStream fis = new FileInputStream("SerSingleton.obj");  
    ObjectInputStream ois = new ObjectInputStream(fis);  
    s1 = (SerSingleton)ois.readObject();  
    System.out.println(s==s1);  
}

}

输出结果:

Singleton is create
false

解决办法:

在被序列化的类中添加readResolve方法
    Deserializing an object via readUnshared invalidates the stream handle associated with the returned object. Note that this in itself does not always guarantee that the reference returned by readUnshared is unique; the deserialized object may define a readResolve method which returns an object visible to other parties, or readUnshared may return a Class object or enum constant obtainable elsewhere in the stream or through external means. If the deserialized object defines a readResolve method and the invocation of that method returns an array, then readUnshared returns a shallow clone of that array; this guarantees that the returned
array object is unique and cannot be obtained a second time from an invocation of readObject or readUnshared on the ObjectInputStream, even if the underlying data stream has been manipulated.

添加readResolve方法

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SerSingleton implements Serializable {

private static final long serialVersionUID = 1L;
String name;

private SerSingleton() {
System.out.println("Singleton is create");
name = "SerSingleton";
}

private static SerSingleton instance = new SerSingleton();

public static SerSingleton getInstance() {
return instance;
}

public static void createString() {
System.out.println("createString in Singleton");
}
private Object readResolve(){
return instance;
}


public static void main(String[] args) throws IOException, ClassNotFoundException {
SerSingleton s1 = null;
SerSingleton s = SerSingleton.getInstance();

FileOutputStream fos = null;
ObjectOutputStream oos = null;

FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fos = new FileOutputStream("SerSingleton.obj");
oos = new ObjectOutputStream(fos);
oos.writeObject(s);
} finally {
oos.flush();
oos.close();
fos.close();
}

try{
fis = new FileInputStream("SerSingleton.obj");
ois = new ObjectInputStream(fis);
s1 = (SerSingleton) ois.readObject();
}finally{
ois.close();
fis.close();
}
System.out.println(s == s1);
}

}

总结,实现单例模式的唯一推荐方法,使用枚举类来实现。使用枚举类实现单例模式,在对枚举类进行序列化时,还不需要添加readRsolve方法就可以避免单例模式被破坏。


clone 能不能破坏单例模式

不能,虽然clone()方法,是直接从内存区copy一个对象,但是单例的类不能实现cloneable接口,普通对象直接调用clone()会抛出异常。


  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值