xylz,imxylz

关注后端架构、中间件、分布式和并发编程

   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  111 随笔 :: 10 文章 :: 2680 评论 :: 0 Trackbacks

Java Concurrency

Inside into Concurrency
posted @ 2013-08-17 17:44 imxylz 阅读(3827) | 评论 (3)  编辑

posted @ 2013-08-05 16:45 imxylz 阅读(29817) | 评论 (6)  编辑

posted @ 2011-12-31 14:13 imxylz 阅读(7437) | 评论 (5)  编辑

posted @ 2011-12-30 17:25 imxylz 阅读(6900) | 评论 (0)  编辑

     摘要: 线程池

并发最常见用于线程池,显然使用线程池可以有效的提高吞吐量。
最常见、比较复杂一个场景是Web容器的线程池。Web容器使用线程池同步或者异步处理HTTP请求,同时这也可以有效的复用HTTP连接,降低资源申请的开销。通常我们认为HTTP请求时非常昂贵的,并且也是比较耗费资源和性能的,所以线程池在这里就扮演了非常重要的角色。
在线程池的章节中非常详细的讨论了线程池的原理和使用,同时也提到了,线程池的配置和参数对性能的影响是巨大的。不尽如此,受限于资源(机器的性能、网络的带宽等等)、依赖的服务,客户端的响应速度等,线程池的威力也不会一直增长。达到了线程池的瓶颈后,性能和吞吐量都会大幅度降低。
一直增加机器的性能或者增大线程的个数,并不一定能有效的提高吞吐量。高并发的情况下,机器的负载会大幅提升,这时候机器的稳定性、服务的可靠性都会下降。
尽管如此,线程池依然是提高吞吐量的一个有效措施,配合合适的参数能够有效的充分利用资源,提高资源的利用率。  阅读全文
posted @ 2011-12-29 16:31 imxylz 阅读(8110) | 评论 (0)  编辑

     摘要: 死锁与活跃度

前面谈了很多并发的特性和工具,但是大部分都是和锁有关的。我们使用锁来保证线程安全,但是这也会引起一些问题。
锁顺序死锁(lock-ordering deadlock):多个线程试图通过不同的顺序获得多个相同的资源,则发生的循环锁依赖现象。
动态的锁顺序死锁(Dynamic Lock Order Deadlocks):多个线程通过传递不同的锁造成的锁顺序死锁问题。
资源死锁(Resource Deadlocks):线程间相互等待对方持有的锁,并且谁都不会释放自己持有的锁发生的死锁。也就是说当现场持有和等待的目标成为资源,就有可能发生此死锁。这和锁顺序死锁不一样的地方是,竞争的资源之间并没有严格先后顺序,仅仅是相互依赖而已。  阅读全文
posted @ 2011-12-29 14:04 imxylz 阅读(8202) | 评论 (2)  编辑

posted @ 2011-07-12 23:15 imxylz 阅读(17374) | 评论 (3)  编辑

     摘要: 本节中将探讨线程池是如何处理任务结果以及延迟、周期性任务调度是如何实现的。
结合上节线程池的原理和实现,彻底分析了线程池对任务的结果处理,尤其是对延迟、周期性任务是如何执行的。
这也是并发系列中源码分析的最后一小节。  阅读全文
posted @ 2011-02-13 20:21 imxylz 阅读(11258) | 评论 (6)  编辑

     摘要: 这一节中我们将详细讨论线程池是如何执行一个任务的,尤其是如何处理线程池的大小、核心大小、最大大小、任务队列等之间的关系的。  阅读全文
posted @ 2011-02-11 23:48 imxylz 阅读(11641) | 评论 (2)  编辑

     摘要: 我们根据线程池的要求也很能够猜测出其数据结构出来。
线程池需要支持多个线程并发执行,因此有一个线程集合Collection来执行线程任务;
涉及任务的异步执行,因此需要有一个集合来缓存任务队列Collection
很显然在多个线程之间协调多个任务,那么就需要一个线程安全的任务集合,同时还需要支持阻塞、超时操作,那么BlockingQueue是必不可少的;
既然是线程池,出发点就是提高系统性能同时降低资源消耗,那么线程池的大小就有限制,因此需要有一个核心线程池大小(线程个数)和一个最大线程池大小(线程个数),有一个计数用来描述当前线程池大小;
如果是有限的线程池大小,那么长时间不使用的线程资源就应该销毁掉,这样就需要一个线程空闲时间的计数来描述线程何时被销毁;
前面描述过线程池也是有生命周期的,因此需要有一个状态来描述线程池当前的运行状态;
线程池的任务队列如果有边界,那么就需要有一个任务拒绝策略来处理过多的任务,同时在线程池的销毁阶段也需要有一个任务拒绝策略来处理新加入的任务;
上面种  阅读全文
posted @ 2011-01-18 23:43 imxylz 阅读(16052) | 评论 (6)  编辑

     摘要: 在JDK 5.0之前,java.util.Timer/TimerTask是唯一的内置任务调度方法,而且在很长一段时间里很热衷于使用这种方式进行周期性任务调度。
首先研究下Timer/TimerTask的特性(至于javax.swing.Timer就不再研究了)。
上面三段代码反映了Timer/TimerTask的以下特性:
Timer对任务的调度是基于绝对时间的。
所有的TimerTask只有一个线程TimerThread来执行,因此同一时刻只有一个TimerTask在执行。
任何一个TimerTask的执行异常都会导致Timer终止所有任务。
由于基于绝对时间并且是单线程执行,因此在多个任务调度时,长时间执行的任务被执行后有可能导致短时间任务快速在短时间内被执行多次或者干脆丢弃多个任务。  阅读全文
posted @ 2011-01-10 23:39 imxylz 阅读(14430) | 评论 (32)  编辑

     摘要: 上一节中提到关闭线程池过程中需要对新提交的任务进行处理。这个是java.util.concurrent.RejectedExecutionHandler处理的逻辑。

在没有分析线程池原理之前先来分析下为什么有任务拒绝的情况发生。
这里先假设一个前提:线程池有一个任务队列,用于缓存所有待处理的任务,正在处理的任务将从任务队列中移除。因此在任务队列长度有限的情况下就会出现新任务的拒绝处理问题,需要有一种策略来处理应该加入任务队列却因为队列已满无法加入的情况。另外在线程池关闭的时候也需要对任务加入队列操作进行额外的协调处理。

RejectedExecutionHandler提供了四种方式来处理任务拒绝策略。  阅读全文
posted @ 2011-01-08 22:47 imxylz 阅读(9957) | 评论 (0)  编辑

     摘要:
我们知道线程是有多种执行状态的,同样管理线程的线程池也有多种状态。JVM会在所有线程(非后台daemon线程)全部终止后才退出,为了节省资源和有效释放资源关闭一个线程池就显得很重要。有时候无法正确的关闭线程池,将会阻止JVM的结束。
线程池Executor是异步的执行任务,因此任何时刻不能够直接获取提交的任务的状态。这些任务有可能已经完成,也有可能正在执行或者还在排队等待执行。因此关闭线程池可能出现一下几种情况:
平缓关闭:已经启动的任务全部执行完毕,同时不再接受新的任务
立即关闭:取消所有正在执行和未执行的任务
另外关闭线程池后对于任务的状态应该有相应的反馈信息。  阅读全文
posted @ 2011-01-04 22:54 imxylz 阅读(12555) | 评论 (6)  编辑

     摘要: Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
下面这张图完整描述了线程池的类体系结构。  阅读全文
posted @ 2010-12-21 23:32 imxylz 阅读(13743) | 评论 (4)  编辑

     摘要: 从这一节开始正式进入线程池的部分。其实整个体系已经拖了很长的时间,因此后面的章节会加快速度,甚至只是一个半成品或者简单化,以后有时间的慢慢补充、完善。
其实线程池是并发包里面很重要的一部分,在实际情况中也是使用很多的一个重要组件。
下图描述的是线程池API的一部分。广义上的完整线程池可能还包括Thread/Runnable、Timer/TimerTask等部分。这里只介绍主要的和高级的API以及架构和原理。  阅读全文
posted @ 2010-12-19 13:24 imxylz 阅读(11940) | 评论 (5)  编辑

     摘要: 本小节是《并发容器》的最后一部分,这一个小节描述的是针对List/Set接口的一个线程版本。
在《并发队列与Queue简介》中介绍了并发容器的一个概括,主要描述的是Queue的实现。其中特别提到一点LinkedList是List/Queue的实现,但是LinkedList确实非线程安全的。不管BlockingQueue还是ConcurrentMap的实现,我们发现都是针对链表的实现,当然尽可能的使用CAS或者Lock的特性,同时都有通过锁部分容器来提供并发的特性。而对于List或者Set而言,增、删操作其实都是针对整个容器,因此每次操作都不可避免的需要锁定整个容器空间,性能肯定会大打折扣。要实现一个线程安全的List/Set,只需要在修改操作的时候进行同步即可,比如使用java.util.Collections.synchronizedList(List)或者java.util.Collections.synchronizedSet(Set)。当然也可以使用Lock来实现线程安全的List/Set。
通常情况下我们的高并发都发生在“多读少写”的情况,因此如果  阅读全文
posted @ 2010-11-23 22:22 imxylz 阅读(14670) | 评论 (1)  编辑

     摘要: 可以在对中对元素进行配对和交换的线程的同步点。每个线程将条目上的某个方法呈现给 exchange 方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象。Exchanger 可能被视为 SynchronousQueue 的双向形式。
换句话说Exchanger提供的是一个交换服务,允许原子性的交换两个(多个)对象,但同时只有一对才会成功。先看一个简单的实例模型。  阅读全文
posted @ 2010-11-22 22:31 imxylz 阅读(7723) | 评论 (0)  编辑

     摘要: 这个小节介绍Queue的最后一个工具,也是最强大的一个工具。从名称上就可以看到此工具的特点:双向并发阻塞队列。所谓双向是指可以从队列的头和尾同时操作,并发只是线程安全的实现,阻塞允许在入队出队不满足条件时挂起线程,这里说的队列是指支持FIFO/FILO实现的链表。

首先看下LinkedBlockingDeque的数据结构。通常情况下从数据结构上就能看出这种实现的优缺点,这样就知道如何更好的使用工具了。  阅读全文
posted @ 2010-08-18 16:01 imxylz 阅读(9725) | 评论 (5)  编辑

     摘要:
有一段时间没有更新了。接着上节继续吧。
Queue除了前面介绍的实现外,还有一种双向的Queue实现Deque。这种队列允许在队列头和尾部进行入队出队操作,因此在功能上比Queue显然要更复杂。下图描述的是Deque的完整体系图。需要说明的是LinkedList也已经加入了Deque的一部分(LinkedList是从jdk1.2 开始就存在数据结构)。  阅读全文
posted @ 2010-08-12 00:13 imxylz 阅读(13586) | 评论 (4)  编辑

     摘要:
在Set中有一个排序的集合SortedSet,用来保存按照自然顺序排列的对象。Queue中同样引入了一个支持排序的FIFO模型。
并发队列与Queue简介 中介绍了,PriorityQueue和PriorityBlockingQueue就是支持排序的Queue。显然一个支持阻塞的排序Queue要比一个非线程安全的Queue实现起来要复杂的多,因此下面只介绍PriorityBlockingQueue,至于PriorityQueue只需要去掉Blocking功能就基本相同了。  阅读全文
posted @ 2010-07-30 16:15 imxylz 阅读(14109) | 评论 (0)  编辑

     摘要: 在上一节中详细分析了LinkedBlockingQueue 的实现原理。实现一个可扩展的队列通常有两种方式:一种方式就像LinkedBlockingQueue一样使用链表,也就是每一个元素带有下一个元素的引用,这样的队列原生就是可扩展的;另外一种就是通过数组实现,一旦队列的大小达到数组的容量的时候就将数组扩充一倍(或者一定的系数倍),从而达到扩容的目的。常见的ArrayList就属于第二种。前面章节介绍过的HashMap确是综合使用了这两种方式。
对于一个Queue而言,同样可以使用数组实现。使用数组的好处在于各个元素之间原生就是通过数组的索引关联起来的,一次元素之间就是有序的,在通过索引操作数组就方便多了。当然也有它不利的一面,扩容起来比较麻烦,同时删除一个元素也比较低效。
ArrayBlockingQueue 就是Queue的一种数组实现。  阅读全文
posted @ 2010-07-27 22:04 imxylz 阅读(12911) | 评论 (0)  编辑

     摘要: 在《并发容器 part 4 并发队列与Queue简介》节中的类图中可以看到,对于Queue来说,BlockingQueue是主要的线程安全版本。这是一个可阻塞的版本,也就是允许添加/删除元素被阻塞,直到成功为止。
BlockingQueue相对于Queue而言增加了两个操作:put/take。下面是一张整理的表格。  阅读全文
posted @ 2010-07-24 00:02 imxylz 阅读(19604) | 评论 (6)  编辑

     摘要: ConcurrentLinkedQueue是Queue的一个线程安全实现。先来看一段文档说明。
一个基于链接节点的无界线程安全队列。此队列按照 FIFO(先进先出)原则对元素进行排序。队列的头部 是队列中时间最长的元素。队列的尾部 是队列中时间最短的元素。新的元素插入到队列的尾部,队列获取操作从队列头部获得元素。当多个线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许使用 null 元素。

由于ConcurrentLinkedQueue只是简单的实现了一个队列Queue,因此从API的角度讲,没有多少值的介绍,使用起来也很简单,和前面遇到的所有FIFO队列都类似。出队列只能操作头节点,入队列只能操作尾节点,任意节点操作就需要遍历完整的队列。
重点放在解释ConcurrentLinkedQueue的原理和实现上。  阅读全文
posted @ 2010-07-23 14:11 imxylz 阅读(19984) | 评论 (2)  编辑

     摘要: Queue是JDK 5以后引入的新的集合类,它属于Java Collections Framework的成员,在Collection集合中和List/Set是同一级别的接口。通常来讲Queue描述的是一种FIFO的队列,当然不全都是,比如PriorityQueue是按照优先级的顺序(或者说是自然顺序,借助于Comparator接口)。
下图描述了Java Collections Framework中Queue的整个家族体系。
对于Queue而言是在Collection的基础上增加了offer/remove/poll/element/peek方法,另外重新定义了add方法。对于这六个方法,有不同的定义。  阅读全文
posted @ 2010-07-21 12:21 imxylz 阅读(21426) | 评论 (5)  编辑

     摘要: 在上一篇中介绍了HashMap的原理,这一节是ConcurrentMap的最后一节,所以会完整的介绍ConcurrentHashMap的实现。

ConcurrentHashMap原理

在读写锁章节部分介绍过一种是用读写锁实现Map的方法。此种方法看起来可以实现Map响应的功能,而且吞吐量也应该不错。但是通过前面对读写锁原理的分析后知道,读写锁的适合场景是读操作>>写操作,也就是读操作应该占据大部分操作,另外读写锁存在一个很严重的问题是读写操作不能同时发生。要想解决读写同时进行问题(至少不同元素的读写分离),那么就只能将锁拆分,不同的元素拥有不同的锁,这种技术就是“锁分离”技术。
默认情况下ConcurrentHashMap是用了16个类似HashMap 的结构,其中每一个HashMap拥有一个独占锁。也就是说最终的效果就是通过某种Hash算法,将任何一个元素均匀的映射到某个HashMap的Map.Entry上面,而对某个一个元素的操作就集中在其分布的HashMap上,与其它HashMap无关。这样就支持最多16个并发的写操作。  阅读全文
posted @ 2010-07-20 17:48 imxylz 阅读(20975) | 评论 (9)  编辑

     摘要: 本来想比较全面和深入的谈谈ConcurrentHashMap的,发现网上有很多对HashMap和ConcurrentHashMap分析的文章,因此本小节尽可能的分析其中的细节,少一点理论的东西,多谈谈内部设计的原理和思想。
要谈ConcurrentHashMap的构造,就不得不谈HashMap的构造,因此先从HashMap开始简单介绍。

HashMap原理
我们从头开始设想。要将对象存放在一起,如何设计这个容器。目前只有两条路可以走,一种是采用分格技术,每一个对象存放于一个格子中,这样通过对格子的编号就能取到或者遍历对象;另一种技术就是采用串联的方式,将各个对象串联起来,这需要各个对象至少带有下一个对象的索引(或者指针)。显然第一种就是数组的概念,第二种就是链表的概念。所有的容器的实现其实都是基于这两种方式的,不管是数组还是链表,或者二者俱有。HashMap采用的就是数组的方式。
有了存取对象的容器后还需要以下两个条件才能完成Map所需要的条件。  阅读全文
posted @ 2010-07-20 00:22 imxylz 阅读(22878) | 评论 (3)  编辑

     摘要: 从这一节开始正式进入并发容器的部分,来看看JDK 6带来了哪些并发容器。
在JDK 1.4以下只有Vector和Hashtable是线程安全的集合(也称并发容器,Collections.synchronized*系列也可以看作是线程安全的实现)。从JDK 5开始增加了线程安全的Map接口ConcurrentMap和线程安全的队列BlockingQueue(尽管Queue也是同时期引入的新的集合,但是规范并没有规定一定是线程安全的,事实上一些实现也不是线程安全的,比如PriorityQueue、ArrayDeque、LinkedList等,在Queue章节中会具体讨论这些队列的结构图和实现)。

在介绍ConcurrencyMap之前先来回顾下Map的体系结构。下图描述了Map的体系结构,其中蓝色字体的是JDK 5以后新增的并发容器。  阅读全文
posted @ 2010-07-19 15:25 imxylz 阅读(24594) | 评论 (8)  编辑

     摘要:
主要谈谈锁的性能以及其它一些理论知识,内容主要的出处是《Java Concurrency in Practice》,结合自己的理解和实际应用对锁机制进行一个小小的总结。

首先需要强调的一点是:所有锁(包括内置锁和高级锁)都是有性能消耗的,也就是说在高并发的情况下,由于锁机制带来的上下文切换、资源同步等消耗是非常可观的。在某些极端情况下,线程在锁上的消耗可能比线程本身的消耗还要多。所以如果可能的话,在任何情况下都尽量少用锁,如果不可避免那么采用非阻塞算法是一个不错的解决方案,但是却也不是绝对的。  阅读全文
posted @ 2010-07-16 00:15 imxylz 阅读(16557) | 评论 (2)  编辑

     摘要: 这是一份完整的Java 并发整理笔记,记录了我最近几年学习Java并发的一些心得和体会。  阅读全文
posted @ 2010-07-08 19:17 imxylz 阅读(167865) | 评论 (43)  编辑

     摘要: 在这个小结里面重点讨论原子操作的原理和设计思想。
由于在下一个章节中会谈到锁机制,因此此小节中会适当引入锁的概念。
在Java Concurrency in Practice中是这样定义线程安全的:
当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替运行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安全的。  阅读全文
posted @ 2010-07-03 20:40 imxylz 阅读(46525) | 评论 (16)  编辑


©2009-2014 IMXYLZ