设计模式1——单例模式

  • 时间:
  • 来源:互联网
  • 文章标签:

单例模式(Singleton)

特点:

  • 类构造器私有
  • 持有自己类型的属性
  • 对外提供获取实例的静态方法

总之,单例模式就是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种方法。

饿汉模式

饿汉式天生就是线程安全的,比较常用,但容易产生垃圾,因为一开始就要初始化

// 饿汉式单例
public class Hungry {

  private final static Hungry HUNGRY = new Hungry();
    
  private Hungry() {}

  public static Hungry getInstance() {
    return HUNGRY;
  }
}

懒汉模式

线程不安全!

// 懒汉式单例,多线程测试
public class Lazy {

  private Lazy() {
    System.out.println(Thread.currentThread().getName() + "ok");
  }

  private static Lazy Lazy;

  public static Lazy getInstance() {
    if (Lazy == null) {
      Lazy = new Lazy();
    }
    return Lazy;
  }

  public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
      new Thread(() -> {
        getInstance();
      }).start();
    }
  }
}

总之,从速度和反应时间角度来讲,饿汉式(又称立即加载)要好一些;从资源利用效率上说,懒汉式(又称延迟加载)要好一些。

在单线程环境下,两种模式都能正常工作。但在多线程环境下,饿汉式天生线程安全,所以不会出现问题,但懒汉式有可能出现多个实例的情况

DCL懒汉式(双重锁模式)

线程安全,双重加载,延迟初始化

package DesignPatterns;

// 懒汉式单例
public class DCLazy {

  private DCLazy() {
    System.out.println(Thread.currentThread().getName() + "ok");
  }

  // 一定要加volatile!!
  private volatile static DCLazy Lazy;

  // 双重检测锁模式的懒汉式单例,DCL懒汉式
  public static DCLazy getInstance() {
    if (Lazy == null) {
      synchronized (DCLazy.class) {
        if (Lazy == null) {
          // 如果不是volatile,那就原子性操作(会有指令重排)
          // 1.分配内存空间 2.执行构造方法初始化对象 3.把这个对象指向这个空间
          Lazy = new DCLazy();
        }
      }
    }
    return Lazy;
  }

  public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
      new Thread(DCLazy::getInstance).start();
    }
  }
}

问题一:为什么饿汉式天生线程安全?

**类加载的方式是按需加载,且只加载一次。**因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用。

换句话说,在线程访问单例对象之前就已经创建好了。再加上,一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例,也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。因此就说,饿汉式单例天生就是线程安全的。

问题二:为什么懒汉式线程不安全

在执行过程中,可能会有多个线程同时进行 Lazy == null 的判断,这种情况就也可能会创建出多个实例,违背单例模式初衷。

同步延迟加载——内部类

私有内部类,按需加载,也就是延迟加载

// 静态内部类
public class Holder {

  private Holder() {
    System.out.println(Thread.currentThread().getName() + "新建实例");
  }

  private static Holder getInstance() {
    return InnerClass.HOLDER;
  }

  private static class InnerClass {
    private static final Holder HOLDER = new Holder();
  }

  public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
      new Thread(Holder::getInstance).start();
    }
  }
}

问题三:为什么要用枚举类实现单例模式?

在《Effective Java》中提到了几句话:“享有特权的客户端可以借助 AccessibleObject.setAccessible 方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。”

Hollis大神的文章讲解,怒赞!

// 枚举单例的实现
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

// enum 是一个什么? 本身也是一个Class类 
public enum EnumSingle {
    
    INSTANCE;
    
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

本文链接http://www.taodudu.cc/news/show-82879.html