转载请注明出处: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 件事情。
- 给 instance 分配内存
- 调用 Singleton 的构造函数来初始化成员变量
- 将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)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例