单例模式
0x00、 Singleton Pattern
需求 : 保证一个类只有一个实例,并且提供这个实例的全局访问点。
0x01、 饿汉方式
优点 : 线程安全,执行效率高,这一点存疑
缺点 : 在类加载的时候实例对象就被创建,不论这个实例在后续的程序中是否会被使用。
0x02、 懒汉方式
优点 : 在真正时候到该类实例的时候才被实例化避免了不必要的资源占用。
缺点 : 未考虑线程安全。如果两个线程同时调用到
getInstance()
方法造成的结果是不满足单例模式的设计需求。
0x03、 懒汉 · 尝试解决线程安全问题
3.1、 方式一 · 失败的尝试
优点 : 使用时实例化
缺点 : 未能彻底解决线程安全问题。假设两个线程同时访问
getInstance()
方法并且同时执行到标记①
,这时两个线程会依次初始化一个Singleton
实例,从而导致不满足单例模式设计需求的情况出现。
3.2、 方式二
优点 : 使用时实例化,并且避免了线程安全问题。
缺点 : 锁加在方法上导致多线程的逐个访问方法的结果访问降低了多线程执行效率(如果并发访问效率更高)。
0x04、 双重校验锁
优点 : 在兼顾了执行效率的同时有效避免了线程安全问题。
0x05 提问
5.1. 加锁(synchronized)会导致执行效率降低?
暂不深究
5.2、 synchronized
什么时候应该使用锁 : 如果你正在写一个变量,它可能接下来被另一个线程读取,或者正在读取一个上一次已经被线程写过的变量,那么你必须使用同步,并且读写线程都必须使用相同的监视锁同步。
如果某一个任务处于一个被 synchronized
关键字修饰的代码(方法或者代码块)中,如果线程①正在执行这个任务(我们称这部分代码被锁住了),此时要执行这个任务的其他线程将会处于阻塞状态,直到这个锁被释放。
关于 synchronized method(){}
所有的对象都自动含有单一锁(称为对象锁),当在某个确定的对象上调用任意一个
synchronized
方法的时候该对象的其他synchronized
方法也都会被锁住知道这个方法执行完成并返回。一个对象中的
synchronized
方法能够调用这个对象的其它的synchronized
方法,这时候 JVM 负责追踪并对象被枷锁的次数进行增减,直到对象被枷锁次数为 0 的时候表示对象锁被释放。
关于 synchronized(Object){}
如果两个线程拿着同样的对象
obj
访问test()
方法,这两个线程必将有一个会阻塞直到另一个线程执行完成业务代码
再继续执行业务代码
。如果两个线程拿着不同的对象
obj
访问test()
方法,则不会发生阻塞。
5.3、 关于原子性与可视性 volatile
关于原子性
有关 Java 线程的讨论中,一个不正确的知识是 “原子操作不需要进行同步控制”。
一个危险的认知是 “原子操作是不能被线程调度中断的机制,一旦操作开始,它一定可以在可能发生的‘上下文切换’前执行完毕。”
在排除可视性的前提下依赖原子性是没有问题的,但事实上可视性的问题不容忽略。所以依赖于原子性是很棘手且危险的。
事实上原子性可以用于除
long
、double
之外的所有基本数据类型之上的‘简单操作’。 对除long
、double
之外的基本数据类型的读写操作可以被保证是不可分的(原子)操作来操作内存。JVM 可以将 64位 数据类型(
long
、double
)的操作分解为两次对 32位 数据的操作,这就产生了在一个读取或写入操作的间隙发生上下文切换从而导致不同线程看到统一变量不同结果的现象。但是你定义的
long
、double
被volatile
修饰的话你就能得到(简单的赋值与返回操作的)原子性(在 Java SE5 之前volatile
一直不能正确的工作)。使用原子操作来替代同步对于非专家程序员来说是危险的。
关于可视性
对于多处理器系统(多核即将多个CPU集成在单个芯片上的技术),可视性问题比原子性问题严重的多(相对于单处理器操作系统)。因为一个任务做出的修改即便在在不中断的意义上将是原子性的,也可能出现可视性的问题(因为操作的结果肯能只是暂时性的存放在 CPU 的缓存中没有存储到内存),因此不同任务对应用的视图是不同的。
如果将一个域用
volatile
标识,只要对该域产生了写操作,所有的读操作都能看到这个修改。即便使用了本地缓存(CPU缓存)情况也是如此,volatile
的操作结果会立即写入主存中,读取操作就是读取的主存中的数据。
5.4、 小结
同步机制保证了两个因素: 1. 操作线程的唯一性。—— synchronized
负责 2. 对数据操作结果在应用中的原子性和可视性。—— volatile
负责
参考
抄录 : Java 编程思想
设计模式 - 可复用面向对象软件的基础
Last updated