23种设计模式之单例模式
饿汉式
一、饿汉式
//饿汉式单例
public class Hungry {
//缺点:可能会浪费空间
private byte[] data = new byte[1024*1024];
private Hungry() {
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance() {
return HUNGRY;
}
}
二、懒汉式
懒汉式
//懒汉模式
public class SingletonDemo2 {
//私有化构造器
private LazyMan() {
}
//只提供一个实例,并不创建对象
private static LazyMan lazyMan;
//提供公共的获取方法,因为不是在类加载时就创建对象,因此存在线程安全问题,使用synchronized关键字保证线程安全,效率降低
public static synchronized LazyMan getInstance() {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
}
DCL懒汉式
//DCL懒汉式 双重检测锁
public class LazyMan {
private static boolean secret = false;
private LazyMan() {
synchronized (LazyMan.class) {
if (secret == false) {
secret = true;
} else {
throw new RuntimeException("不要试图使用反射破坏异常!");
}
}
System.out.println(Thread.currentThread().getName() + "ok");
}
//volatile:对于同一个变量,在一个线程中值发生了改变,则在另一个线程中立即生效,可以大幅度避免下面的问题,不排除极端情况
private volatile static LazyMan lazyMan;
//双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan(); //不是原子性操作
/**
* 执行步骤:
* 1、分配内存空间、
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 可能会指令重排
*/
}
}
}
return lazyMan;
}
三、静态内部类
静态内部类
//静态内部类实现
public class Holder {
private Holder() {
}
public static Holder getInstance() {
return InnerClass.HOLDER;
}
public static class InnerClass {
private static final Holder HOLDER = new Holder();
}
}
从 Java1.5 发行版本起,只需要编写一个包含单个元素的枚举类型,这种方法在功能上与公有域方法相近,但是它更加简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使实在面对复杂的序列化或者反射攻击的时候。虽然这种方法还没有广泛使用,但是单元素的枚举类型已经成为实现Singleton的最佳方式。
——《Effective Java》 Joshua Bloch
四、枚举
以上所有方式线程并不安全,因为反射机制的存在,可以破坏类的私有属性,并通过反射创建对象。以DCL懒汉式为例,类中编写main方法
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Field secret = LazyMan.class.getDeclaredField("secret");
secret.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
secret.set(instance1,false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
// 输出结果:
// mainok
// mainok
// singleton.LazyMan@4554617c
// singleton.LazyMan@74a14482
输出结果表示,单例被破坏,那么我们想单例不被破坏,就要来看一下反射是如何创建对象的
从源码中可以看出,当反射遇到枚举时直接抛出异常,那么我们来了解一下如何用枚举实现单例模式
//enum 是一个什么? 本身也是一个Class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
//NoSuchMethodException: singleton.EnumSingle.<init>()
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
// 运行结果:(不能通过反射创建对象)
//Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
// at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
// at singleton.Test.main(EnumSingle.java:23)
枚举类型反编译后代码(无空参构造函数)
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingle.java
package singleton;
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(singleton/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
五、总结
本文中介绍了几种实现单例的方法,主要包括饿汉、懒汉、双重校验锁、静态内部类、枚举等。