1. 定义
单例模式是Java中最简单的设计模式之一,属于创建型的设计模式。
2. 作用
单例模式可以保证一个类只有一个实例,为系统提供一个全局访问点。
3. 实现思路
- 单例类的构造方法应定义为私有方法,防止在类外部通过构造方法实例化该类的对象;
- 单例类的内部提供一个静态的方法供外部调用来获取单例对象。
4. 实现方法
单例模式的实现方法按创建单例的时机可分为两大类,每大类又可以细分为多种不同的实现。
- 立即创建,即初始化单例类时就创建单例对象;
- 延迟创建,等需要的时候再创建单例对象。
下面将对每种不同的实现方式做详细讲解。
4.1 立即创建
a. 饿汉式
最基础的单例实现方式,单例是static
的,类加载时创建单例且只会创建一次,所以是线程安全的。
public class HungryManSingleton {
private static HungryManSingleton instance = new HungryManSingleton();
// 私有构造方法,防止外部实例化对象
private HungryManSingleton() {}
// 静态方法供外部代码调用获取单例对象
public static HungryManSingleton getInstance() {
return instance;
}
public static void main(String[] args) {
// 获取单例的方式
HungryManSingleton.getInstance();
}
}
b. 枚举实现
据说是最好的实现方式,线程安全、简洁。
public enum EnumSingleton {
INSTANCE; // 1. 枚举类型为 public statis final T 可保证类初始化时创建单例且只创建一个
// 2. 枚举有隐藏的private的构造方法,可保证外部不能初始化
// EnumSingleton() {}
// 3. 默认枚举实例的创建是线程安全的,保证了单例的线程安全性
public void test() {
System.out.println("this is a test.");
}
public static void main(String[] args) {
// 获取单例方式
EnumSingleton ins = EnumSingleton.INSTANCE;
ins.test();
}
}
《Effective Java》中称:
虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。
4.2 延迟创建
c. 懒汉式 - 基础实现
类加载时不创建单例,等需要时才手动创建。
public class LazyManSingleton {
// 1.类加载时,不创建单例,单例置为null
private static LazyManSingleton instance = null;
// 2.私有构造函数禁止外部创建实例
private LazyManSingleton() {}
// 3.需要时才手动调用getInstance()创建单例
public static LazyManSingleton getInstance() {
if (instance == null) {
instance = new LazyManSingleton();
}
return instance;
}
public static void main(String[] args) {
// 获取单例方式
LazyManSingleton.getInstance();
}
}
这种实现方式在多线程情况下是线程不安全的,原因是第一个线程执行到创建对象new LazyManSingleton()这里时,因为创建对象需要一定时间,在第二个线程过来的时候对象可能还没创建完成,所以第二个线程判断对象为空也会去创建对象,最终导致两个线程分别获得了一个对象,单例模式失败。
d. 懒汉式改进 - 同步锁实现
给创建单例的方法getInstance()
加同步锁synchronized
,保证该方法同一时间只能被一个线程调用,可以避免单例重复创建。
public class LazyManSynSingleton {
// 1.类加载时,不创建单例,单例置为null
private static LazyManSynSingleton instance = null;
// 2.私有构造函数禁止外部创建实例
private LazyManSynSingleton() {}
// 3.需要时才手动调用getInstance()创建单例,加同步锁保证只有一个线程能调用改方法
public static synchronized LazyManSynSingleton getInstance() {
if (instance == null) {
instance = new LazyManSynSingleton();
}
return instance;
}
public static void main(String[] args) {
// 获取单例方式
LazyManSynSingleton.getInstance();
}
}
同步锁的实现方法会造成一定的性能开销,实际上只在第一次调用的时候需要同步,没有必要每次调用getInstance()都进行线程同步,这样会造成额外的同步开销。
e. 懒汉式改进 - 双重校验锁
在加同步锁之前,先判断一下单例有没有创建,若单例已创建,则不需要加锁操作直接获取单例即可,可以提升性能。
public class LazyManDoubleCheckSingleton {
// 1.类加载时,不创建单例,单例置为null
private static LazyManDoubleCheckSingleton instance = null;
// 2.私有构造函数禁止外部创建实例
private LazyManDoubleCheckSingleton() {}
// 3. 需要时才手动调用getInstance()创建单例,加入双重校验锁
public static LazyManDoubleCheckSingleton getInstance() {
// 校验锁1:第1个if判断单例是否已创建,若未创建才进入锁,若已创建直接返回单例
if (instance == null) {
synchronized (LazyManDoubleCheckSingleton.class) {
// 校验锁2:第2个if判断单例是否已创建,若未创建就创建一个
if (instance == null) {
instance = new LazyManDoubleCheckSingleton();
}
}
}
return instance;
}
public static void main(String[] args) {
// 获取单例方式
LazyManDoubleCheckSingleton.getInstance();
}
}
f. 静态内部类
在静态内部类里创建单例,装载静态内部类时才创建单例,静态内部类只会装载一次,所以只会创建一个单例。这种方式实现简洁、线程安全。
public class StaticInnerClassSingleton {
// 静态内部类
private static class InnerClass {
private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
}
private StaticInnerClassSingleton() {}
public static StaticInnerClassSingleton getInstance() {
// 获取单例的方式
return InnerClass.instance;
}
}