创建型模式一:单例模式
学习来自狂神说
kuangstudy.com
最重点思想: 私有构造器.
这就是不让在外部调用构造器.
单例: 只有一个对象.
单例:
- 饿汉式
- 懒汉式 --> DCL懒汉式
- 枚举
饿汉式单例:
一上来就先 new 一个对象, 好像饿汉一样, 所以叫饿汉单例
饿汉的问题: 因为一上来就 new 出对象, 所以可能会浪费内存
// 饿汉式单例子
public class Hungry {
// 可能会浪费空间
private byte[] data1 = new byte[1024 * 1024];
private byte[] data2 = new byte[1024 * 1024];
private byte[] data3 = new byte[1024 * 1024];
private byte[] data4 = new byte[1024 * 1024];
private Hungry() {
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance() {
return HUNGRY;
}
}
因为不想不用就创建这个单例, 平时就放在这, 用的时候再创建
所以就出现了懒汉式单例
懒汉式单例
// 懒汉式单例
public class LazyMan {
private LazyMan() {
}
private static LazyMan lazyMan;
public static LazyMan getInstance() {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
}
发现问题:
单线程 确实单例OK
但如果要是多线程并发 , 就没用了
解决办法
// 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) {
synchronized (LazyMan.class){
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
// 多线程并发
public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
new Thread(() -> {
LazyMan.getInstance();
}).start();
}
}
}
为什么要两次检测 lazyMan == null, 因为怕出现并发问题. 必须线程上锁.
简单复习一下多线程和Lambda表达式
new Thread(() -> { LazyMan.getInstance(); }).start();
但是这样还是有问题
因为 lazyMan = new LazyMan();
不是一个原子性操作
不是原子性操作就有以下问题:
/**
* 1. 分配内存空间
* 2. 执行构造方法, 初始化对象
* 3. 把这个对象指向这个空间
*
* 指令可能不是 123这样运行的, 可能指令重排
* 比如运行顺序是 132
* 123
* 132 A
* B //此时lazyMan还没有完成构造
*/
如果A中在3和2之间, B中开始运行, 这样B中认为lazyMan已经构造好了, 可实际A中只开辟了空间, 还没有初始化对象, 这时候B中会出现错误
解决办法:
避免123指令重排: 使用关键字: volatile 添加在lazyMan上
private volatile static LazyMan lazyMan;
静态内部类:
//静态内部类
public class Holder {
private Holder() {
}
public static Holder getInstance() {
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
但是这些 统统不安全: 因为 **反射**实在太牛逼
反射统统破解(例子: DCL懒汉)
//反射!
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
}
与反射斗智斗勇:
在构造器中加一个判断:
private LazyMan() {
synchronized (LazyMan.class) {
if (lazyMan != null) {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
System.out.println(Thread.currentThread().getName() + "ok");
}
道高一尺魔高一丈:
第一个例子不用正常手段创建:
public static void main(String[] args) throws Exception {
//LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
仍然可以创建多个单例对象
解决上面办法:
用标志作记号, 反射也能获取标志
private static boolean haozhancc = false;
private LazyMan() {
synchronized (LazyMan.class) {
if (haozhancc == false) {
haozhancc = true;
}else {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
解决办法的办法 (魔高一丈):
解密 找到haozhancc就可以:
public static void main(String[] args) throws Exception {
//LazyMan instance1 = LazyMan.getInstance();
Field traceThis = LazyMan.class.getDeclaredField("haozhancc");
traceThis.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
traceThis.set(instance1, false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
想解决办法:
发现枚举:
new Instance源码中:
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
枚举自带单例模式:
引出: 枚举是什么?
枚举本身也是一个Class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
反射不能破坏枚举单例
另外:
- 饿汉式 (线程安全)
- 懒汉式 (线程安全)
- 双重检查锁实现 (线程安全)
- 静态内部类实现 (线程安全)
- 枚举类实现 (线程安全)