单例模式
- 单例模式:程序运行中,类(Person)只会生成一个(Person)对象(实例),大家共用的是同一个实例。
- 场景:配置项 —— 对象
- 实现:1.饿汉模式 2.懒汉模式
1.1饿汉模式
// 饿汉模式的单例
public class SingletonHungry {
// 不允许外边调用构造方法
private SingletonHungry() {}
// 在类加载的时候就创建对象 只执行一次 没有线程安全问题
// 原因:类加载时,在JVM内部保证了线程间的同步互斥
// (1)只执行一次 (2)线程安全
private static final SingletonHungry instance = new SingletonHungry();
public static SingletonHungry getInstance() {
return instance;
}
}
1.2懒汉模式
1.2.1懒汉模式-单线程版
/**
* 懒汉模式的单例:单线程环境下正确
* 多线程环境下有线程安全问题
* (可见性和原子性的问题)
*/
public class SingletonLazyVersion1 {
private SingletonLazyVersion1() {}
// 多线程下instance可见性不能保证
private static SingletonLazyVersion1 instance = null;
// getInstance 被第一次调用时,意味着有人需要 instance
// 再进行初始化
public static SingletonLazyVersion1 getInstance() {
//多线程情况下原子性不能保证
// 原子开始
if (instance == null) {
instance = new SingletonLazyVersion1(); // new对象有原子性问题
}
// 原子结束
return instance;
}
}
多线程环境下有线程安全问题:
1.可见性问题: private static SingletonLazyVersion1 instance = null;
2.原子性问题: if (instance == null) { instance = new SingletonLazyVersion1(); }
1.2.2懒汉模式-多线程版-性能低
// 线程安全版本的懒汉单例
public class SingletonLazyVersion2 {
private SingletonLazyVersion2() {}
//不用给instance专门加锁 已经保证了instance的可见性了
//因为getInstance方法释放锁时内存都是可见的(清理工作内存的缓存 读到最新的i)
private static SingletonLazyVersion2 instance = null;
//整体用synchronized加锁 保证原子性
//getInstance()方法是static 对其加锁相当于全局锁(大家用的都是同一把锁)
public synchronized static SingletonLazyVersion2 getInstance2() {
if (instance == null) {
instance = new SingletonLazyVersion2();
}
return instance;
}
}
缺点:虽保证线程安全,但锁的粒度过大(开始锁住 结束才释放 每次都在竞争锁)。
低性能详细分析:
1.2.3懒汉模式-多线程版-二次判断-性能高
public class SingletonLazyVersion3 {
private SingletonLazyVersion3() {}
//注意:instance必必须加volatile 才能防止synchronized里面代码的重排序带来的问题
private volatile static SingletonLazyVersion3 instance = null;
private static SingletonLazyVersion3 getInstance3() {
if (instance == null) {
//只有在初始化时才需要抢锁 保证只有一个在线程初始化
synchronized (SingletonLazyVersion3.class) {
if (instance == null) { //二次判断法 确保instance没有被初始化
instance = new SingletonLazyVersion3();
}
}
/* 直接写 错误 必须二次判断
//A B C 都为null return
//假设A抢到锁 A去初始化
//A初始完后 B、C接着枪锁 B抢到锁B初始化
//二次初始化发生错误
//注意:开始抢锁到抢到锁有时间间隔 有可能期间其他对像已经初始化过了
synchronized (SingletonLazyVersion3.class) {
instance = new SingletonLazyVersion3();
}
*/
}
return instance; //不为空 return
}
}
/*
未加volatitle
//假设A、B两个线程
//A进去发现instance = null 加锁
//二次判断instance = null
//假设执行初始化时发生重排序:new——赋值——初始化 A被new——赋值,然后A被切出去
//B进来发现 instance 已经不为null 直接退出去
//但此时 instance 不可用 发生错错误
//注意:synchronized只能保证外面的程序不会重排序到里面 不能保证里面的代码的顺序
*/
未加volatile存在的问题详细分析:
加volatile后线程安全详细分析: