单例模式是我们实际开发中常用到的开发模式,目的是保证实例的唯一性,确保这个类在内存中只会存在一个对象,但我们现在用到的单例模式相关代码可能不是最优的,今天让我们探索一下单例模式的正确写法。 单例模式通常分为饿汉式和懒汉式,我们这里来一个最简单的代码: 饿汉式相关代码:
public class SingletonPattern {
//无参构造私有化,不允许直接new获得实例
private SingletonPattern() {
}
//创建静态啊私有实例
private static SingletonPattern hungerSingleton =new SingletonPattern();
//同过公共静态方法获取实例,确保唯一
public static SingletonPattern getHungerInstance(){
return hungerSingleton;
}
}
缺点:
提前创建类实例,无论是否需要,只要类加载就进行了实例化,浪费资源.
懒汉式相关代码:
//懒汉式,
private static SingletonPattern lazySingleton;
public static SingletonPattern getLazyInstance(){
//判断如果没有实例创建,则创建实例
if(lazySingleton == null){
lazySingleton =new SingletonPattern();
}
return lazySingleton;
}
缺点:
需要的时候创建类实例,没有考虑到多线程,多线程环境下无法保证单例效果,会多次执行SingletonPattern instance=new SingletonPattern()
懒汉式和饿汉式主要区别是否先创建类的实例,一个是拿时间换空间,一个是拿空间换时间,懒汉式只有我需要他的时候才去加载它,懒加载机制,饿汉式不管需不需要我先在内存中开辟空间。这两种是最基本的单列模式,接下来我们就以懒汉式为例,分析如何正确创建和使用单例。
我们分析上面的懒汉式代码的问题是:多线程情况下无法保证单例,也就是说:如果同时多个线程访问可能会创建多个实例的情况出现.那我们首先想到的是-synchronized,来进行同步方法或同步块,代码如下:
//懒汉式加锁同步
private static SingletonPattern syncSingleton;
public static SingletonPattern getSyncyInstance(){
if(syncSingleton == null){
//在此处加锁同步比在方法出加锁同步缩小了范围,性能稍高
synchronized (SingletonPattern.class){
syncSingleton =new SingletonPattern();
}
}
return syncSingleton;
}
缺点:
虽然使用了synchronized进行了线程的同步,还是会存在多次执行的可能SingletonPattern instance=new SingletonPattern()
进一步优化采用DCL(Double Check Lock)双重检查来确定单例模式的线程安全和唯一,相关代码如下:
//懒汉式双层检查
private static SingletonPattern dclSingleton;
public static SingletonPattern getDCLInstance(){
if(dclSingleton == null){//第一层检查
//在此处加锁同步比在方法出加锁同步缩小了范围,性能稍高
synchronized (SingletonPattern.class){
if(dclSingleton == null){//第二层检查
dclSingleton =new SingletonPattern();
}
}
}
//此处有可能多线返回null对象。导致崩溃
return dclSingleton;
}
缺点: 首先我们先了解一下:通常我们进行实例化包含以下几步: 1.给 Singleton 实例分配内存,将函数压栈,并且申明变量类型; 2.初始化构造函数以及里面的字段,在堆内存开辟空间; 3.将 instance 对象指向分配的内存空间;
java 编译器允许执行无序,并且 jdk1.5之前不限制处理器重排序,不能保证按序执行,处理器会进行指令重排序优化导致程序崩溃。举例:正常顺序是1-2-3,优化重排后执行顺序可能为:1-3-2, 这时假如有 A 和 B 两条线程, A线程执行到3的步骤,但是未执行2,这时候 B 线程来了抢了权限,判断不为空,直接取走 instance可能会造成程序崩溃。
也就是说在jdk1.5之前有两个问题:
//volatile+懒汉式双层检查(DCL,Double Check Lock)
private static volatile SingletonPattern volatileSingleton;
public static SingletonPattern getVolatileInstance(){
if(volatileSingleton == null){//第一层检查
//在此处加锁同步比在方法出加锁同步缩小了范围,性能稍高
synchronized (SingletonPattern.class){
if(volatileSingleton == null){//第二层检查
volatileSingleton =new SingletonPattern();
}
}
}
return dclSingleton;
}
分析: 先说一下volatile关键字的两大作用:
所以使用volatile关键字可以保证实例化的赋值操作是最后一步完成,实现了正确的单例模式。
其他单例的实现方法:
public static SingletonPattern InnerSingletonInstance(){
return staticSingleInstance.staticSingleton;
}
private static class staticSingleInstance{
private static SingletonPattern staticSingleton=new SingletonPattern();
}
枚举类是java1.5才出现的,采用枚举的方式除了写起来很简便,还有个好处是安全:因为JVM会保证enum不能被反射并且构造器方法只执行一次,但枚举会很耗内存,所以看情况而定吧 相关代码如下:
//枚举实现
public static SingletonPattern getEnumInstance(){
return EnumSingleton.INSTANCE.getEnumSingleton();
}
private enum EnumSingleton {
INSTANCE;
private SingletonPattern enumSingleton;
//JVM会保证此方法绝对只调用一次
private SingletonPattern getEnumSingleton() {
enumSingleton = new SingletonPattern();
return enumSingleton;
}
}
关于单例模式的实现,今天就说这么多,其实核心就是:构造私有,并且通过静态方法获取一个实例,在这个过程中必须保证线程的安全性。 告辞。