无知的我正在学习设计模式中的单例模式。。。
如果有遇到有任何无法进展问题或者疑惑的地方,应该在讨论区留言 或者 其他途径以寻求及时的帮助,以加快学习效率 或者 培养独立解决问题的能力、扫清盲点、补充细节
目录
单例模式-饿汉式
是什么
一个类只能创建一个实例对象
如何实现
- 准备一个类
- 定义一个无参私有构造方法。//为了其不能被调用而被重写
- 创建当前类的实例,并赋给一个私有的、静态的、最终的成员变量
- 定义一个静态的方法,并编写这个方法,使得这个方法被调用时会返回第三步创建的类实例
代码实现-定义一个饿汉式
public class Singleton1 implements Serializable {
private Singleton1() {
System.out.println("private Singleton1()");
}
private static final Singleton1 INSTANCE = new Singleton1();
public static Singleton1 getInstance() {
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
引出问题-该类实例是在什么时候被创建的?
在该类被触发的时候,就是调用该类的时候。这是因为在调用时,该类的被静态修饰的会被自动地调用,从而创建了该类的实例
误区是 在调用 能够返回该类实例的方法 的时候。
举例说明 上一个问题
可看见
- 当调用Singleton1.otherMethod()时,类的实例已经被创建。所以下面语句输出的是同一个对象
破坏用反射
原理
- 获取类对象
- 根据类对象获取其无参构造方法
- 让该无参构造方法可以被访问
- 通过无参构造方法创建新的对象
代码实现
package day01.pattern;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class TestSingleton {
public static void main(String[] args) throws Exception {
Singleton5.otherMethod();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
System.out.println(Singleton5.getInstance());
System.out.println(Singleton5.getInstance());
// 反射破坏单例 1
reflection(Singleton3.class);
}
// 反射破坏单例 2
private static void reflection(Class<?> clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<?> constructor = clazz.getDeclaredConstructor();//根据传入的类对象得到该类的无参构造方法
constructor.setAccessible(true);//通过上一条语句获得的无参构造方法,让私有的构造方法也可以被使用
System.out.println("反射创建实例:" + constructor.newInstance());//通过无参构造方法,创建对象
}
}
运行结果如下图
解决是 在该类构造方法中,写一个判断该类实例是否已经创建方法的条件,如果已经创建有,则抛出异常
public class Singleton1 implements Serializable {
private Singleton1() {
//解决
if (INSTANCE != null) {
throw new RuntimeException("单例对象不能重复创建");
}
System.out.println("private Singleton1()");
}
private static final Singleton1 INSTANCE = new Singleton1();
public static Singleton1 getInstance() {
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
破坏用反序列化
原理
- 获取单例对象
- 把对象转换为字节流
- 通过反序列化的字节数组创建新的对象
- 把字节流还原为对象
代码实现
前提是 饿汉类实现了序列化
package day01.pattern;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class TestSingleton {
public static void main(String[] args) throws Exception {
Singleton5.otherMethod();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
System.out.println(Singleton5.getInstance());
System.out.println(Singleton5.getInstance());
// 反序列化破坏单例
serializable(Singleton3.getInstance());
}
private static void serializable(Object instance) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
System.out.println("反序列化创建实例:" + ois.readObject());
}
}
运行结果如下图
解决是 在饿汉类中,重写一个返回值为object的readResolve方法
public class Singleton1 implements Serializable {
private Singleton1() {
System.out.println("private Singleton1()");
}
private static final Singleton1 INSTANCE = new Singleton1();
public static Singleton1 getInstance() {
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
public Object readResolve() {
return INSTANCE;
}
}
这是因为当通过反序列化创建该类实例时,会首先执行该方法来返回已经创建的实例
破坏用unsafe
原理
利用unsafe获取类对象而直接创建新的实例对象
代码实现
package day01.pattern;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class TestSingleton {
public static void main(String[] args) throws Exception {
Singleton5.otherMethod();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
System.out.println(Singleton5.getInstance());
System.out.println(Singleton5.getInstance());
// Unsafe 破坏单例
unsafe(Singleton3.class);
}
private static void reflection(Class<?> clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
System.out.println("反射创建实例:" + constructor.newInstance());
}
}
解决是 暂时没有
单例模式-枚举饿汉式
实现
代码实现
public enum Singleton2 {
INSTANCE;
private Singleton2() {
System.out.println("private Singleton2()");
}
@Override
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public static Singleton2 getInstance() {
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
反序列化破坏失效。这是因为,当进行反序列化的字节数组想要创建新对象时,如果遇到了枚举类,则会直接返回已经创建的实例
原理
package day01.pattern;
enum Sex {
MALE, FEMALE;
}
//下面是经过翻译后的当前类生成的class文件
/*final class Sex extends Enum<Sex> {
public static final Sex MALE;
public static final Sex FEMALE;
private Sex(String name, int ordinal) {
super(name, ordinal);
}
static {
MALE = new Sex("MALE", 0);
FEMALE = new Sex("FEMALE", 1);
$VALUES = values();
}
private static final Sex[] $VALUES;
private static Sex[] $values() {
return new Sex[]{MALE, FEMALE};
}
public static Sex[] values() {
return $VALUES.clone();
}
public static Sex valueOf(String value) {
return Enum.valueOf(Sex.class, value);
}
}*/
破坏用反射
代码实现
package day01.pattern;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class TestSingleton {
public static void main(String[] args) throws Exception {
Singleton5.otherMethod();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
System.out.println(Singleton5.getInstance());
System.out.println(Singleton5.getInstance());
// 反射破坏单例
reflection(Singleton3.class);
private static void reflection(Class<?> clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
System.out.println("反射创建实例:" + constructor.newInstance("OTHER", 1));
}
}
结果是 反射破坏失效,会抛出异常。这是因为同反序列化的原因类似
破坏用unsafe
代码实现
package day01.pattern;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import sun.misc.Unsafe;
// 示例:通过 Unsafe 造出一个 Enum 对象
public class EnumCreator {
public static void main(String[] args) throws Exception {
Unsafe unsafe = UnsafeUtils.getUnsafe();
long nameOffset = unsafe.objectFieldOffset(Enum.class.getDeclaredField("name"));
long ordinalOffset = unsafe.objectFieldOffset(Enum.class.getDeclaredField("ordinal"));
Sex o = (Sex) unsafe.allocateInstance(Sex.class);
unsafe.compareAndSwapObject(o, nameOffset, null, "阴阳人");
unsafe.compareAndSwapInt(o, ordinalOffset, 0, 2);
System.out.println(o.name());
System.out.println(o.ordinal());
}
}
结果是 破坏失败
单例模式-懒汉式
代码实现
public class Singleton3 implements Serializable {
private Singleton3() {
System.out.println("private Singleton3()");
}
private static Singleton3 INSTANCE = null;
// Singleton3.class
public static Singleton3 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton3();
}
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
引出问题 当多线程时,有可能破环单例
解决办法是 给这个方法加上一把锁(就是synchronized修饰符)。如下
public class Singleton3 implements Serializable {
private Singleton3() {
System.out.println("private Singleton3()");
}
private static Singleton3 INSTANCE = null;
// Singleton3.class
public static synchronized Singleton3 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton3();
}
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
引出问题 加锁加导致性能降低。这是因为只需要在第一次并发时,才需要加锁维护,但是在创建实例完成后,就不需要了。
单例模式-DCL懒汉式
双检锁懒汉式
解决上一个问题的办法是 在加锁前再做一次判断
public class Singleton4 implements Serializable {
private Singleton4() {
System.out.println("private Singleton4()");
}
private static volatile Singleton4 INSTANCE = null; // 可见性,有序性 @volatile 必须加
public static Singleton4 getInstance() {
if (INSTANCE == null) {//在这再加一个判断
synchronized (Singleton4.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton4();
}
}
}
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
容易产生的疑惑:为什么内部还需要一次判断呢?可以假设没有内部判断,做代码模拟,可知道会发生多线程问题。
单例模式-DCL懒汉式为何加volatile
INSTANCE = new Singleton4()
不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值,其中后两步可能被指令重排序优化,变成先赋值、再调用构造
如果线程1 先执行了赋值,线程2 执行到第一个 INSTANCE == null
时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象
演示图像
出现错误的情况
添加后volatile
饿汉式、枚举饿汉式为什么不需要考虑多线程问题?
这是因为创建的对象赋值给了静态变量,静态变量储存在静态代码块,静态代码块自动加锁
单例模式-内部懒汉式
public class Singleton5 implements Serializable {
private Singleton5() {
System.out.println("private Singleton5()");
}
private static class Holder {
static Singleton5 INSTANCE = new Singleton5();
}
public static Singleton5 getInstance() {
return Holder.INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
好处是 既防止了多线程问题,又防止了效率降低(第一次访问时才用到)