深入浅出 Java Concurrency (38): 并发总结 part 2 常见的并发场景[转]

常见的并发场景

线程池

并发最常见用于线程池,显然使用线程池可以有效的提高吞吐量。

最常见、比较复杂一个场景是Web容器的线程池。Web容器使用线程池同步或者异步处理HTTP请求,同时这也可以有效的复用HTTP连接,降低资源申请的开销。通常我们认为HTTP请求时非常昂贵的,并且也是比较耗费资源和性能的,所以线程池在这里就扮演了非常重要的角色。

线程池的章节中非常详细的讨论了线程池的原理和使用,同时也提到了,线程池的配置和参数对性能的影响是巨大的。不尽如此,受限于资源(机器的性能、网络的带宽等等)、依赖的服务,客户端的响应速度等,线程池的威力也不会一直增长。达到了线程池的瓶颈后,性能和吞吐量都会大幅度降低。

一直增加机器的性能或者增大线程的个数,并不一定能有效的提高吞吐量。高并发的情况下,机器的负载会大幅提升,这时候机器的稳定性、服务的可靠性都会下降。

尽管如此,线程池依然是提高吞吐量的一个有效措施,配合合适的参数能够有效的充分利用资源,提高资源的利用率。

任务队列

除了线程池是比较发杂的并发场景外,任务队列也是一个不错的并发工具。JDK内部有大量的队列(Queue),这些工具不仅能够方便使用,提高生产力,也能够进行组合适应于不同的场景。即使线程池内部,也是用了任务队列来处理任务的积压,平衡资源的消耗。

安全的任务队列能够有效的平衡机器的复杂,抵消由于峰值和波动带来的不稳定,有效提高服务的可靠性。同时任务队列的处理也有助于统计和分析服务的状况。

任务队列也可以在多个线程之间传递数据,有助于并行处理任务。例如经典的“生产者-消费者”模型就可以有效的提高多个线程的并行处理能力。在IO延时比较大的服务中尤其有效。 我最喜欢的一个案例是导数据是,一个线程负责往固定大小的任务队列中压入大量的数据,队列满了以后就暂停,另外几个线程负责从任务队列中获取数据并消费。这将串行的“生产-消费”,变成了并行的“生产-消费”。实践证明极大的节省任务处理时间。

异步处理

线程池也是异步处理的一种表现形式,除此之外,使用异步处理的目的也是为了提高服务的处理速度。 例如AOP的一个例子就是使用切面来记录日志,如果说我们要远程收集日志,显然不希望由于收集日志而影响服务本身。这时候就将日志收集的过程进行异步处理。

如今大量的开源组件都喜欢使用异步处理来提高IO的效率,某些不需要同步返回的操作使用异步处理后能够有效的提高吞吐量。

当然,异步也不总是令人满意的,也会有相应的问题。例如引入异步设计后的复杂性,线程中断后的处理机制,失败后的处理策略,产生的消息比消费的还快时怎么办,关闭程序时如何关闭异步处理逻辑等等。这都会增加系统的复杂性。

尽管大量的服务、业务使用异步来处理,但是很显然需要有保障机制能够保证异步处理的逻辑正确性。如果认为异步处理的任务不是特别重要,或者说主业务不能因为附属业务的逻辑出错而崩溃,那么使用异步处理是正确的选择。

同步操作

并发操作的同时还需要维护数据的一致性,或多或少的会涉及到同步操作。正确的使用原子操作,合理的使用独占锁和读写锁也是一个很大的挑战。

线程间的协调与通信,尤其是状态的同步都是比较困难的。我们看到线程池ThreadPoolExecutor的实现为了解决各个线程的执行状态,引入的很多的同步操作。线程越来越多的情况下,同步的成本会越来越高,同时也有可能引入死锁的情况。

尽管如此,单个JVM内部的多线程同步还是比较容易控制的。JDK内部也提供了大量的工具来方便完成数据的同步。例如Lock/Condition/CountDownLatch/CyclicBarrier/Semaphore/Exchanger等等。

分布式锁

分布式的并发问题更难以处理,根据CAP的原理,基本上没有一个至善至美的方案。 分布式资源协调使用分布式锁是一个不错的选择。Google的分布式锁(建立在BigTable之上),Zookeeper的分布式锁,甚至简单的利用memcache的add操作或者redis的setnx操作建立伪分布式锁也可以解决类似的问题。

时间: 2025-01-02 15:29:42

深入浅出 Java Concurrency (38): 并发总结 part 2 常见的并发场景[转]的相关文章

深入浅出 Java Concurrency (17): 并发容器 part 2 ConcurrentMap (2)

本来想比较全面和深入的谈谈ConcurrentHashMap的,发现网上有很多对HashMap和ConcurrentHashMap分析的文章,因此本小节尽可能的分析其中的细节,少一点理论的东西,多谈谈内部设计的原理和思想. 要谈ConcurrentHashMap的构造,就不得不谈HashMap的构造,因此先从HashMap开始简单介绍. HashMap原理 我们从头开始设想.要将对象存放在一起,如何设计这个容器.目前只有两条路可以走,一种是采用分格技术,每一个对象存放于一个格子中,这样通过对格子

深入浅出 Java Concurrency - 目录 [转]

这是一份完整的Java 并发整理笔记,记录了我最近几年学习Java并发的一些心得和体会. J.U.C 整体认识 原子操作 part 1 从AtomicInteger开始 原子操作 part 2 数组.引用的原子操作 原子操作 part 3 指令重排序与happens-before法则 原子操作 part 4 CAS操作 锁机制 part 1 Lock与ReentrantLock 锁机制 part 2 AQS 锁机制 part 3 加锁的原理 (Lock.lock) 锁机制 part 4 锁释放与

深入浅出 Java Concurrency (18): 并发容器 part 3 ConcurrentMap (3)[转]

在上一篇中介绍了HashMap的原理,这一节是ConcurrentMap的最后一节,所以会完整的介绍ConcurrentHashMap的实现. ConcurrentHashMap原理 在读写锁章节部分介绍过一种是用读写锁实现Map的方法.此种方法看起来可以实现Map响应的功能,而且吞吐量也应该不错.但是通过前面对读写锁原理的分析后知道,读写锁的适合场景是读操作>>写操作,也就是读操作应该占据大部分操作,另外读写锁存在一个很严重的问题是读写操作不能同时发生.要想解决读写同时进行问题(至少不同元素

深入浅出 Java Concurrency (39): 并发总结 part 3 常见的并发陷阱

常见的并发陷阱 volatile volatile只能强调数据的可见性,并不能保证原子操作和线程安全,因此volatile不是万能的.参考指令重排序 volatile最常见于下面两种场景. a. 循环检测机制 volatile boolean done = false; while( ! done ){        dosomething();    } b. 单例模型 (http://www.blogjava.net/xylz/archive/2009/12/18/306622.html)

深入浅出 Java Concurrency (35): 线程池 part 8 线程池的实现及原理 (3)[转]

线程池任务执行结果 这一节来探讨下线程池中任务执行的结果以及如何阻塞线程.取消任务等等. 1 package info.imxylz.study.concurrency.future;2 3 public class SleepForResultDemo implements Runnable {4 5     static boolean result = false;6 7     static void sleepWhile(long ms) {8         try {9      

深入浅出 Java Concurrency (33): 线程池 part 6 线程池的实现及原理 (1)[转]

线程池数据结构与线程构造方法 由于已经看到了ThreadPoolExecutor的源码,因此很容易就看到了ThreadPoolExecutor线程池的数据结构.图1描述了这种数据结构. 图1 ThreadPoolExecutor 数据结构 其实,即使没有上述图形描述ThreadPoolExecutor的数据结构,我们根据线程池的要求也很能够猜测出其数据结构出来. 线程池需要支持多个线程并发执行,因此有一个线程集合Collection<Thread>来执行线程任务: 涉及任务的异步执行,因此需要

深入浅出 Java Concurrency (28): 线程池 part 1 简介[转]

从这一节开始正式进入线程池的部分.其实整个体系已经拖了很长的时间,因此后面的章节会加快速度,甚至只是一个半成品或者简单化,以后有时间的慢慢补充.完善. 其实线程池是并发包里面很重要的一部分,在实际情况中也是使用很多的一个重要组件. 下图描述的是线程池API的一部分.广义上的完整线程池可能还包括Thread/Runnable.Timer/TimerTask等部分.这里只介绍主要的和高级的API以及架构和原理. 大多数并发应用程序是围绕执行任务(Task)进行管理的.所谓任务就是抽象.离散的工作单元

深入浅出 Java Concurrency (15): 锁机制 part 10 锁的一些其它问题[转]

主要谈谈锁的性能以及其它一些理论知识,内容主要的出处是<Java Concurrency in Practice>,结合自己的理解和实际应用对锁机制进行一个小小的总结. 首先需要强调的一点是:所有锁(包括内置锁和高级锁)都是有性能消耗的,也就是说在高并发的情况下,由于锁机制带来的上下文切换.资源同步等消耗是非常可观的.在某些极端情况下,线程在锁上的消耗可能比线程本身的消耗还要多.所以如果可能的话,在任何情况下都尽量少用锁,如果不可避免那么采用非阻塞算法是一个不错的解决方案,但是却也不是绝对的.

深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则[转]

在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到锁机制,因此此小节中会适当引入锁的概念. 在Java Concurrency in Practice中是这样定义线程安全的: 当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替运行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安全的. 显然只有资源竞争时才会导致线程不安全,因此无状态对象永远是线程安全的. 原子操作的描述是: 多个线程执行一个操作时,其