设计模式—单件模式

转载请注明出处:https://blog.csdn.net/qq_39071530/article/details/84919432

github地址 https://github.com/esmusssein777/designpatterns

设计模式目录:设计模式目录

 

单件模式确保一个类只有一个实例,并提供一个全局访问点。

单件模式的代码比较简单,我们直接来看经典的代码。

懒汉式,线程不安全

/**
 * @author ligz
 */
public class Singleton {
	//利用一个静态变量来记录Singleton唯一的实例
	private static Singleton uniqueInstance;
	
	//将构造器设置为私有的,只有在Singleton类内才能调用构造器
	private Singleton() {}
	
	public static Singleton getInstance() {
		if(uniqueInstance == null) {
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}
	//其他方法
}

其中最关键的代码就是

public static Singleton getInstance() {
		if(uniqueInstance == null) {
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}

这个对象只有一个实例,那就是uniqueInstance。

因为他的构造方法是私有的,所以我们不能通过new 来创建对象。

我们只有通过getInstance的方法来获取实例。

但我们通过getInstance的方法只会返回同一个实例。

这就是单件模式。一个类只有同一个实例。

 

当制造出多个实例会造成像:程序行为异常、资源使用过量、或者是不一致的结果等等,那么就用单件模式。

 

唯一要注意的一点是,在多线程当中可能会出现问题。

两个线程先后执行了 if 判断条件,再先后执行new操作。会出现两个实例的bug。

那么使用下面来创建实例。

懒汉式,线程安全

public static synchronized Singleton getInstance() {
		if(uniqueInstance == null) {
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}

synchronized 迫使每个线程在进入这个方法之前,要等别的线程离开该方法。

 

还有一种更加复杂的方法,下面这种是保证了在多线程里面的性能。

利用了双重检查加锁,首先检查实例是否创建,如果还未创建,才进行同步。

双重检验锁

/**
 * @author ligz
 */
public class Singleton {
	private volatile static Singleton uniqueInstance;
	
	private Singleton() {};
	
	public static Singleton getInstance() {
		if(uniqueInstance == null) {
			synchronized (Singleton.class) {
				if(uniqueInstance == null) {
					uniqueInstance = new Singleton();
				}
			}
		}
		return uniqueInstance;
	}
}

这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

  1. 给 instance 分配内存
  2. 调用 Singleton 的构造函数来初始化成员变量
  3. 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

 

饿汉式 static final field

public class Singleton{
    //类加载时就初始化
    private static final Singleton instance = new Singleton();
    
    private Singleton(){}

    public static Singleton getInstance(){
        return instance;
    }
}

因为单例被设置成了static 和 final 。在类第一次加载道内存中的时候就已经初始化了。所以创建实例是线程安全的。

这种写法如果完美的话,就没必要在啰嗦那么多双检锁的问题了。缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了

静态内部类 static nested class

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本

 

枚举 Enum

用枚举写单例实在太简单了!这也是它最大的优点。下面这段代码就是声明枚举实例的通常做法。

public enum EasySingleton{
    INSTANCE;
}

我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。

 

就我个人而言,一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值