简介:简述单例模式的几种实现
懒汉式
懒汉的"懒",主要体现在懒加载(延迟加载)上。我们知道,在类加载到JVM的过程中,会先加载静态属性等相关操作,最后一步才是初始化。而类的初始化,是有条件或者说时机的,也就是说,类很有可能是只被加载和连接,而不被初始化的。而所谓的延迟加载,就是让类被初始化时,才去执行创建对象的操作,而不是一开始就创建,从而达到延后加载时机的目的。懒汉式写法如下:
//世界上只能用一个中国,那就是中华人民共和国
public class China {
private static China PRC;
private China() {}
public static China getInstance() {
if (PRC == null)
PRC = new China();
return PRC;
}
}
但是这种写法并不是线程安全的,容易有多个线程同时在if (PRC == null)
这里为真,这样就会创建出多个对象。所以我们可以为它加上同步锁:
public class China {
private static China PRC;
private China() {}
public synchronized static China getInstance() {
if (PRC == null)
PRC = new China();
return PRC;
}
}
这样虽能解决问题,但明显会导致效率低下,因为很多时候,线程其实就是想得到对象,而不是去新建,结果加锁后,每次只有一个线程走创建流程,大大耽误了执行效率。所以,新的改进方式就是,先把大家放进来,如果不需要创建对象,那就拿现成的对象走人,如果需要创建,那就进入同步块,来保证只有一个线程能创建对象。这样被耽误的线程也只是少数在同一时间也判定为空的少数线程而已。这就是双重锁验证:
public class China {
private volatile static China PRC;
private China(){}
public static China getInstance() {
if (PRC == null) {
synchronized (China.class) {
if (PRC == null) {
PRC = new China();
}
}
}
return PRC;
}
}
这样一来,在大部分时候,就不会进入同步块,从而提高了效率,也保证了延迟加载。
这里对PRC变量使用volatile
是为了保证JVM在进行该语句操作时,不会去进行自动优化从而导致一些错误。
饿汉式
所谓饿汉式,就是不去等到类初始化时才创建对象,而是直接在类加载时就创建好(因为他很饥饿,不能接受延迟加载)。
public class China{
private static China PRC = new China();//把new的操作放在给静态变量赋值上,这样就不用等待类的初始化。
private China() {}
public static China getInstance() {
return PRC;
}
}
这种方式比较简单粗暴,而且因为一开始就创建好了,所以也不存在线程不安全的问题。但是实例化是很耗资源的操作,有时候类根本就不用被实例化,而饿汉式也依然会创建出对象,这样对象就成为了垃圾对象,从而造成了资源浪费。因此就有了如下的改进——静态内部类:
静态内部类
在类眼中,内部类是被当做成员变量的
,而静态内部类是被当成类变量
,并且静态内部类的加载不需要依附外部类,在使用时才加载,也就是说,我们通过在China内中写一个静态内部类当工具人,把刚才的饿汉代码写在这个工具类中,从而实现延迟加载。值得一提的是,JVM在加载一个类的时候,这个过程是自带线程互斥的,也就是说自带线程安全BUFF。
再补充一个小知识点:成员内部类是不可以有静态属性的,会编译不通过(因为外部类的静态方法是可以调用内部类的静态属性的,而内部类作为成员变量,自己的属性却被类直接调用了而不是对象,自然会引发很多问题)。
public class China {
private static class Utils {
//加final保证了不论getInstance()执行了多少次,都是只有一个
private static final China PRC = new China();
}
private China() {}
public static final China getInstance() {
return Utils.PRC;
}
}
几种方式的比较
懒汉的优点在于延迟加载,缺点在于线程不安全以及保证安全后的效率低下。最终的双重锁版本使得该模式有较好的性能。
饿汉的优点在于实现简单和线程安全,但无法实现懒加载。而且在遇到需要给对象传参时,无法动态实现(因为加载时并不知道要传什么参数)。
而静态内部类结合了这两种的优点,当然,传参的问题还是没有根本上解决,所以在这方面,懒汉(特指双重锁)有着比较大的优势。
枚举
以上的方法,都存在着反射和序列化情况下,被创建多个实例的可能。而使用枚举,真正做到了单例,并且操作简单。
public enum China{
PRC;
}
枚举类的构造方法默认就是私有的,同时也像普通类一样可以有方法和属性,所以枚举类可以完美得实现单例操作。