单例模式
设计模式之单例模式
单例模式:即类对象在全局只有一个实例。
饿汉式单例模式
在类加载的时候,就创建类的实例,这个是线程安全的单例模式。
public class SingleTon1 {
// 私有构造方法
private SingleTon1() {
}
// 类加载时创建实例对象
private static SingleTon1 instance = new SingleTon1();
// 获取实例对象的方法,不需要synchronize关键字,因为本身就是线程安全的
public static SingleTon1 getInstance() {
return instance;
}
}
懒汉式单例模式
在需要使用类实例的时候,再去创建这个类的实例,在高并发情况下可能会出现多个实例的情况,避免方法,可以在获取实例的方法上使用synchronize关键字进行检查。但是对性能是有损失的。
public class SingleTon2 {
// 私有构造方法
private SingleTon2() {
}
// 类加载时创建实例对象
private static SingleTon2 instance = null;
// 获取实例对象的方法,用synchronize关键字保证线程安全
public static synchronized SingleTon2 getInstance() {
if(null == instance){
instance = new SingleTon2();
}
return instance;
}
}
双重检查锁单例模式
也是延时加载,在需要时创建这个类的实例,在获取实例的方法内部进行双重检查锁,来保证线程安全。但是根据jvm内部的优化,可能会导致创建多个实例。
public class SingleTon3 {
// 私有构造方法
private SingleTon3() {
}
// 类加载时创建实例对象
private static SingleTon3 instance = null;
// 获取实例对象的方法,用synchronize关键字保证线程安全
public static SingleTon3 getInstance() {
if(null == instance){
synchronized (SingleTon3.class) {
if(null == instance){
instance = new SingleTon3();
}
}
}
return instance;
}
}
静态内部类单例模式
使用静态内部类的方式创建类的实例,可以实现延时加载,而且是线程安全的。
public class SingleTon4 {
// 私有构造方法
private SingleTon4() {
}
// 使用静态内部类
private static final class SingTonInner {
private static final SingleTon4 INSTANCE = new SingleTon4();
}
// 获取实例对象的方法
public static SingleTon4 getInstance() {
return SingTonInner.INSTANCE;
}
}
枚举类单例模式
线程安全的方式创建类的实例,在类加载时创建。
public enum SingleTon5 {
INSTANCE;// 实例
//添加方法
public void say() {
}
}
上述的5种模式,都要求私有化构造方法。
利用反射区破解单例模式(不含枚举)
利用java的反射机制去获取类的实例
public class Test1 {
public static void main(String[] args) throws Exception {
SingleTon1 s1 = SingleTon1.getInstance();
SingleTon1 s2 = SingleTon1.getInstance();
System.out.println(s1 == s2);
// 获取类对象
Class<SingleTon1> clazz = (Class<SingleTon1>) Class.forName("com.pattern.singleton.SingleTon1");
// 获取构造方法
Constructor<SingleTon1> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
SingleTon1 s3 = constructor.newInstance();
System.out.println(s1 == s3);
}
}
测试结果
true
false
如何防止反射破解
防止也很简单,在构造方法内部判断实例是否存在,存在的话抛异常就可以了。
修改后的SingleTon1.java:
public class SingleTon1 {
// 私有构造方法
private SingleTon1() {
if(null != instance){
throw new RuntimeException();
}
}
// 类加载时创建实例对象
private static SingleTon1 instance = new SingleTon1();
// 获取实例对象的方法,不需要synchronize关键字,因为本身就是线程安全的
public static SingleTon1 getInstance() {
return instance;
}
}
修改后的测试结果:
true
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.pattern.singleton.Test1.main(Test1.java:15)
Caused by: java.lang.RuntimeException
at com.pattern.singleton.SingleTon1.<init>(SingleTon1.java:7)
... 5 more
利用序列化和反序列化破解单例模式
要实现序列化和反序列化,必须要类实现Serializable接口。
测试代码:
public class Test2 {
public static void main(String[] args) throws Exception {
SingleTon1 s1 = SingleTon1.getInstance();
SingleTon1 s2 = SingleTon1.getInstance();
System.out.println(s1 == s2);
//序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
oos.writeObject(s1);
oos.flush();
oos.close();
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
SingleTon1 s3 = (SingleTon1) ois.readObject();
ois.close();
System.out.println(s1 == s3);
}
}
测试结果:
true
false
如何防止序列化和反序列化破解
自己写一个方法,readResolve方法,空参,返回Object
修改后的SingleTon1.java:
public class SingleTon1 implements Serializable{
// 私有构造方法
private SingleTon1() {
if(null != instance){
throw new RuntimeException();
}
}
// 类加载时创建实例对象
private static SingleTon1 instance = new SingleTon1();
// 获取实例对象的方法,不需要synchronize关键字,因为本身就是线程安全的
public static SingleTon1 getInstance() {
return instance;
}
private Object readResolve(){
return instance;
}
}
测试结果:
true
true