单例模式没你想的那么简单
网上到处都是懒汉,饿汉模式。给两个Demo就算过去了吧。
饿汉单例模式:在类加载的时候,就开始实例化了。
public class HungrySingleton { private static HungrySingleton one=new HungrySingleton(); private HungrySingleton(){} public static HungrySingleton getInstance(){ return one; } public static void main(String[] args) { HungrySingleton one1=HungrySingleton.getInstance(); HungrySingleton one2=HungrySingleton.getInstance(); System.out.println(one1==one2); } }
懒汉模式:在第一次获取实例化对象的时候,开始实例化。
public class LazySingleton { private static LazySingleton one=null; private LazySingleton() { } public static LazySingleton getInstance(){ if(one==null){ one=new LazySingleton(); } return one; } public static void main(String[] args) { LazySingleton one1=LazySingleton.getInstance(); LazySingleton one2=LazySingleton.getInstance(); System.out.println(one1 == one2); } }
无论何种模式先要把构造函数给私有化,否则就变成了“勤快汉”模式了;这名字是我瞎编的。
饿汉模式典型用法:Spring中IOC容器ApplictionContext,连接池.
懒汉模式典型用法:不知道啊。
饿汉模式没缺点,最多也就在没使用的时候,分配个内存空间。
下面着重说说懒汉模式,所以来个分割线吧。
=================================
懒汉单例模式的线程安全
上面的懒汉模式有线程安全问题,就是多个线程在同时执行的时候,怎么保证LazySingleton只被实例化了一次。
线程类:
public class ExectorThread implements Runnable { @Override public void run() { LazySimpleSingleton one=LazySimpleSingleton.getInstance(); System.out.println(Thread.currentThread().getName() + ":" + one); } }
单例类:
public class LazySimpleSingleton { private static LazySimpleSingleton one=null; private LazySimpleSingleton() { } public static LazySimpleSingleton getInstance(){ if(one==null){ one= new LazySimpleSingleton(); } return one; } }
测试类:
public class LazyTest { public static void main(String[] args) { Thread t1=new Thread(new ExectorThread()); Thread t2=new Thread(new ExectorThread()); t1.start(); t2.start(); System.out.println("end"); } }
第一个线程把one实例化完成之后,还没有来得及刷新到内存,第二个线程就把one读入内存,又进行了一次实例化。
最简单的办法就是给实例化方法getInstance()添加一个synchronized.
修改后代码如下
public class LazySimpleSingleton { private static LazySimpleSingleton one=null; private LazySimpleSingleton() { } public static synchronized LazySimpleSingleton getInstance(){ if(one==null){ one= new LazySimpleSingleton(); } return one; } }
这种模式有一个性能问题;比如100个线程在同时调用getInstance()的时候,99个全部都阻塞在这个位置了,
包括one已经不是空值的时候,依然在阻塞中;改造上面的代码,让已经实例化之后的线程不在阻塞。
1 public class LazySimpleSingleton { 2 3 private static LazySimpleSingleton one=null; 4 5 private LazySimpleSingleton() { 6 } 7 public static LazySimpleSingleton getInstance(){ 8 //索前判断是否实例化了,实例化了就不用进入synchronized中了 9 if(one==null){ 10 synchronized(LazySimpleSingleton.class){ 11 //上面one==null了,不代表此时还是null 12 if(one==null){ 13 one= new LazySimpleSingleton(); 14 } 15 return one; 16 } 17 } 18 return one; 19 } 20 }
反射破坏单例
以饿汉单例的Demo为例子进行改造。
1 public class HungrySingleton { 2 3 private static HungrySingleton one=new HungrySingleton(); 4 5 private HungrySingleton(){} 6 7 public static HungrySingleton getInstance(){ 8 return one; 9 } 10 11 public static void main(String[] args) throws Exception{ 12 HungrySingleton one1=HungrySingleton.getInstance(); 13 Constructor constructor=HungrySingleton.class.getDeclaredConstructor(null); 14 //强制访问构造器,包括私有成员 15 constructor.setAccessible(true); 16 HungrySingleton one2=(HungrySingleton) constructor.newInstance(); 17 System.out.println(one1==one2); 18 } 19 }
打印结果显示false.说明被实例化了两次;修改代码如下。
public class HungrySingleton { private static HungrySingleton one=new HungrySingleton(); private HungrySingleton(){ if(one!=null){ throw new RuntimeException("已经实例化过了,本次实例化失败"); } } public static HungrySingleton getInstance(){ return one; } public static void main(String[] args) throws Exception{ HungrySingleton one1=HungrySingleton.getInstance(); Constructor constructor=HungrySingleton.class.getDeclaredConstructor(null); //强制访问构造器,包括私有成员 constructor.setAccessible(true); HungrySingleton one2=(HungrySingleton) constructor.newInstance(); System.out.println(one1==one2); } }
打印结果:
序列化破坏单例模式
以饿汉模式为例:
public class SeriableSingleton implements Serializable { public final static SeriableSingleton one=new SeriableSingleton(); private SeriableSingleton() { } public static SeriableSingleton getInstance(){ return one; } }
测试类:
1 public class SeriableSingletonTest { 2 public static void main(String[] args) { 3 SeriableSingleton s1=null; 4 SeriableSingleton s2=SeriableSingleton.getInstance(); 5 6 FileOutputStream fos=null; 7 try { 8 fos=new FileOutputStream("one.obj"); 9 ObjectOutputStream oos=new ObjectOutputStream(fos); 10 oos.writeObject(s2); 11 oos.flush(); 12 oos.close(); 13 14 FileInputStream fis=new FileInputStream("one.obj"); 15 ObjectInputStream ois=new ObjectInputStream(fis); 16 s1=(SeriableSingleton) ois.readObject(); 17 ois.close(); 18 19 System.out.println(s1 == s2); 20 21 } catch (Exception e){ 22 23 } 24 } 25 }
显示结果是false.
这个问题很好办,加一行代码的事情。
1 public class SeriableSingleton implements Serializable { 2 public final static SeriableSingleton one=new SeriableSingleton(); 3 4 private SeriableSingleton() { 5 } 6 7 public static SeriableSingleton getInstance(){ 8 return one; 9 } 10 11 private Object readResolve(){ 12 return one; 13 } 14 }
上面红色就是添加的一个方法。
这是一个奇怪的现象,直接从源码中找答案吧。
readObject()源码
readObject0()源码
private Object readObject0(boolean unshared) throws IOException { ... case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); ........ }
上面代码去除了一些无用代码。
继续看readOrdinaryObject()源码
private Object readOrdinaryObject(boolean unshared) throws IOException { //判断是否有readResolve()方法 if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); } return obj; }
invokeReadResolve()源码
Object invokeReadResolve(Object obj) throws IOException, UnsupportedOperationException { .......... if (readResolveMethod != null) { try {//利用反射调用readResolve()方法 return readResolveMethod.invoke(obj, (Object[]) null); } } }
因为JDK在readObject()时候,判断了有没有readResolve()方法,如果有的话就执行这个方法,没有就不执行了,我们充分利用了这个特点,给他直接返回了一个one对象;
所以就不执行实例化了。其实这个地方在readObject()时候实例化了一次,只不过新创建的对象没有被返回而已。