单例模式特点
1.构造方法私有化
2.实例化变量引用私有化
3.获取实例方法共有
饿汉式实现
public class Singleton {
private static Singleton instance= new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}
懒汉式双重锁机制
public class Singleton {
private volatile static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if(instance== null){
synchronized(Singleton.class){
if(instance== null){
instance= new Singleton();
}
}
}
return instance;
}
}
静态内部类
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
单例可能产生的问题
1. 《effective java》中只简单的提了几句话:“享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要低于这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。
2. 任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例。”当然,这个问题也是可以解决的,想详细了解的同学可以翻看《effective java》第77条:对于实例控制,枚举类型优于readResolve
以上问题怎样解决:枚举单例实现 Java规范字规定,每个枚举类型及其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。在序列化的时候Java仅仅是将枚举对象的name属性输到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。也就是说,序列化的时候只将DATASOURCE这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。
public enum SingletonEnum {
DATASOURCE;
private DBConnection connection = null;
private SingletonEnum() {
connection = new DBConnection();
}
public DBConnection getConnection() {
return connection;
}
}
class DBConnection {
}
class Test {
public static void main(String[] args) {
DBConnection conn1 = SingletonEnum.DATASOURCE.getConnection();
DBConnection conn2 = SingletonEnum.DATASOURCE.getConnection();
System.out.println(conn1 == conn2);
}
}
javap -c class文件
D:\dest\sso\target\classes\com\explain>javap -c SingletonEnum.class
Compiled from "SingletonEnum.java"
public final class com.explain.SingletonEnum extends java.lang.Enum<com.explain.SingletonEnum> {
public static final com.explain.SingletonEnum DATASOURCE;
public static com.explain.SingletonEnum[] values();
Code:
0: getstatic #1
3: invokevirtual #2
6: checkcast #3
9: areturn
public static com.explain.SingletonEnum valueOf(java.lang.String);
Code:
0: ldc #4
2: aload_0
3: invokestatic #5
6: checkcast #4
9: areturn
public com.explain.DBConnection getConnection();
Code:
0: aload_0
1: getfield #7
4: areturn
static {};
Code:
0: new #4
3: dup
4: ldc #10
6: iconst_0
7: invokespecial #11
10: putstatic #12
13: iconst_1
14: anewarray #4
17: dup
18: iconst_0
19: getstatic #12
22: aastore
23: putstatic #1
26: return
}
通过反射实例化对象
public static void main(String[] args) throws Exception{
DBConnection conn1 = SingletonEnum.DATASOURCE.getConnection();
DBConnection conn2 = SingletonEnum.DATASOURCE.getConnection();
System.out.println(conn1 == conn2);
Constructor<SingletonEnum> declaredConstructor = SingletonEnum.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
SingletonEnum singletonEnum = declaredConstructor.newInstance();
}
反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。报错。
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.explain.Test.main(SingletonEnum.java:30)
我们可以清楚地看到 newInstance 这段代码
(clazz.getModifiers() & Modifier.ENUM) != 0
如果是则抛出异常,反射失败
@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;
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
那它是如何避免序列号问题的呢?
public enum EnumSingleton implements Serializable {
INSTANCE;
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
private EnumSingleton() {
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
EnumSingleton s = EnumSingleton.INSTANCE;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("EnumSingleton.obj"));
oos.writeObject(s);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
EnumSingleton s1 = (EnumSingleton)ois.readObject();
ois.close();
System.out.println(s+"\n"+s1);
System.out.println("枚举序列化后读取其中的内容:"+s1.getContent());
System.out.println("枚举序列化前后两个是否同一个:"+(s==s1));
}
}
枚举序列化前读取其中的内容:枚举单例序列化
INSTANCE
INSTANCE
枚举序列化后读取其中的内容:枚举单例序列化
枚举序列化前后两个是否同一个:true
枚举类型的序列化和反序列化上,Java做了特殊的规定。在序列化的时候Java仅仅是将枚举对象的name属性输到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。也就是说,序列化的时候只将DATASOURCE这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。
|