单例模式相信大家都已经很熟悉,主要就是为了保证在堆区中只能有一个对象,下面给出一个典型的例子.
public class Singleton {
private static Singleton singleton;
private Singleton(){
}
public static Singleton newInstance(){
if(singleton==null)
singleton=new Singleton();
return singleton;
}
}
因为构造方法是私有的,不能直接通过new关键字实例化对象,那么只能调用静态方法,以得到相同的结果!
通常我们是这样做:Singleton singleton=Singleton.newInstance();的确,这样做是能保证我们得到相同对象,但前提是在单线程的程序.
假设有如下线程类生成Singleton对象:
public class User extends Thread{
private Singleton singleton;
public User(){
}
public void run(){
singleton=Singleton.newInstance();
System.out.println(singleton);
}
}
测试类:
public class SingletonTest{
public static void main(String args[]){
User user1=new User();
User user2=new User();
user1.start();
user2.start();
}
}
}
结果是什么?不清楚......
if(singleton==null) singleton=new Singleton();这行决定着我们的对象是否单例!
为了方便说明问题,把Singleton类稍微改动下:
public class Singleton {
private static Singleton singleton;
private Singleton(){
}
public static Singleton newInstance(){
if(singleton==null){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton=new Singleton();
}
return singleton;
}
}
再运行下,发现什么?线程的run方法中打印的是2个不同的对象!
为什么?一个线程调用newInstance(),发现singleton是null,就进入了if(singleton==null){}方法体,这时当前线程sleep了500毫秒,就在这500毫秒期间,另一个线程也调用了newInstance(),发现singleton也是null,也进入了if(singleton==null){}方法体,当前线程也sleep了500毫秒,这时候第一个线程得到机会继续向下运行,执行singleton=new Singleton();返回后,第二个线程也得到运行的机会,同样也执行了singleton=new Singleton();这2次实例化的对象显然不是同一个对象
单例模式不就被我们打破了吗?!但这种打破是计算机的行为,如果不显示地让线程sleep,我们无法预知能生成几个对象!不过这种问题还是可以被解决,给对象加锁,也就是在你调用的方法前加synchronized关键字,或者把你的方法写在synchronized(lock){},lock可以是任何实例化的对象,因为任何对象都有个内部锁!
说了这么多,其实还没到正题,我们要人为地打破单例!
首先:得到一个对象的方法有几种呢?
1.new 关键字,最常用的,也最方便的
2.工厂模式,内部可以用任何实例化对象的方式返回对象,解耦用的
3.反序列化,从硬盘或网络中反序列化对象
4.反射
首先看个例子:
public class SingletonTest{
public static void main(String args[]){
Singleton singleton1=null;
Singleton singleton2=null;
Class clazz=null;
Class[] param=new Class[]{};
try {
clazz=Singleton.class;
Constructor constructor=clazz.getDeclaredConstructor(param);
constructor.setAccessible(true);
singleton1=(Singleton)constructor.newInstance(new Object[]{});
singleton2=(Singleton)constructor.newInstance(new Object[]{});
System.out.println(singleton1==singleton2);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
运行结果:false!充分说明singleton1,singleton1这2个引用指向的是2个不同的对象!,之所以做到这点,
就是充分利用了java的反射机制!constructor.setAccessible(true);使得构造方法的访问权限修饰符形同虚设!同样可以适用Field,Method!
至此,我们已经人为地打破了单例模式!
做这个,只是为了说明反射的重要及有趣!