内部类实现->避免了加锁,是性能最优的实现方法
实现方法
**
* 静态内部类的单例模式
* 性能最优
*/
public class LazyInnerClassSingleTon {
/**
* 私有化构造方法
*/
private LazyInnerClassSingleTon(){}
/**
* 全局访问点
*/
public static final LazyInnerClassSingleTon getInstance(){
return LazyHolder.LAZY;
}
/**
* 静态内部类的实现方式,避免了加锁,使用了类加载的特征:内部类比外部类先加载--懒汉式
* 懒汉式:因为LazyHolder里面的代码要等外部调用时才执行,使用JVM底层逻辑,避免线程安全问题
*/
private static class LazyHolder{
private static final LazyInnerClassSingleTon LAZY = new LazyInnerClassSingleTon();
}
}
测试代码
public class MySingletonTest {
public static void main(String[] args) {
new Thread(new MyExecoter()).start();
new Thread(new MyExecoter()).start();
System.out.println("--------main线程结束---------");
}
static class MyExecoter implements Runnable{
@Override
public void run() {
LazyInnerClassSingleTon lazy = LazyInnerClassSingleTon.getInstance();
System.out.println(Thread.currentThread()+":"+lazy);
}
}
}
结果:
缺点1:通过反射被破坏单例
但有可能被通过反射的方式攻击,因为可以通过反射直接获取构造方法,故有可能被误用,示例:
public class MySingletonTest {
public static void main(String[] args){
Class<?> clzz = LazyInnerClassSingleTon.class;
try {
Constructor constructor = clzz.getDeclaredConstructor(null);//获取构造方法
constructor.setAccessible(true);//授权
Object o1 = constructor.newInstance(null);//获取实例1
Object o2 = LazyInnerClassSingleTon.getInstance();//获取实例2
System.out.println(o1);
System.out.println(o2);
System.out.println(o1==o2);//直接比较地址是否相同
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("--------main线程结束---------");
}
}
运行结果
可以看出,通过反射得到的实例与直接调用得到的实例不相同,未能实现单例。
解决办法
修改构造方法即可,其他地方不变
/**
* 私有化构造方法
*/
private LazyInnerClassSingleTon(){
//禁止使用反射创建实例
if (LazyHolder.LAZY != null){
throw new RuntimeException("不允许构建多个实例");
}
}
在构造方法中通过禁止反射,强迫用户只能通过正常调用来获取实例。
此时原来的测试代码的运行结果为报错:
缺点2:通过序列化被破坏单例
如果对象实现了序列化接口,则有可能通过序列化和反序列化的方式被破坏单例
通过序列化破坏单例的代码实例
public class MySingletonTest {
public static void main(String[] args) {
LazyInnerClassSingleTon lazy1 = null;
LazyInnerClassSingleTon lazy2 = LazyInnerClassSingleTon.getInstance();
FileOutputStream fos = null;
try {
//----------------将lazy2通过序列化写入磁盘--------------------------
fos = new FileOutputStream("LazyInnerClassSingleTon.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(lazy2);
oos.flush();
oos.close();
//----------------将从磁盘反序列化读入一个实例lazy1---------------------
FileInputStream fis = new FileInputStream("LazyInnerClassSingleTon.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
lazy1 = (LazyInnerClassSingleTon) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(lazy1);
System.out.println(lazy2);
System.out.println(lazy1==lazy2);
}
}
结果为
可见,已经变成了两个不同的实例,不是单例了
解决办法
在LazyInnerClassSingleTon类中重写readResolve方法即可,其余不变
/**
* 解决序列化破坏单例的情况
* @return
*/
private Object readResolve(){
return LazyHolder.LAZY;
}
运行结果
解决序列化破坏单例问题的原理:
1.从ois.readObject();开始进入看源码
2.继续点击readObject
3.在readObject方法中点击readObject0
4.由于是读入的文件,所以在readObject0中走到了二进制,进入readOrdinaryObject
5.在readOrdinaryObject里,看到这里是在判断对象是否有构造方法,如果有就新建一个实例,没有则返回空,我们的LazyInnerClassSingleTon有构造方法,故此处会新建一个实例
6.继续在readOrdinaryObject里往下走,会判断这个对象是否有readResolve的方法,如果有,则会调用这个方法,并用这个方法的值来代替前面新建的实例。故在这里完成了覆盖,保证了单例。
7.hasReadResolveMethod())的方法结果来自于
查找readResolveMethod的赋值
继续通过此方法查下去,最终来到Class.java,看到是通过反射查找到这个无参数的readResolve方法,并将此方法返回来了,所以在第7步的hasReadResolveMethod的结果为true,第6步的覆盖的过程得以执行,保证了单例。
此方法其实还是实例化了两次,只不过反序列化的那个示例被覆盖掉了,会被gc回收