单例模式的8种写法
为什么需要单例模式
- 节省内存和计算
- 保证结果正确
- 方便管理
单例模式适用场景
- 无状态的工具类:如日志工具类
- 全局信息类:如网站访问次数记录类
饿汉式(静态常量)(可用)
/**
* 〈饿汉式(静态常量)〉
* 可用
* @author Chkl
* @create 2020/3/4
* @since 1.0.0
*/
public class Singleton1 {
private final static Singleton1 INSTANCE =
new Singleton1();
private Singleton1(){
}
public static Singleton1 getInstance(){
return INSTANCE;
}
}
饿汉式(静态代码块)(可用)
/**
* 〈饿汉式(静态代码块)〉
* 可用
*
* @author Chkl
* @create 2020/3/4
* @since 1.0.0
*/
public class Singleton2 {
private final static Singleton2 INSTANCE;
static {
INSTANCE = new Singleton2();
}
private Singleton2() {
}
public static Singleton2 getInstance() {
return INSTANCE;
}
}
懒汉式(线程不安全)(不可用)
/**
* 〈懒汉式(线程不安全)〉
* 不可用
*
* @author Chkl
* @create 2020/3/4
* @since 1.0.0
*/
public class Singleton3 {
private static Singleton3 instance;
private Singleton3() {
}
public static Singleton3 getInstance() {
//多线程下可能多次创建实例,就不是单例了
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}
懒汉式(线程安全)(不推荐)
/**
* 〈懒汉式(线程安全)〉
* 效率低不推荐使用
*
* @author Chkl
* @create 2020/3/4
* @since 1.0.0
*/
public class Singleton4 {
private static Singleton4 instance;
private Singleton4() {
}
//加上synchronized 安全,但是效率低
public synchronized static Singleton4 getInstance() {
if (instance == null) {
instance = new Singleton4();
}
return instance;
}
}
懒汉式(加锁,线程不安全)(不可用)
/**
* 〈懒汉式(线程不安全)〉
* 依然不可用
*
* @author Chkl
* @create 2020/3/4
* @since 1.0.0
*/
public class Singleton5 {
private static Singleton5 instance;
private Singleton5() {
}
public static Singleton5 getInstance() {
if (instance == null) {
synchronized (Singleton5.class) {
instance = new Singleton5();
}
}
return instance;
}
}
双重检查(推荐面试使用)(可用)
/**
* 懒汉式
* 〈双重检查(推荐面试使用)〉
* 可用
*
* @author Chkl
* @create 2020/3/4
* @since 1.0.0
*/
public class Singleton6 {
private volatile static Singleton6 instance;
private Singleton6() {
}
public static Singleton6 getInstance() {
if (instance == null) {
/*
线程a,b都到了这个位置
a先进入获得锁
初始化instance
a释放锁b获得锁进入
发现instance已经初始化了
跳过初始化代码
*/
synchronized (Singleton6.class) {
if (instance == null) {
instance = new Singleton6();
}
}
}
return instance;
}
}
双重检查的优点:
- 线程安全
- 延迟加载,效率较高
为什么要使用双重检查,单次检查不行吗:
- 双重检查能保证线程安全
- 如果只是单次检查,第一个检查后可能多个线程都进入等待锁,锁释放之后会重复初始化
为什么要用volatile?
- 新建对象实际上有3个步骤,并不是原子性的
- 创建一个空对象
- 调用构造方法
- 创建好的实例赋值给引用
- 重排序会带来NPE
- 步骤二和步骤三发生重排序,执行到步骤三时实例就不为空了,如果有新线程进来发现不为空就拿去用了,此时的实例是未调用构造方法的不完整的
- 防止重排序
- 保证可见性(初始化完了之后其他线程能马上看到)
静态内部类(推荐用)(可用)
/**
* 〈懒汉式(静态内部类)〉
* 推荐使用
*
* @author Chkl
* @create 2020/3/4
* @since 1.0.0
*/
public class Singleton7 {
private Singleton7() {
}
private static class SingletonInstance {
private static final Singleton7 INSTANCE
= new Singleton7();
}
public static Singleton7 getInstance() {
return SingletonInstance.INSTANCE;
}
}
枚举(推荐用)(可用)(生产中最佳写法)
/**
* 〈枚举〉
* 推荐使用
*
* 调用:Singleton8.INSTANCE.whatever();
*
* @author Chkl
* @create 2020/3/4
* @since 1.0.0
*/
public enum Singleton8 {
INSTANCE;
//其中的方法
public void whatever(){
}
}
哪种单例的实现方案最好啊?
- 枚举最好!
- 《Effective Java》中明确表示枚举是最佳的
- 写法简单
- 线程安全
- 符合懒加载机制
- 避免反序列化破坏单例
饿汉式的缺点
- 资源效率不高
懒汉式的缺点
- 写法复杂
- 容易写成线程不安全