单例模式
指确保一个类只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,可以直接访问。

实现方式
1.懒汉式
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式在第一次调用时才初始化,避免了浪费内存。但是如果要保证多线程安全,必须加锁synchronized才能保证单例,加锁会影响效率。
2.饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
饿汉式在类加载时就初始化,虽然会浪费一定的内存,但避免了一般多线程同步的问题,不用加锁。
3.双重校验锁
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
4.静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
5.枚举
public enum Singleton {
INSTANCE;
public Singleton getInstance(){
return INSTANCE;
}
}
多线程、反射下各方式详解
1.懒汉式
在getInstance()整个方法上加关键词synchronized,会导致多线程下方法调用阻塞。当该方法内需要较多操作时,会导致程序效率大大降低。
2.饿汉式
类的静态成员变量在类加载时就会进行初始化并分配内存空间,当该类里面有需要一定内存空间的成员变量时,会浪费很多内存。
3.双重校验锁
最常用的单例方法
在方法内部进行加锁判断,相比懒汉式会提高效率。双重检验锁先进行一次判断if (singleton == null),排除已经初始化的情况;而如果singleton还未初始化,可能有多个线程同时通过第一次判断,此时再对Singleton类进行加锁,确保只有一个线程进入初始化,其他线程进入队列等待;当第一个进入的线程初始化完毕后,队列中其他线程则不必再初始化,直接返回即可,这是第二次判断if(singleton == null)。
需要注意的是:类的初始化语句singleton = new Singleton() 并不是一个原子性操作。其初始化流程如下:
- 分配内存空间
- 执行构造方法,初始化对象
- 将这个对象指向内存空间
在初始化时,操作系统并不完全安装1->2->3的顺序去执行,另一种可能的顺序为1->3->2:即先分配内存空间,然后用一个空对象指向这个内存空间,最后再执行构造方法,初始化这个对象。
所以可能会出现以下情况:线程A进入双重锁中进行初始化,操作系统使用第二种初始化顺序,当执行完第三步而第二部还未执行时,操作系统已经为其分配内存空间,此时线程B调用了getInstance()方法,线程B会判断出singleton不为空,直接调用return语句。此时线程B返回的是还未完成构造方法的对象,可能出现空指针的情况。
所以,为避免这种情况发生,要在返回的对象singleton上添加volatile关键字。
4.静态内部类
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,因为SingletonHolder类没有被主动使用,只有通过显式调用 getInstance方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。
5.枚举
最安全的单例方法
这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
关于反射与枚举:
在使用以上4中单例模式中,锁机制只能防止多线程破坏单例的情况,但是还可以通过Java的反射机制来破坏单例。
Singleton instance1 = Singleton.getInstance(); //这是正常获取的实例
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
Singleton instance2 = constructor.newInstance(); //反射获取的实例
Singleton instance3 = constructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance3); //会得到三个不同的对象
而在java引入枚举类型后,阅读Java源码可知,反射是无法创建枚举类型的!因此,使用枚举建立单例的话,最安全。
//java.lang.reflect.Constructor.java
@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");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
枚举的完整实现
public class User {
//私有化构造函数
private User(){ }
//定义一个静态枚举类,此类也可定义在User类外部
static enum SingletonEnum{
//创建一个枚举对象,该对象天生为单例
INSTANCE;
private User user;
//私有化枚举的构造函数
private SingletonEnum(){
user=new User();
}
public User getInstnce(){
return user;
}
}
//对外暴露一个获取User对象的静态方法
public static User getInstance(){
return SingletonEnum.INSTANCE.getInstnce();
}
}
public class Test {
public static void main(String [] args){
System.out.println(User.getInstance());
System.out.println(User.getInstance());
System.out.println(User.getInstance()==User.getInstance());
}
}
//结果为true