单例模式
懒汉式和饿汉式区别:
实例化方面: 懒汉式默认不会实例化,外部什么时候调用什么时候new。饿汉式在类加载的时候就实例化,并且创建单例对象。
线程安全方面:
饿汉式线程安全:在线程还没出现之前就已经实例化了,因此饿汉式线程一定是安全的)。
懒汉式线程不安全:因为懒汉式加载是在使用时才会去new实例的
饿汉式
一上来直接创建对象
类加载的时候就实例化,并且创建单例对象。
public class Hungry {
private Hungry() {
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance() {
return HUNGRY;
}
}
懒汉式
默认不会实例化,什么时候用什么时候new。
public class Lazy {
private Lazy() {
}
private static Lazy lazy;
public static Lazy getInstance() {
if (lazy == null) {
lazy = new Lazy();
}
return lazy;
}
}
单线程情况下可以,那多线程呢
public class Lazy {
private Lazy() {
System.out.println(Thread.currentThread().getName() + "ok");
}
private static Lazy lazy;
public static Lazy getInstance() {
if (lazy == null) {
lazy = new Lazy();
}
return lazy;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
Lazy.getInstance();
}).start();
}
}
}
运行结果每次都不一样
进阶
双重检测锁模式(DCL懒汉式)
public class Lazy {
private Lazy() {
System.out.println(Thread.currentThread().getName() + "ok");
}
private static Lazy lazy;
//双重检测锁模式(DCL懒汉式)
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
Lazy.getInstance();
}).start();
}
}
}
在极端情况下有问题
new 不是原子性操作
- 分配内存空间
- 执行构造方法,初始化对象
- 把这个对象指向这个空间
举个例子
期望123
真实可能是132
A线程 进来132(先占空间再放内存对象)
突然来了B线程,线程B指向了这个空间,lazy就!=null,直接return 了lazy,lazy其实还没有完成构造,空间是一片虚无
由于指令重排可能导致的现象
加volatile
完整的双重检测锁加原子性操作
public class Lazy {
private Lazy() {
System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static Lazy lazy;
//双重检测锁模式(DCL懒汉式)
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
Lazy.getInstance();
}).start();
}
}
}
静态内部类实现懒汉式
public class Holder {
private Holder() {
}
public static Holder getInstance() {
return InnerClass.HOLDER;
}
public static class InnerClass {
private static final Holder HOLDER = new Holder();
}
}
用反射破解完整的双重检测锁加原子性操作
public class Lazy {
private Lazy() {
System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static Lazy lazy;
//双重检测锁模式(DCL懒汉式)
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
public static void main(String[] args) throws Exception {
Lazy lazy1 = Lazy.getInstance();
//获得空参构造器
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor();
//暴力
declaredConstructor.setAccessible(true);
Lazy lazy2 = declaredConstructor.newInstance();
System.out.println(lazy1);
System.out.println(lazy2);
}
}
hash值不同,已经不是一个对象了
在构造器中加锁
private Lazy() {
synchronized (Lazy.class) {
if (lazy != null) {
throw new RuntimeException("不要使用反射破坏");
}
}
}
完整代码
public class Lazy {
private Lazy() {
synchronized (Lazy.class) {
if (lazy != null) {
throw new RuntimeException("不要使用反射破坏");
}
}
}
private volatile static Lazy lazy;
//双重检测锁模式(DCL懒汉式)
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
public static void main(String[] args) throws Exception {
//Lazy lazy1 = Lazy.getInstance();
//获得空参构造器
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor();
//暴力
declaredConstructor.setAccessible(true);
Lazy lazy2 = declaredConstructor.newInstance();
Lazy lazy1 = declaredConstructor.newInstance();
System.out.println(lazy1);
System.out.println(lazy2);
}
}
那2个对象都是反射拿到构造器然后创建呢
Lazy lazy2 = declaredConstructor.newInstance();
Lazy lazy1 = declaredConstructor.newInstance();
System.out.println(lazy1);
System.out.println(lazy2);
单例模式又被破坏了
设置一个标志位,走构造器就会改变
private static boolean aka = false;
private Lazy() {
synchronized (Lazy.class) {
if (aka == false) {
aka = true;
}else {
throw new RuntimeException("不要使用反射破坏");
}
}
}
完整代码
public class Lazy {
private static boolean aka = false;
private Lazy() {
synchronized (Lazy.class) {
if (aka == false) {
aka = true;
}else {
throw new RuntimeException("不要使用反射破坏");
}
}
}
private volatile static Lazy lazy;
//双重检测锁模式(DCL懒汉式)
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
public static void main(String[] args) throws Exception {
//Lazy lazy1 = Lazy.getInstance();
//获得空参构造器
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor();
//暴力
declaredConstructor.setAccessible(true);
Lazy lazy2 = declaredConstructor.newInstance();
Lazy lazy1 = declaredConstructor.newInstance();
System.out.println(lazy1);
System.out.println(lazy2);
}
}
不通过反编译的话是拿不到这个标志的
如果我们能知道这个标志位,我们就可以绕过
Field aka = Lazy.class.getDeclaredField("aka");
aka.setAccessible(true);
//获得空参构造器
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor();
//暴力
declaredConstructor.setAccessible(true);
Lazy lazy2 = declaredConstructor.newInstance();
aka.set(lazy2,false);
Lazy lazy1 = declaredConstructor.newInstance();
System.out.println(lazy1);
System.out.println(lazy2);
完整代码
public class Lazy {
private static boolean aka = false;
private Lazy() {
synchronized (Lazy.class) {
if (aka == false) {
aka = true;
}else {
throw new RuntimeException("不要使用反射破坏");
}
}
}
private volatile static Lazy lazy;
//双重检测锁模式(DCL懒汉式)
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
public static void main(String[] args) throws Exception {
//Lazy lazy1 = Lazy.getInstance();
Field aka = Lazy.class.getDeclaredField("aka");
aka.setAccessible(true);
//获得空参构造器
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor();
//暴力
declaredConstructor.setAccessible(true);
Lazy lazy2 = declaredConstructor.newInstance();
aka.set(lazy2,false);
Lazy lazy1 = declaredConstructor.newInstance();
System.out.println(lazy1);
System.out.println(lazy2);
}
}
看看源码
枚举是jdk1.5就有的
枚举本身就是一个Class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}
class Test {
public static void main(String[] args) {
EnumSingle instance = EnumSingle.INSTANCE;
EnumSingle instance2 = EnumSingle.INSTANCE;
System.out.println(instance);
System.out.println(instance2);
}
}
进target里看发现有无参构造器
反射破解一波
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}
class Test {
public static void main(String[] args) throws Exception {
EnumSingle instance = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
/* EnumSingle instance2 = EnumSingle.INSTANCE;*/
System.out.println(instance==instance2);
}
}
没想到idea骗人
枚举类没有空参构造器
分析一下源码
反编译
还是有构造器,用jad试试
jad官网点这里去
有参构造器
反射修改一下
抛出了想要的异常
Q.E.D.