前言
本文会把我所掌握的所有单例模式都写下
说实话 写法一大堆 网上一查一大把 我总结一下 同时写一下自己的理解和各种单例模式的关系,也算是一个笔记
分类
单例模式写法千千万 总共分为四大类:
- 懒汉式
- 饿汉式
- 注册式
- ThreadLocal
恶汉式
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton (){
throws new RunTime
}
public static HungrySingleton getInstance(){
return instance;
}
}
特点:
·线程安全,但容易造成资源浪费,如果不instance不被使用,这个对象就占用无意义的内存
简单懒汉式
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
特点
·不浪费内存 但是线程不安全 举例说明 如果T1 线程判断实例为空 开始创建对象并赋值
在创建对象过程中 T2进入 也发现实例为空 所以又会创建对象 这样打破了单例规则
懒汉式 同步锁方式
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public synchronized static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
特点
·不浪费内存 线程安全 但当实例已经创建完成的时候依然会上锁 非常影响性能
懒汉式 双重检查锁方式
public class DoubleCheck {
private static DoubleCheck instance;
private DoubleCheck() {
}
public static DoubleCheck getInstance() {
if (instance == null) {
synchronized (DoubleCheck.class) {
if (instance == null) {
instance = new DoubleCheck(); //标记
}
}
}
return instance;
}
}
特点
懒汉式上锁的变种
·绝大部分解决了效率问题 可惜也有线程安全性问题
下面详解
在《并发编程的艺术》一书中提到过 在代码标记处 可能出现内存指令重排
标记处一共经历了三件事
- 分配内存地址 memory
- 创建对象 初始化对象
- 赋值 memory = 对象
这个过程是完全符合指令重排的要求的 如果发生了指令重排 会变成
线程安全的懒汉式
public class Safe {
private volatile static Safe instance;
private Safe() {
}
public static Safe getInstance() {
if (instance == null) {
synchronized (Safe.class) {
if (instance == null) {
instance = new Safe();
}
}
}
return instance;
}
}
增加volatile关键字禁止指令重排
静态内部类
public class StaticInner {
private StaticInner(){
}
public static StaticInner getInstance(){
return A.instance;
}
private static class A{
private static StaticInner instance = new StaticInner();
}
}
看似像饿汉 其实是懒汉
利用的是JVM机制 JVM对于内部类是延迟加载的 当内部类被使用到的时候才会加载
自动帮我们做了懒汉式的处理
但是能被反射破坏
反射破坏单例
我们可以通过反射获取私有的构造方法 手动调用
我们可以在构造方法中抛出异常
private AAA(){
if( null != instance){
throw new RuntimeException();
}
}
枚举单例
public enum EnumSingleton {
// 单例
INSTANCE;
private Object object;
public EnumSingleton getInstance(){
return INSTANCE;
}
public Object getObject() {
return object;
}
public EnumSingleton setObject(Object object) {
this.object = object;
return this;
}
}
直接调用getInstance方法 得到的实例一定是单例的 然后通过setObject 进行属性赋值
枚举在JDK层面就已经定义了不能使用构造方法创建
注册式单例
最后一种也是最简单的一种单例模式
其实就是缓存容器
spring的ApplicationContext就是一个大容器 我们通过getBean获取实例 其一定是单例的
spring的bean默认也是单例的