单例设计模式详解

单例模式

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);
  }
}

 

posted @ 2020-01-17 11:02  第二人生Bonnie  阅读(125)  评论(0编辑  收藏  举报