单例模式
单例模式是指Java虚拟机中一个类只有一个实例。
饿汉式
// 1. 饿汉式
public class Singleton1 {
//私有构造方法
private Singleton1() {
System.out.println("private Singleton1()");
}
//私有静态变量
private static final Singleton1 INSTANCE = new Singleton1();
//公开获取对象的方法
public static Singleton1 getInstance() {
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
实现要点:
- 构造方法是私有的,不然其他类有机会调用构造方法来创造实例对象
- 提供一个静态的成员变量INSTANCE,成员变量的类型就是单例类型,值就是用私有构造方法创建出来的一个唯一实例。
- 因为静态变量一般是私有的不能直接访问,一般会提供公共的静态方法,方法名就叫getInstance()获得实例,实现是返回刚刚的成员变量。
所谓饿汉式是相对于懒汉式来说的,懒汉式是指当第一次调用getInstance()方法时才会去创建实例。而饿汉式则不是,只要类加载初始化了,这个实例就能被创建。
这里的otherMethod()方法是为了测试用的。在测试中先调用otherMethod(),这时候类就会被初始化,执行类初始化操作的时候就会导致
反射破坏单例及解决办法
反射直接调用私有的构造方法破坏单例。
reflection(Singleton1.class);
private static void reflection(Class<?> clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
//获取无参构造方法
Constructor<?> constructor = clazz.getDeclaredConstructor();
//使私有的构造方法也可以被使用
constructor.setAccessible(true);
//调用构造方法的newInstance()
System.out.println("反射创建实例:" + constructor.newInstance());
}
可以预防这种情况的发生:在构造方法中做个判断
public class Singleton1 {
private Singleton1() {
//增加判断解决反射破坏单例
if (INSTANCE != null) {
throw new RuntimeException("单例对象不能重复创建");
}
System.out.println("private Singleton1()");
}
private static final Singleton1 INSTANCE = new Singleton1();
public static Singleton1 getInstance() {
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
}
反序列化破坏单例
一旦对象实现了反序列化接口----->implements Serializable------>就有可能被利用反序列化来破坏单例:因为反序列化创建对象是不走构造方法的
// 反序列化破坏单例
serializable(Singleton1.getInstance());
private static void serializable(Object instance) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
//把对象变成字节流
oos.writeObject(instance);
//再把字节流还原成一个实例
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
System.out.println("反序列化创建实例:" + ois.readObject());
}
预防的方法:在类中写一个方法readResolve(),在反序列化创建对象的时候如果发现了类中有重写这个方法,就会把这个方法的返回值作为结果返回
public Object readResolve() {
return INSTANCE;
}
Unsafe 破坏单例
unsafe是JDK内置的一个类,也是不会走类的构造方法创建实例。
// Unsafe 破坏单例
unsafe(Singleton1.class);
private static void unsafe(Class<?> clazz) throws InstantiationException {
Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz);
System.out.println("Unsafe 创建实例:" + o);
}
枚举饿汉式
只定义了一个变量INSTANCE,这样就控制了只有唯一的实例
// 2. 枚举饿汉式
public enum Singleton2 {
INSTANCE;
}
枚举类不会被反序列化破坏单例,因为在反序列化中遇到的如果是枚举类会特殊处理,直接返回。
反射也不会破坏单例,如果反射中发现是枚举类会直接抛异常。
unsafe会破坏单例。
懒汉式单例
// 3. 懒汉式单例
public class Singleton3 implements Serializable {
private Singleton3() {
System.out.println("private Singleton3()");
}
private static Singleton3 INSTANCE = null;
// Singleton3.class
public static Singleton3 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton3();
}
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
懒汉式单例是指当第一次调用getInstance()方法时才会去创建实例,那么在一开始的私有变量INSTANCE中就先不赋值,在调用getINSTANCE()的时候判断是否为null,如果是就创建,否则什么都不做。
但是在多线程的时候会出现并发安全问题。考虑如下情况:有两个线程,在判断INSTANCE==null中,此时为true,线程一准备去创建实例对象,这时候切换到线程二,因为线程一的对象创建还没完成,所以线程二的判断也为true,也进入到了对象的创建。解决办法:在getINSTANCE()方法加上synchronized关键字,就可以加上线程安全的保护。原理:加在静态方法上的synchronized就是相当于在singleton3.class上加了一把锁,当线程一运行到这个方法时发现是静态方法而且这里加了synchronized关键字,就会去尝试获取singleton3.class的锁,然后去运行;如果有其他线程来到了这个方法也要去判断是否能拿到这个锁。但是这样性能并不好,因为这种情况只有发生在第一次创建INSTANCE时才会出现,所以希望加锁是只针对第一次创建INSTANCE的情况。
DCL懒汉式单例
对上一种懒汉式的优化 ,双检锁懒汉式。加锁之前先判断INSTANCE是否为null,如果不为null就直接返回了,如果为null,就进入synchronized方法
// 4. 懒汉式单例 - DCL
public class Singleton4 implements Serializable {
private Singleton4() {
System.out.println("private Singleton4()");
}
private static volatile Singleton4 INSTANCE = null; // 可见性,有序性
public static Singleton4 getInstance() {
if (INSTANCE == null) {
//如果为null证明是第一次进入
synchronized (Singleton4.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton4();
}
}
}
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
为什么在synchronized方法还要再次判断INSTANCE是否为null?假设没有这次判断,在竞争锁后线程一进入了代码块并创建出INSTANCE,释放锁,线程二获取锁之后也会再次创建INSTANCE。
为何要加volatile
创建对象方法对应的Java底层指令是创建对象(计算需要多少空间内存等),调用构造(给成员变量赋值)、给instance赋值。CPU执行的时候可能会对指令的执行进行优化,调换指令执行的次序。因为给成员变量赋值和给instance赋值没有因果关系,所以执行的时候就有可能调换这两条指令的顺序,这种调换在单线程下没有影响,但是在多线程下就不一定了
-
INSTANCE = new Singleton4()
不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值,其中后两步可能被指令重排序优化,变成先赋值、再调用构造 -
如果线程1 先执行了赋值,线程2 执行到第一个
INSTANCE == null
时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象
加上volatile就是对修饰的变量的赋值语句之后加上一个内存屏障,阻止之前的赋值语句越过屏障
饿汉式的实例创建是放在静态代码块中实现,而静态代码块的线程安全JVM会保证,而懒汉式就要考虑,于是有了内部类懒汉式。
内部类懒汉式单例
// 5. 懒汉式单例 - 内部类
public class Singleton5 implements Serializable {
private Singleton5() {
System.out.println("private Singleton5()");
}
private static class Holder {
static Singleton5 INSTANCE = new Singleton5();
}
public static Singleton5 getInstance() {
return Holder.INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
在单例对象内部创建一个静态内部类,因为内部类可以访问外部类的私有方法,所以在内部类里创建单例对象并赋值给静态变量,所以是线程安全的
JDK中单例模式的体现
-
Runtime 体现了饿汉式单例
public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {}
-
Console 体现了双检锁懒汉式单例(volatile是明显的标志)
private static volatile Console cons = null; /** * Returns the unique {@link java.io.Console Console} object associated * with the current Java virtual machine, if any. * * @return The system console, if any, otherwise <tt>null</tt>. * * @since 1.6 */ public static Console console() { if (cons == null) { synchronized (System.class) { cons = sun.misc.SharedSecrets.getJavaIOAccess().console(); } } return cons; }
-
Collections 中的 EmptyNavigableSet 内部类懒汉式单例
private static class EmptyNavigableSet<E> extends UnmodifiableNavigableSet<E> implements Serializable { private static final long serialVersionUID = -6291252904449939134L; public EmptyNavigableSet() { super(new TreeSet<E>()); } private Object readResolve() { return EMPTY_NAVIGABLE_SET; } }
-
ReverseComparator.REVERSE_ORDER 内部类懒汉式单例
private static class ReverseComparator implements Comparator<Comparable<Object>>, Serializable { private static final long serialVersionUID = 7207038068494060240L; static final ReverseComparator REVERSE_ORDER = new ReverseComparator(); public int compare(Comparable<Object> c1, Comparable<Object> c2) { return c2.compareTo(c1); } private Object readResolve() { return Collections.reverseOrder(); } @Override public Comparator<Comparable<Object>> reversed() { return Comparator.naturalOrder(); } }
-
Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例
enum NaturalOrderComparator implements Comparator<Comparable<Object>> { INSTANCE;