// 懒汉式
public class Singleton {// 延迟加载保证多线程安全Private volatile static Singleton singleton;private Singleton(){}public static Singleton getInstance(){if(singleton == null){synchronized(Singleton.class){if(singleton == null){singleton = new Singleton();}}}return singleton;}
}
-
使用
volatile
:volatile
修饰符确保了变量的可见性和防止指令重排序。在懒汉式单例模式中,当多个线程访问getInstance()
方法时,如果不使用volatile
,由于指令重排序的问题,可能会导致一个线程获取到一个半初始化状态的对象。通过使用volatile
关键字,可以确保变量在多线程环境下的可见性,也就是当一个线程修改了instance
变量的值,其他线程能够立即看到最新的值。 -
第一层
if (singleton == null)
:这是一种最基本的检查,用于防止多个线程同时创建实例。如果singleton
已经被创建,后续的线程不需要再进入同步块,从而提高性能。 -
synchronized
:在getInstance()
方法中使用synchronized
关键字是为了确保在多线程环境下只有一个线程可以进入临界区(也就是同步块),从而避免多个线程同时创建实例。这种方式会带来一定的性能开销,因为每次调用getInstance()
都需要获取和释放锁。 -
第二层
if (singleton == null)
:这是一种双重检查锁定,用于确保只有一个线程能够在同步块内创建实例。在第一次检查中,如果singleton
不为 null,那么后续线程不会进入同步块,从而避免了不必要的锁竞争。只有当singleton
为 null 时,才会进一步检查并创建实例。
总的来说,这种方式结合了懒加载和线程安全的特性,确保只有在需要时才创建实例,并且在多线程环境下能够正确地工作。但需要注意的是,虽然这是一种有效的懒汉式单例模式实现,但在某些情况下,可能会有更好的替代方案,如使用静态内部类来实现单例。这种方式不仅线程安全,而且具有良好的性能。
为什么进入同步块之后还要判断?
进入同步块之后再次判断的目的是为了避免多个线程在同一时刻创建多个实例。虽然同步块可以确保只有一个线程可以进入临界区(即同步块),但在多线程环境中,可能会出现以下情况:
- 线程 A 进入了同步块,此时
instance
仍然为null
,线程 A 开始创建实例。 - 此时线程 B 也通过了第一次检查,因为线程 A 还没有完成实例的创建,所以
instance
仍然为null
。 - 线程 B 也进入了同步块,如果没有第二次检查,它将继续创建一个新的实例,这就导致了多个实例的创建。
通过在同步块内再次检查 instance
是否为 null
,可以确保只有一个线程在同一时刻创建实例。如果线程 A 创建了实例,线程 B 进入同步块后会发现 instance
已经不为 null
,因此不会再次创建实例,从而避免了多个实例的产生。
这就是双重检查锁定的思想,它既能够实现懒加载(即只有在需要时才创建实例),又能够确保线程安全。这种方式在性能上相对较好,因为只有在第一次创建实例时才需要进入同步块,后续的线程都可以避免同步块的开销。