单例模式
单例模式(Singleton Pattern),保证一个类只有一个实例,并提供一个全局访问点以供外部代码使用。
结构图
- 定义一个私有成员变量instance;
- 构造函数为private私有的;
- 声明了一个名为getInstance的public公有静态方法,返回其唯一实例,供客户端Client使用。
单例核心代码(Java实现)
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
客户端代码
class Client {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
if (singleton1 == singleton2) {
System.out.println("两个对象是相同的实例");
}
}
}
// 输出:两个对象是相同的实例
多线程时的单例
多线程时,同时并发访问Singleton类,调用getInstance()方法,可能会创建多个实例。
通过给进程加锁,例如使用synchronized关键字,其作用是当一个线程没有退出之前,先锁住这段代码不被其他线程调用执行,以保证同一时间只有一个线程在执行此段代码。
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
此种方式是解决了多线程的问题,这样使得每次调用都需要锁,影响性能。
双重锁定
通过使用volatile关键字,作用是当synchronized变量被初始化成Singleton时,多线程能够正确处理synchronized变量。
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
// 检查实例是否存在,不存在,进行下一步
if (instance == null) {
// 防止多个线程同时进入创建实例
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
为什么在synchronized里面还要做一次instance实例的判断?
- instance存在,直接返回
- 当instance为null,两个线程同时调getInstance()方法时,他们都会通过第一重instance==null的判断。加锁之后,由于“锁”机制,只会有一个线程进入,另一个在外面等待。
- 此时,如果没有第二重instance==null判断,第一个创建了实例,此时第一个线程结束,第二个线程从等待位置开始执行,直接执行创建新的实例逻辑。
- 加了第二重判断后,第一个创建了实例,此时第一个线程结束,第二个线程从等待位置开始执行,重新判断instance==null,第一个线程已经创建实例,这时第二个线程才不会创建实例。
饿汉式和懒汉式
饿汉式写法
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
特点:
- 静态初始化方式在类被加载时,就将自己实例化。
- 提前占用系统资源。
懒汉式
特点:
- 被引用时,才将自己实例化
- 懒汉式面临多线程访问的安全性,需要双重锁定处理保证安全。