单例模式的几种写法
(1)饿汉模式
public class Singleton{
private Singleton(){};
private static final Singleton INSTANCE = new Singleton();
public static Singleton getInstance(){
renturn INSTANCE;
}
}
分析: 第一次加载到内存中的就会被初始化 ,并对外提供一个获取该实例对象的方法,优点:立即执行初始化,线程安全。缺点:会受到反射,序列化影响,浪费资源
(2)懒汉模式:线程不安全,单线程下只在第一次调用时才会实例化
public class Singleton{
private Singleton(){};
private static Singleton singleton = null;
public static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
懒汉加锁模式(1):线程安全,但是每次调用都会加锁,效率很低
public class Singleton{
private Singleton(){};
private static Singleton singleton = null;
public static synchronized Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
双重检查锁的懒汉单例模式(2):保证线程安全,只有在singleton为null时才会加锁,优化锁带来的消耗
public class Singleton{
private static volatile Singleton singleton = null;
private Singleton(){};
public static Singleton getInstance(){
if(singleton == null){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
(3)静态内部类延迟初始化,线程安全
public class Singleton {
private Singleton(){};
private static class initInstance{
private static final Singleton SINGLETON = new Singleton();
}
public static Singleton getSingleton(){
return initInstance.SINGLETON;
}
}
分析:根据JVM规范,当第一次调用Singleleton.getInstance( )时,Singleleton被首次调用,JVM对其进行初始化操作,此时不会调用Singleleton的构造方法。接下来会调用getInstance()方法,又首次主动使用initInstance()内部类,所以JVM会对initInstance()进行初始化操作,在这个初始化操作中,静态常量INSTANCE被赋值时才调用Singleton()构造方法,完成实例化并返回该对象。
当再次调用Singleleton.getInstance( )时,因为已经进行过初始化操作了,就不会再进行初始化操作,直接返回INSTANCE常量。
解决反射与序列化的影响思路:(以饿汉单例模式为例)
public class Singleton{
private Singleton(){
//在私有的无参构造器中进行判断,这样可以解决反射问题
if (INSTANCE != null){
throw new RuntimeException("禁止反射创建单例模式");
}
};
private static final Singleton INSTANCE = new Singleton();
public static Singleton getInstance(){
return INSTANCE;
}
//添加readResolve()方法可以防止序列化问题
private Object readResolve() throws ObjectStreamException{
return INSTANCE;
}
}
枚举类单例模式:可以满足线程安全,天生防止序列化生成新的实例,防止反射攻击
public class Singleton{}
public enum SingletonEnum{
INSTANCE;
private Singleton singleton = null;
private SingletonEnum(){
singleton = new Singleton();
}
public Singleton getInstance(){
return singleton;
}
}
调用:
Singleton singleton = SingletonEnum.INSTANCE.getInstance();
Enum是一个普通的类,继承自Java.lang.Enum类
public enum SingletonEnum{
INSTANCE;
}
将上面枚举编译后的字节码反编译,得到如下代码:
public abstract class SingletonEnum extends Enum<SingletonEnum>{
public static final SingletonEnum INSTANCE;
public static SIngletonEnum[]values();
public static SingletonEnum valuesof(String s);
static{};
}
通过反编译后得到的代码可知:
INSTANCE 被声明为static,虚拟机会保证一个类的<clinit>() 方法在多线程环境中被正确的加锁、同步。所以,枚举实现是在实例化时是线程安全。
接下来再看序列化的问题
在Java的规范中,每一个枚举类型及其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化与反序列化中,JVM做了一些特殊的规定:在序列化时,Java仅仅只是将枚举对象的name属性输出到结果中,反序列化时则是通过Java.lang.Enum的valuesof()方法根据名称获取枚举对象。
比如:
public enum SingletonEnum{
INSTANCE;
}
上面枚举类在序列化时,只将 INSTANCE 这个名称输出,反序列化时,再通过这个名称,寻找对应的枚举类型,因此反序列化后的实例与之前被序列化的对象实例相同
接下来看反射的问题
枚举单例
public enum Singleton {
INSTANCE {
@Override
protected void read() {
System.out.println("read");
}
@Override
protected void write() {
System.out.println("write");
}
};
protected abstract void read();
protected abstract void write();
}
反编译后
public abstract class Singleton extends Enum
{
private Singleton(String s, int i)
{
super(s, i);
}
protected abstract void read();
protected abstract void write();
public static Singleton[] values()
{
Singleton asingleton[];
int i;
Singleton asingleton1[];
System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);
return asingleton1;
}
public static Singleton valueOf(String s)
{
return (Singleton)Enum.valueOf(singleton/Singleton, s);
}
Singleton(String s, int i, Singleton singleton)
{
this(s, i);
}
public static final Singleton INSTANCE;
private static final Singleton ENUM$VALUES[];
static
{
INSTANCE = new Singleton("INSTANCE", 0) {
protected void read()
{
System.out.println("read");
}
protected void write()
{
System.out.println("write");
}
};
ENUM$VALUES = (new Singleton[] {
INSTANCE
});
}
}
①类的修饰为abstract,没法实例化
②在static中完成实例化的,所以线程安全
从源码上来看
java.lang.reflect 中的 Constructor 的newInstance中规定了不准通过反射创建枚举对象
(反射在通过newInstance创建对象时,会检查该类是否是枚举类,如果是,则抛出异常,反射失败。)
参考文章:
singleton模式四种线程安全的实现