- 介绍之前先简单的说一下内部类:
无论是静态内部类还是非静态内部类,在类初始化的时候都不会被加载 ,其实就是为了解决下面B的情况
B.饿汉模式获取全局属性会进行类的初始化
下面重复介绍一下带有全局属性的饿汉模式
/**
* xiyou-todo B恶汉模式
* 1. 如果在恶汉单例模式里面添加一个它的全局属性
* 2. 如果想获取它的yourClass 可以调用直接调用yourClass
* 3. 但是这样对象会初始化(虽然只会初始化一次),构造方法初始化,我不想让类初始化怎么办
* 4
*/
public class EHanMonitor {
public static String yourClass = "恶汉模式,通信工程1班";
private static EHanMonitor classMonitor = new EHanMonitor();
private EHanMonitor() {
System.out.println("恶汉模式:构造方法初始化,只会初始化一次");
}
public static EHanMonitor getClassMonitor() {
System.out.println("恶汉模式,获取对象" + classMonitor);
return classMonitor;
}
public static void main(String[] args) {
System.out.println(EHanMonitor.yourClass );
System.out.println("上面是只调用单例模式的属性,但是也会进行类的初始化");
System.out.println(EHanMonitor.yourClass + getClassMonitor());
System.out.println(EHanMonitor.yourClass + EHanMonitor.getClassMonitor());
}
}
A:静态内部类实现单例模式(简单版本)
/**
* @author xiyou
* 静态内部类实现单例模式
* 看似近似完美,但是其实还是有问题的,比如反射破坏单例
*/
public class InnerClassSingleton {
private InnerClassSingleton() {
}
private static final class LazyHolder {
private static final InnerClassSingleton INNER_INSTANCE = new InnerClassSingleton();
}
public static final InnerClassSingleton getInstance() {
return LazyHolder.INNER_INSTANCE;
}
看似比懒汉模式、饿汉模式、DDL(volatile)模式都要好,但是有没有缺点呢?
有:反射可以破坏单例
A1 但是上面的静态内部类真的是保证单例吗?
答案:虽然线程安全,但是反射可以破坏单例模式
public static void main(String[] args) {
Object instance = InnerClassSingleton.getInstance();
System.out.println(instance);
//通过非正常手段,反射来破坏单例
Class<?> oneClass = InnerClassSingleton.class;
try {
Constructor c = oneClass.getDeclaredConstructor();
c.setAccessible(true);
Object instance1 = c.newInstance();
Object instance2 = c.newInstance();
Object instance3 = c.newInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance3);
System.out.println(c);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 结果:发现已经破坏单例模式了
* cn.net.health.tools.design.single.lazy.InnerClassSingleton@783e6358
* cn.net.health.tools.design.single.lazy.InnerClassSingleton@17550481
* cn.net.health.tools.design.single.lazy.InnerClassSingleton@735f7ae5
* cn.net.health.tools.design.single.lazy.InnerClassSingleton@180bc464
* private cn.net.health.tools.design.single.lazy.InnerClassSingleton()
*/
A2 怎么保证反射也是单例模式?
import java.lang.reflect.Constructor;
/**
* @author xiyou
* 在构造函数里面判断
*/
public class InnerClassSingleton2 {
private InnerClassSingleton2() {
if (LazyHolder.instance != null) {
throw new RuntimeException("不允许反射创建实例");
}
}
private static final class LazyHolder {
private static final InnerClassSingleton2 instance = new InnerClassSingleton2();
}
public static final InnerClassSingleton2 getInstance() {
return InnerClassSingleton2.LazyHolder.instance;
}
public static void main(String[] args) {
Object instance = InnerClassSingleton2.getInstance();
System.out.println(instance);
//通过非正常手段,反射来破坏单例
Class<?> oneClass = InnerClassSingleton2.class;
try {
Constructor c = oneClass.getDeclaredConstructor();
c.setAccessible(true);
Object instance1 = c.newInstance();
Object instance2 = c.newInstance();
Object instance3 = c.newInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance3);
System.out.println(c);
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果:虽然可以抵制反射创建实例,但是构造方法里面抛出异常,有点诡异,那怎么办呢?
cn.net.health.tools.design.single.lazy.InnerClassSingleton2@783e6358
java.lang.reflect.InvocationTargetException
Caused by: java.lang.RuntimeException: 不允许反射创建实例
枚举可以解决线程安全和反射问题
A3 枚举单例可以防止反射破坏单例
C.静态内部类实现单例模式(带有静态属性)
public class StaticPropertyHungry {
/**
* todo-xiyou 饿汉模式
* 1. 如果在饿汉单例模式里面添加一个它的全局属性
* 2. 如果想获取它的yourClass 可以调用直接调用yourClass
* 3. 但是这样对象会使当前对象初始化(构造方法初始化,虽然指挥初始化一次),但我还是不想让类初始化怎么办
* 4.
*/
public static final String YOUR_PROPERTY = "xiyou";
private static StaticPropertyHungry classMonitor = new StaticPropertyHungry();
private StaticPropertyHungry() {
System.out.println("饿汉模式:构造方法初始化,只会初始化一次" + YOUR_PROPERTY);
}
public static StaticPropertyHungry getClassMonitor() {
System.out.println("饿汉模式,获取对象" + classMonitor);
return classMonitor;
}
public static void main(String[] args) {
System.out.println(StaticPropertyHungry.YOUR_PROPERTY);
System.out.println(StaticPropertyHungry.YOUR_PROPERTY);
System.out.println("上面是只调用单例模式的属性,但是也会进行类的初始化");
System.out.println(StaticPropertyHungry.YOUR_PROPERTY + getClassMonitor());
System.out.println(StaticPropertyHungry.YOUR_PROPERTY + StaticPropertyHungry.getClassMonitor());
}
}
结果:
饿汉模式:构造方法初始化,只会初始化一次xiyou
xiyou
xiyou
上面是只调用单例模式的属性,但是也会进行类的初始化
饿汉模式,获取对象cn.net.health.tools.design.singleton.StaticPropertyHungry@783e6358
xiyoucn.net.health.tools.design.singleton.StaticPropertyHungry@783e6358
饿汉模式,获取对象cn.net.health.tools.design.singleton.StaticPropertyHungry@783e6358
xiyoucn.net.health.tools.design.singleton.StaticPropertyHungry@783e6358
1. 为什么静态内部类能保证单例?
我们再回头看下getInstance()方法,调用的是MonitorCreator.classMonitor,
取的是SingleTonHoler里的INSTANCE对象,
跟上面那个DCL方法不同的是,getInstance()方法并没有多次去new对象,
故不管多少个线程去调用getInstance()方法,取的都是同一个INSTANCE对象,而不用去重新创建。
当getInstance()方法被调用时,SingleTonHoler才在SingleTon的运行时常量池里,把符号引用替换为直接引用,
这时静态对象INSTANCE也真正被创建,然后再被getInstance()方法返回出去,这点同饿汉模式。
2. 那么,是不是可以说静态内部类单例就是最完美的单例模式了呢?
其实不然,静态内部类也有着一个致命的缺点,就是传参的问题,由于是静态内部类的形式去创建单例的,
故外部无法传递参数进去,例如Context这种参数, 所以,我们创建单例时,可以在静态内部类与DCL(双重双重锁懒汉模式(Double Check Lock))模式里自己斟酌。
3. 静态内部类实现的单例模式为什么是线程安全的
虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。如果在一个类的()方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但如果执行()方法后,其他线程唤醒之后不会再次进入()方法。同一个加载器下,一个类型只会初始化一次。),在实际应用中,这种阻塞往往是很隐蔽的。