写在最前面
导师贪腐出逃美国,两年未归,可怜了我。拿了小米和美团的offer,要被延期,offer失效,工作重新找。把准备过程纪录下来,共勉。
单例模式有几种经典写法,核心思想:就是将构造函数私有化,并且通过静态方法获取一个唯一的实例。
饿汉式
public class Singleton{
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
懒汉式
public class Singleton{
private Singleton(){}
private static Singleton instance = null;
public static Singleton getInstance(){
instance = new Singleton();
return instance;
}
}
双锁式
public class Singleton{
private Singleton(){
}
private volatile static Singleton instance = null;
public static Singleton getInstance(){
if(instance == null){
synchonized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
关于volatile关键字
上面添加了volatile关键字,如果没有volatile关键字,在执行instance = new Singleton()时可能会出现问题,伪代码如下:
inst = allocat(); // 第一步:分配内存
constructor(inst); // 第二步:执行构造函数
instance = inst; // 第三步:赋值,将instance对象指向分配的内存空间(此时instance就不是null了)
由于Java编译器允许处理器乱序执行,所以第二步和第三步的顺序无法保证。如果第三步先执行完毕、第二步未执行时,有另外的线程调用了instance,由于已经赋值,将判断不为null,拿去直接使用,但其实构造函数还未执行,成员变量等字段都未初始化,直接使用,就会报错。这就是DCL失效问题,而且很难复现。
对volatile变量的写操作,不允许和它之前的读写操作打乱顺序;对volatile变量的读操作,不允许和它之后的读写乱序。
当一个线程要使用共享内存中的volatile变量时,它会直接从主内存中读取,而不是使用自己本地内存中的副本。当一个线程对一个volatile变量进行写时,它会将这个共享变量值刷新到共享内存中。
枚举类
public enum Singleton{
instance;
}
关于序列化、反序列化
一个enum常量(这里是INSTANCE)代表了一个enum的实例,enum类型只能有这些常量实例。标准保证enum常量(INSTANCE)不能被克隆,也不会因为反序列化产生不同的实例,想通过反射机制得到一个enum类型的实例也不行的。
避免序列化、反序列化时产生多个实例,有两种解决方法
-
抛出异常
/** * prevent default deserialization */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("can't deserialize enum"); }
-
在readResolve()方法中返回实例
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException; ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
以上两个方法可以理解为序列化和反序列化过程的入口和出口。writeReplace()返回的对象,就是要被序列化的对象,我们有机会在序列化前把这个对象给换成我们确定好的那个(如果不是“故意捣乱”,暂时没想到有什么用);而readResolve()方法就是在反序列化完成得到对象前,把这个对象给换成我们确定好的那个。
/**
* 反序列化时内存Hook这段代码
* @return
*/
private Object readResolve() {
return instance;
}
静态内部类
内部类的机制来巧妙实现懒汉式单例模式的实现 :Lazy initialization holder class模式。
内部类分为对象级别和类级别,类级内部类指的是,有static修饰的成员变量的内部类。如果没有static修饰的成员变量的内部类被称为对象级内部类。
类级内部类相当于其外部类的static成员,它的对象与外部类对象间不存在依赖关系,相互独立,因此可直接创建。而对象级内部类的实例,是必须绑定在外部对象实例上的。类级内部类只有在第一次被使用的时候才被会装载。采用类级内部类,在这个类级内部类里面去创建对象实例,能够让类装载的时候不去初始化对象。
当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,内部类SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。
这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。
public class Singleton{
private Singleton(){}
private static class SingletonHolder{
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。