并发编程 - 单例模式讲解
2023-09-11 14:16:28 时间
单例模式的两种创建形式
积极加载,在类加载的时候直接创建初始化对象,代码如下:
/**
* 如果是饿汗模式 直接初始化 每个对象获取都是一个对象
* <p>
* 懒加载
*/
public static SingleInstance instance = new SingleInstance();
//第一种
public static SingleInstance getInstance1() {
if (instance == null) {
instance = new SingleInstance();
}
return instance;
}
对于大量没有使用的对象,因为频繁创建对象会消耗额外的内容,所以一般会根据具体场景使用另外一种加载模式,当然上面这种模式的优点在于书写简单,且线程安全
懒汉式加载的几种实现
第一种,简单明了
public static SingleInstance instance2;
/**
* 多线程模式下 如果线程1走到了3 线程2走到了1 或者2 都会生成新的instance
* 这个 需要加锁解决 避免多次创建对象 消耗资源 且造成线程之间共享数据不安全
*
* @return
*/
public static SingleInstance getInstance2() {
if (instance == null) { //1
instance = new SingleInstance(); //2
}
return instance; //3
}
针对第一种现象,为了保证线程之间的安全性,我们采用加锁的方式
public static synchronized SingleInstance getInstance3() {
if (instance == null) {
instance = new SingleInstance();
}
return instance;
}
加锁 虽然给方法加锁可以避免前面造成的问题。但是如果频繁地访问这个对象, 那么这种频繁加锁和释放锁方式就会产生严重的效率问题。为了保证并发效率,采用双重检测
public static synchronized SingleInstance getInstance4() {
if (instance == null) { //1
synchronized (SingleInstance.class){//2
//加类锁
if(instance ==null){ //3
instance = new SingleInstance(); //4
}
}
}
return instance; //5
}
双重检测
虽然加了锁和双重判断,但其实这个类是线程不安全的。 现象是线程A进入同步块创建实例的时候,线程B会返回一个没有初始化的Instance对象。 原因是JIT编译器会优化,会在编译时会发生“重排序”的状况。 线程A 走到了 5 线程B 走到了2 这个时候因为线程A已经释放了类锁 所以线程B就会进入到 3 再次判断 instance是否为空, 这就是JVM 模型的问题 , 因为每一个线程的工作内存都拷贝一份
指令重排序
编译器遇到singleton = new xxxxx();会分为如下三个步骤:
memory = allocate() // 1. 分配对象的内存空间
ctorInstance(memory) // 2. 初始化对象
instance = memory; // 3. 设置instance指向刚分配的内存地址
Java语言规范没有规定编译器的优化不会改变单线程的执行结果,但是并没有对多线程做出这样的保证。多线程情况下有时候编译器会对指令进行重排序优化,它可能把2和3颠倒过来,如下:
memory = allocate() // 1. 分配对象的内存空间
instance = memory; // 3. 设置instance指向刚分配的内存地址
ctorInstance(memory) // 2. 初始化对象
结合指令重排序分析上面的双重检测机制,就会发现 当线程A走到5的时候,先执行了instance = memory; 然后线程B执行到1时,发现singleton不为null,那么就获得了singleton对象。然而这个singleton对象其实还没有经过ctorInstance(memory),这是不正确的。
所以为了保证双重检测的安全性,禁止指令重排序,在实际开发中会采用 volatile关键字,volatile的具体使用,以及volatile是如何利于JVM内容屏障机制以及JMM来保证可见性以及禁止指令重排
public static volatile SingleInstance instance = new SingleInstance();
public static synchronized SingleInstance getInstance4() {
if (instance == null) { //1
synchronized (SingleInstance.class){//2
//加类锁
if(instance ==null){ //3
instance = new SingleInstance(); //4
}
}
}
return instance; //5
}
相关文章
- Java 并发工具包 java.util.concurrent 用户指南
- 《Netty Redis Zookeeper 高并发实战》 勘误
- 【转】Java并发编程:synchronized
- bilibili高并发实时弹幕系统的实战之路
- 聊聊并发(十)生产者消费者模式
- Java SE 8 在并发工具方面的加强
- Java并发编程之CAS
- [书籍]用UWP复习《C#并发编程经典实例》
- 并发编程--计数器不同实现方案性能对比【synchronized、LongAdder、LongAccumulator、AtomicLong】
- P9 力荐!阿里巴巴最新出品 776 页 JDK 源码 + 并发核心原理解析小册
- Java并发——Executor框架详解(转)
- java并发5-volatile关键字解析
- 《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.2 桥接模式
- 《Storm分布式实时计算模式》——1.4 Storm的并发机制
- JAVA NIO non-blocking模式实现高并发服务器(转)
- 半同步/半异步并发模式进程池实现
- Active Object 并发模式在 Java 中的应用--转载
- JVM+微服务+多线程+锁+高并发性能
- 高并发笔记(慕课网的老师说的很好)