单例模式
一、介绍
含义:
属于创建模式的一种,保证一个类仅有一个实例化对象。
适用场景:
需要频繁访问一个对象的时候,或者系统中一个类只实例化一个对象并且该对象与外界进行通讯时,适用单例模式可以节约资源,便于维护。所有的请求都用一个对象来处理。
实现要素:
单例类的构造函数为私有;
提供一个自身的静态私有成员变量;
提供一个公有的静态工厂方法;
代码实现:
public class Singleton{
//静态私有成员变量
private static Singleton instance=null;
//私有构造函数
private Singleton(){}
//静态公有工厂方法,返回唯一实例
public static Singleton getInstance(){
if(instance==null)
instance=new Singleton();
return instance;
}
}
二、饿汉式单例模式
类被加载时,静态变量instance就会被实例化,
类的私有构造函数就会被调用,类中的唯一实例就会被创建
缺点:有可能会浪费内存空间。eg:当类成员变量为数组或占用较大存储空间的集合时,如果不需要用到该变量,则会造成该空间的浪费。
//饿汉式单例模式
public class Singleton{
//静态私有成员变量
private static final Singleton instance=new Singleton();
//私有构造函数
private Singleton(){}
//静态公有工厂方法,返回唯一实例
public static Singleton getInstance(){
return instance;
}
三、懒汉式单例模式(synchronized):
默认不会实例化,new的时候才创建单例对象。使用synchronized同步化机制
public class lazySingleton{
private static lazySingleton instance=null;
private lazySingleton(){
System.out.println(Thread.currentThread().getName()+"ok");
}
//使用同步化机制,以处理多线程环境
synchronized public static lazySingleton getInstance(){
if(instance==null)
instance=new lazySingleton();
return instance;
}
//测试并发情况下是否满足只实例化一个对象
public static void main(String[] args){
for(int i=0;i<10;i++){
new Thread(()->{
lazysingleton.getInstance();
}).start();
}
}
//输出 Thread-0ok;满足
}
四、懒汉式单例模式(DCL双重检测锁模式)
public class lazyMan{
private lazyMan(){
System.out.println(Thread.currentThread().getName()+"ok");
}
private static lazyMan lazyMan;
//双重检测锁模式 DCL懒汉式
public static lazyMan getInstance(){
if(lazyMan==null){ //首先检查lazyMan是否实例化,如果没有实例化则进行创建
synchronized(lazyMan.class){ //对lazyMan对象加锁,保证对象只有一个,使其他事务不能创建
if(lazyMan==null){
lazyMan=new lazyMan();
}
}
}
return lazyMan;
}
}
五、懒汉式单例模式(volatile防止指令重排)
DCL双重检测锁模式存在的漏洞:
lazyMan=new lazyMan()的过程非原子性操作,即不是一次性完成,而是通过以下几个步骤才完成对象的创建:
1:分配内存空间;
2:执行构造方法,初始化对象;
3:将对象指向空间。
上述过程并不都是按序 1-2-3 进行的,有可能乱序进行1-3-2,即发生指令重排。
eg:当线程 A 按1-3-2的顺序执行,先分配内存空间,然后用空对象占用该内存空间,此时还没有执行构造方法完成对象的初始化,将对象放入内存空间。多线程情况下,出现线程B,由于此时空对象指向内存空间,线程B 认为 lazyMan不等于null,会直接return lazyMan,此时lazyMan还没有完成构造,所以内存空间没有对象,导致异常。
因此,要保证lazyMan的构造过程不发生指令重排现象,在lazyMan的实例对象加volatile。
//在DCL模式基础上增加volatile关键词
private volatile static lazyMan lazyMan;
六、懒汉式单例模式(静态内部类)
//初始代码
public class Hoder{
//私有构造函数
private Holder(){}
//返回内部类创建的对象
public static Holder getInstance(){
return InnerClass.HOLDER;
}
//使用内部类创建对象
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
七、使用反射破解懒汉式单例模式(volatile)
// DCL+volatile下的单例模式
public class lazyMan{
private lazyMan(){ }
private volatile static lazyMan lazyMan;
//双重检测锁模式 DCL懒汉式
public static lazyMan getInstance(){
if(lazyMan==null){ //首先检查lazyMan是否实例化,如果没有实例化则进行创建
synchronized(lazyMan.class){ //对lazyMan对象加锁,保证对象只有一个,使其他事务不能创建
if(lazyMan==null){
lazyMan=new lazyMan();
}
}
}
return lazyMan;
}
}
利用反射可以破坏单例模式,创建多个对象。
// 攻:1
//反射:
public static void main(String[] args) throws Exception{
//获得第一个对象
lazyMan instance=lazyMan.getInstance();
//获得空参构造器
Constructor<lazyMan> declaredConstructor= lazyMan.class.getDeclaredConstructor();
//无视私有构造器
declaredConstructor.setAccessible(true);
//利用构造函数创建第二个对象
lazyMan instance2=declaredConstructor.newInstance(null);
System.out.println(instance); //输出lazyMan@15db9742
System.out.println(instance2); //输出lazyMan@6d06d69c
}
//防:1
//在私有构造函数中加锁,使第二次试图创建对象时抛出异常
private lazyMan(){
//对对象加锁
synchronized(lazyMan.class){
//如果此时对象已创建
if(lazyMan!=null)
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
//攻:2
//对象不使用lazyMan.getInstance()方法取得,即此时lazyMan没有创建对象,lazyMan==null,利用反射仍然可创建多个对象.
lazyMan instance1=declaredConstructor.newInstance(null);
lazyMan instance2=declaredConstructor.newInstance(null);
//防:2
//使用红绿灯:设置私有变量,为提高安全性可将变量加密,来标记是否调用构造函数创建对象
private static boolean leelee=false;
private lazyMan(){
synchronized(lazyMan.class){
if(leelee==false) //如果leelee为false,那么创建对象,并设置leelee为true
leelee=true;
else
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
//攻:3
//通过反射获取静态私有变量,破解变量,并在创建对象之后恢复,再创建新的对象。
Field leelee=lazyMan.class.getDeclaredField("leelee");
leelee.setAccessible(true);
lazyMan instance3=declaredConstructor.newInstance(null);
leelee.set(instance, false);
lazyMan instance4=declaredConstructor.newInstance(null);
八、懒汉式单例模式(枚举)
推荐使用枚举类型来实现单例模式,因为使用枚举类型的单例模式不能被反射破坏
//枚举:
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return EnumSingle.INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1= EnumSingle.INSTANCE;
Constructor<EnumSingle> declaConstructor=EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaConstructor.setAccessible(true);
EnumSingle instance2=declaConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
//提示不能使用反射破坏枚举单例
}
参考:
https://www.bilibili.com/video/BV1K54y197iS