单例模式

参考原文博客:点击进入

在此谢过原博主。

单例模式有5种形式;分别是饿汉模式懒汉模式双重检测锁模式静态内部类模式枚举模式。其中双重检测锁本人不清楚这么做的原因,并且原博主建议在JDK修复同步块嵌套漏洞之前不使用。静态内部类模式咋一看还不如写回饿汉模式,但是静态内部类模式实现了延迟加载,提供了系统性能。
以下是代码:
(1)饿汉模式

package cn.test.Singleton.one;

/**
 * 饿汉式
 * 未实现延时加载,线程安全?为什么说是线程安全,
 * 因为在类加载的时候就已经实例化了instance,所以当多个线程并发
 * 访问getInstance()的时候都是得到同一个实例instance,不会因为
 * 并发的问题出现实例化两个对象
 * @author cjc
 *
 */
public class SingleTon_EH {
    private static SingleTon_EH instance = new SingleTon_EH();

    private SingleTon_EH(){}

    public static SingleTon_EH getInstance(){
        return instance;
    }

}

(2)懒汉模式

package cn.test.Singleton.two;

/**
 * 懒汉式
 * 实现了延迟加载
 * 线程不安全。为什么线程不安全?
 * 因为在多个线程并发访问getInstance()的时候,可能因为
 * 并发的问题同时执行if(null == instance){instance = new SingleTon_LH();}
 * 这段代码,导致创建了不止一个实例。所以可以在getInstance()加synchronized锁,但是牺牲
 * 了高并发性能
 * 
 * 总的来说:
 * 实现延迟加载
 * 线程安全但是牺牲了高并发性能
 * 
 * @author cjc
 *
 */
public class SingleTon_LH {
    private static SingleTon_LH instance = null;

    private SingleTon_LH(){}

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

(3)双重检测锁模式

package cn.test.Singleton.three;

/**
 * 双重检测锁式单例模式
 * 实现延迟加载
 * 线程安全
 * 
 * 在jdk修复同步块嵌套之前不推荐,这里只作了解
 * @author cjc
 *
 */
public class SingleTon_DoubleLock {
    private static SingleTon_DoubleLock instance = null;
    private SingleTon_DoubleLock(){}

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

        return instance;
    }
}

(4)静态内部类模式

package cn.test.Singleton.four;

/**
 * 静态内部类式单例模式
 * 实现延迟加载。
 * 线程安全
 * @author cjc
 *
 */
public class SingleTon_staticInner {
    private SingleTon_staticInner(){}

    private static class Inner{
        public static final SingleTon_staticInner instance = new SingleTon_staticInner(); 
    }

    public static SingleTon_staticInner getInstance(){
        return Inner.instance;
    }
}

(5)枚举类模式

package cn.test.Singleton.five;

/**
 * 枚举式单例。
 * 未延迟加载
 * 线程安全
 * 原生防止发射与序列化击穿
 * @author cjc
 *
 */
public enum SingleTon_Enum {
    INSTANCE;
}

测试:

package cn.test.Singleton.instance;

import org.junit.Test;

import cn.test.Singleton.five.SingleTon_Enum;
import cn.test.Singleton.four.SingleTon_staticInner;
import cn.test.Singleton.one.SingleTon_EH;
import cn.test.Singleton.three.SingleTon_DoubleLock;
import cn.test.Singleton.two.SingleTon_LH;

public class Test1 {
    @Test
    public void test01(){
        SingleTon_EH s11 = SingleTon_EH.getInstance();
        SingleTon_EH s12 = SingleTon_EH.getInstance();
        System.out.println(s11 == s12);

        SingleTon_LH s21 = SingleTon_LH.getInstance();
        SingleTon_LH s22 = SingleTon_LH.getInstance();
        System.out.println(s21 == s22);

        SingleTon_DoubleLock s31 = SingleTon_DoubleLock.getInstance();
        SingleTon_DoubleLock s32 = SingleTon_DoubleLock.getInstance();
        System.out.println(s31 == s32);

        SingleTon_staticInner s41 = SingleTon_staticInner.getInstance();
        SingleTon_staticInner s42 = SingleTon_staticInner.getInstance();
        System.out.println(s41 == s42);

        SingleTon_Enum s51 = SingleTon_Enum.INSTANCE;
        SingleTon_Enum s52 = SingleTon_Enum.INSTANCE;
        System.out.println(s51.getClass()+","+s52.getClass());
        System.out.println(s51 == s52);


    }
}

输出:

true
true
true
true
class cn.test.Singleton.five.SingleTon_Enum,class cn.test.Singleton.five.SingleTon_Enum
true

总结:以上每一个都是线程安全的,其中懒汉模式是通过synchronize保证了线程安全但牺牲了高并发性能。

以上的几种模式都有漏洞,可以用序列化的方式或者是用反射机制去破解,得到两个不同的实例,下面我们看看如何避免这种情况的发生:
(1)序列化
例子:饿汉模式:
代码是:

public class SingleTon_EH implements Serializable{

    private static final long serialVersionUID = 1L;

    private static SingleTon_EH instance = new SingleTon_EH();

    private SingleTon_EH(){}

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

序列化代码:

    /**
     * 测试序列化,饿汉模式
     */
    @Test
    public void test03(){
        SingleTon_EH instance = SingleTon_EH.getInstance();
        ObjectOutputStream out = null;
        ObjectInputStream in = null;
        try{
            out = new ObjectOutputStream(
                    new FileOutputStream("temp.txt"));
            out.writeObject(instance);

            //反序列化
            in = new ObjectInputStream(
                    new FileInputStream("temp.txt"));
            SingleTon_EH insIn = (SingleTon_EH)in.readObject();
            System.out.println(instance);
            System.out.println(insIn);
        }catch (Exception e) {

        }finally{

        }
    }

那么得到的结果是:

cn.test.Singleton.one.SingleTon_EH@b23b25c
cn.test.Singleton.one.SingleTon_EH@575fadcf

在这里,必须提醒一下,当实例化一个对象,再把这个对象序列化到磁盘,以后每次反序列化得到的对象都是不同的,也就是把in流close关了,再打开,再反序列化,得到的对象和之前反序列化的对象是不同的。

那如何去避免了。我们用序列化知识的一个方法readResolve()方法,readResolve()方法有什么作用呢?readResolve()是序列化机制的一个特殊的方法,它可以实现保护性复制整个对象。这个方法会紧接着readObject()或defaultReadObject()被调用,替换读取到的序列化对象,并且将它返回,修改饿汉模式代码如下:

public class SingleTon_EH implements Serializable{

    private static final long serialVersionUID = 1L;

    private static SingleTon_EH instance = new SingleTon_EH();

    private SingleTon_EH(){}

    public static SingleTon_EH getInstance(){
        return instance;
    }

    /**
     * 如何避免反序列化得到不同的实例呢?
     * 反序列化时,如果定义了readResolve(),则直接次方法指定的对象
     */
    public Object readResolve(){
        return instance;
    }   
}

再次执行test03()方法得到

cn.test.Singleton.one.SingleTon_EH@b23b25c
cn.test.Singleton.one.SingleTon_EH@b23b25c

同样的道理,将其他模式都一样的加上以上代码就可以防止序列化了。

(2)反射
反射机制很强大,可以得到private的构造器,并且将private的构造器设为可访问的,那么就可以通过反射得到的构造器new出一个实例出来了,这样又破坏了单例模式只有唯一的实例的情况。
例子:懒汉模式

public class SingleTon_LH {
    private static SingleTon_LH instance = null;    
    public static synchronized SingleTon_LH getInstance(){
        if(null == instance){
            instance = new SingleTon_LH();
        }
        return instance;
    }
    /**
     * 如何避免反序列化得到不同的实例呢?
     * 反序列化时,如果定义了readResolve(),则直接次方法指定的对象
     */
    public Object readResolve(){
        return instance;
    }   
}

反射机制代码:

    /**
     * 反射破解懒汉模式的单例模式,其他模式同理,但是枚举模式原生避免这种风险
     * @throws Exception
     */
    @Test
    public void test02() throws Exception{
        //反射加载类
        Class<SingleTon_LH> clazz = (Class<SingleTon_LH>) Class.forName("cn.test.Singleton.two.SingleTon_LH");
        //反射获得构造器
        Constructor<SingleTon_LH> contructor = clazz.getDeclaredConstructor(null);  //无参构造器

        contructor.setAccessible(true);

        SingleTon_LH s1 = contructor.newInstance(); //无参构造器,参数为空
        SingleTon_LH s2 = contructor.newInstance();
        SingleTon_LH s3 = SingleTon_LH.getInstance();
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
    }

得到的结果:

cn.test.Singleton.two.SingleTon_LH@5430d082
cn.test.Singleton.two.SingleTon_LH@50c931fc
cn.test.Singleton.two.SingleTon_LH@48f0c0d3

但是当我们加上:

contructor.setAccessible(true);
SingleTon_LH ss = SingleTon_LH.getInstance();
System.out.println(ss);

这段代码后,程序执行就会保存,报出现运行时错误,这是因为执行了以上代码后instance已经不再是null了。
其他模式的代码一样,只有懒汉模式有一个漏洞,就是当没有执行过getInstance()时,才不能避免反射机制的破坏,修改代码如下:

public class SingleTon_LH {
    private static SingleTon_LH instance = null;

    private SingleTon_LH(){
        if(instance != null){
            throw new RuntimeException();
        }
    }

    public static synchronized SingleTon_LH getInstance(){
        if(null == instance){
            instance = new SingleTon_LH();
        }
        return instance;
    }
    /**
     * 如何避免反序列化得到不同的实例呢?
     * 反序列化时,如果定义了readResolve(),则直接次方法指定的对象
     */
    public Object readResolve(){
        return instance;
    }   
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值