最简单的两种实现模式:饿汉式和懒汉式
饿汉式
public class Hungry {
private Hungry() {
}
private final static Hungry instance = new Hungry();
public static Hungry getInstance() {
return instance;
}
}
即私有化构造器,并将实例作为静态变量,在类初始化时加载完成,线程安全,但是单例在还没有使用到的时候,初始化就已经完成了,这就造成了不必要的资源浪费。
懒汉式
public class Lazy {
private static Lazy instance;
private Lazy(){}
public static synchronized Lazy getInstance(){
if(instance==null){
instance=new Lazy();
}
return instance;
}
}
线程安全 ,做到了延时加载,但是有一个很严重的问题,就是外部每次调用getinstance()方法时都要先获取synchronized锁,也就说访问效率很低。为了改进该方法,衍生出了所谓DCL( Double Check Lock )懒汉
DCL( Double Check Lock )懒汉式
public class DCLLazy {
private DCLLazy() {
}
private static DCLLazy instance;
public static DCLLazy getInstance() {
if (instance == null) {
synchronized (DCLLazy.class) {
if (instance == null) {
instance = new DCLLazy();
}
}
}
return instance;
}
}
这样写与上面写有一个显著的好处,只有在单例还没有实例化的时候需要获取类锁,如果单例已经实例化,之后的访问只需要不会进入if句块。保证了线程的安全性,又符合了懒加载,只有在用到的时候,才会去初始化,调用效率也比较高,但是这种写法在极端情况还是可能会有一定的问题。
Java在new 对象的过程不是原子性操作,至少会经过三个步骤:
- 分配内存
- 执行构造方法
- 指向地址
步骤2和步骤3不存在数据依赖关系.而且无论重排前还是重排后程序执行的结果在单线程中并没有改变,因此这种重排优化是允许的。但是指令重排只会保证串行语义的执行一致性(单线程) 并不会关心多线程间的语义一致性
所以当一条线程访问instance不为null时,由于instance实例未必完成初始化,也就造成了线程安全问题 。
具体来说,就是由于指令重排,导致A线程执行instance = new DCLLazy();的时候,可能先执行了第三步(还没执行第二步),此时线程B又进来了,发现instance已经不为空了,直接返回了instance,并且后面使用了返回的instance,由于线程A还没有执行第二步,导致此时instance还不完整,可能会有一些意想不到的错误。
改进方法: 将instance使用volatile进行修饰;
private static volatile LazyLoad instance = null;
静态内部类(饿汉式改进)
public class Holder {
private Holder() {
}
public static Holder getInstance() {
return InnerClass.holder;
}
private static class InnerClass {
private static final Holder holder = new Holder();
}
}
这种方式是第一种饿汉式的改进版本,同样也是在类中定义static变量的对象,并且直接初始化,不过是移到了静态内部类中,内部类没有被调用时不会被加载,满足了懒加载。而且由于所有访问都是通过外部类来实现,所以内部类只会被Holder访问,保证了线程的安全性。
但是这个方法有一个很严重的问题,如果使用反射进行对象的创建,它可以无视private修饰的构造方法,可以直接在外面newInstance()。
验证代码如下:
public static void main(String[] args) {
try {
LazyMan lazyMan1 = LazyMan.getInstance();
Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1.hashCode());
System.out.println(lazyMan2.hashCode());
System.out.println(lazyMan1 == lazyMan2);
} catch (Exception e) {
e.printStackTrace();
}
}
两个对象哈希值不相等,解决方法如下
public class LazyMan {
private LazyMan() {
synchronized (LazyMan.class) {
if (lazyMan != null) {
throw new RuntimeException("不要试图用反射破坏单例模式");
}
}
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
}
在私有的构造函数中做一个判断,如果lazyMan不为空,说明lazyMan已经被创建过了,如果正常调用getInstance方法,是不会出现这种事情的,所以直接抛出异常
但是这种写法还是有问题:
上面我们是先正常的调用了getInstance方法,创建了LazyMan对象,所以第二次用反射创建对象,私有构造函数里面的判断起作用了,反射破坏单例模式失败。但是如果破坏者干脆不先调用getInstance方法,一上来就直接用反射创建对象,我们的判断就不生效了,如下:
public static void main(String[] args) {
try {
Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan lazyMan1 = declaredConstructor.newInstance();
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1.hashCode());
System.out.println(lazyMan2.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
防止这种反射破坏,在这里,我定义了一个boolean变量flag,初始值是false,私有构造函数里面做了一个判断,如果flag=false,就把flag改为true,但是如果flag等于true,就说明有问题了,因为正常的调用是不会第二次跑到私有构造方法的,所以抛出异常。
看起来很美好,但是还是不能完全防止反射破坏单例模式,因为可以利用反射修改flag的值。
public class LazyMan {
private static boolean flag = false;
private LazyMan() {
synchronized (LazyMan.class) {
if (flag == false) {
flag = true;
} else {
throw new RuntimeException("不要试图用反射破坏单例模式");
}
}
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
}
枚举(最推荐)
public Enum EnumSingleton {
instance;
public EnumSingleton getInstance(){
return instance;
}
}
足够简单,不需要开发自己保证线程的安全,同时又可以有效的防止反射破坏我们的单例模式 ,因为newinstance源码中会检查是否为enum对象。
@CallerSensitive
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");//看此处对enum做了判断
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}