Java单例模式及破坏单例的解决方法

单例模式分为懒汉式与饿汉式

先来说说懒汉式

public class Test {
    public static void main(String[] args) {
		LazySingleton.getInstance();
    }
}
//懒汉式单例
class LazySingleton{
    private static LazySingleton instance= null;
    private LazySingleton(){

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

上面的懒汉式有什么缺点呢?

首先就是线程不安全,如果在多线程的情况下,首先线程一完成判断,但是还没有实例化对象时,线程二进入判断,此时线程一完成实例化,而线程二也会再次实例化,从而破坏单例。

解决方法是:加锁

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

这个单例又有什么缺点呢?

如果锁的是静态方法,那么相当于锁的整个类,比较消耗内存资源。因此引入双重检查机制。

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

这个单例的优点就是锁的范围小了。降低了内存开销。

那么它真的完美了吗?其实还是有点小小隐患,那就是在实例化对象的时候发生重排序(在java的语言规范中是允许单线程进行重排序的,增加效率,但是在多线程中就会存在隐患),这时就需要引入我们的volatile关键字,来避免重排序。

class LazySingleton{
    //加上volatile关键字,使的所有线程都能看到内存状态,保证内存的可见性。
    private volatile static LazySingleton instance = null;		
    private LazySingleton(){

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

使用了双重检查和volatile关键字之后,在性能和线程安全方面都得到了满足~~

饿汉式

public class Test {
    public static void main(String[] args) {
        HungrySingleton.getInstance();
    }
}
//饿汉式
class HungrySingleton{
    //final声明的变量必须在类加载完成的时候就赋值
    private final static HungrySingleton instance;
    static {
        instance = new HungrySingleton();
    }
    private HungrySingleton(){

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

饿汉式有什么优点呢?

那就是在类加载的时候就已经初始化了,避免了线程同步的问题,当然缺点也就是没了延迟加载的特性,如果没有使用过,就会造成资源的浪费。

还有一种基于静态内部类的单例模式,由于类初始化的延迟加载(只有一个线程会获得这个静态内部类对象的初始化锁),会使得线程2在线程1初始化类对象的时候看不到静态内部类中的重排序。
话不多说,直接上Demo吧。

public class StatIcinnerSingleton {
    private StatIcinnerSingleton(){
    }
    private static class InnerClass{
        private static StatIcinnerSingleton instance = new StatIcinnerSingleton();
    }
    public static StatIcinnerSingleton getInstance(){
        return InnerClass.instance;
    }
}

重点来了,哪些情况会破坏单例呢?如何预防呢?

  1. 序列化破坏单例
public class Test {
    public static void main(String[] args) throws Exception {
        LazySingleton instance = LazySingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file"));
        oos.writeObject(instance);
        File f = new File("file");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
        LazySingleton newInstance = (LazySingleton) ois.readObject();
        
        System.out.println(instance==newInstance);	//结果为false
        
    }
}
//懒汉式单例
class LazySingleton implements Serializable{
    private volatile static LazySingleton instance = null;
    private LazySingleton(){

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

console打印结果为false,单例已经被破坏了~
如何解决呢?

    //在单例类中加入这个方法即可
    private Object readResolve(){
        return instance;
    }

它的作用是不管序列化重新实例化对象没有,都会返回指定的这个对象。

  1. 反射破坏单例
public class Test {
    public static void main(String[] args) throws Exception {
        Class c = LazySingleton.class;
        Constructor constructor = c.getDeclaredConstructor();
        constructor.setAccessible(true);
        //正常单例
        LazySingleton instance = LazySingleton.getInstance();
        LazySingleton newInstance = (LazySingleton) constructor.newInstance();
        
        System.out.println(instance==newInstance);	//打印false
        
    }
}
//懒汉式单例
class LazySingleton implements Serializable{
    private volatile static LazySingleton instance = null;
    private LazySingleton(){

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

console打印false,单例又被破坏了~,因为通过反射打开了构造器的权限(constructor.setAccessible(true))。

那又如何解决呢?

对于饿汉式与静态内部类单例(因为在类加载的时候就已经创建了本类对象),可以在构造器中加入反射拦截代码即可(判断对象如果不为空则抛出异常)。

至于懒汉式嘛,现在还没有想到如何防止反射攻击(汗。。。),那位大佬路过,可以讨论下~

你可能感兴趣的:(Java)