单例模式:保证一个类只有一个实例 ,并且提供一个访问该实例的全局访问点
常见的单例模式形式
1.饿汉式单例模式
为什么叫饿汉式呢?就比如你饿得很快,手边必须事先放着吃的东西才可以,有所准备,不管我饿不饿反正必须得有!所以饿汉式单例模式就是不管程序是否需要这个类的实例,总是在类加载的时候就创建好实例
。请看代码, 通过私有构造器
,在类的内部创建一个final static
实例,这样当该类被初始化的时候,实例就会被创建好并且唯一,通过get方法这一访问点就可以访问该类的实例了。可是你准备的吃的放在那里不吃,不就占你家的空间吗?所以饿汉式的一个缺点比如你在代码里声明的一些数组,你没使用就白白浪费着空间~
//饿汉式单例
public class HungryPattern {
//public byte[] bytes = new byte[1024];
//私有化构造器
private HungryPattern(){
}
public void hello(){
System.out.println("Hello");
}
//类初始化的时候就立即加载该类的实例,不能被继承
private final static HungryPattern instance = new HungryPattern();
//提供获取该对象的方法 没有synchronized 效率高!
public static HungryPattern getInstance(){
return instance;
}
}
//缺点 代码一运行,bytes已经占用了空间,如果长时间不调用这个类,就会浪费空间
通过代码我们知道,饿汉式单例模式线程安全(不排除反射),效率较高(没有加锁),但是不能延时加载
2.懒汉式单例模式
懒汉总是不愿意动手,除非自己饿的实在受不了了,才去找吃的。所以懒汉式单例模式就是等我真正需要的时候我才创建实例
,考虑到线程安全的问题,我们需要在get方法加一个synchronized
关键字,这样我们就能保证多线程的情况下仍然只有一个实例生成了~
//懒汉式单例
public class LazyPattern {
//私有化构造器
private LazyPattern(){
}
//类初始化的时候不立即加载该对象
private static LazyPattern instance;
//提供获取该对象的方法 并发情况下会生成多个实例所以加synchronized
//有synchronized 效率较低
public static synchronized LazyPattern getInstance(){
if(instance == null){
instance = new LazyPattern();
}
return instance;
}
}
通过代码我们知道,懒汉式单例模式线程安全(不排除反射),由于加了锁所以效率不高,但可以延时加载
3.DCL(Double Checked Locking)懒汉式单例模式
看英文就知道我们需要上两层锁,为什么需要两层锁呢?我个人是这样理解的,比如有两个线程小白和小黑,其中小白先进来发现instance为空,于是小白就走到了创建实例这一步,但是小白还没创建完呢,此时小黑突然也进来了,小黑发现instance也为空,于是小黑也往创建实例这一步走了,这样可能就创建了两个实例,所以我们第一步就要锁着这个类,当小白进来的时候,不让其他的线程再进来,等我创建完成后其他的线程你才能打这个类的主意,这样其他线程就不会发现没有instance了~这里我们还需要使用volatile关键字避免指令重排
,为什么呢?由于我们new对象的时候,不是一蹴而就的,它不是一个原子性操作,分为几步比如1.分配内存空间2.执行构造方法,初始化对象3.把这个对象指向这个空间。线程正常走123没有问题,但由于指令重排,线程可能会走132这一条路,比如小白走了132,当小白走到3的时候,这个空间已经不为空了,但是还没初始化对象呢。此时小黑也走到了3,但小黑看见空间不为空,认为对象已经初始化好了,就傻傻的走return了,这样就出现了问题,所以我们要加volatile关键字避免指令重排。
//DCL懒汉式
public class LazyDCL {
//私有化构造器
private LazyDCL(){
}
//类初始化的时候不立即加载该对象,volatile避免指令重排
private volatile static LazyDCL instance;
//双重检测锁模式下的懒汉式单例 DCL懒汉式
public static LazyDCL getInstance(){
if(instance == null){
synchronized (LazyDCL.class){
if(instance==null){
instance = new LazyDCL(); //不是一个原子性操作
}
}
}
return instance;
}
}
似乎这种方法更好一点,但很多人说不建议使用,具体我也没接触过,毕竟就是个小白。
4.静态内部类实现单例模式
//静态内部类实现单例模式
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
5.枚举单例
//枚举本身是一个类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
EnumSingle enumSingle = EnumSingle.INSTANCE;
EnumSingle enumSingle1 = EnumSingle.INSTANCE; //可以看到两者相等,枚举确实是单例的
//破坏枚举的单例,反射无法破坏枚举的单例
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle2 = declaredConstructor.newInstance();
System.out.println(enumSingle);
System.out.println(enumSingle1);
System.out.println(enumSingle2);
}
}
利用反射破坏单例模式
1.
我们都知道反射这个魔鬼,通过反射可以破坏单例模式,怎么破坏呢?我们可以通过反射获得私有的构造器,通过把这个构造器的setAccessible属性设置为true,这样直接无视了构造器的私有性
,我们先通过正常的getInstance()方法创建一个实例,再通过反射得到的构造器创建一个实例,经过测试,发现生成的实例确实不是同一个。怎么解决呢?在构造器里加锁
,当第二次创建实例的时候,instance已经不为空,知道你是搞破坏的,直接给你个异常。
/**
* 使用反射破坏
* 破坏操作:无视私有构造器创建实例
* 解决办法:在私有构造器里上锁解决
*/
public class Crackdemo01 {
//私有化构造器
private Crackdemo01(){
synchronized (Crackdemo01.class){
if(instance!=null){
throw new RuntimeException("不要用反射破坏单例模式");
}
}
}
//类初始化的时候不立即加载该对象,volatile避免指令重排
private volatile static Crackdemo01 instance;
//双重检测锁模式下的懒汉式单例 DCL懒汉式
public static Crackdemo01 getInstance(){
if(instance == null){
synchronized (Crackdemo01.class){
if(instance==null){
instance = new Crackdemo01();
}
}
}
return instance;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Crackdemo01 instance = Crackdemo01.getInstance();
Constructor<Crackdemo01> declaredConstructor = Crackdemo01.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); //无视私有的构造器
Crackdemo01 instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
2.
在上述破坏方式1中,当我们不使用类的getInstance()的时候,而是通过反射获得的构造器创建两个或者多个实例时,此时instance将会一直为null,所以在构造器里加的是否为空判断条件失效了,单例模式又被破坏了,怎么解决呢?设置一个标志位来判断
,不管你怎么创建实例,只要第一次创建我会把标志位设置成已创建,当再想创建的时候,代码发现标志位已经是创建标志位,直接抛出一个异常。
/**
* 使用反射破解
* 1.两个实例都无视私有构造器创建实例 单例模式又被破坏
* 2.通过红绿灯标志位方法解决
*/
public class Crackdemo02 {
private static boolean safe = false;
//私有化构造器
private Crackdemo02(){
synchronized (Crackdemo02.class){
if(safe == false){
safe = true;
}else {
throw new RuntimeException("不要用反射破坏异常");
}
}
}
//类初始化的时候不立即加载该对象,volatile避免指令重排
private volatile static Crackdemo02 instance;
//双重检测锁模式下的懒汉式单例 DCL懒汉式
public static Crackdemo02 getInstance(){
if(instance == null){
synchronized (Crackdemo02.class){
if(instance==null){
instance = new Crackdemo02(); //不是一个原子性操作
}
}
}
return instance;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<Crackdemo02> declaredConstructor = Crackdemo02.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); //无视私有的构造器
Crackdemo02 instance = declaredConstructor.newInstance();
Crackdemo02 instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
3.
你挡住了反射两次,反射又不服了,它通过反射获取到了标志位的字段,和构造方法一样,将标志位字段的setAccessible属性设置为true
,然后创建一个实例后,就把标志位改成初始标志位,也就是我创建一个将标志位改为未创建,创建一个将标志位改为未创建…这样单例模式又被破坏了,这样可以通过一些加密来保护这个标志位字段。
/**
* 使用反射破解
* 1.两个实例都无视私有构造器创建实例,单例模式又被破坏
* 2.通过红绿灯标志位方法解决
* 3.通过反射拿到标志位字段
* 4.通过反射创建一个实例后,修改字段为初始字段,发现单例又被破坏
*/
public class Crackdemo03 {
private static boolean safe = false;
//私有化构造器
private Crackdemo03(){
synchronized (Crackdemo03.class){
if(safe == false){
safe = true;
}else {
throw new RuntimeException("不要用反射破坏异常");
}
}
}
//类初始化的时候不立即加载该对象,volatile避免指令重排
private volatile static Crackdemo03 instance;
//双重检测锁模式下的懒汉式单例 DCL懒汉式
public static Crackdemo03 getInstance(){
if(instance == null){
synchronized (Crackdemo03.class){
if(instance==null){
instance = new Crackdemo03(); //不是一个原子性操作
}
}
}
return instance;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Field safe = Crackdemo03.class.getDeclaredField("safe");
safe.setAccessible(true);
Constructor<Crackdemo03> declaredConstructor = Crackdemo03.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); //无视私有的构造器
Crackdemo03 instance = declaredConstructor.newInstance();
safe.set(instance,false);
Crackdemo03 instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}