1.对单例模式的理解
核心: 构造方法私有化,保证在整个项目中该类的实例最多只有一个
分类:
饿汉式与懒汉式的区别:
饿汉式都是在类初始化就创建对象,该阶段的线程安全交由JVM来保证,我们不用考虑线程安全问题。而懒汉式需要我们人为思考如何解决线程安全问题。
2.写出5种单例模式的具体实现
普通饿汉式
/**
* 饿汉式 只要类初始化该实例就已被创建,提前创建
*/
public class Singleton1 implements Serializable {
private Singleton1(){
//防止通过反射技术来破环单例
if(INSTANCE != null){
throw new RuntimeException("单例对象不能重复创建");
}
System.out.println("private Singleton1()");
}
private static final Singleton1 INSTANCE = new Singleton1();
public static Singleton1 getInstance(){
return INSTANCE;
}
//防止反序列化破环单例
public Object readResolve(){
return INSTANCE;
}
}
枚举饿汉式
/**
* 枚举饿汉式,只要类初始化该枚举对象就已被创建,提前创建
*/
public enum Singleton2{
INSTANCE;
//枚举类的构造方法默认修饰符就是private
Singleton2(){
System.out.println("private Singleton2");
}
public static Singleton2 getInstance(){return INSTANCE;}
}
普通懒汉式
/**
* 懒汉式,要注意线程安全问题破环单例
*/
public class Singleton3 implements Serializable {
private Singleton3(){
System.out.println("private Singleton3()");
}
private static Singleton3 INSTANCE = null;
//加入synchronized防止多线程安全问题,虽能解决,但严重影响性能
public static synchronized Singleton3 getInstance(){
if(INSTANCE == null){
INSTANCE = new Singleton3();
}
return INSTANCE;
}
//防止反序列化破环单例
public Object readResolve(){
return INSTANCE;
}
}
DCL懒汉式
/**
* 懒汉式 DCL(Double Checked Lock),双检锁解决线程安全
*/
public class Singleton4 implements Serializable {
private Singleton4(){
System.out.println("private Singleton4()");
}
private static volatile Singleton4 INSTANCE = null;//可见性,有序性,加入volatile防止指令重排
//加锁之前先判断,只有当INSTANCE还未创建阶段才会加锁,一旦INSTANCE创建完毕,则直接返回,提高了性能
public static Singleton4 getInstance(){
if(INSTANCE == null){
synchronized (Singleton4.class){
if(INSTANCE == null){
INSTANCE = new Singleton4();
}
}
}
return INSTANCE;
}
//防止反序列化破环单例
public Object readResolve(){
return INSTANCE;
}
}
为什么用 volatile?
这是因为new
关键字创建对象不是原子操作,会经历以下三步:
- 在堆内存开辟内存空间
- 调用构造方法,初始化对象
- 引用变量指向堆内存空间
操作系统有权对指令的执行顺序进行优化,改变在逻辑上没有关联的指令的执行顺序。这是1 2 3三个步骤就有可能变为 1 3 2,那么就有可能出线程1刚执行完1 3 步,还未给对象初始化,线程2执行到了INSTACE == null
的if
判断,结果未false
,直接返回了还未初始化的对象。而一旦加了volatile
关键字,就会禁止对INSTANCE
赋值过程时的指令重排列。
静态内部类懒汉式
/**
* 懒汉式 - 静态内部类实现(比较推荐的懒汉式实现方式)
* 由于静态成员变量的赋值会放在静态代码块执行,而静态代码块会由JVM来保证线程安全问题,它里面的代码只会被执行一次
*/
public class Singleton5 {
private Singleton5() {
System.out.println("private Singleton5");
}
private static class Holder {
static Singleton5 INSTANCE = new Singleton5();
}
public static Singleton5 getInstance() {
return Holder.INSTANCE;
}
}
3. 如何破环单例?
3.1. 反射(对枚举饿汉式无效)
public static void reflection(Class<?> clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true); //修改访问权限
System.out.println("反射获得实例:"+constructor.newInstance());
}
3.2 反序列化(看该类是否有readResolve()
方法)
public static void serializable(Object instance) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
//该新对象的创建不走构造方法
//然而要注意当执行readObject方法时,会去原类里寻找readResolve方法,如果找到走该方法逻辑,如果该方法不存在则直接创建新对象
System.out.println("反序列化创建实例:"+ ois.readObject());
}
3.3 Unsafe类(无预防措施)
public static void unsafe(Class<?> clazz) throws InstantiationException {
//这里需要借助Spring提供的UnsafeUtils拿到Unsafe对象,该方法同样不走构造方法,无法避免
Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz);
System.out.println("Unsafe创建实例:"+ o);
}
4. 哪里用到了单例模式?
1. JDK
Runtime
类Console
类
2. Windows
- Task Manager(任务管理器)
- Recycle Bin(回收站)