单例模式是设计模式中最简单的一种。单例模式属于创建型模式,该类负责创建自己的对象,同时确保只有单个对象被创建。
使用场景
当需要保证一个类只有一个实例,并且全局获取到的实例相同。
优点:
实现方法
public static class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
调用方法
Signalton signalton = Signalton.getInstance();
静态常量方法通过声明时直接创建单例对象。
线程安全
优点:不需要加锁就可以达到线程安全
缺点:如果不需要调用getInstance
方法会无意义占用内存
public static class HungrySingleton1 {
private static final HungrySingleton1 instance = new HungrySingleton1();
private HungrySingleton1() {
}
public static HungrySingleton1 getInstance() {
return instance;
}
静态块初始化方法和静态常量方法一样,在调用getInstance
前就完成创建单例对象。
线程安全
优点:不需要加锁就可以达到线程安全
缺点:如果不需要调用getInstance
方法会无意义占用内存
public static class HungrySingleton2 {
private static final HungrySingleton2 instance;
static {
instance = new HungrySingleton2();
}
private HungrySingleton2() {
}
public static HungrySingleton2 getInstance() {
return instance;
}
}
同步锁方法在调用getInstance
方法时才创建对象,使用synchronized
关键字来达到线程安全。
线程安全
优点:不会浪费内存
缺点:效率低
public static class LazySingleton2 {
private static LazySingleton2 instance;
private LazySingleton2() {
}
public static synchronized LazySingleton2 getInstance() {
if (instance == null) {
instance = new LazySingleton2();
}
return instance;
}
}
双重检查方法是同步锁方法的改进,利用volatile关键字和使用synchronized
关键字,做两次判断来达到效率和线程安全的兼顾。
线程安全
优点:不会浪费内存
public static class LazySingleton4 {
//利用volatile关键字
private static volatile LazySingleton4 instance;
private LazySingleton4() {
}
public static LazySingleton4 getInstance() {
if (instance == null) {
synchronized (LazySingleton4.class) {
if (instance == null) {
instance = new LazySingleton4();
}
}
}
return instance;
}
}
静态内部类方法通过利用类加载机制来达到单例效果。
线程安全
优点:不会浪费内存
public static class LazySingleton5 {
private LazySingleton5() {
}
private static class SingleTon {
private static final LazySingleton5 INSTANCE = new LazySingleton5();
}
public static LazySingleton5 getInstance() {
return LazySingleton5.SingleTon.INSTANCE;
}
}
jdk1.5
后支持的方法。
线程安全
优点:能防止反射操作
缺点:不能继承类,但是可以实现其它接口
enum LazySignalTon6 {
INSTANCE;
public void init() {
}
}
写了一段测试代码,在构造函数中sleep
200ms,然后分别统计第一次和执行一百万次getInstance
的耗时。
序号 | 方案 | 第一次耗时(ms) | 一百万次调用耗时(ms) | 一百亿次(ms) |
---|---|---|---|---|
1 | 静态常量 | 242 | 8.2 | 1344 |
2 | 静态初始化块 | 205 | 2 | 1238 |
3 | 同步锁 | 201 | 27.1 | 31178 |
4 | 双重检查 | 217 | 5.7 | 1347 |
5 | 静态内部类 | 216 | 5.3 | 1190 |
6 | 枚举 | 1009 | 7.1 | 1231 |
从结果上看,第一次初始化最快的是静态初始化块方案,最慢的是枚举方案。
一百万次调用上,最慢的是同步锁方案,最快的是静态初始化块方案。除了同步锁,其它方案在调用效率上其实差不多。