Java设计模式之单例模式(Singleton Pattern)

 

目录

单例模式的概念

单例模式的要点

单例模式类图

单例模式归类

单例模式的应用场景

单例模式解决的问题

单例模式的实现方式

单例模式实现方式对比


单例模式的概念

单例模式,顾名思义就是只有一个实例,并且由它自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象

单例模式的要点

1、一个类有且仅有一个实例

这个是单例模式的的精髓所在,如何保证类的实例有且只有一个是单例模式必须要保证的,单例实现的所有方式都是围绕着这个出发点来展开的

2、必须自行创建这个实例

在java中,通常是将类的构造方法私有化,从而保证无法在外部进行创建实例

3、必须自行向整个系统提供这个实例

在java中,是通过类的静态方法获取类的实例

单例模式类图

单例模式归类

单例模式属于创建型模式的一种

单例模式的应用场景

单例模式可以说无论是在我们的生活中,还是工作中,都有许多的应用场景,例如:

1、我们使用的电脑里的回收站

2、Spring的抽象工厂

3、日常开发用的工具类

单例模式解决的问题

全局使用的类的实例被频繁的创建和销毁,如果这些类的实例创建和销毁非常消耗性能,对系统来说无疑增加了负担

单例模式的实现方式

1、饿汉式

预创建实例,不管实例有没有用到。

代码实现:

public final class SingletonEHS {

    private static final SingletonEHS singleton = new SingletonEHS();
    private SingletonEHS() {}
    public static SingletonEHS getInstance() {
        return singleton;
    }
}

延迟初始化:否,由于实例是预创建的,所以没有达到延迟初始化的效果

线程安全:是,基于 classloader 机制避免了多线程的同步问题

反射破解:可破解

@Test
public void testSingletonEHS() {

    try {
        Constructor<? extends SingletonEHS> declaredConstructor = SingletonEHS.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        SingletonEHS singletonReflect = declaredConstructor.newInstance();
        SingletonEHS singletonReflect1 = declaredConstructor.newInstance();
        System.out.println("反射获取实例:" + singletonReflect);
        System.out.println("反射获取实例_1:" + singletonReflect1);
        System.out.println(singletonReflect == singletonReflect1);
    } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
        e.printStackTrace();
    }
}
反射获取实例:com.cn87.constants.test.designpattern.singleton.SingletonEHS@543c6f6d
反射获取实例_1:com.cn87.constants.test.designpattern.singleton.SingletonEHS@13eb8acf
false

序列化破解:可破解

序列化实例:com.cn87.constants.test.designpattern.singleton.SingletonDCL@75881071
反序列化实例:com.cn87.constants.test.designpattern.singleton.SingletonDCL@1efee8e7
false

2、懒汉式

实例在使用的时候才去创建。

代码实现:

public final class SingletonLHS {

    private static SingletonLHS singleton;
    private SingletonLHS() {}
    public static SingletonLHS getInstance() {

        if (singleton == null) {
            singleton = new SingletonLHS();
        }
        return singleton;
    }
}

严格意义上来讲,这种不属于单例模式,因为无法避免多线程同步问题。所以衍生了下面这种写法。

public final class SingletonLHS {

    private static SingletonLHS singleton;
    private SingletonLHS() {}
    public static synchronized SingletonLHS getInstance() {

        if (singleton == null) {
            singleton = new SingletonLHS();
        }
        return singleton;
    }
}

通过synchronized关键字解决了多线程访问getInstance方法获取多个实例的问题。但是由于synchronized锁的粒度较大,在高并发环境下会产生锁竞争问题。于是又派生出了下面的DCL单例实现。DCL意为double check lock 双检锁。

/**
 * 声明final是为了不被继承
 */
public final class SingletonDCL {
    /**
     * volatile 防止指令重排序
     */
    private static volatile SingletonDCL singleton;

    /**
     * 私有化构造方法,阻止在外部创建实例
     */
    private SingletonDCL() {}

    /**
     * 提供获取唯一实例的静态方法
     */
    public static SingletonDCL getInstance() {
        //double check
        if (singleton == null) {
            //lock
            synchronized (SingletonDCL.class) {
                //double check
                if (singleton == null) {
                    singleton = new SingletonDCL();
                }
            }
        }
        return singleton;
    }
}

DCL单例不但避免了多线程下多个实例的创建,同时又降低了锁的粒度,在多线程情况下能保持高性能。volatile关键字的引入更是锦上添花,对单例对象有特殊要求的业务场景不妨尝试一下。

延迟初始化:

线程安全:

反射破解:可破解,同饿汉式

序列化破解:可破解

3、登记式/静态内部类

静态内部类的方式效果类似双检锁,但实现更简单。但这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

代码实现:

public final class SingletonInnerClass {

    private static class SingletonHolder {
        private static final SingletonInnerClass singleton = new SingletonInnerClass();
    }

    public static SingletonInnerClass getInstance() {
        return SingletonHolder.singleton;
    }
}

延迟初始化:

线程安全:

反射破解:可破解,同饿汉式

序列化破解:可破解

4、枚举

枚举的方式是比较少见的一种实现方式,但是看上面的代码实现,却更简洁清晰。并且她还自动支持序列化机制,绝对防止多次实例化。

代码实现:

public class SingletonEnum {

    private SingletonEnum() {}

    private enum Singleton {
        INSTANCE;
        private final Gson gson;
        Singleton() {
            gson = new Gson();
        }

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

延迟初始化:

线程安全:

反射破解:不可破解

由于枚举没有无参构造,只有如下一个构造方法

protected Enum(String name, int ordinal) {
    this.name = name;
    this.ordinal = ordinal;
}

但是JDK反射机制内部完全禁止了用反射创建枚举实例的可能性,所以反射破解在枚举单例中是不存在的 

//通过有参构造反射创建枚举会报如下异常
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    at me.lmagics.singleton.SingletonAttack.reflectionAttack(SingletonAttack.java:38)
    at me.lmagics.singleton.SingletonAttack.main(SingletonAttack.java:19)

//这是因为反射机制禁止创建枚举类型
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    throw new IllegalArgumentException("Cannot reflectively create enum objects");

序列化破解:不可破解

对Enum的反序列化过程中,对枚举类型有一个专门的readEnum()方法来处理

private Enum<?> readEnum(boolean unshared) throws IOException {
	...
    //获取枚举value的name,即INSTANCE
	String name = readString(false);
	Enum<?> result = null;
	Class<?> cl = desc.forClass();
	if (cl != null) {
		try {
			@SuppressWarnings("unchecked")
            //通过Enum.valueOf根据枚举类型和枚举值的名字,获得最终的单例
			Enum<?> en = Enum.valueOf((Class)cl, name);
			result = en;
		} catch (IllegalArgumentException ex) {
			throw (IOException) new InvalidObjectException(
				"enum constant " + name + " does not exist in " +
				cl).initCause(ex);
		}
		if (!unshared) {
			handles.setObject(enumHandle, result);
		}
	}

    ...
	return result;
}

单例模式实现方式对比

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值