单例模式
实际开发中,使用 Spring 的 Bean 工厂保证单例。
饿汉式
类加载到内存,就实例化一个单例,[JVM 保证线程安全](因为一个类 class 只在内存中加载一次)
优点:简单实用(推荐)
缺点:不管有没有用到,[类加载](就算是 Class.forName( ) 只将类加载到内存,静态变量也会一同初始化。)时就已经实例化(但是不用的话为什么要加载。。)
public class User {
private static final User user = new User();
// 私有化构造方法(别人不能 new)
private User() {
}
// 对外暴露一个获取 User 对象的静态方法
public static User getInstance() {
return user;
}
}
当然了,也可以用静态代码块的方式来初始化:
private static final User user;
static {
user = new User();
}
懒汉式
只有真正使用时,才进行实例化。
优点:达到了按需初始化的目的
缺点:带来了线程安全问题
public class User {
private static final User user;
private User() {
}
public static User getInstance() {
if (user == null) { // 当多个线程同时到达,因为判断时延,会产生多次初始化。
user = new User();
}
return user;
}
}
解决线程安全问题:
synchronized 方法锁:将实例化方法上锁,每次只给一个线程调用。
缺点:不管对象存不存在,都需要进行:判断、检查锁状态、获得锁、释放锁,浪费性能。
public static synchronized User getInstance() { if (user == null) { user = new User(); } return user; }
synchronized 对象锁:目的是为了可以在上锁之前进行判断([双判断](双重检查锁定(Double-Checked Locking))),限流。
public static User getInstance() { if (user == null) { synchronized (User.class) { if (user == null) { user = new User(); } } } return user; }
引入 volatile 关键字
new 一个对象有几个步骤:
- 看 class 对象是否加载,如果没有就先加载 class 对象
- 分配内存空间,
- 调用构造函数初始化实例
- 将实例对象指向刚分配的内存地址
而 CPU 为了优化程序,可能会进行指令重排序:颠倒步骤 3、4,导致实例内存还没分配,就被使用了。
加上 volatile 关键字,就保证new
不会被指令重排序。(被 volatile 关键字修饰的变量是被禁止重排序的)
为静态变量加上 volatile 关键字:
private static final volatile User user;
静态内部类式
加载外部类时,不会加载内部类。只有在调用 getInsance()
时,才会加载内部类 UserManager。
相比饿汉式,静态内部类给静态变量贴了一层保护,利用资深特性,实现了懒加载。(比较推荐)
public class User {
private User() {
}
private static class UserManager {
private static User user = new User();
}
public static User getInstance() {
return UserManager.user;
}
}
枚举单例
对于以上方案,都存在一个问题:就算构造方法私有化了,依然可以通过反射得到实例对象。(还要防 25 仔?)
所以,Java 创始人之一 Joshua Bloch 提出了 “最完美” 的方式:[单元素枚举](“单元素的枚举类型已经成为实现 Singleton 的最佳方法”)。
枚举天生单例,它在第一次被真正用到的时候,会被虚拟机加载并初始化,而这个初始化过程是线程安全的。
普通类通过反射实现反序列化,**枚举类的反序列化是通过
java.lang.Enum
#valueOf()
来根据名字查找枚举对象。**所以,枚举不会发生由于反序列化导致的单例破坏问题。
优点:不仅可以解决线程同步,还可以防止反序列化
缺点:豆芽杀手
public enum User {
INSTANCE; // User 的一个实例
public void eat() {
System.out.println("吃");
}
}
public static void main(String[] args) {
User.INSTANCE.eat();
}
还看见另一种写法:
public class User {
private User() {
}
// 枚举只有一个,所以其属性 user 也只有一个。
enum UserManager {
INSTANCE;
private User user;
private UserManager() {
this.user = new User();
}
public User getUser() {
return user;
}
}
public static User getInstance() {
return UserManager.INSTANCE.getUser();
}
}