单例设计模式

单例模式的定义(what)

定义:一个类只有一份,每次使用只有一个实例被创建。即类只会被new一次, 此实例被共享使用。

特点: 一个私有属性并且为final static, 一个私有构造, 一个公共获取实例的方法。

1645080050539

单例模式的实现(how)

实现步骤

  • private static单例类属性
  • private 无参构造方法
  • public static 获取实例方法
public class Singleton {
  private static Singleton singleton = new Singleton();
  private Singleton(){
    
  }
  public static Singleton getInstance(){
    return singleton;
  }
}

单例模式使用场景

  • 系统配置类
  • 处理资源访问冲突 公共的日志使用

复杂的单例模式分类

多种实现方式都是为了解决两个问题:

  1. 并发问题
  2. 加载时机问题 内存占用的问题

饿汉式

特点:

  • new IdGenerator() 线程安全类加载的
  • 不支持懒加载
public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private static final IdGenerator instance = new IdGenerator();
  private IdGenerator() {}
  public static IdGenerator getInstance() {
    return instance;
  }
  public long getId() { 
    return id.incrementAndGet();
  }
}

懒汉式

特点:

  • 支持懒加载
  • 不过懒汉式的缺点也很明显,我们给 getInstance() 这个方法加了一把大锁(synchronzed),导致这个函数的并发度很低。量化一下的话,并发度是 1,也就相当于串行操作了。而这个函数是在单例使用期间,一直会被调用。如果这个单例类偶尔会被用到,那这种实现方式还可以接受。但是,如果频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈,这种实现方式就不可取了

public class IdGenerator { 
  //AtomicLong支持并发
  private AtomicLong id = new AtomicLong(0);
  
  private static IdGenerator instance;
  private IdGenerator() {}
  //懒加载
  public static synchronized IdGenerator getInstance() {
    if (instance == null) {
      instance = new IdGenerator();
    }
    return instance;
  }
  public long getId() { 
    return id.incrementAndGet();
  }
}

双重检测


public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private static IdGenerator instance;
  private IdGenerator() {}
  public static IdGenerator getInstance() {
    if (instance == null) {							// 非并发竞争判断
      synchronized(IdGenerator.class) { // 此处为类级别的锁
        if (instance == null) {					// 并发竞争判断
          instance = new IdGenerator();
        }
      }
    }
    return instance;
  }
  public long getId() { 
    return id.incrementAndGet();
  }
}

注意点:

要解决这个问题,我们需要给 instance 成员变量加上 volatile 关键字,禁止指令重排序才行。实际上,只有很低版本的 Java 才会有这个问题。我们现在用的高版本的 Java 已经在 JDK 内部实现中解决了这个问题(解决的方法很简单,只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序)

静态内部类


public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private IdGenerator() {}

  //静态内部类
  private static class SingletonHolder{
    private static final IdGenerator instance = new IdGenerator();
  }
  
  public static IdGenerator getInstance() {
    return SingletonHolder.instance;
  }
 
  public long getId() { 
    return id.incrementAndGet();
  }
}

说明:

SingletonHolder 是一个静态内部类,当外部类 IdGenerator 被加载的时候,并不会创建 SingletonHolder 实例对象。只有当调用 getInstance() 方法时,SingletonHolder 才会被加载,这个时候才会创建 instance。instance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。

枚举


public enum IdGenerator {
  INSTANCE;
  private AtomicLong id = new AtomicLong(0);
 
  public long getId() { 
    return id.incrementAndGet();
  }
}
public class test {
    long aLong = IdGenerator.INSTANCE.getId();
}

说明:

通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。IdGenerator.INSTANCE只有一份。

总结

image-20220219135019350

posted @ 2022-02-23 15:37  非学无以致疑  阅读(34)  评论(0)    收藏  举报