java 单例模式
饿汉式
简介: 一上来就加载
/**
* @author pikaqiu
* @version 1.0
* @date 2020/12/29 9:46
* 饿汉式
*/
public class Hungry {
// 假如
// 十分的浪费空间
private byte[] bytes1 = new byte[1024*1024];
private byte[] bytes2 = new byte[1024*1024];
private byte[] bytes3 = new byte[1024*1024];
private byte[] bytes4 = 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 = null;
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 volatile LazyMan lazyMan = null;
// 双重检测锁模式 的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance() {
// 加锁
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan(); // 不是一个原子性操作
/**
* 1. 分配内存空间
* 2. 执行构造方法, 初始化对象
* 3. 把对象指向这个空间
*
* 123
* 132 A
*/
}
}
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LazyMan.getInstance();
}, "Thread——" + i).start();
}
}
}
静态内部类实现
public class Holder {
private Holder() {
}
public static Holder getInstance() {
return InnerClass.HOLDER;
}
public static class InnerClass {
private static final Holder HOLDER = new Holder();
}
}
反射进行破解
// 传统
LazyMan lazyMan1 = LazyMan.getInstance();
// 反射
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan lazyMan2 = declaredConstructor.newInstance();
解决:
class LazyMan {
// 多线程情况下测试
private LazyMan() {
synchronized (LazyMan.class) {
if (lazyMan != null) {
throw new RuntimeException("不要试图用反射来破坏异常");
}
}
System.out.println(Thread.currentThread().getName() + "ok");
}
private static volatile LazyMan lazyMan = null;
// 双重检测锁模式 的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance() {
// 加锁
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan(); // 不是一个原子性操作
/**
* 1. 分配内存空间
* 2. 执行构造方法, 初始化对象
* 3. 把对象指向这个空间
*
* 123
* 132 A
*/
}
}
}
return lazyMan;
}
}
破解:
// 双反射
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan lazyMan1 = declaredConstructor.newInstance();
LazyMan lazyMan2 = declaredConstructor.newInstance();
解决:(红绿灯模式)
class LazyMan {
private static boolean redAndGreen = false;
// 多线程情况下测试
private LazyMan() {
synchronized (LazyMan.class) {
if (redAndGreen == false) {
redAndGreen = true;
} else {
throw new RuntimeException("不要试图用反射来破坏异常");
}
}
System.out.println(Thread.currentThread().getName() + "ok");
}
private static volatile LazyMan lazyMan = null;
// 双重检测锁模式 的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance() {
// 加锁
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan(); // 不是一个原子性操作
/**
* 1. 分配内存空间
* 2. 执行构造方法, 初始化对象
* 3. 把对象指向这个空间
*
* 123
* 132 A
*/
}
}
}
return lazyMan;
}
}
破坏:
// 反射
Field redAndGreen = LazyMan.class.getDeclaredField("redAndGreen");
redAndGreen.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan lazyMan1 = declaredConstructor.newInstance();
redAndGreen.set(lazyMan1, false);
LazyMan lazyMan2 = declaredConstructor.newInstance();
总结: 可以通过反射破坏私有
查看反射的newInstance 源码得知 不能使用反射来破坏枚举
枚举
enum 是什么?
枚举本身也是一个类,它本身就是单例的, 没有空参的构造方法
枚举单例模式实现
public enum EnumSingle {
INSTANCE;
public static EnumSingle getInstance() {
return INSTANCE;
}
}
单例常用场景
- servlet中每个servlet都是单例
- 数据库连接池一般都是单例的
- Spring中每个Bean都是单例的