简单场景:
多线程环境,每个线程携带惟一的key去组装数据,相同的key会有相同的数据结果。为了提高响应速度,在线程访问的入口处设置缓存。线程根据key先从缓存中取数据,如果缓存中没有,线程就去做具体的逻辑处理。
模型如下图:假定每个线程的key如A, B等,同时有多个携带同一key的线程进来。
最基本的处理方式如此:
private static Map<String, Object> cache
= new ConcurrentHashMap<String, Object>();
//Entry
public Object run(String key) {
Object result = cache.get(key);
if (result == null) {
result = doHardWork(key);
cache.put(key, result);
}
return result;
}
private Object doHardWork(String key) {
Object result = null;
//Concrete work
return result;
}
它的缺点很明显,同时会有多个相同key的线程在做事,资源浪费严重。
先看段使用Double-check模式来完成相同功能的代码:
private static Map<String, Object> cache
= new ConcurrentHashMap<String, Object>();
public Object run(String key) {
Object result = cache.get(key);//First checking
if (result == null) {
synchronized (cache) {
result = cache.get(key);//Second checking
if (result == null) {
result = doHardWork(key);
cache.put(key, result);
}
}
}
return result;
}
private Object doHardWork(String key) {
Object result = null;
//Concrete work
return result;
}
假定某个线程T1的参数是A,如果它能从Cache中取到之前A的执行结果,就立马返回。否则在同步块外等待,期望此时在同步块中有另外一个参数也是A的线程T2正在运行,然后将运行结果放入缓存中,在T2执行完成退出同步块后,T1可以从Cache读取T2的执行结果,退出请求。Double-check模型有两次对Cache内容的check,一次在同步块外,一次在同步块里面。它的执行流程如图:
系统初始时,假定有30个参数,每个参数有10个请求线程,那么同时会有300个线程从Cache中读数据,在没有读到任何数据时,只会有一个线程进入同步块,其它299个线程在外面等着。Double-check的好处在于,每个参数第一个进入同步块的线程才会去执行正式逻辑,其它拥有同样参数的线程只要从Cache中取数据即可,效率很高。如果参数A的某个线程之前执行过,其它参数A的线程在进入同步块后,能从Cache中取到数据,立马退出同步块。但同时它的缺点就是因为有同步块的存在,每个参数的第一个线程不能并行进入具体逻辑执行过程,得一个一个的来。如此30个参数,每个参数的第一个线程得依次串行进入具体逻辑。
对于这样的应用场景,最好的流程是:相同参数的线程只有一个进入具体逻辑,其它线程等待这个参数的执行结果,在得到结果后,直接返回;不同参数的线程在具体逻辑阶段可以并发执行。期望的执行流程如下图:
这篇帖子的目的是改进Double-check模型的这种缺点,但不是修改Double-check来满足需求。实现可以很简单,一是多个线程的数据共享,二是对于同样参数多个线程的通知。具体模型如下图:
从代码来看:
/**
* 用来标识当前参数有线程正在做具体逻辑
*/
public static Object lock = new Object();
/**
* 假定参数为'A',系统初始时检查lockMap中‘A’的value是否为null,如果为null,那当前线程就得做具体逻辑,把'A'的value设置为固定的lock,其它线程看到有这个lock就什么事也不做,然后suspend。当有返回数据时,将value由lock替换为正式返回数据,以在多个线程间共享
*/
private Map<String, Object> lockMap
= new ConcurrentHashMap<String, Object>();
/**
* 所有suspend的线程都要在这里注册,以便随后得到通知
*/
private Map<String, List<Thread>> caller = new ConcurrentHashMap<String, List<Thread>>();
它的方法有:
/*
*返回值是lock时,做具体逻辑,返回值不为lock时,是真正的返回数据,线程得到这个数据,直接返回
*/
public Object runOrWait(String key);
/*
*做具体逻辑的那个线程在做完事后,需要把result写入共享空间,让其它线程看到。然后通知所有注册这个参数的线程知道
*/
public void releaseLock(String key, Object result)
具体程序见附件,里面有一个测试类,用来模拟测试Case。然后列举了以上出现的几种cache Demo。这个程序只是用来验证这个处理策略,对于细节问题,值得商榷,欢迎提出意见,十分感谢!
- 大小: 17 KB
- 大小: 18.7 KB
- 大小: 20.4 KB
- 大小: 51.4 KB
分享到:
相关推荐
- **Double-Check Locking**:理解双重检查锁定模式,以及其在单例模式中的应用。 - ** volatile与synchronized的区别**:深入探讨两者的异同。 通过本课程的学习,开发者不仅能掌握Java并发编程的基础知识,还能...
- 并发性:并发编程是系统能够同时执行多个任务的能力,是现代多核处理器环境下提高系统效率的关键。 - 线程:线程是程序执行的最小单元,一个进程可以包含多个线程。 - 并发控制:包括互斥(互斥锁)、同步...
- `volatile`关键字:确保变量在多线程环境下的可见性,但不保证原子性。 - `java.util.concurrent`包:提供了高级并发工具类,如`Semaphore`信号量、`CyclicBarrier`屏障、`CountDownLatch`倒计时器等。 3. **...
4. 双重检查锁定模式(Double-Check Locking):用于安全地创建单例,防止多线程环境下的多次实例化。 5. 原子操作模式:使用java.util.concurrent.atomic包中的原子类,如AtomicInteger,提供无锁的并发操作。 四...
Java内存模型,简称JMM(Java Memory Model),是Java虚拟机规范中定义的一个抽象概念,它描述了在多线程环境下,如何保证各个线程对共享数据的一致性视图。JMM的主要目标是定义程序中各个变量的访问规则,以及在...
- **单例模式**:在多线程环境下,双重检查锁定(Double-Check Locking)是实现线程安全的单例模式。 5. **JVM优化** 面对高并发场景,优化JVM的性能至关重要。这包括调整内存分配(堆、栈、元空间等),设置垃圾...
- **线程安全初始化**:使用Double-Check Locking或Initialization-on-Demand Holder Class模式初始化单例。 - **避免长时间阻塞操作**:尽量减少I/O、网络等可能导致长时间阻塞的操作在主线程中执行。 以上只是...
在某些复杂的并发场景下,如双重检查锁定(Double-Check Locking)模式,单纯依赖volatile可能导致“幻象实例”问题,这时需要结合synchronized来保证正确初始化单例。 文档可能还分析了volatile变量在循环中的使用...
- **双检锁/双重校验锁(DCL,Double-Check Locking)**:用于单例模式,保证多线程环境下的正确初始化。 8. **并发性能调优** - **线程池大小的设置**:根据系统资源和应用需求合理配置线程池的大小。 - **并发...
10. **线程安全问题**:了解常见的线程不安全场景,如Double-Check Locking、ThreadLocal等解决方案,并能够识别和解决这些问题。 11. **Java内存模型(JMM)**:理解Java内存模型对于线程间通信的规定,以及...
在实际应用中,如DCL(Double Check Locking)单例模式,我们需要关注重排序可能导致的问题。为了解决这个问题,我们可以使用volatile关键字来禁止重排序,或者利用类加载机制,通过类初始化阶段的锁来确保线程安全...
在并发编程领域,Java语言提供了一系列机制来保证多线程环境下的正确性和效率。"04 并发编程专题05.zip"中的内容主要聚焦于Java内存模型(JMM)和volatile关键字,这两个概念是理解Java并发编程核心的基石。 Java...
Java内存模型(JVM Memory Model,简称JMM)是Java平台中的一个重要概念,它定义了在多线程环境下,如何在共享内存中读写变量的行为。JMM的主要目标是确保多线程环境下的可见性、有序性和原子性,从而避免数据不一致...
10. **双重检查锁定(Double-Check Locking)**:这是一种常见的单例模式实现方式,通过 JMM 来保证线程安全的单例创建。 深入学习 JMM 不仅能帮助开发者理解 Java 并发的基础原理,还能提高程序的并发性能,避免因...
- 双重检查锁定(Double-Check Locking)与初始化-on-demand holder类设计模式。 5. **IO流与NIO** - 流的分类:字节流与字符流,输入流与输出流,缓冲流与转换流。 - 文件操作:File类的常用方法,文件复制与...
`synchronized`关键字或`Double-Check Locking`策略可用于实现线程安全的单例。 5. **守护线程模式**:用于后台服务,当所有非守护线程结束时,程序退出。Java中的`Thread.setDaemon(true)`方法可以将线程设置为...
4. **线程安全的初始化(Double-Check Locking)**:JSR133修正了经典的双重检查锁定模式,使其在多线程环境中能够正确工作。这涉及到对 volatile 的理解和正确使用。 5. ** Happens-Before 关系**:这是JMM中的一...
在Java中,DCL(Double Check Locking)是一种常见的实现方式,但在多线程环境下,如果不使用volatile,可能存在指令重排导致线程安全问题。通过将instance声明为volatile,可以避免指令重排,确保线程安全。 总结...
在实际应用中,volatile常用于简单的状态标记,如标志位(例如:一个线程是否应该继续运行),或者是单例模式中的Double-Check Locking(双重检查锁定)中用来确保实例化过程的正确性。不过,需要注意的是,过度依赖...