Java单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点。主要用于解决一个全局使用的类被频繁地创建与销毁。常用的几种实现方式整理如下:
1.饿汉模式
在类被初始化的时候就已经在内存中创建了对象,所以不存在线程安全问题,属于以空间换时间的方式,示例:
public class SingleTon{
private static SingleTon instance = new SingleTon();
private SingleTon(){}
public static SingleTon getInstance(){
return instance;
}
}
2.懒汉模式
在方法被调用后才创建对象,属于以时间换空间的方式,在多线程环境下存在风险,示例:
public class SingleTon{
private static SingleTon instance = null;
private SingleTon(){}
public static SingleTon getInstance(){
if(instance==null){
instance = new SingleTon();
}
return instance;
}
}
3.双重锁懒汉模式(DCL)
在方法被调用后才创建对象,和懒汉模式不同的是,在创建过程中增加了双重判空及同步锁,所以是线程安全的方式;需要注意的是在实例声明时增加了volatile
关键字,确保instance
实例化时的有序执行,防止DCL
出现失效返回未初始化构造方法的空对象,示例:
public class SingleTon{
//由于jvm存在乱序执行功能,DCL也会出现线程不安全的情况,所以在实例申明前加上volatile关键字,
//确保instance实例化时的有序执行
private volatile static SingleTon instance = null;
private SingleTon(){}
public static SingleTon getInstance(){
//第一次判断非null是为了避免非必要的加锁,当第一次加载时才对实例进行加锁再实例化;
if(instance == null){
synchronized(SingleTon.class){
if(instance == null){
instance = new SingleTon();
}
}
return instance;
}
}
}
关于jvm的乱序执行:
例如 instance = new SingleTon();
这个步骤,其实在jvm中分为三步来执行:
(1) 在堆内存开辟内存空间;
(2) 在堆内存中实例化SingleTon里面的各个参数;
(3) 把对象指向堆内存空间。
所以在instance
实例化时,可能在(2)还没执行时就先执行了(3),如果此时刚好其他线程进入,由于执行了(3),instance
已经非空了,会被直接拿出来用,这样的话,就会出现异常了,这个就是著名的DCL失效问题。不过在JDK1.5之后,官方也发现了这个问题,故而具体化了volatile
,即在JDK1.6及以后,只要实例声明时增加了volatile
关键字,就可解决DCL失效问题。
4.静态内部类
优点是外部类在加载时并不需要立即加载内部类,内部类不被加载则不会去实例化instance
,故而不占用内存,即当SingleTon
第一次被加载时,并不需要去加载GetSingleTon
,只有当getInstance()
方法第一次被调用时,才会去初始化instance
,第一次调用getInstance()
方法会导致虚拟机加载GetSingleTon
类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化;示例:
public class SingleTon{
private SingleTon(){}
private static class GetSingleTon{
private static SingleTon instance = new SingleTon();
}
public static SingleTon getInstance(){
return GetSingleTon.instance;
}
}
那么,是不是可以说静态内部类单例就是最完美的单例模式了呢?其实不然,静态内部类也有着一个致命的缺点,就是传参的问题,由于是静态内部类的形式去创建单例的,故外部无法传递参数进去,例如Context这种参数,所以,我们创建单例时,可以在静态内部类与DCL模式里自己斟酌。
5.枚举
枚举在java中与普通类一样,都能拥有字段与方法,而且枚举实例创建是线程安全的,在任何情况下,它都是一个单例。我们可直接以 SingleTon.INSTANCE
的方式调用,示例:
public enum SingleTon{
INSTANCE;
public void method(){
//todo
}
}
由于单例模式保证了系统内存中只存在一个该类的实例对象,节省了系统资源;所以一般用于需要频繁创建销毁的工具类对象、频繁访问数据库或文件的对象(如数据源)的创建。