懒汉式和饿汉式区别:单例模式中的两种加载方式详解
在写代码的时候,尤其是做Java开发,经常会遇到“单例模式”这个概念。而提到单例,绕不开的两个词就是“懒汉式”和“饿汉式”。听起来像是形容人的性格,其实它们指的是对象创建的时机不同。
饿汉式:一上来就准备好
饿汉式,顾名思义,像一个饿急了的人,不管用不用得到,先创建好实例再说。类一加载,就把对象new出来了。这种方式最直接,也最省事。
比如下面这段代码:
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}可以看到,instance是静态常量,在类加载时就完成了初始化。只要有人调用getInstance(),直接返回已经创建好的对象。没有判断,没有延迟,简单粗暴。
懒汉式:用的时候再创建
懒汉式则相反,它比较“懒”,不到真正需要的时候绝不创建对象。这样做的好处是节省内存,尤其当这个实例很占资源,但程序运行期间还不一定用到时,延迟加载就显得很聪明。
最基本的懒汉式写法长这样:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}这里有个关键判断:if (instance == null),只有第一次调用时才会new对象,之后都直接返回。但问题来了——多线程环境下,两个线程同时发现instance为空,就会创建两个实例,单例就失效了。
线程安全怎么解决?
为了解决懒汉式的线程安全问题,常见的做法是给方法加锁:
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}加上synchronized后确实安全了,但每次调用都要加锁,性能受影响。于是有了“双重检查锁定”优化:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}用了volatile防止指令重排,又通过两次判断减少锁的竞争,既保证了安全,又提升了效率。
选哪种更合适?
如果你的对象创建成本不高,而且几乎肯定会用到,那用饿汉式完全没问题,写起来简单,读起来清楚,天然线程安全。
但如果对象初始化耗时长、占资源多,又不确定是否一定调用,那就用懒汉式,配合双重检查,做到按需加载。
就像吃饭,饿汉式是饭点一到就开饭,懒汉式是饿了才下厨房。看你的场景需要哪种节奏。