引言
程序运行中,往往会生成很多实例.当我们希望某个类只会存在一个实例的时候,单例模式便派上了用场.本篇将介绍在
Java
中,单例模式
的几种实现方法并比较各自优缺点.
一丶 饿汉式写法
public class Singleton {
/*饿汉式写法 直接在成员变量处实例化对象*/
private static Singleton singleton = new Singleton();
/*构造函数私有化 确保外界不能通过new关键字实例化对象*/
private Singleton() {
}
/*暴露给外界的静态方法 用来获取Singleton类的实例*/
public static Singleton getInstance(){
return singleton;
}
}
复制代码
可以看到饿汉式的写法简单粗暴,直接在静态成员变量
处实例化对象,我们都知道,静态成员变量
只会在类加载
的时候被加载到静态域
一次,因而能保证单例,并且是线程安全的。但是这种写法也有一个问题就是无论你是否使用到实例
,实例
总会被加载,白白浪费了资源,因此也有下面的这种写法.
二丶懒汉式写法
-
常规写法
,非线程安全public class Singleton { /*懒汉式 暂时不实例化*/ private static Singleton singleton = null; /*构造函数私有化 确保外界不能通过new关键字实例化对象*/ private Singleton() { } /*暴露给外界的静态方法 用来获取Singleton类的实例*/ public static Singleton getInstance(){ /*先进行判空操作 如果未空,进行实例*/ if (singleton==null) singleton = new Singleton(); return singleton; } } 复制代码
可以看到,懒汉式的写法是在第一次调用
getInstance()
方法时才实例化对象,一定程度上节省了资源.但是这种写法是线程不安全的。考虑这种情况:A和B两个线程同时进行到判空语句,此时对象还未被实例化
,所以均返回true
,然后A和B两个线程均会进行实例化
操作,造成不可预知的错误.因此,该方法线程不安全.而下面的写法就是针对于这种写法进行的改进。 -
对
getInstance()
方法加锁的写法,解决线程不安全的问题.public class Singleton { /*懒汉式 暂时不实例化*/ private static Singleton singleton = null; /*构造函数私有化 确保外界不能通过new关键字实例化对象*/ private Singleton() { } /*暴露给外界的静态方法 用来获取Singleton类的实例 对该方法加锁,解决同步问题*/ public synchronized static Singleton getInstance() { /*先进行判空操作 如果未空,进行实例*/ if (singleton == null) singleton = new Singleton(); return singleton; } } 复制代码
这种方法之比于
普通写法
,对方法加锁,固然是解决了线程安全问题,但是每次调用getInstance()
方法都要进行加锁和释放锁操作,也显得有点浪费资源,于是又诞生出了下面的写法. -
双重检查锁
写法,解决线程不安全和浪费资源的问题public class Singleton { /*懒汉式 暂时不实例化*/ private static Singleton singleton = null; /*构造函数私有化 确保外界不能通过new关键字实例化对象*/ private Singleton() { } /*暴露给外界的静态方法 用来获取Singleton类的实例*/ public static Singleton getInstance() { /*先进行判空操作 如果未空,进行实例*/ if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) singleton = new Singleton(); } } return singleton; } } 复制代码
这种写法被称为双重检查锁写法,双重检查是值对
null
进行两次检查,为什么要这样写呢?假如我们只检查一次会怎么样?也就是代码长这样:public class Singleton { /*懒汉式 暂时不实例化*/ private static Singleton singleton = null; /*构造函数私有化 确保外界不能通过new关键字实例化对象*/ private Singleton() { } /*暴露给外界的静态方法 用来获取Singleton类的实例*/ public static Singleton getInstance() { /*先进行判空操作 如果未空,进行实例*/ if (singleton == null) { synchronized (Singleton.class) { singleton = new Singleton(); } } return singleton; } } 复制代码
对于这种写法,考虑这样一种情形:A和B线程同时执行到非空判断
for循环
内部,此时A拿到锁,先进行实例化
操作,完成后,A释放锁
,B又进行实例化操作,可以发现,如果不再次进行null
判断,这种写法简直比普通写法
还要糟糕,而如果多进行一次null
判断,就比较完美了.
三丶静态内部类写法
public class Singleton {
/*构造函数私有化 确保外界不能通过new关键字实例化对象*/
private Singleton() {
}
/*暴露给外界的静态方法 用来获取Singleton类的实例*/
public static Singleton getInstance() {
//静态内部类的特性,第一次调用该类才开始加载,达到了延迟加载的特性,同时线程安全
return SingletonHelper.singleton;
}
private static class SingletonHelper{
//静态成员变量 singleton的实例
private static Singleton singleton = new Singleton();
}
}
复制代码
关于这种写法有人会觉得和饿汉式的写法差不多,但这种写法由于静态内部类
的加载机制,达到了延迟加载
的效果,是优于饿汉式的,并且没有同步代码
的开销,是比较推荐的写法.
四丶单元素枚举写法
public enum Singleton {
INSTANCE; //单元素
public void fun1(){
}
}
复制代码
写法也很简单,保证枚举中只有单个元素,然后这样调用:
Singleton.INSTANCE.fun1();
复制代码
在Effective Java一书中对该方法的评价是:这种写法十分简洁,而且相比于其他写法,绝对的防止了多次实例化,即使是在面对复杂的序列化或者反射攻击的时候.总之,单元素的枚举类型已经成为实现Singleton的最佳方法.
总结
以上就是单例模式在Java中常见的几种写法啦,各种方法的优缺点相信大家也能有所了解,实际使用中,使用哪种方法还是看大家的个人习惯.在Kotlin
中,一个Object
关键字就能实现单例,也是十分的方便,大家有空可以了解了解.