单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
线程不安全的单例模式。
public class Singleton {
private Singleton() {}
private static Singleton single=null;
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
Singleton通过将构造方法限定为private避免了其他类通过访问构造器进行实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过静态的getInstance()方法进行访问。但在并发的情况下是可能出现这种情况,就是a线程先进入getInstance()方法在创建实例化的时候,也就是还没创建成功,b线程也进入了getInstance()方法,这个时候a线程实例还没建成功,b线程判断single为空也开始创建实例,导致会出现创建出两个实例来。
但并发的时候也只能一个一个排队进行getInstance()方法访问。
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
在并发量高的情况下,不需要排队访问getInstance()方法,性能上会优于synchronized 修饰的懒汉单例模式。
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
静态内部类实现单例模式,这种方式优于上的方式,它即实现了线程安全,又省去了null的判断
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
public class Singleton {
private Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return this.instance;
}
}
public enum EnumSingleton {
INSTANCE;
public void whateverMethod() {
}
}
枚举其实底层是依赖Enum类实现的,这个类的成员变量都是static类型的,并且在静态代码块中实例化的,和饿汉单例模式有点像。
因为以上几种虽然没有直接使用synchronized,但是也是间接用到了。类加载过程的线程安全性保证,以上的静态内部类、饿汉等模式均是通过定义静态的成员变量,以保证单例对象可以在类初始化的过程中被实例化。其利用了ClassLoader的线程安全机制。ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。所以, 除非被重写,这个方法默认在整个装载过程中都是线程安全的。所以在类加载过程中对象的创建也是线程安全的。
public class Singleton {
private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();
private Singleton() {}
public static Singleton getInstance() {
for (;;) {
Singleton singleton = INSTANCE.get();
if (null != singleton) {
return singleton;
}
singleton = new Singleton();
if (INSTANCE.compareAndSet(null, singleton)) {
return singleton;
}
}
}
}
CAS实现单例模式优缺点
CAS是一种基于忙等待的算法,依赖底层硬件的实现,相对于锁它没有线程切换和阻塞的额外消耗,可以支持较大的并行度。但CAS的缺点在于如果忙等待一直执行不成功,一直在 for (;;)死循环中,会对CPU造成较大的执行开销。另外,代码中,如果N个线程同时执行到 singleton = new Singleton();的时候,会有大量对象被创建,也就违背了单例原则,可能导致内存溢出。
public class Singleton {
private static final ThreadLocal<Singleton> singleton =
new ThreadLocal<Singleton>() {
@Override
protected Singleton initialValue() {
return new Singleton();
}
};
public static Singleton getInstance() {
return singleton.get();
}
private Singleton() {}
}
枚举的单例模式。枚举在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject等方法。
而使用反射时,EnumSingleton.class.getDeclaredConstructors()获取所有构造器,然而并没有设置无参构造器,而且在反射在通过newInstance创建对象时,会检查该类是否是ENUM,如果是则抛出异常,反射失败,所以枚举是不怕反射攻击的。
// newInstance源码
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
/********************注意这里************************/
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor;
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
面试官真是搞笑!让实现线程安全的单例,又不让使用synchronized!