使用静态字段保存实例,在类加载时进行实例化
并发场景:在 JVM 层面保证了多线程访问的安全性
public class HungryMan {
private static final HungryMan man = new HungryMan();
private HungryMan() {
}
public static HungryMan instance() {
return man;
}
}
使用静态字段保存实例,在调用 instance 方法时进行实例化
并发场景:多线程访问不安全,可能会重复 new 新实例
public class LazyMan {
private static LazyMan man;
private LazyMan() {
}
public static LazyMan instance() {
if (man == null) {
man = new LazyMan();
}
return man;
}
}
使用静态字段保存实例,在调用 instance 方法时进行实例化
并发场景:
为什么要使用 volatile?
new 操作:分配内存-初始化对象-引用赋值 这三步可能会重排,可能先将地址赋值给引用,再初始化对象,导致其他线程第一次检查时虽然不为 null 了,但其实没有对象,引发空指针问题
public class SafeLazyMan {
private static volatile SafeLazyMan man;
private SafeLazyMan() {
}
public static SafeLazyMan instance() {
if (man == null) { // 第一次检查
synchronized (SafeLazyMan.class) {
if (man == null) { // 第二次检查
man = new SafeLazyMan();
}
}
}
return man;
}
}
使用一个静态内部类的静态属性保存实例,可以在调用 instane 方法时进行实例化,节约系统资源
并发场景:在 JVM 层面保证了多线程访问的安全性
public class SafeLazyManByInner {
private SafeLazyManByInner() {
}
private static final class ManHolder {
static final SafeLazyManByInner man = new SafeLazyManByInner();
}
public static SafeLazyManByInner instance() {
return ManHolder.man;
}
}
使用枚举实现单例,是《Effective Java》中推荐的写法
并发场景:线程安全
public enum SingletonByEnum {
INSTANCE,
;
public static SingletonByEnum getInstance() {
return INSTANCE;
}
}
单例模式的类在内存中只有一个实例,减少内存开销;便于控制对类的访问
常见的单例类:
// ServletContext
// ApplicationContext
// 数据库连接池对象