单例模式



主要优点:

1、提供了对唯一实例的受控访问。

2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。

3、允许可变数目的实例。

4、可以避免对资源的多重占用

5、设置全局访问点,严格控制访问

主要缺点:

1、由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。

2、单例类的职责过重,在一定程度上违背了“单一职责原则”。

3、滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

懒汉模式

  • 单线程懒汉模式

       适合单线程:多线程不安全 。

       该种方法属于懒加载方式,但是在多线程状态下并不安全。

       解释:当两个线程同时使用该方法时候, A执行到instance= new LazySingletonSingleThread ()时候,A还未被赋值,还是null。 B开始执行进入方法,是可以通过if判断的,当B也执行到instance= new LazySingletonSingleThread (),A执行一步,此时B的instance也等于A执行的值,在这里,先A继续执行直到结束的话,B再执行,就会得到两个不同的值;如果A执行到 return lazySingleton不动,等待B也执行 到该处,则B的instance会覆盖A的对象instance的值。虽然得到的是同一个对象,但是由于多创建了一个对象内存开销会增大。

public class LazySingletonSingleThread {
    private static LazySingletonSingleThread instance = null;

    private LazySingletonSingleThread() {

    }
    
    private static LazySingletonSingleThread getInstance(){
        if(instance == null){
            instance = new LazySingletonSingleThread();
        }
        return instance;
    }
}
  • 同步锁方法解决多线程时候的饿汉模式不安全

同步锁方式不灵活
由于此时synchronized锁定的是一个静态类,相当一锁定的是整个类,而不是一个方法。
这样的方法虽依然可以解决同步问题,但是加锁解锁性能开销比较大,而且对类方法加锁相当给类加锁,范围过大,也不是最好的方案。
public class LazySingletonSynLock {
    private static LazySingletonSynLock instance = null;

    private LazySingletonSynLock() {

    }

    private synchronized static LazySingletonSynLock getInstance(){
        if(instance == null){
            instance = new LazySingletonSynLock();
        }
        return instance;
    }
}
  • 双重加锁方式解决多线程时候的饿汉模式

双重加锁
使用synchronized(LazySingleton.class)锁定代码块,使得锁定范围减小,这是锁一;
锁二为声明成员变量时候的volatile关键字,该关键字用于使得对象在生成时候不允许重排序生成对象;
重排序:即在生成对象时候有三个步骤,分配内存、初始化、变量指向内存三个步骤,正常情况也是该顺序,但是在生成对象时候,为了方便快捷以及效率,程序会先分配内存,然后变量指向内存再初始化,这样一来,就会造成变量已经指向了分配的内存,但是内存中并未有初始化,造成空异常。
public class LazySingletonDoubleLuck {
    private volatile static LazySingletonDoubleLuck instance = null;

    private LazySingletonDoubleLuck(){

    }

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

        }
        return instance;
    }
}

静态内部类

    类初始化时候会有同步锁,所以当第一个线程调用该方法时候,该方法调用内部类先初始化,类加锁初始化完成后
    供调用,不存在出现线程安全问题以及重排序问题

public class StaticInnerSingleton {
    private static class Singleton{
        private static StaticInnerSingleton staticInnerSingleton = new StaticInnerSingleton();
    }

    public static StaticInnerSingleton getInstance(){
        return Singleton.staticInnerSingleton;
    }
}

饿汉模式

因为再类初始化时候便生成了对象,当类不被使用时候,造成浪费
public class HungrySingleton {
    private final static HungrySingleton instance = new HungrySingleton();

    private HungrySingleton(){

    }

    public static HungrySingleton getInstance()
    {
        return instance;
    }
}
  • 饿汉模式序列化破解

即类要实现Serializable接口,可序列化;方法中要实现

private Object readResolve(){
    return instance;
}
public class HungrySingleton implements Serializable {
    private final static HungrySingleton instance = new HungrySingleton();

    private HungrySingleton(){

    }

    public static HungrySingleton getInstance()
    {
        return instance;
    }

    private Object readResolve(){
        return instance;
    }
}

class Test{
    public static void main(String[] args) {
        HungrySingleton instance = HungrySingleton.getInstance();
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\test.txt")));
            oos.writeObject(instance);

            File file=new File("D:\\test.txt");
            ObjectInputStream ios = new ObjectInputStream(new FileInputStream(file));
            HungrySingleton newInstance = (HungrySingleton) ios.readObject();
            System.out.println(instance);
            System.out.println(newInstance);
            System.out.println(instance==newInstance);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
  • 饿汉模式反射攻击破解

下图代码为未破解的,使用

constructor.setAccessible(true);

即可访问私有成员构造函数,生成新的单例对象;

public class HungrySingleton implements Serializable {
    private final static HungrySingleton instance = new HungrySingleton();

    private HungrySingleton(){

    }

    public static HungrySingleton getInstance()
    {
        return instance;
    }

    private Object readResolve(){
        return instance;
    }
}

class Test2{
    public static void main(String[] args) {
        Class objectClass = HungrySingleton.class;
        try {
            Constructor constructor = objectClass.getDeclaredConstructor();
            //设置private可以访问权限
            constructor.setAccessible(true);
            HungrySingleton instance = HungrySingleton.getInstance();
            HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
            System.out.println(instance);
            System.out.println(newInstance);
            System.out.println(instance==newInstance);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

防御方法为在单例类的私有构造方法中,加成员变量实例的判空即:

private HungrySingleton(){
    if(instance!=null){
        throw new RuntimeException("单例构造器禁止反射调用");
    }

}
/*
* 懒汉式
* 因为再类初始化时候便生成了对象,当类不被使用时候,造成浪费
*/
public class HungrySingleton implements Serializable {
    private final static HungrySingleton instance = new HungrySingleton();

    private HungrySingleton(){
        if(instance!=null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }

    }

    public static HungrySingleton getInstance()
    {
        return instance;
    }

    private Object readResolve(){
        return instance;
    }
}

class Test2{
    public static void main(String[] args) {
        Class objectClass = HungrySingleton.class;
        try {
            Constructor constructor = objectClass.getDeclaredConstructor();
            //设置private可以访问权限
            constructor.setAccessible(true);
            HungrySingleton instance = HungrySingleton.getInstance();
            HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
            System.out.println(instance);
            System.out.println(newInstance);
            System.out.println(instance==newInstance);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

这样修改的原因是,反射攻击可以直接访问方法、无视方法变量的权限,所以我们在构造方法中加上一个判断,如果已经是生成了实例,即构造方法已经被调用过一次,则下一次不能被使用了。该种方法仅对饿汉、静态内部类的单例适用。关键点就是要明白,getInstance()方法的到的对象是单例里面声明成员变量,反射得到的是新的变量,两者并不是一个!

枚举单例

  枚举单例非常建议适用,该方式可以避免反射攻击、多线程、序列化攻击等问题。

public enum EnumInstance {
    INSTANCE;

    private Object data;

    public Object getData(){
        return data;
    }

    public void setData(Object data){
        this.data = data;
    }

    public static EnumInstance getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) {
        EnumInstance instance = EnumInstance.getInstance();
        instance.setData(new Object());
        instance.getData();
    }
}

容器单例

优点是方便管理

缺点为线程不安全,可以把hashmap换为线程安全的集合,但是该种集合会上锁,效率低。

public class ContainerSingleton {
    private static Map<String,Object> singletion = new HashMap<String, Object>();

    private ContainerSingleton(){

    }

    public static void putInstance(String key,Object instance){
        if(StringUtils.isNotBlank(key)&&null!=instance){
            if(!singletion.containsKey(key)){
                singletion.put(key,instance);
            }
        }
    }

    public static Object getInstance(String key){
        return singletion.get(key);
    }
}

单线程单例

该种实现方式为为每个线程实现一个单例,不同线程使用ThreadLocalInstance.getInstance()得到的对象是不一样的,也就说这是一个用空间资源置换时间的方式,没个线程都会根据该线程实现对应这个线程的单例对象,可以避免多线程的资源冲突,但是缺点就是占用空间。

public class ThreadLocalInstance {
    private static final ThreadLocal<ThreadLocalInstance> instance  = new ThreadLocal<ThreadLocalInstance>(){
        @Override
        protected ThreadLocalInstance initialValue() {
            return new ThreadLocalInstance();
        }
    };

    private ThreadLocalInstance(){

    }

    public static ThreadLocalInstance getInstance(){
        return instance.get();
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值