单例模式
定义:保证一个类仅有一个实例,并提供一个全局访问点
适用场景:想确保任何情况下都绝对只有一个实例(单机情况下的计数器,数据库连接池等)
重点:
1:私有构造器
2:线程安全
3:延迟加载
4:序列化和反序列化安全
5:反射攻击
懒加载单例:
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton(){
}
public synchronized static LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
注意这里synchronized是加在静态方法上,相当于锁的是当前类的class文件;若此方法是非静态方法,则锁的是在堆内存中生成的对象。
懒汉式双重检查锁:
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
private LazyDoubleCheckSingleton(){
}
public static LazyDoubleCheckSingleton getInstance(){
if(lazyDoubleCheckSingleton == null){
synchronized (LazyDoubleCheckSingleton.class){
if(lazyDoubleCheckSingleton == null){
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazyDoubleCheckSingleton;
}
}
饿汉式:
public class HungrySingleton{
private final static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
使用了final属性,所以在类加载的时候就创建对象了,不用考虑线程安全问题。
序列化和反序列化的对象不是同一个:
HungrySingleton 类实现Serializable接口
public class HungrySingleton implements Serializable{
private final static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
然后写一个测试类看看通过序列化和反序列化后的对象
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
HungrySingleton hungrySingleton = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file.txt"));
oos.writeObject(hungrySingleton);
File file = new File("singleton_file.txt");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
HungrySingleton newHungrySingleton = (HungrySingleton) ois.readObject();
System.out.println(hungrySingleton);
System.out.println(newHungrySingleton);
System.out.println(hungrySingleton == newHungrySingleton);
}
}
可以看到通过序列化和反序列化之后的对象并不是之前的对象
要想解决上述问题,可以在类中加上readResolve方法并返回单例对象即可,具体如下:
public class HungrySingleton implements Serializable{
private final static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
//防止序列化和反序列化安全
private Object readResolve(){
return hungrySingleton;
}
}
可以看到修改后对象就相等了
为什么会这样呢?我们进入readObject方法找找原因
可以看到这是一个从ObjectInputStream读取Object的方法,并将obj对象返回
/**
* Read an object from the ObjectInputStream.
* ...
**/
public final Object readObject()
throws IOException, ClassNotFoundException
{
//判断是否在ObjectOutputStream的子类中重写
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
//下面继续进入方法查看
Object obj = readObject0(false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
进入readObject0()方法继续查看,从注释看出这个是readObject 的底层实现
/**
* Underlying readObject implementation.
*/
private Object readObject0(boolean unshared) throws IOException {
//若为块数据模式,则为true
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
//currentBlockRemaining()方法注释
//如果处于块数据模式,则返回当前数据块中剩余的未使用字节数。
//如果不在块数据模式下,则抛出IllegalStateException。
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
/*
* Fix for 4360508: stream is currently at the end of a field
* value block written via default serialization; since there
* is no terminating TC_ENDBLOCKDATA tag, simulate
* end-of-custom-data behavior explicitly.
*/
//从上面这段话看出来这是修复了bug,先不管,重点不在这
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
}
byte tc;
//从流中读取数据
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}
depth++;
totalObjectRefs++;
try {
//根据标志位判断
switch (tc) {
case TC_NULL:
return readNull();
case TC_ENUM:
return checkResolve(readEnum(unshared));
...
//因为我们读取的是Object对象,那么下面条件就会满足
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
case TC_EXCEPTION:
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);
...
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
继续进入readOrdinaryObject()方法查看,可以看到其返回一个Object对象
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
//这里是重点
//isInstantiable方法大概意思是:如果表示的类是可序列化和在运行期间可用序列化实例化,返回true
//newInstance是通过构造器创建对象
//很明显我们这里的obj走到这一步已经创建出来了
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
...
//下面来看看hasReadResolveMethod方法
//其内容是:如果当前类实现了serializable接口,并实现了readResolve,返回true
//由此可见,下面全部条件都是满足的
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
//执行到这里,来看看invokeReadResolve方法
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
继续查看invokeReadResolve方法,看注释已经明白其调用readResolve方法的返回值,就是返回之前的单例对象
/**
* Invokes the readResolve method of the represented serializable class and
* returns the result. Throws UnsupportedOperationException if this class
* descriptor is not associated with a class, or if the class is
* non-serializable or does not define readResolve.
*/
Object invokeReadResolve(Object obj)
throws IOException, UnsupportedOperationException
{
requireInitialized();
if (readResolveMethod != null) {
try {
return readResolveMethod.invoke(obj, (Object[]) null);
} catch (InvocationTargetException ex) {
Throwable th = ex.getTargetException();
if (th instanceof ObjectStreamException) {
throw (ObjectStreamException) th;
} else {
throwMiscException(th);
throw new InternalError(th); // never reached
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
通过HungrySingleton实现Serializable接口并增加readResolve方法,可以将ObjectInputStream反序列化的对象与序列化之前的对象相同,从而达到单例的目的
private Object readResolve(){
return hungrySingleton;
}
通过反射破坏单例:
还是刚刚的HungrySingleton代码
public class HungrySingleton implements Serializable{
private final static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
private Object readResolve(){
return hungrySingleton;
}
}
测试代码
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
HungrySingleton instance = HungrySingleton.getInstance();
HungrySingleton object = (HungrySingleton) constructor.newInstance();
System.out.println(instance);
System.out.println(object);
}
可以看到这个类不能进入
现在我们加上一行代码,把无参构造器的private给忽略掉
constructor.setAccessible(true);
最后输出,可以看到两个不同的实例
对于防御像HungrySingleton在类加载就创建的对象,我们可以在无参构造器判断是否调用反射来破坏单例
private HungrySingleton(){
if(hungrySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
可以看到修改后抛出异常
下面来看看延迟加载的类中使用反射破坏单例的例子
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton(){
if(lazySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
public synchronized static LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
测试代码
Class objectClass = LazySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton instance = LazySingleton.getInstance();
LazySingleton object = (LazySingleton) constructor.newInstance();
System.out.println(instance);
System.out.println(object);
可以发现在懒加载时候,先创建单例对象,再用反射构造会抛出异常,但是反过来保证也会吗
仅仅调换两行的顺序
LazySingleton object = (LazySingleton) constructor.newInstance();
LazySingleton instance = LazySingleton.getInstance();
可以看到又出现两个不同的对象,原因是反射是根据构造器来创建对象的,并没有调用getInstance方法;而后面调用getInstance方法创建的对象就给该类的静态属性赋值上了,所以两个不是同一个对象。
接下来看看Effective Java里面推荐用枚举类来写的单例模式:
创建一个EnumInstance 枚举类
public enum EnumInstance {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance(){
return INSTANCE;
}
}
先来看看反射条件下是否可以破坏单例
EnumInstance instance = EnumInstance.getInstance();
instance.setData(new Object());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file.txt"));
oos.writeObject(instance);
File file = new File("singleton_file.txt");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
EnumInstance newInstance = (EnumInstance) ois.readObject();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
可以看到输出的是相同的实例,显然能破坏单例
究竟是为什么呢?进入ObjectInputStream看看,找到readEnum方法进入
case TC_ENUM:
return checkResolve(readEnum(unshared));
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
因为枚举类中的name是唯一的,而readEnum方法就是通过唯一的name来获取对象,通过反序列化创建的对象根据name来赋值对象,那么当然与之前枚举类创建的对象是同一个。
private Enum<?> readEnum(boolean unshared) throws IOException {
...
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
//可以看到这里用name来获取枚举对象,本来枚举对象的name就是全局唯一的,拿到的当然是同一个对象
@SuppressWarnings("unchecked")
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
if (!unshared) {
handles.setObject(enumHandle, result);
}
}
handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}
下面来看看通过反射是否可以破坏枚举类的单例
Class objectClass = EnumInstance.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
EnumInstance object = (EnumInstance) constructor.newInstance();
EnumInstance instance = EnumInstance.getInstance();
System.out.println(instance);
System.out.println(object);
输出无方法异常
进入枚举类代码查看构造器,发现只有一个有参构造器
protected Enum(String var1, int var2) {
this.name = var1;
this.ordinal = var2;
}
在方法中传入String.class,int.class两个参数
Class objectClass = EnumInstance.class;
Constructor constructor = objectClass.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
EnumInstance object = (EnumInstance) constructor.newInstance("Rory", 666);
EnumInstance instance = EnumInstance.getInstance();
System.out.println(instance);
System.out.println(object);
输出很明显,不能通过反射来创建枚举对象。由此可见,用枚举类来保证单例还是很靠谱的
下面来反编译Enum类来看看为什么有这么靠谱的单例功能,首先在JAD官网上下载相应的版本
这里解压后有两个文件
然后找到class的存储位置
最后在终端上用jad命令行来反编译class文件,生成jad文件
打开JAD文件如下:
// 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: EnumInstance.java
package cn.rory.design.pattern.creational.singleton;
public final class EnumInstance extends Enum
{
public static EnumInstance[] values()
{
return (EnumInstance[])$VALUES.clone();
}
public static EnumInstance valueOf(String name)
{
return (EnumInstance)Enum.valueOf(cn/rory/design/pattern/creational/singleton/EnumInstance, name);
}
private EnumInstance(String s, int i)
{
super(s, i);
}
public Object getData()
{
return data;
}
public void setData(Object data)
{
this.data = data;
}
public static EnumInstance getInstance()
{
return INSTANCE;
}
//可以看到类编类INSTANCE用了static修饰,是静态的
public static final EnumInstance INSTANCE;
private Object data;
private static final EnumInstance $VALUES[];
//下面这个静态块在类初始化的时候就会执行,没有延迟初始化,即线程安全的
static
{
INSTANCE = new EnumInstance("INSTANCE", 0);
$VALUES = (new EnumInstance[] {
INSTANCE
});
}
}
枚举类实现单例有点像我们上面实现的饿汉式(在类加载时候就初始化),也有上面的IO类,反射类来保护枚举类防止破坏单例
线程单例:
创建一个ThreadLocalInstance 类
public class ThreadLocalInstance {
private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal
= new ThreadLocal<ThreadLocalInstance>(){
@Override
protected ThreadLocalInstance initialValue() {
return new ThreadLocalInstance();
}
};
private ThreadLocalInstance(){
}
public static ThreadLocalInstance getInstance(){
return threadLocalInstanceThreadLocal.get();
}
}
写测试代码,可以看到main主线程拿到的都是同一个对象
每个ThreadLocal都维持了一个ThreadLocalMap的表,由当前线程信息作为key来获取ThreadLocalMap对象,因此在多线程访问的时候,不会相互影响。
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
单例模式在源码中的实现:
搜索Runtime类(win下Ctrl+N快捷键)
可以看到Runtime的currentRuntime 属性用static修饰了,与之前的饿汉式类似,在类初始化时候就加载了
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;
}
...
}
下面看看MyBatis里的ErrorContext类,可以看到LOCAL 属性是不可变的静态ThreadLocal
/**
* @author Clinton Begin
*/
public class ErrorContext {
private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
private ErrorContext stored;
private String resource;
private String activity;
private String object;
private String message;
private String sql;
private Throwable cause;
private ErrorContext() {
}
public static ErrorContext instance() {
//确保同一个线程拿到的都是自己线程内的上下文,线程之间并不会相互影响
ErrorContext context = LOCAL.get();
if (context == null) {
context = new ErrorContext();
LOCAL.set(context);
}
return context;
}
...
}
参考:
http://www.imooc.com/t/2705746