欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

结合JDK源码看设计模式——单例模式(未完待续)

程序员文章站 2023-04-07 23:10:24
定义: 保证一个类仅有一个实例,并提供一个全局访问点 适用场景: 确保任何情况下这个对象只有一个实例 详解: 1.私有构造器: 将本类的构造器私有化,其实这是单例的一个非常重要的步骤,没有这个步骤,可以说你的就不是单例模式。这个步骤其实是防止外部函数在new的时候能构造出来新的对象,我们说单例要保证 ......

定义:

  保证一个类仅有一个实例,并提供一个全局访问点

适用场景:

  确保任何情况下这个对象只有一个实例

详解:

  1. 私有构造器
  2. 单利模式中的线程安全
  3. 延迟加载
  4. 序列化和反序列化安全,
  5. 防止反射攻击
  6. 结合jdk源码分析设计模式

1.私有构造器:
  将本类的构造器私有化,其实这是单例的一个非常重要的步骤,没有这个步骤,可以说你的就不是单例模式。这个步骤其实是防止外部函数在new的时候能构造出来新的对象,我们说单例要保证一个类只有一个实例,如果外部能new新的对象,那我们单例就是失败的。所以无论什么时候一定要将这个构造器私有化

2.单例模式中的线程安全+延时加载(懒汉式):

  其实从单线程角度来看,懒汉式是安全。这里我们先来介绍一个线程安全的懒汉式接下来我们从三个版本的懒汉式来分析如何即做到线程安全又做到效率提高

  2.1原始版本

public class lazysingleton {
private static lazysingleton lazysingleton = null;
private lazysingleton(){
if(lazysingleton != null){
throw new runtimeexception("单例构造器禁止反射调用");
}
}
public static lazysingleton getinstance(){
if(lazysingleton == null){
lazysingleton = new lazysingleton();
}
return lazysingleton;
}

  我们来稍微分析一下为什么线程不安全,现在有a,b两个线程,假设两个线程同时都走到了lazysingleton = new lazysingleton();这个创建对象的行,当都执行完的时候,就会创建两个不同的对象然后分别返回。所以违背了单例模式的定义

  2.2加锁

  可能很多人会直接在getinstance()方法上加一个synchronize关键字,这样做完全可以但是效率会较慢,因为synchronize相当于锁了整个对象,下面的双锁结构就会比较轻量级一点

public class lazydoublechecksingleton {
private volatile static lazydoublechecksingleton lazydoublechecksingleton = null;
private lazydoublechecksingleton(){

}
public static lazydoublechecksingleton getinstance(){
if(lazydoublechecksingleton == null){
synchronized (lazydoublechecksingleton.class){
if(lazydoublechecksingleton == null){
lazydoublechecksingleton = new lazydoublechecksingleton();
}
}
}
return lazydoublechecksingleton;
}
}

  可能很多人一眼就看见synchronize关键字位置变换了,锁的范围变小了,但是最关键的一个是private volatile static lazydoublechecksingleton lazydoublechecksingleton = null;中的volatile关键字,因为如果不加这个关键字的时候,jvm会对没有依赖关系的语句进行重排序,就是可能会在线程a的时候底层先设置lazydoublechecksingleton 指向刚分配的内存地址,然后再来初始化对象,线程b呢在线程a设置lazydoublechecksingleton 指向刚分配的内存地址完后就走到了第一个if,这时判断是不为空的所以都没有竞争synchronize中的资源就直接返回了,但是注意线程a并没有初始化完对象,所以这时就会出错。为了解决上述问题,我们可以引入volatile关键字,这个关键字是会有读屏障写屏障的,也就是由这个关键字修饰的变量,它中间的操作会额外加一层屏障来隔绝,详情可以参考这篇博客。就会禁止一些操作的重排序。
  2.3静态内部类

public class staticinnerclasssingleton {
private static class innerclass{
private static staticinnerclasssingleton staticinnerclasssingleton = new staticinnerclasssingleton();
}
public static staticinnerclasssingleton getinstance(){
return innerclass.staticinnerclasssingleton;
}
private staticinnerclasssingleton(){
if(innerclass.staticinnerclasssingleton != null){
throw new runtimeexception("单例构造器禁止反射调用");
}
}


}

  我在类内部直接定义一个静态内部类,在这个类需要加载的时候我直接把初始化的工作放在了静态内部类中,当有几个线程进来的时候,在class加载后被线程使用之前都是类的初始化阶段,在这个阶段jvm会获取一个锁,这个锁可以同步多个线程对一个类的初始化,然后在内部类的初始化中会进行staticinnerclasssingleton类的初始化。可以这么理解,其实我们这个也是加了锁,不过这是jvm内部加的锁。

备注:未完待续,明天介绍下面的知识点,并且会带入jdk源码中的单例模式进行解释