个单例对象创建好后,有时候需要将对象序列化然后写入磁盘,下次使用时再从磁盘中读取对象并进行反序列化,将其转化为内存对象。
反序列化后的对象会重新分配内存,即重新创建。
public class SeriableSingleton implements Serializable {
/*
序列化
把内存中对象的状态转换为字节码的形式
把字节码通过IO输出流,写到磁盘上
永久保存下来,持久化
-----------------
反序列化
将持久化的字节码内容,通过IO输入流读到内存中来
转化成一个Java对象
*/
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
//添加这个固定格式的方法(桥接模式)
private Object readResolve(){ return INSTANCE;}
}
public class SeriableSingletonTest {
public static void main(String[] args) {
SeriableSingleton s1 = null;
SeriableSingleton s2 = SeriableSingleton.getInstance();
FileOutputStream fos = null;
try {
//序列化
fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
//反序列化
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SeriableSingleton)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2); //上面SeriableSingleton类,加上readResolve():true; 去掉readResolve():false;
} catch (Exception e) {
e.printStackTrace();
}
}
}
通过JDK 源码分析我们可以看出,虽然增加 readResolve0方法返回实例解决了单例模式被破坏的问题,
但是实际上实例化了两次,只不过新创建的对象没有被返回而已。如果创建对象的动作发生频率加快,就意味着内存分配开销也会随之增大。
注册式单例模式又称为登记式单例模式,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。
注册式单例模式有两种:一种为枚举式单例模式,另一种为容器式单例模式。
(1)枚举式单例模式:
public enum EnumSingleton {
INSTANCE;
//属性get和set赋值
private Object data;
public Object getData() { return data;}
public void setData(Object data) { this.data = data; }
//返回单列实例
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
//-------------------------------------
public class EnumSingletonTest {
public static void main(String[] args) {
EnumSingleton s1 = null;
EnumSingleton s2 = EnumSingleton.getInstance();
s2.setData(new Object());
FileOutputStream fos = null;
try {
fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (EnumSingleton)ois.readObject();
ois.close();
System.out.println(s1.getData());
System.out.println(s2.getData());
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果:
java.lang.Object@b4c966a
java.lang.Object@b4c966a
true
解决:
反序列化单例模式readResolve()实际上实例化了两次,只不过新创建的对象没有被返回而已。
如果创建对象的动作发生频率加快,就意味着内存分配开销也会随之增大。
public class EnumSingletonTest {
public static void main(String[] args) {
try {
Class clazz = EnumSingleton.class;
Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
c.setAccessible(true);
Object o = c.newInstance();
}catch (Exception e){
e.printStackTrace();
}
}
}
结果:[即不能用反射来创建枚举类型]
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.gupaoedu.vip.pattern.singleton.test.EnumSingletonTest.main(EnumSingletonTest.java:65)
(2)容器式单例模式:
其实枚举式单例,虽然写法优雅,但是也会有一些问题。因为它在类加载之时就将所有的对象初始化放在类内存中,这其实和饿汉式并无差异,不适合大量创建单例对象的场景。
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getInstance(String className){
Object instance = null;
if(!ioc.containsKey(className)){
try {
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
}catch (Exception e){
e.printStackTrace();
}
return instance;
}else{
return ioc.get(className);
}
}
}
//-------------------------------
public class Pojo {}
public class ContainerSingletonTest {
public static void main(String[] args) {
Object instance1 = ContainerSingleton.getInstance("com.gupaoedu.vip.pattern.singleton.test.Pojo");
Object instance2 = ContainerSingleton.getInstance("com.gupaoedu.vip.pattern.singleton.test.Pojo");
System.out.println(instance1 == instance2);
}
}
结果:true
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocaLInstance =
new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton(){}
public static ThreadLocalSingleton getInstance(){
return threadLocaLInstance.get();
}
}
//-------------------------------------------------
public class ExectorThread implements Runnable{
public void run() {
ThreadLocalSingleton instance = ThreadLocalSingleton.getInstance();
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(Thread.currentThread().getName() + ":" + instance);
}
}
public class ThreadLocalSingletonTest {
public static void main(String[] args) {
//main线程
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
//t1和t2线程
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
t1.start();
t2.start();
System.out.println("End");
}
}
结果:[在同一个线程下所有的实例都是案例。不同线程间不是单列的]
com.gupaoedu.vip.pattern.singleton.threadlocal.ThreadLocalSingleton@6e0be858
com.gupaoedu.vip.pattern.singleton.threadlocal.ThreadLocalSingleton@6e0be858
com.gupaoedu.vip.pattern.singleton.threadlocal.ThreadLocalSingleton@6e0be858
End
com.gupaoedu.vip.pattern.singleton.threadlocal.ThreadLocalSingleton@50230424
Thread-0:com.gupaoedu.vip.pattern.singleton.threadlocal.ThreadLocalSingleton@50230424
com.gupaoedu.vip.pattern.singleton.threadlocal.ThreadLocalSingleton@6a3899f6
Thread-1:com.gupaoedu.vip.pattern.singleton.threadlocal.ThreadLocalSingleton@6a3899f6
特点:
在同一个线程下所有的实例都是案例。不同线程间不是单列的。
没有接口,扩展困难
如果要扩展单例对象,只有修改代码,没有其他途径。
私有化构造器
保证线程安全
延迟加载
防止序列化和反序列化破坏单例
防御反射攻击单例