单例设计模式详解
1、饿汉式(延迟加载)
指不管用不用这个类,此类都会被创建。
代码:
public class EagerSingleton {
private static EagerSingleton eagerSingleton=new EagerSingleton();
public static EagerSingleton getInstance(){
return eagerSingleton;
}
}
2、懒汉式(非延迟加载)
指用到这个类的时候,才会被创建。
代码:(此类线程不安全)
public class LazySingleton {
private static LazySingleton lazySingleton=null;
private static LazySingleton getInstance(){
if(lazySingleton==null){
lazySingleton=new LazySingleton();
}
return lazySingleton;
}
}
3、线程安全的三种单例写法
3.1、双重检查单例写法
代码:
1 public class DoubleCheckSingleton {
2 private static DoubleCheckSingleton doubleCheckSingleton=null;
3 public static DoubleCheckSingleton getInstance(){
4 if(doubleCheckSingleton==null){
5 synchronized (DoubleCheckSingleton.class){
6 if(doubleCheckSingleton==null){
7 doubleCheckSingleton=new DoubleCheckSingleton();
8 }
9 }
10 }
11 return doubleCheckSingleton;
12 }
private int a;
public int getA(){
return a;
}
13}
双重检查的写法会引发有序性问题。
分析:
对象在JVM中的创建步骤大致分为以下三步:
1.new:开辟JVM堆中的内存空间
2.将内存空间初始化(指的就是对象的成员变量初始化为0值)
3.将内存空间地址(引用地址)赋值给引用类型的变量
在new对象的时候,JIT即时编译器会根据运行情况,对对象创建的过程进行指令重排序(按照1、3、2步骤执行)。
如果线程1执行到了第7行,只完成了1、3步加载,第2步还没有执行的时候,这时线程2从第4行开始执行,这时doubleCheckSingleton不为空,直接返回doubleCheckSingleton。当调用DoubleCheckSingleton的getA()方法时,就会报错,因为这时成员变量a还没有被初始化。
为了解决这个问题,可以给doubleCheckSingleton变量加上volatile关键字,防止重排序。
private volatile static DoubleCheckSingleton doubleCheckSingleton=null;
3.2、内部静态类单例写法
代码:
public class InnerStaticSingleton {
private static class SingletonFactory{
private static InnerStaticSingleton instance=new InnerStaticSingleton();
}
public InnerStaticSingleton getInstance(){
return SingletonFactory.instance;
}
}
原理:
内部静态类只有在被调用时,才会执行内部静态类中的静态代码块、给成员静态变量赋值,并且只执行一次。所以既保证了单例,又保证了延迟加载。
3.3、枚举单例写法
代码:
public enum EnumSingleton {
INSTANCE;
public void talk() {
System.out.println("This is an EnumSingleton " + this.hashCode());
}
}
4、破坏单例
4.1、反射破坏单例
代码:
public class ReflectBreakSingleton {
public static void main(String[] args) throws Exception {
Class clazz = InnerStaticSingleton.class;
InnerStaticSingleton singleton1 = (InnerStaticSingleton) clazz.newInstance();
InnerStaticSingleton singleton2 = (InnerStaticSingleton) clazz.newInstance();
System.out.println(singleton1 == singleton2);//false
}
}
4.2、反序列化破坏单例
public class SerializeBreakSingleton {
public static void main(String[] args)throws Exception{
InnerStaticSingleton innerStaticSingleton=InnerStaticSingleton.getInstance();
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream outputStream=new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(innerStaticSingleton);
ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream);
InnerStaticSingleton innerStaticSingleton1=(InnerStaticSingleton) objectInputStream.readObject();
System.out.println(innerStaticSingleton==innerStaticSingleton1);
}
}