沐风网网站源码分享?沐风网软件

大家好,今天给各位分享沐风网网站源码分享的一些知识,其中也会对沐风网软件进行解释,文章篇幅可能偏长,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在就马上开始吧!

cnblogs.com/chanmufeng/p/15471075.htm

单例模式

有些对象我们只需要一个,比如线程池、ServletContext、ApplicationContext、Windows中的回收站,此时我们便可以用到单例模式。

单例模式就是确保一个类在任何情况下只有一个实例,并提供一个全局访问点

1.饿汉式单例

/**\n*@author蝉沐风\n*饿汉式单例\n*/\npublicclassHungrySingleton{\n//类初始化的时候便进行对象实例化\nprivatestaticfinalHungrySingletonhungrySingleton=newHungrySingleton();\n\nprivateHungrySingleton(){\n}\n\npublicstaticHungrySingletongetInstance(){\nreturnhungrySingleton;\n}\n}\n

优点:

饿汉式单例是最简单的一种单例形式,它没有添加任何的锁,执行效率最高线程安全

缺点:

某些情况下,造成内存浪费,因为对象未被使用的情况下就会被初始化,如果一个项目中的类多达上千个,在项目启动的时候便开始初始化可能并不是我们想要的。

2.简单的懒汉式单例

想解决饿汉式单例一开始就会进行对象的初始化的问题,一个很自然的想法就是当用户调用getInstance方法的时候再进行实例的创建,修改代码如下:

/**\n*@author蝉沐风\n*饿汉式单例\n*/\npublicclassLazySimpleSingleton{\nprivatestaticLazySimpleSingletoninstance;\n\nprivateLazySimpleSingleton(){\n}\n\npublicstaticLazySimpleSingletongetInstance(){\n\t//如果实例不存在,则进行初始化\nif(instance==null){\ninstance=newLazySimpleSingleton();\n}\nreturninstance;\n}\n}\n

上述代码在单线程下能够完美运行,但是在多线程下存在安全隐患。大家可以使用IDEA进行手动控制线程执行顺序来跟踪内存变化,下面我用图解的形式进行多线程下3种情形的说明。

情形1:

每个线程依次执行getInstance方法,得到的结果正是我们所期望的

情形2:

此种情形下,该种写法的单例模式会出现多线程安全问题,得到两个完全不同的对象

情形3:

该种情形下,虽然表面上最终得到的对象是同一个,但是在底层上其实是生成了2个对象,只不过是后者覆盖了前者,不符合单例模式绝对只有一个实例的要求。

3.升级的懒汉式单例

/**\n*@author蝉沐风\n*饿汉式单例-同步锁\n*/\npublicclassLazySynchronizedSingleton{\nprivatestaticLazySynchronizedSingletoninstance;\n\nprivateLazySynchronizedSingleton(){\n}\n\t\n//添加synchronized关键字\npublicsynchronizedstaticLazySynchronizedSingletongetInstance(){\nif(instance==null){\ninstance=newLazySynchronizedSingleton();\n}\nreturninstance;\n}\n}\n

升级之后的程序能完美地解决线程安全问题。

但是用synchronized加锁时,在线程数量较多的情况下,会导致大批线程阻塞,从而导致程序性能大幅下降

有没有一种形式,既能兼顾线程安全又能提升程序性能呢?有,这就是双重检查锁。

4.双重检查锁

/**\n*@author蝉沐风\n*双重检查锁\n*/\npublicclassLazyDoubleCheck{\n//需要添加volatile关键字\nprivatevolatilestaticLazyDoubleCheckinstance;\n\nprivateLazyDoubleCheck(){\n}\n\t\t\n\npublicstaticLazyDoubleCheckgetInstance(){\n//一重检查:检查实例,如果不存在,进入同步区块\nif(instance==null){\nsynchronized(LazyDoubleCheck.class){\n//双重检查:进入同步区块后,再检查一次,如果仍然是null,才创建实例\nif(instance==null){\ninstance=newLazyDoubleCheck();\n}\n}\n}\nreturninstance;\n}\n}\n

第一重检查是为了确认instance是否已经被实例化,如果是,则无需再进入同步代码块,直接返回实例化对象,否则进入同步代码块进行创建,避免每次都排队进入同步代码块影响效率;

第二重检查是真正与实例的创建相关,如果instance未被实例化,则在此过程中被实例化。

双重检查锁版本的单例模式需要使用到volatile关键字,本文不对volatile关键字进行深入分析,之后会单独开一篇文章进行解释

但是,使用synchronized关键总归是要上锁的,对程序性能还是存在影响,下面介绍一种利用Java本身语法特性来实现的一种单例写法。

5.静态内部类实现单例

/**\n*@author蝉沐风\n*静态内部类实现单例\n*/\npublicclassLazyStaticInnerClassSingleton{\n\nprivateLazyStaticInnerClassSingleton(){\n}\n\npublicstaticfinalLazyStaticInnerClassSingletongetInstance(){\nreturnLazyHolder.LAZY;\n}\n\n//静态内部类,未被使用时,是不会被加载的\nprivatestaticclassLazyHolder{\nprivatestaticfinalLazyStaticInnerClassSingletonLAZY=newLazyStaticInnerClassSingleton();\n}\n}\n

用静态内部类实现的单例本质上是一种懒汉式,因为在执行getInstance中的LazyHolder.LAZY语句之前,静态内部类并不会被加载。

这种方式既避免了饿汉式单例的内存浪费问题,又摆脱了synchronized关键字的性能问题,同时也不存在线程安全问题。

到此为止,我们介绍了5种单例写法(除去简单的懒汉式单例由于多线程问题无法用于生产中,其实只有4种),我们发现上述单例模式本质上都是将构造方法私有化,避免外部程序直接进行实例化来达到单例的目的。

那如果我们能够想办法获取到类的构造方法,或者将创建好的对象写入磁盘,然后多次加载到内存,是不是可以破坏上述所有的单例呢?

答案是肯定的,下面我们用反射和序列化两种方法亲自毁灭我们一手搭建的单例。

6.反射破坏单例

/**\n*@author蝉沐风\n*利用反射破坏单例\n*/\npublicclassSingletonBrokenByReflect{\n\npublicstaticvoidmain(String[]args){\n\ntry{\nClass<?>clazz=LazyStaticInnerClassSingleton.class;\n\n//通过反射弧获取类的私有构造方法\nConstructorc=clazz.getDeclaredConstructor(null);\n//强制访问\nc.setAccessible(true);\n\nObjectobj1=c.newInstance();\nObjectobj2=c.newInstance();\n\n//输出false\nSystem.out.println(obj1==obj2);\n\n}catch(Exceptione){\ne.printStackTrace();\n}\n}\n\n\n}\n

如此,我们便使用反射破坏了单例。现在我们以静态内部类单例为例,解决这个问题。

我们在构造方法中添加一些限制,一旦检测到对象已经被实例化,但是构造方法仍然被调用时直接抛出异常。

/**\n*@author蝉沐风\n*静态内部类实现单例\n*/\npublicclassLazyStaticInnerClassSingleton{\n\nprivateLazyStaticInnerClassSingleton(){\nif(LazyHolder.LAZY!=null){\nthrownewRuntimeException(&34;);\n}\n}\n\npublicstaticfinalLazyStaticInnerClassSingletongetInstance(){\nreturnLazyHolder.LAZY;\n}\n\n//静态内部类,未被使用时,是不会被加载的\nprivatestaticclassLazyHolder{\nprivatestaticfinalLazyStaticInnerClassSingletonLAZY=newLazyStaticInnerClassSingleton();\n}\n}\n

7.序列化破坏单例

单例对象创建好之后,有时需要将对象序列化然后写入磁盘,在需要时从磁盘中读取对象并加载至内存,反序列化后的对象会重新分配内存,如果序列化的目标对象恰好是单例对象,就会破坏单例模式。

/**\n*@author蝉沐风\n*可序列化的单例\n*/\npublicclassSeriableSingletonimplementsSerializable{\n//类初始化的时候便进行对象实例化\nprivatestaticfinalSeriableSingletonhungrySingleton=newSeriableSingleton();\n\nprivateSeriableSingleton(){\n}\n\npublicstaticSeriableSingletongetInstance(){\nreturnhungrySingleton;\n}\n}\n\n\n/**\n*@author蝉沐风\n*序列化破坏单例\n*/\npublicclassSingletonBrokenBySerializing{\n\npublicstaticvoidmain(String[]args){\nSeriableSingletons1=SeriableSingleton.getInstance();\nSeriableSingletons2=null;\n\nFileOutputStreamfos=null;\ntry{\nFilefile;\nfos=newFileOutputStream(&34;);\nOutputStreamout;\nObjectOutputStreamoos=newObjectOutputStream(fos);\noos.writeObject(s1);\noos.flush();\noos.close();\nfos.close();\n\nFileInputStreamfis=newFileInputStream(&34;);\nObjectInputStreamois=newObjectInputStream(fis);\ns2=(SeriableSingleton)ois.readObject();\nois.close();\nfis.close();\n\n\n//输出为false\nSystem.out.println(s1==s2);\n}catch(Exceptione){\n\n}\n}\n\n\n}\n

从运行结果上看,反序列化和手动创建出来的对象是不一致的,违反了单例模式的初衷。

那到底如何保证在序列化的情况下也能够实现单例模式呢,其实很简单,只需要增加一个readResolve方法即可。

publicclassSeriableSingletonimplementsSerializable{\n//类初始化的时候便进行对象实例化\nprivatestaticfinalSeriableSingletonhungrySingleton=newSeriableSingleton();\n\nprivateSeriableSingleton(){\n}\n\npublicstaticSeriableSingletongetInstance(){\nreturnhungrySingleton;\n}\n\n//只需要添加这一个函数即可\nprivateObjectreadResolve(){\nreturnhungrySingleton;\n}\n}\n

实现的原理涉及到ObjectInputStream的源码,不属于本文的研究重点,如果读者需要,我可以另开一篇来进行讲解。

8.注册式单例模式

8.1枚举式单例模式

很多博客和文章的实现方式如下(文件名:EnumSingleObject.java)

/*\n*@author蝉沐风\n*枚举式单例1\n*/\npublicclassEnumSingleObject{\nprivateEnumSingleObject(){\n}\n\nenumSingletonEnum{\nINSTANCE;\n\nprivateEnumSingleObjectinstance;\n\nprivateSingletonEnum(){\ninstance=newEnumSingleObject();\n}\n\npublicEnumSingleObjectgetInstance(){\nreturnINSTANCE.instance;\n}\n}\n\n//对外暴露一个获取EnumSingleObject对象的静态方法\npublicstaticEnumSingleObjectgetInstance(){\nreturnSingletonEnum.INSTANCE.getInstance();\n}\n}\n

枚举式的写法为什么可以实现我们的单例模式呢,我们首先使用javacEnumSingleObject.java生成EnumSingleObject.class文件,用反编译工具Jad在.class所在的目录下执行jadEnumSingleObject.class命令,得到EnumSingleObject.jad文件,代码如下

staticfinalclassEnumSingleObject$SingletonEnumextendsEnum{\n\npublicstaticEnumSingleObject$SingletonEnum[]values(){\nreturn(EnumSingleObject$SingletonEnum[])$VALUES.clone();\n}\n\npublicstaticEnumSingleObject$SingletonEnumvalueOf(Strings){\nreturn(EnumSingleObject$SingletonEnum)Enum.valueOf(com/chanmufeng/Singleton/registerSingleton/EnumSingleObject$SingletonEnum,s);\n}\n\npublicEnumSingleObjectgetInstance(){\nreturnINSTANCE.instance;\n}\n\npublicstaticfinalEnumSingleObject$SingletonEnumINSTANCE;\nprivateEnumSingleObjectinstance;\nprivatestaticfinalEnumSingleObject$SingletonEnum$VALUES[];\n\n\t\t//该static代码块是枚举写法能够实现单例模式的关键\nstatic{\nINSTANCE=newEnumSingleObject$SingletonEnum(&34;,0);\n$VALUES=(newEnumSingleObject$SingletonEnum[]{\nINSTANCE\n});\n}\n\nprivateEnumSingleObject$SingletonEnum(Strings,inti){\nsuper(s,i);\ninstance=newEnumSingleObject();\n}\n}\n

其实,枚举式单例在静态代码块中就为INSTANCE进行了赋值,是一种饿汉式单例模式的体现,只不过这种饿汉式是JDK底层为我们做的操作,我们只是利用了JDK语法的特性罢了。

序列化能否破坏枚举式单例

//测试序列化能否破坏\npublicstaticvoidmain(String[]args){\nEnumSingleObjects1=EnumSingleObject.getInstance();\nEnumSingleObjects2=null;\n\nFileOutputStreamfos=null;\ntry{\nfos=newFileOutputStream(&34;);\nObjectOutputStreamoos=newObjectOutputStream(fos);\noos.writeObject(s1);\noos.flush();\noos.close();\nfos.close();\n\nFileInputStreamfis=newFileInputStream(&34;);\nObjectInputStreamois=newObjectInputStream(fis);\ns2=(EnumSingleObject)ois.readObject();\nois.close();\nfis.close();\n\n\n//输出为false\nSystem.out.println(s1==s2);\n}catch(Exceptione){\ne.printStackTrace();\n}\n}\n

很遗憾,序列化依然会破坏枚举式单例EnumSingleObject

What???不是说枚举式单例非常的优雅吗?连EffectiveJava都推荐使用吗?

别急,接下来我们观察另一种写法

/**\n*@author蝉沐风\n*枚举式单例2\n*/\npublicenumEnumSingleObject2{\n\nINSTANCE;\n\nprivateObjectdata;\n\npublicObjectgetData(){\nreturndata;\n}\n\npublicvoidsetData(Objectdata){\nthis.data=data;\n}\n\npublicstaticEnumSingleObject2getInstance(){\nreturnINSTANCE;\n}\n}\n\n

我们再来进行序列化测试

publicstaticvoidmain(String[]args){\nEnumSingleObject2s1=EnumSingleObject2.getInstance();\ns1.setData(newObject());\nEnumSingleObject2s2=null;\n\nFileOutputStreamfos=null;\ntry{\nfos=newFileOutputStream(&34;);\nObjectOutputStreamoos=newObjectOutputStream(fos);\noos.writeObject(s1);\noos.flush();\noos.close();\nfos.close();\n\nFileInputStreamfis=newFileInputStream(&34;);\nObjectInputStreamois=newObjectInputStream(fis);\ns2=(EnumSingleObject2)ois.readObject();\nois.close();\nfis.close();\n\n\n//输出为true\nSystem.out.println(s1==s2);\n}catch(Exceptione){\ne.printStackTrace();\n}\n}\n

打印结果为true,说明枚举式单例2的写法可以防止序列化破坏。

而很多文章和博客用的往往是第1种写法,下面我们解释这两种写法的区别.

我们进入ObjectInputStream类的readObject()方法

publicfinalObjectreadObject()\nthrowsIOException,ClassNotFoundException\n{\n…\ntry{\nObjectobj=readObject0(false);\n…\nreturnobj;\n}finally{\n…\n}\n}\n\n

在readObject()方法中又调用了readObject0()方法

privateObjectreadObject0(booleanunshared)throwsIOException{\n…\n//枚举式单例1的程序会进入到这里\ncaseTC_CLASS:\n\treturnreadClass(unshared);\n\t\t…\n//枚举式单例2的程序会进入到这里\ncaseTC_ENUM:\n\treturncheckResolve(readEnum(unshared));\n\n}\n

我们先看一下readEnum()方法

privateEnum<?>readEnum(booleanunshared)throwsIOException{\n…\nStringname=readString(false);\nEnum<?>result=null;\nClass<?>cl=desc.forClass();\nif(cl!=null){\ntry{\n@SuppressWarnings(&34;)\n\t//!!!!这里是重点\nEnum<?>en=Enum.valueOf((Class)cl,name);\nresult=en;\n}catch(IllegalArgumentExceptionex){\n…\n}\n\n}\n…\nreturnresult;\n}\n\n

到这里我们发现,枚举类型其实通过类名和类对象找到唯一一个枚举对象,因此,枚举对象不会被类加载器加载多次。

而readClass()并无此功能。

反射能否破坏枚举式单例

publicstaticvoidmain(String[]args){\n\ntry{\n\nClass<?>clazz=EnumSingleObject2.class;\n\n//通过反射获取类的私有构造方法\nConstructorc=clazz.getDeclaredConstructor(null);\n//强制访问\nc.setAccessible(true);\n\nObjectobj1=c.newInstance();\nObjectobj2=c.newInstance();\n\n//输出false\nSystem.out.println(obj1==obj2);\n\n}catch(Exceptione){\ne.printStackTrace();\n}\n}\n

运行结果如下

结果报了java.lang.NoSuchMethodException异常,原因是java.lang.Enum中没有无参的构造方法,我们查看java.lang.Enum的源码,只有下面一个构造函数

protectedEnum(Stringname,intordinal){\nthis.name=name;\nthis.ordinal=ordinal;\n}\n

我们改变一下反射构建的方式

publicstaticvoidmain(String[]args){\n\ntry{\nClass<?>clazz=EnumSingleObject2.class;\n\n//通过反射获取类的私有构造方法\n//Constructorc=clazz.getDeclaredConstructor(null);\nConstructorc=clazz.getDeclaredConstructor(String.class,int.class);\n//强制访问\nc.setAccessible(true);\n\nObjectobj1=c.newInstance();\nObjectobj2=c.newInstance();\n\n//输出false\nSystem.out.println(obj1==obj2);\n\n}catch(Exceptione){\ne.printStackTrace();\n}\n}\n

运行结果如下

Cannotreflectivelycreateenumobjects,即不能用反射来创建枚举对象,这是Constructor的newInstance()方法在源码上决定的,继续看

publicTnewInstance(Object…initargs)\nthrowsInstantiationException,IllegalAccessException,\nIllegalArgumentException,InvocationTargetException\n{\n…\nif((clazz.getModifiers()&Modifier.ENUM)!=0)\nthrownewIllegalArgumentException(&34;);\nConstructorAccessorca=constructorAccessor;//readvolatile\nif(ca==null){\nca=acquireConstructorAccessor();\n}\n@SuppressWarnings(&34;)\nTinst=(T)ca.newInstance(initargs);\nreturninst;\n}\n

从源码中可以看出,newInstance()方法中做了强制性的判断,如果修饰符是Modifier.ENUM类型,则直接抛出异常。

最后介绍一种注册时单例的另一种写法:容器式单例

Spring单例模式

Spring依赖注入Bean实例默认是单例的。Spring的依赖注入(包括lazy-init方式)都是发生在AbstractBeanFactory的getBean里。getBean的doGetBean方法调用getSingleton进行bean的创建。分析getSingleton()方法

publicObjectgetSingleton(StringbeanName){\n//参数true设置标识允许早期依赖\nreturngetSingleton(beanName,true);\n}\nprotectedObjectgetSingleton(StringbeanName,booleanallowEarlyReference){\n//检查缓存中是否存在实例\nObjectsingletonObject=this.singletonObjects.get(beanName);\nif(singletonObject==null&&isSingletonCurrentlyInCreation(beanName)){\n//如果为空,则锁定全局变量并进行处理。\nsynchronized(this.singletonObjects){\n//如果此bean正在加载,则不处理\nsingletonObject=this.earlySingletonObjects.get(beanName);\nif(singletonObject==null&&allowEarlyReference){\n//当某些方法需要提前初始化的时候则会调用addSingleFactory方法将对应的ObjectFactory初始化策略存储在singletonFactories\nObjectFactory<?>singletonFactory=this.singletonFactories.get(beanName);\nif(singletonFactory!=null){\n//调用预先设定的getObject方法\nsingletonObject=singletonFactory.getObject();\n//记录在缓存中,earlysingletonObjects和singletonFactories互斥\nthis.earlySingletonObjects.put(beanName,singletonObject);\nthis.singletonFactories.remove(beanName);\n}\n}\n}\n}\nreturn(singletonObject!=NULL_OBJECT?singletonObject:null);\n}\n

getSingleton()过程图

ps:spring依赖注入时,使用了双重判断加锁的单例模式

总结

单例模式定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

spring对单例的实现:spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例,这是因为spring管理的是任意的java对象。

OK,关于沐风网网站源码分享和沐风网软件的内容到此结束了,希望对大家有所帮助。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平