- 浏览: 1904493 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
July01:
最近了解到一款StratoIO打印控件,功能如下:1、Html ...
jquery打印指定的div -
GentlemanQc:
...
quartz系列(二)spring3.2.5与quartz2.1.7集群版集成简要说明 -
静夜独窗:
你好,能说一下server.xml增加的配置是怎么影响性能的吗 ...
tomcat7.0性能优化-挑战极限精简版 -
beyondfengyu:
beyondfengyu 写道如果每个客户进程的时间不同步,时 ...
java并发(二十二)分布式锁 -
beyondfengyu:
如果每个客户进程的时间不同步,时间超前的进程是不是更容易得到锁 ...
java并发(二十二)分布式锁
概述
几乎所有应用程序,都会使用某种形式的缓存。重用之前的计算结果,能降低延时,提高吞吐量,但却要消耗更多的内存。用内存“换”CPU。缓存看上去非常简单,然而简单的缓存可能会将性能瓶颈装变为可伸缩性瓶颈,即使缓存是用于提升单线程的性能。笔者会循序渐进的介绍缓存的使用方法演进。
模拟定义接口和功能
声明一个计算函数,使用泛型,输入是A,输出是V。然后我们实现这个接口,再开发一个包装器,可以缓存计算的结果。
接口:
实现:
使用HashMap和同步机制来初始化缓存
这种方法是最基本的缓存用法,是安全的。但是有一个明显的可伸缩性问题:每次只有一个线程能够执行compute。如果另一个线程正在计算结果,那么其他调用coumpute的线程可能被阻塞很长时间。如果有多个线程在排队等待还未计算出的结果,那么compute方法的计算时间可能比没有“记忆”操作的计算时间更长。
用ConcurrentHashMap替换HashMap
Memorizer2比Memorizer1有着更好的并发行为,ConcurrentHashMap是线程安全的,所以不需要同步compute方法。但是作为缓存仍然有问题----2个线程同时调用的compute的时候,可能会导致计算得到相同的值。因为缓存是用来避免相同的数据被计算多次,但对于更通用的缓存机制来说,这种情况是更糟糕的,对于提供单词初始化对象缓存来说,这个漏洞会存在安全风险。
Memorizer2问题在于,如果某个线程启动了一个开销很大的计算,而其他线程并不知道这个计算正在进行,那么很可能会重复这个计算。我们希望通过某种方法来表达“线程X正在计算f(1226)”这种情况,这样当另一个线程查找f(1226)时,他能够知道最高效的方法是等待线程X计算结束,然后去查询缓存“f(1226)的结果是多少”。
基于FutureTask的Memorizer封装器
FutureTask表示一个计算过程,这个过程可能已经计算完成,也可能正在进行。如果有结果可用,那么FutureTask.get将立即返回结果,否则它会一直阻塞,直到结果计算出来再将其返回。
Memorizer3进一步改进了代码。基于ConcurrentHashMap表现出了更好的并发性。但是他仍然有一个漏洞,就是2个线程计算相同值的漏洞。这个漏洞的概率远远小于Memorizer2的情况。但是compute方法中的if代码块仍然是非原子的“先检查再执行”操作。
基于原子操作putIfAbsent的改进
Memorizer4使用了putIfAbsent的原子方法,从而有效避免了Memorizer3的漏洞。但是这个缓存仍然存在问题。
缓存污染
缓存逾期
缓存清理
几乎所有应用程序,都会使用某种形式的缓存。重用之前的计算结果,能降低延时,提高吞吐量,但却要消耗更多的内存。用内存“换”CPU。缓存看上去非常简单,然而简单的缓存可能会将性能瓶颈装变为可伸缩性瓶颈,即使缓存是用于提升单线程的性能。笔者会循序渐进的介绍缓存的使用方法演进。
模拟定义接口和功能
声明一个计算函数,使用泛型,输入是A,输出是V。然后我们实现这个接口,再开发一个包装器,可以缓存计算的结果。
接口:
package com.chinaso.phl; /** * @author piaohailin * @date 2014-4-23 */ public interface Computable<A, V> { V compute(A arg) throws InterruptedException; }
实现:
package com.chinaso.phl; import java.math.BigInteger; /** * @author piaohailin * @date 2014-4-23 */ public class ExpensiveFunction implements Computable<String, BigInteger> { @Override public BigInteger compute(String arg) throws InterruptedException { return new BigInteger(arg); } }
使用HashMap和同步机制来初始化缓存
package com.chinaso.phl; import java.util.HashMap; import java.util.Map; import net.jcip.annotations.GuardedBy; /** * @author piaohailin * @date 2014-4-23 */ public class Memorizer1<A, V> implements Computable<A, V> { @GuardedBy("this") private final Map<A, V> cache = new HashMap<A, V>(); private final Computable<A, V> c; public Memorizer1(Computable<A, V> c) { this.c = c; } @Override public synchronized V compute(A arg) throws InterruptedException { V result = cache.get(arg); if (result == null) { result = c.compute(arg); cache.put(arg, result); } return result; } }
这种方法是最基本的缓存用法,是安全的。但是有一个明显的可伸缩性问题:每次只有一个线程能够执行compute。如果另一个线程正在计算结果,那么其他调用coumpute的线程可能被阻塞很长时间。如果有多个线程在排队等待还未计算出的结果,那么compute方法的计算时间可能比没有“记忆”操作的计算时间更长。
用ConcurrentHashMap替换HashMap
package com.chinaso.phl; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author piaohailin * @date 2014-4-23 */ public class Memorizer2<A, V> implements Computable<A, V> { private final Map<A, V> cache = new ConcurrentHashMap<A, V>(); private final Computable<A, V> c; public Memorizer2(Computable<A, V> c) { this.c = c; } @Override public V compute(A arg) throws InterruptedException { V result = cache.get(arg); if (result == null) { result = c.compute(arg); cache.put(arg, result); } return result; } }
Memorizer2比Memorizer1有着更好的并发行为,ConcurrentHashMap是线程安全的,所以不需要同步compute方法。但是作为缓存仍然有问题----2个线程同时调用的compute的时候,可能会导致计算得到相同的值。因为缓存是用来避免相同的数据被计算多次,但对于更通用的缓存机制来说,这种情况是更糟糕的,对于提供单词初始化对象缓存来说,这个漏洞会存在安全风险。
Memorizer2问题在于,如果某个线程启动了一个开销很大的计算,而其他线程并不知道这个计算正在进行,那么很可能会重复这个计算。我们希望通过某种方法来表达“线程X正在计算f(1226)”这种情况,这样当另一个线程查找f(1226)时,他能够知道最高效的方法是等待线程X计算结束,然后去查询缓存“f(1226)的结果是多少”。
基于FutureTask的Memorizer封装器
FutureTask表示一个计算过程,这个过程可能已经计算完成,也可能正在进行。如果有结果可用,那么FutureTask.get将立即返回结果,否则它会一直阻塞,直到结果计算出来再将其返回。
package com.chinaso.phl; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @author piaohailin * @date 2014-4-23 */ public class Memorizer3<A, V> implements Computable<A, V> { private final Map<A, FutureTask<V>> cache = new ConcurrentHashMap<A, FutureTask<V>>(); private final Computable<A, V> c; public Memorizer3(Computable<A, V> c) { this.c = c; } @Override public V compute(final A arg) throws InterruptedException { FutureTask<V> f = cache.get(arg); if (f == null) { Callable<V> eval = new Callable<V>() { @Override public V call() throws Exception { return c.compute(arg); } }; FutureTask<V> ft = new FutureTask<V>(eval); f = ft; cache.put(arg, ft); ft.run(); // 这里调用的是c.compute(arg); } try { return f.get(); } catch (ExecutionException e) { throw new InterruptedException(e.getMessage()); } } }
Memorizer3进一步改进了代码。基于ConcurrentHashMap表现出了更好的并发性。但是他仍然有一个漏洞,就是2个线程计算相同值的漏洞。这个漏洞的概率远远小于Memorizer2的情况。但是compute方法中的if代码块仍然是非原子的“先检查再执行”操作。
基于原子操作putIfAbsent的改进
package com.chinaso.phl; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @author piaohailin * @date 2014-4-23 */ public class Memorizer4<A, V> implements Computable<A, V> { private final ConcurrentMap<A, FutureTask<V>> cache = new ConcurrentHashMap<A, FutureTask<V>>(); private final Computable<A, V> c; public Memorizer4(Computable<A, V> c) { this.c = c; } @Override public V compute(final A arg) throws InterruptedException { FutureTask<V> f = cache.get(arg); if (f == null) { Callable<V> eval = new Callable<V>() { @Override public V call() throws Exception { return c.compute(arg); } }; FutureTask<V> ft = new FutureTask<V>(eval); // 只有第一个线程添加的时候才会为空,第二个线程此处会获取之前的FutureTask f = cache.putIfAbsent(arg, ft); if (f == null) { f = ft; ft.run(); // 这里调用的是c.compute(arg); } } try { return f.get(); } catch (CancellationException e) { cache.remove(arg, f); return null; } catch (ExecutionException e) { throw new InterruptedException(e.getMessage()); } } }
Memorizer4使用了putIfAbsent的原子方法,从而有效避免了Memorizer3的漏洞。但是这个缓存仍然存在问题。
缓存污染
缓存逾期
缓存清理
发表评论
-
java并发(三十四)协程kilim
2015-10-02 11:29 5661概述 对协程的技术已经觊觎很久,他有高性能的优点,但目前工具对 ... -
java并发(三十三)栅栏CyclicBarrier
2015-09-12 16:09 3386如果说CountDownLatch(闭锁)是一次性的, ... -
java并发(三十二)非阻塞算法
2014-06-25 14:08 1359如果在某算法中,一个线程的失败或挂起不会导致其他线程也 ... -
java并发(三十一)Amdahl定律
2014-05-19 16:15 2230阿姆达尔定律 阿姆达尔(Amdahl)定律是计算机系统设计的重 ... -
java并发(三十)闭锁CountDownLatch
2014-05-07 23:33 918CountDownLatch,一个同步辅助类,在完成一组正在其 ... -
java并发(二十八)并发随机数,原子变量,并发集合
2014-04-13 12:04 4094原子变量 java.util.concurrent.a ... -
java并发(二十七) 并发性标注
2014-04-07 10:49 8996一介绍 <dependency> < ... -
java并发(二十六)正确使用Volatile变量
2014-03-30 19:41 1404概述 您只能在有限的一 ... -
java并发(二十五)java7之fork-join框架
2014-03-26 14:12 11536如果让我个人理解什么 ... -
java并发(二十四)多线程结果组装
2014-03-24 22:01 3967本文主要介绍多线程的 ... -
java并发(二十三)阻塞、非阻塞、同步、异步
2014-03-14 17:01 1453因为中文语意的问题, ... -
java并发(二十二)分布式锁
2014-03-12 16:24 18765Redis有一系列的命令, ... -
java并发(二十一)剖析同步器
2014-03-11 18:03 1226虽然许多同步器(如锁,信号量,阻塞队列等)功能上各不相同,但它 ... -
java并发(二十)线程池
2014-03-10 15:02 3951基本介绍 线程池(Thread Pool)对于限制应用程序中同 ... -
java并发(十九)阻塞队列
2014-03-10 14:46 1357阻塞队列与普通队列的 ... -
java并发(十八)信号量
2014-03-10 14:03 1298Semaphore(信号量) 是一个线程同步结构,用于在线程间 ... -
java并发(十七)重入锁死
2014-03-10 11:33 1145重入锁死与死锁和嵌套管程锁死非常相似。当一个线程重新获取锁,读 ... -
java并发(十六)Java中的读/写锁
2014-03-10 10:17 1241相比Java中的锁(Locks in Ja ... -
java并发(十五)Java中的锁
2014-03-09 12:07 964锁像synchronized同步块一样,是一种线程同步机制,但 ... -
java并发(十四)Slipped Conditions
2014-03-09 11:28 1099所谓Slipped conditions,就 ...
相关推荐
在构建高效可伸缩的缓存系统中,我们需要考虑的关键因素包括性能、容错性、扩展性和资源管理。本demo提供了实现这些目标的实例代码和测试案例,让我们深入探讨其中涉及的技术点。 首先,缓存的主要目的是提高数据...
5.6 构建高效且可伸缩的结果缓存 第二部分 结构化并发应用程序 第6章 任务执行 6.1 在线程中执行任务 6.1.1 串行地执行任务 6.1.2 显式地为任务创建线程 6.1.3 无限制创建线程的不足 6.2 Executor框架 6.2.1...
书中通过实例解释了诸如`ExecutorService`、`Future`、`Callable`、`BlockingQueue`等高级并发工具类的使用方法,并详细阐述了如何利用这些工具来构建可伸缩、高性能的应用系统。 #### 二、并发基础知识 本书不仅...
《java并发编程实战》读书笔记-第3章-对象的共享,脑图形式,使用xmind8制作 包括同步容器类、并发容器类、阻塞队列和生产者消费者模式、阻塞和中断方法、同步工具类。最后是构建高效且可伸缩的结果缓存
Java 5以及6在开发并发程序取得了显著的进步,提高了Java虚拟机的性能,提高了并发类的可伸缩性,并加入了丰富的新并发构建块。在本书中,这些便利工具的创造者不仅解释了它们究竟如何工作、如何使用,同时,还阐释...
5.6 构建高效且可伸缩的结果缓存 第二部分 结构化并发应用程序 第6章 任务执行 6.1 在线程中执行任务 6.1.1 串行地执行任务 6.1.2 显式地为任务创建线程 6.1.3 无限制创建线程的不足 6.2 Executor框架 6.2.1...
为计算结果建⽴⾼效、可伸缩的缓存是指在多线程环境下,使用缓存来实现高效、可靠的计算结果。例如,在桌⾯搜索时,使用缓存来实现高效、可靠的计算结果,以避免出现死锁、竞态条件等问题。 JAVA并发编程实践-构建...
在探讨Java并发编程与高并发解决方案的过程...从CPU多级缓存、缓存一致性、Java内存模型,到具体的高并发系统设计策略,都是构建高性能、可伸缩应用的基石。掌握这些知识对于开发大型的、高性能的互联网应用尤为重要。
5.6 为计算结果建立高效、可伸缩的高速缓存 第2部分 构建并发应用程序 第6章 任务执行 6.1 在线程中执行任务 6.2 executor 框架 6.3 寻找可强化的并行性 第7章 取消和关闭 7.1 任务取消 7.2 停止基于线程的服务 7.3 ...
Java SE 5和Java SE 6在并发方面做出了重要的改进,提供了改进的虚拟机性能、高可伸缩性的并发类和一系列新的并发构建块。《JAVA并发编程实践》的作者们,他们大多来自Java标准化组织JSR166专家组,为Java并发工具的...
5.6 为计算结果建立高效、可伸缩的高速缓存 第2部分 构建并发应用程序 第6章 任务执行 6.1 在线程中执行任务 6.2 executor 框架 6.3 寻找可强化的并行性 第7章 取消和关闭 7.1 任务取消 7.2 停止基于线程的服务 7.3 ...
5.6 为计算结果建立高效、可伸缩的高速缓存 第2部分 构建并发应用程序 第6章 任务执行 6.1 在线程中执行任务 6.2 executor 框架 6.3 寻找可强化的并行性 第7章 取消和关闭 7.1 任务取消 7.2 停止基于线程的服务 7.3 ...
《大型分布式网站架构设计与实践》是一本深入探讨如何构建高效、可扩展的分布式系统的技术专著,结合了Java并发编程的实际应用。本书旨在帮助读者理解在高流量、大规模应用场景下,如何通过精心设计的架构和Java并发...
另外,Java并发API(java.util.concurrent包)提供了一系列高级并发工具,如`ConcurrentHashMap`(线程安全的哈希映射)、`BlockingQueue`(阻塞队列)和`Future`(异步计算结果)。这些工具可以帮助开发者更好地...
Java并发程序设计教程主要围绕Java语言如何实现高效并发编程展开。在WEB开发领域,多线程编程是提高服务器处理能力的核心技术之一。并发编程的知识点包括但不限于以下几个方面: 1. **线程的使用经验**:包括如何...
在Java开发领域,构建高性能、高并发的Web应用是一项核心任务。这涉及到多个技术层面的综合运用,包括但不限于系统架构设计、线程管理、数据访问优化、缓存策略、负载均衡以及性能监控等。以下是一些关键的知识点,...
采用缓存策略,预先计算和存储常用结果;使用数据库的分区和并行查询功能,提升查询性能;或者利用搜索引擎如Elasticsearch进行全文搜索和数据分析。 总结来说,这些文档涵盖了从数据库优化、分布式系统设计到网络...
总结来说,Java高并发高性能分布式框架的构建涉及到微服务架构设计、服务拆分、独立部署、通信机制的选择,以及各种缓存策略和池化技术的运用。通过这些手段,开发者可以构建出既能处理大规模并发请求,又能保持高效...
此外,还会讨论Java并发库的使用,如ExecutorService和并发集合,以及如何设计低延迟的系统架构。 第五部分:构建高可用、可伸缩的系统 最后,本书将引导读者设计和实施高可用和可伸缩的Java系统。内容包括负载均衡...