8、单例模式
饿汉式单例
饿汉式单例, 在类中直接就实例化了一个对象, 提供对外的方法返回该对象。构造器私有
public class Hungry {
private Hungry() {
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance() {
return HUNGRY;
}
}
懒汉式
懒汉式单例, 只是简单的申明了一个对象, 在对外的方法中才会实例化改对象。构造器私有,但是在多线程的情况下, 该单例模式会被破坏, 需要对该单例使用synchronized 把该类锁住, 但是这样的单例还是会出现问题的, 因为在实例化对象的时候 new(1、分配内存空间,2、执行构造器, 初始化对象, 3、把对象指向这个内存空间。) 这个关键字并不是一个原子性的操作, 会发生指令重排的现象。在多线程的情况下他是不安全的, 所以我们需要用 volatile 关键字修饰一下, 防止指令重排。
public class LazyMan2 {
private LazyMan2() {
System.out.println(Thread.currentThread().getName());
}
private volatile static LazyMan2 LAZY_MAN;
/**
* 加锁
* new LazyMan2(); 这个操作不是原子性操作, 可能会发生指令重排
* <p>
* 添加 volatile 关键字防止指令重排
*
* @return
*/
public static LazyMan2 getLazyMan() {
if (LAZY_MAN == null) {
synchronized (LazyMan2.class) {
if (LAZY_MAN == null) {
LAZY_MAN = new LazyMan2();
}
}
}
return LAZY_MAN;
}
/**
* 此时的单例还是存在问题的
* 可以使用反射来破坏单例
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
LazyMan2.getLazyMan();
}).start();
}
}
}
反射破坏
public class LazyMan3 {
private LazyMan3() {
synchronized (LazyMan3.class) {
System.out.println(Thread.currentThread().getName());
if (lazyMan3 != null) {
throw new RuntimeException("不要用反射破坏单例模式");
}
}
}
private volatile static LazyMan3 lazyMan3;
/**
* 加锁
* new LazyMan2(); 这个操作不是原子性操作, 可能会发生指令重排
* <p>
* 添加 volatile 关键字防止指令重排
*
* @return
*/
public static LazyMan3 getLazyMan() {
if (lazyMan3 == null) {
synchronized (LazyMan3.class) {
if (lazyMan3 == null) {
lazyMan3 = new LazyMan3();
}
}
}
return lazyMan3;
}
/**
* 此时的单例还是存在问题的
* 可以使用反射来破坏单例
*
* @param args
*/
public static void main(String[] args) throws Exception {
// LazyMan3 lazyMan = LazyMan3.getLazyMan();
/**
* 因为第一个对象是通过构造器创建的,所以使用反射调用构造器创建第二个对象的时候会出现异常,
*
* - 当我们的对象都是通过反射创建的话,就依旧可以破坏单例模式
* 先得到空参构造器
*
* 然后用构造器创建对象
*/
Constructor<LazyMan3> constructor = LazyMan3.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LazyMan3 man3 = constructor.newInstance();
LazyMan3 lazyMan = constructor.newInstance();
System.out.println(man3);
System.out.println(lazyMan);
}
}
使用标志位来防止被反射
public class LazyMan4 {
private static boolean flag = false;
private LazyMan4() {
synchronized (LazyMan4.class) {
System.out.println(Thread.currentThread().getName());
if (flag == false) {
flag = true;
} else {
throw new RuntimeException("不要用反射破坏单例模式");
}
}
}
private volatile static LazyMan4 lazyMan4;
/**
* 加锁
* new LazyMan2(); 这个操作不是原子性操作, 可能会发生指令重排
* <p>
* 添加 volatile 关键字防止指令重排
*
* @return
*/
public static LazyMan4 getLazyMan() {
if (lazyMan4 == null) {
synchronized (LazyMan4.class) {
if (lazyMan4 == null) {
lazyMan4 = new LazyMan4();
}
}
}
return lazyMan4;
}
/**
* 此时的单例还是存在问题的
* 可以使用反射来破坏单例
*
* @param args
*/
public static void main(String[] args) throws Exception {
// LazyMan3 lazyMan = LazyMan3.getLazyMan();
/**
* 因为第一个对象是通过构造器创建的,所以使用反射调用构造器创建第二个对象的时候会出现异常,
*
* - 当我们的对象都是通过反射创建的话,就依旧可以破坏单例模式
*
* - 此时通过添加标志位来改变这个 问题
*
* - 但是此时依旧可以通过反射来修改我们的字段
* 先得到空参构造器
*
* 然后用构造器创建对象
*/
/**
* 找到标志位, 直接修改
*/
Field flag = LazyMan4.class.getDeclaredField("flag");
flag.setAccessible(true);
Constructor<LazyMan4> constructor = LazyMan4.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LazyMan4 man3 = constructor.newInstance();
/**
* 在执行第一次后直接修改为 true
*/
flag.set(man3, false);
LazyMan4 lazyMan = constructor.newInstance();
System.out.println(man3);
System.out.println(lazyMan);
}
}
使用枚举类
public enum LazyMan5 {
LazyMan5;
private static boolean flag = false;
private LazyMan5() {
synchronized (LazyMan5.class) {
System.out.println(Thread.currentThread().getName());
}
}
private volatile static LazyMan5 lazyMan5 = LazyMan5;
/**
* 加锁
* new LazyMan2(); 这个操作不是原子性操作, 可能会发生指令重排
* <p>
* 添加 volatile 关键字防止指令重排
*
* @return
*/
public static LazyMan5 getLazyMan() {
if (lazyMan5 == null) {
synchronized (LazyMan5.class) {
if (lazyMan5 == null) {
return LazyMan5;
}
}
}
return lazyMan5;
}
/**
* 此时的单例还是存在问题的
* 可以使用反射来破坏单例
*
* @param args
*/
public static void main(String[] args) throws Exception {
// LazyMan3 lazyMan = LazyMan3.getLazyMan();
/**
* 因为第一个对象是通过构造器创建的,所以使用反射调用构造器创建第二个对象的时候会出现异常,
*
* - 当我们的对象都是通过反射创建的话,就依旧可以破坏单例模式
*
* - 此时通过添加标志位来改变这个 问题
*
* - 但是此时依旧可以通过反射来修改我们的字段
*
* 使用枚举来解决问题, 源码如下
*
* if ((this.clazz.getModifiers() & 16384) != 0) {
* throw new IllegalArgumentException("Cannot reflectively create enum objects");
* } else {
*
*
* 不能使用枚举来破坏单例
* Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
* 先得到空参构造器
*
* 然后用构造器创建对象
*/
/**
* 找到标志位, 直接修改
*/
Field flag = LazyMan5.class.getDeclaredField("flag");
flag.setAccessible(true);
Constructor<LazyMan5> constructor = LazyMan5.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
LazyMan5 man3 = constructor.newInstance(String.class, int.class);
/**
* 在执行第一次后直接修改为 true
*/
flag.set(man3, false);
LazyMan5 lazyMan = constructor.newInstance(String.class, int.class);
System.out.println(man3);
System.out.println(lazyMan);
}
}