单例模式
1、什么叫单例模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意点:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
- 不需要实例化该类对象就可以访问。
2、单例实现介绍
**实现意图:**保证一个类仅有一个实例,并提供一个访问它的全局访问点。
**主要解决:**一个全局使用的类频繁地创建与销毁。
**何时使用:**当您想控制实例数目,节省系统资源的时候。
**如何解决:**判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
**关键代码:**构造函数是私有的。
3、具体实现
3.1、饿汉式单例
3.1.1、代码实现
/**
* @author luosong
* @version 1.0
* @date 2021/3/23 9:17
* 饿汉式单例
*/
public class HungrySingle {
// 构造方法私有化
private HungrySingle(){
}
// 单例自己创建自已的对象
private static HungrySingle hungrySingle = new HungrySingle();
// 对外提供唯一的访问方式
public static HungrySingle getInstance(){
return hungrySingle;
}
}
3.1.2、存在的问题
类加载的时候就去初始化类,如果对象并没有使用,浪费内存,增加服务器负担。
3.2、懒汉式单例
3.2.1、代码实现
/**
* @author luosong
* @version 1.0
* @date 2021/3/23 9:16
* 懒汉式单例
*/
public class LazySingle {
private LazySingle(){
}
public static LazySingle lazySingle = null;
public static LazySingle getInstance(){
if (lazySingle == null){
lazySingle = new LazySingle();
}
return lazySingle;
}
}
3.2.2、存在的问题
懒汉式单例在类加载并不会创建,在使用调用getInstance()方法才会创建,但是存在线程安全的问题,多线程情况下可能会出现创建多个单例的情况
测试代码如下:
public class SingleTest {
public static void main(String[] args) {
for (int i = 0; i <20 ; i++) {
new Thread(()->{
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LazySingle.getLazySingle().hashCode());
//System.out.println(HungrySingle.getHungrySingle().hashCode());
}).start();
}
}
}
测试结果如下:在并发情况下懒汉模式并不安全,出现了不同hashCode值的对象,说明并没有保持单例模式的特性。
3.3、懒汉式单例synchronized
基于懒汉式存在的并发问题,加锁解决
3.3.1、代码实现
public class LazySingle {
private LazySingle(){
}
public static LazySingle lazySingle = null;
public static synchronized LazySingle getLazySingle(){
if (lazySingle == null){
lazySingle = new LazySingle();
}
return lazySingle;
}
}
3.3.2、存在的问题
该单例模式确实可以解决并发的问题,但是synchronized是重量级锁,效率极低不推荐使用。
3.4、懒汉式单例DCL
基于上诉synchronized加锁造成的效率低下问题,提出双重锁的机制,同样有加锁的效果,但是效率高。
3.4.1、代码实现
public class LazySingle {
private LazySingle(){
}
public static LazySingle lazySingle = null;
public static synchronized LazySingle getLazySingle(){
if (lazySingle == null){
// 只要lazySingle为空就锁住该类,只允许当前线程操作
synchronized (LazySingle.class){
if (lazySingle == null){
lazySingle = new LazySingle();
}
}
}
return lazySingle;
}
}
3.4.2、存在的问题
DCL双重锁,效率比synchronized有较大的提高,但是内部实现还是基于synchronized锁去实现,所以也不太可取。
3.5、懒汉式单例静态内部类
3.5.1、代码实现
这种方式利用了classloader机制来保证初始化LAZYSINGLE 时只有一个线程,LazySingleStaticClass类被装载了,LAZYSINGLE不一定被初始化。因为SingletonHolder类没有被主动使用,只有通过显式调用 getInstance方法时,才会显式装载LazySingleStaticClass类,从而实例化 LAZYSINGLE
public class LazySingleStaticClass {
private LazySingleStaticClass(){
}
private static class SingletonHolder{
private static LazySingleStaticClass LAZYSINGLE = new LazySingleStaticClass();
}
public static LazySingleStaticClass getInstance(){
return SingletonHolder.LAZYSINGLE;
}
}
4、破坏单例
4.1、指令重排volatile
创建对象的三个步骤
- 分配内存空间
- 执行构造方法,初始化对象
- 把这个对象指向这个空间
在多线程情况下可能会出现指令重排情况本来顺序执行是123,可能因为指令重排执行顺序会变成132针对上诉问题,lazySingle应该加上关键字volatile
public static volatile LazySingle lazySingle = null;
4.2、反射
4.2.1、代码实现
根据反射来破坏单例,引用DCL双重锁单例测试
public class LazySingle {
private LazySingle(){
}
public static volatile LazySingle lazySingle = null;
public static synchronized LazySingle getLazySingle(){
if (lazySingle == null){
synchronized (LazySingle.class){
if (lazySingle == null){
lazySingle = new LazySingle();
}
}
}
return lazySingle;
}
}
反射破坏单例代码
public class ReflectionTest {
// 两个对象返回的结果果然不一样,因为反射绕开了getLazySingle方法走了私有的构造方法
public static void main(String[] args) throws Exception {
LazySingle lazySingle = LazySingle.getLazySingle();
Class<? extends LazySingle> aClass = lazySingle.getClass();
Constructor<? extends LazySingle> declaredConstructor = aClass.getDeclaredConstructor(null);
// 私有方法开启安全模式
declaredConstructor.setAccessible(true);
LazySingle lazySingle1 = declaredConstructor.newInstance(null);
System.out.println(lazySingle==lazySingle1);// false
}
}
4.2.2、解决办法
public class LazySingle {
// flag 必须是static的,不然每个对象都会有一份,构造里面的判断逻辑没什么用
private static Boolean flag = true;
private LazySingle(){
synchronized (LazySingle.class){
if (flag){
// 第一次通过getLazySingle()进来,后续直接报错
flag = false;
}else {
throw new RuntimeException("不要试图用反射破坏单例");
}
}
}
public static volatile LazySingle lazySingle = null;
public static synchronized LazySingle getLazySingle(){
if (lazySingle == null){
synchronized (LazySingle.class){
if (lazySingle == null){
lazySingle = new LazySingle();
}
}
}
return lazySingle;
}
}
但是有问题存在,反射难道不能破坏属性吗?
单例继续破坏
public class ReflectionTest {
public static void main(String[] args) throws Exception {
Class<? extends LazySingle> aClass1 = LazySingle.class;
Constructor<? extends LazySingle> declaredConstructor = aClass1.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazySingle lazySingle = declaredConstructor.newInstance(null);
System.out.println(lazySingle);
Class<? extends LazySingle> aClass2 = LazySingle.class;
Constructor<? extends LazySingle> declaredConstructor1 = aClass2.getDeclaredConstructor(null);
declaredConstructor1.setAccessible(true);
Field flag = aClass2.getDeclaredField("flag");
flag.setAccessible(true);
flag.set(aClass2,true);
LazySingle lazySingle1 = declaredConstructor1.newInstance(null);
System.out.println(lazySingle1);
}
}
执行结果,明显两个值不相等。
com.test.LazySingle@6f75e721
com.test.LazySingle@782830e
那又该如何解决呢?反射初始化方法newInstance源码给出如下提示
4.2.3、枚举防止反射破坏
新建枚举类EnumSingle
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
编译完枚举类可以直接反编译看出,枚举类其实就是一个Java类
为了测试上诉情况,直接使用反射创建枚举,看是否为出现反射定义的枚举错误
先通过idea自带反编译工具查看枚举类型的构造函数,如下可以看出是无参构造
class Test{
public static void main(String[] args) throws Exception {
Class<EnumSingle> enumSingleClass = EnumSingle.class;
Constructor<EnumSingle> declaredConstructor = enumSingleClass.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle = declaredConstructor.newInstance(null);
System.out.println(enumSingle);
}
}
执行结果如下
上诉报错原因是构造方法出错,但明明idea反编译工具显示的是无参构造,为什么会报这样的错呢?
重新使用市面上流行的jad反编译工具在cmd窗口执行jad -sjava EnumSingle.class,结果如下
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/test/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
通过jad编译后的文件直接可以看出,此枚举类型的构造方法并非无参构造,而是private EnumSingle(String s, int i)
修改代码如下
class Test{
public static void main(String[] args) throws Exception {
Class<EnumSingle> enumSingleClass = EnumSingle.class;
// private EnumSingle(String s, int i)
Constructor<EnumSingle> declaredConstructor = enumSingleClass.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle = declaredConstructor.newInstance(null);
System.out.println(enumSingle);
}
}
执行结果,达到目的
4.3、序列化
4.3.1、代码实现
public class SerializableSingle {
public static void main(String[] args) throws Exception {
// Write to file
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testFile"));
oos.writeObject(LazySingle.getLazySingle());
// Read from file
File file = new File("testFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
LazySingle newInstance = (LazySingle) ois.readObject();
System.out.println(newInstance == LazySingle.getLazySingle());
}
}
4.3.2、源码分析
1、因为存在不同的对象,那么从如下代码开始分析
LazySingle newInstance = (LazySingle) ois.readObject();
2、进入源码readObject方法
public final Object readObject()
throws IOException, ClassNotFoundException{
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
// 生成对象的方法
Object obj = readObject0(false);
// 此处省略部分代码
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
3、进入方法readObject0
private Object readObject0(boolean unshared) throws IOException {
// 此处省略代码
byte tc;
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}
depth++;
totalObjectRefs++;
try {
switch (tc) {
// 此处省略代码
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
4、进入方法readOrdinaryObject()
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
// 此处省略代码
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
passHandle = handles.assign(unshared ? unsharedMarker : obj);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(passHandle, resolveEx);
}
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}
handles.finish(passHandle);
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
// 如果实例化对象有readResolve方法,hasReadResolveMethod返回true
// 此方法体主要是取出readResolve方法的值rep将obj引用指向readResolve方法的返回值rep
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
从上面源码可知,实例化对象需要存在一个readResolve方法即可,但是怎么确定参数和返回值呢?
由ObjectStreamClass类构造方法可以得出
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
private static Method getInheritableMethod(Class<?> cl, String name,
Class<?>[] argTypes,
Class<?> returnType){}
综上实例化对象加入方法readResolve如下所示。
// 该方法并非被重写的方法
private Object readResolve(){
return lazySingle;
}
运行代码,返回true成功。