创建模式-单例模式
2022/4/26大约 3 分钟
一,单例模式
单例模式是创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点以供外部代码使用.
1.1 饿汉式
public class HungrySingleton {
// 1,私有化构造方法,避免通过new创建
private HungrySingleton(){
}
// 2,私有静态属性
private static HungrySingleton hungrySingleton = new HungrySingleton();
// 3,向外暴露访问静态属性
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}优缺点:
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题,是线程安全的。
缺点:在类装载的时候就完成实例化,没有达到懒加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
1.2 懒汉式-线程不安全
public class LazySingleton {
// 1,私有化构造方法,避免通过new创建
private LazySingleton(){
}
// 2,私有静态属性
private static LazySingleton lazySingleton;
// 3,向外暴露访问静态属性
public static LazySingleton getSingleton(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}在单线程的情况下是可行的,但是如果是多线程的话就不行了,判断为null和创建是不能不保证原子。
1.3 懒汉式-synchronized
既然想要保证原子性使用synchronized怎么样,如下:
public class LazySingleton {
// 1,私有化构造方法,避免通过new创建
private LazySingleton(){
}
// 2,私有静态属性
private static LazySingleton lazySingleton;
// 3,向外暴露访问静态属性
public static LazySingleton getSingleton(){
synchronized (LazySingleton.class){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
}
return lazySingleton;
}
}这样写这段代码是线程安全的,再多线程环境下并没有什么问题,但是运行的效果不太理想,因为每次获取单例都会加锁,解锁,我们是可以进行优化的。
1.4 懒汉式-双重检测
加锁的目的是为了保证创建对象的过程是单线程的,那我们获取对象就不需要是单线程,如下:
public class LazySingleton {
// 1,私有化构造方法,避免通过new创建
private LazySingleton(){
}
// 2,私有静态属性
private static LazySingleton lazySingleton;
// 3,向外暴露访问静态属性
public static LazySingleton getSingleton(){
if(lazySingleton == null){ // 第一次检查
synchronized (LazySingleton.class){
if(lazySingleton == null){ // 第二次检查
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
}这样写这段代码,如果不细看,大部分人会以为是没问题,但如果在多线程环境下就会出现线程安全问题。原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。因为instance = new LazySingleton();这句代码并不是一个原子操作,分为三步(伪代码)
lazySingleton = allocate(); // 1.分配对象内存空间
instance(lazySingleton); // 2.初始化对象
instance = lazySingleton; // 3.设置instance指向刚分配的内存地址,此时instance!=null由于步骤2和步骤3不存在数据依赖关系,可能会重排序(伪代码)
lazySingleton = allocate(); // 1.分配对象内存空间
instance = lazySingleton; // 3.设置instance指向刚分配的内存地址,此时instance!=null
instance(lazySingleton); // 2.初始化对象当发生指令重拍的话,一条线程访问instance不为null时,由于instance实例未必已初始化完成,获取的是不完整的实例,这不是正常的。
那么该如何 解决呢,很简单,我们使用volatile禁止instance变量被执行指令重排优化即可
public class LazySingleton {
// 1,私有化构造方法,避免通过new创建
private LazySingleton(){
}
// 2,私有静态属性
private static volatile LazySingleton lazySingleton;
// 3,向外暴露访问静态属性
public static LazySingleton getSingleton(){
if(lazySingleton == null){ // 第一次检查
synchronized (LazySingleton.class){
if(lazySingleton == null){ // 第二次检查
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
}