单例模式 java

本文详细介绍了单例模式的多种实现方式,包括饿汉式、懒汉式(synchronized、DCL、volatile、静态内部类)及枚举方式,并讨论了如何防止反射攻击。重点讲解了内存消耗、线程安全和反破解策略。
摘要由CSDN通过智能技术生成

单例模式

一、介绍
含义:

属于创建模式的一种,保证一个类仅有一个实例化对象

适用场景:

需要频繁访问一个对象的时候,或者系统中一个类只实例化一个对象并且该对象与外界进行通讯时,适用单例模式可以节约资源,便于维护。所有的请求都用一个对象来处理。

实现要素:
	单例类的构造函数为私有;
	提供一个自身的静态私有成员变量;
	提供一个公有的静态工厂方法;
代码实现:
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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值