原创

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
    }

}

由于单例模式保证了系统内存中只存在一个该类的实例对象,节省了系统资源;所以一般用于需要频繁创建销毁的工具类对象、频繁访问数据库或文件的对象(如数据源)的创建。

正文到此结束