设计模式之单例模式

加锁的单例模式 

public class ThreadSingleton {

    /**
     * 定义静态属性使用volatile关键字
     * volatile关键字修饰变量来解决无序写入产生的问题,因为volatile关键字的一个重要作用是即使在多线程环境下,也可以禁止指令重排序,
     * 即保证不会出现内存分配、返回对象引用、初始化这样的顺序,从而使得双重检测真正发挥作用
     */
    private static volatile ThreadSingleton instance;
    static String LOCK = "lock";

    private ThreadSingleton(){

    }

    // 锁的是静态类本身,影响并发性能
//    public  static synchronized ThreadSingleton getInstance(){
//        if(null == instance){
//            instance = new ThreadSingleton();
//        }
//        return instance;
//    }

    // 每次获取实例都要判空,性能不好
   public  static  ThreadSingleton getInstance(){
        if(null == instance){
            synchronized(LOCK){
                //再次判断,因为可能两个线程同时到达同步块之前,防止多次实例化
                if(null == instance){
                    instance = new ThreadSingleton();
                }
            }
        }
        return instance;
    }
}

错误和正确的静态变量单例模式

public class StaticSingleton {

    // 错误写法instance没有声明为final,可以被置空
    //private static StaticSingleton instance = new StaticSingleton();
    public static void main(String[] args) {
        // 如果上面对instance的声明没有final,那么此处会被置空
        // StaticSingleton.instance = null;
    }

    // 无论是否需要都会实例化,占用空间
    private static final  StaticSingleton instance = new StaticSingleton();

    private StaticSingleton(){
    }

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

静态内部类单例模式

public class StaticInnerSingleton {
    // 存在反射 和 序列化问题
    private StaticInnerSingleton(){
    }

    // 静态内部类的加载不需要依附外部类,在使用时才加载,所以不会提前实例化
    private static class Inner{
        private static final StaticInnerSingleton instance = new StaticInnerSingleton();
    }

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

上面是性能不错,但不完全的单例模式,不安全是因为反射、序列化还可以实现多例!!!

使用枚举:单元素的枚举已经成为单例模式实现的最佳方法。

public enum EnumSingleton {
    INSTANNCE{
        @Override
        protected void read() {
            System.out.println("read");
        }
    };
    protected abstract void read();
}

注意:上面的方式有abstract方法!!

反射漏洞

Contructor<Singleton> constructor = Singletton.class.getDeclaredContructor();
constructor.setAccessible(true);
Singleton singleton = costructor.newInsatnce();

反射漏洞处理

修改单例类的私有构造器如下: 

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

反序列化漏洞

        Singleton singleton = Singleton.getInstance();  
        File file = new File("MySingleton.txt");  
        try {  
            FileOutputStream fos = new FileOutputStream(file);  
            ObjectOutputStream oos = new ObjectOutputStream(fos);  
            oos.writeObject(singleton);  
            fos.close();  
            oos.close();  
            System.out.println(singleton.hashCode());  
        } catch (FileNotFoundException e) {   
            e.printStackTrace();  
        } catch (IOException e) {   
            e.printStackTrace();  
        } 
 
        try {  
            FileInputStream fis = new FileInputStream(file);  
            ObjectInputStream ois = new ObjectInputStream(fis);  
            Singleton rSingleton = (Singleton) ois.readObject();  
            fis.close();  
            ois.close();  
            System.out.println(rSingleton.hashCode());  
        } catch (FileNotFoundException e) {   
            e.printStackTrace();  
        } catch (IOException e) {   
            e.printStackTrace();  
        } catch (ClassNotFoundException e) {   
            e.printStackTrace();  
        } 

反序列化漏洞处理

需要在单例类中增加:

private Object readResolve(){
    return instance;//instance为单例对象的引用
}

原理:那么这个readResolve()方法是从哪来的,为什么加上之后就能返回同一实例了呢?

找到ObjectInputStream类的readObject方法中间接调用了readResolve方法,如果有则直接调用自身定义的readResolve方法。

单元素枚举是实现单例模式的最好方式,线程安全、防反射攻击、防止序列化生成新的实例(参考:https://segmentfault.com/a/1190000000699591)

原因如下:

1). 枚举自己处理序列化

但是枚举单例,JVM对序列化有保证。

2) 枚举实例创建是thread-safe

是通过类加载机制来保证的,我们看看INSTANCE的实例化时机,是在static块中,JVM加载类的过程显然是线程安全的

3)对于反射,如果枚举含有abstract方法,反编译后类的修饰abstract,所以没法实例化,反射也无能为力。

附上面枚举类反编译的结果:

public abstract class Singleton extends Enum
{

    private Singleton(String s, int i)
    {
        super(s, i);
    }

    protected abstract void read();

    protected abstract void write();

    public static Singleton[] values()
    {
        Singleton asingleton[];
        int i;
        Singleton asingleton1[];
        System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);
        return asingleton1;
    }

    public static Singleton valueOf(String s)
    {
        return (Singleton)Enum.valueOf(singleton/Singleton, s);
    }

    Singleton(String s, int i, Singleton singleton)
    {
        this(s, i);
    }

    public static final Singleton INSTANCE;
    private static final Singleton ENUM$VALUES[];

    static 
    {
        INSTANCE = new Singleton("INSTANCE", 0) {

            protected void read()
            {
                System.out.println("read");
            }

            protected void write()
            {
                System.out.println("write");
            }

        };
        ENUM$VALUES = (new Singleton[] {
            INSTANCE
        });
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值