并发编程实战学习笔记(三)——基础构建模块
2017-03-05 17:09
976 查看
同步容器类的问题
整个容器类加锁,线性访问容器实例,并发性能非常低虽然单个操作是线程安全的,但是复合操作如果不另外加锁,本身无法保证并发安全
迭代器迭代过程中,如果发生元素的操作,会触发ConcurentModificationException异常,使用了“及时失败”机制
建议:通过并发容器来代替同步容器,可以极大地提高伸缩性并降低风险
ConcurrentHashMap的优化手段
不是在每个方法上都在同一个锁上同步并使得每次只能有一线程访问容器,而是使用一种粒度更细的加锁机制来实现更大程序的共享,这种机制称为分段锁。这种机制中,任意数量的读取线程可以并发地访问Map,执行读取操作的线程和执行写入操作的线程可以并发地访问Map,并且一定数量的写入线程可以并发地修改Map。迭代器不会抛出ConcurrentModificationException,因此不需要在迭代过程中对容器加锁。迭代器具有弱一致性,而并非“及时失败”。
对于一些需要在整个Map上进行计算的方法,例如size/isEmpty,这些方法的语义被略微减弱了以反映容器的并发特性。
添加了额外的复合原子操作【没有才插入、映射到值了才移除、映射到旧值才替换、映射到某个值时才替换到新值】
CopyOnWriteArrayList
原理
在每次修改时,都会创建并重新发布一个新的容器副本,从而实现可变性。使用场景
每当修改容器时都会复制底层数组,这需要一定的开销,特别是容器的规模较大时。仅当迭代操作远远多于修改操作时,才应该使用“写入时复制”容器。阻塞队列与生产-消费模式
问题
如果生产者生成工作的速率比消费者处理工作的速率快,那么工作项会在队列中累积起来,最终耗尽内存内存。解决方法
阻塞队列提供了一个offer方法,如果数据项不能被添加到队列中,那么将返回一个失败状态。这样你就能创建更多的灵活策略来处理负荷过载的情况,例如将多余的工作项序列化并写入磁盘,减少生产者线程的数量,或者通过某种方式来抵制生产者线程。在构建高可靠的应用程序时,有界队列是一种强大的资源管理工具:它们能抵制并防止产生过多的工作项,使应用程序在负荷过载的情况下变得更加健壮。
串行线程封闭
优点
线程封闭对象只能由单个线程拥有,通过安全地发布该对象“转移”所有权,实现了转移前由前一线程独占,转移后由后一线程独占。
实现方法
阻塞队列使得这种线程封闭的所有权转移变得容易,其次还可以通过ConcurrentMap的原子方法remove或者AtomicReference的原子方法compareAndSet来完成这项工作。闭锁(CountDownLatch)
使用场景
确保某个计算在其需要的所有资源都被初使化之后才继续执行。确保某个服务在其依赖的所有其它服务都已经启动之后才启动。
等待直到某个操作的所有参与者(如多玩家游戏中的所有玩家)都就绪再继续执行。
使用方法
可以使一个或多个线程等待一组事件发生。闭锁状态包括一个计数器,该计数器被初使化为一个正数,表示需要等待的事件数量。countDown方法递减计数器,表示一个事件已经发生了,而await方法等待计数器达到零,这表示所有需要等待的事件都已经发生。如果计数器的值非零,那么await会一直阻塞直到计数器为零,或者等待中的线程中断,或者等待超时。FutureTask也可以用作闭锁
FutureTask表示的计算是通过callable来实现的,相当于一种可生成结果的Runnable,并且可以处于以下3种状态:等待运行、正在运行和运行完成。执行完成表示计算的所有可能结束方式,包括正常结束、由于取消而结束和由于异常而结束等。
Future.get的行为取决于任务的状态。如果任务已经完成,那么get会立即返回结果,否则get会阻塞直到任务进入完成状态,然后返回结果或者抛出异常。
信号量的作用
计数信号量用来控制同时访问某个特定资源的操作数量,或者同时执行某个特定操作的数量。你可以使用Semaphore将任何一种容器变成有界阻塞容器:信号量的计数值会初使化为容器容量的最大值。add操作在向底层容器添加一个元素之前,首先要获取一个许可。如果add操作没有添加任何元素,那么会立刻释放许可。同样,removew操作释放一个许可,使更多的元素能够添加到容器中。
栅栏
栅栏与闭锁的关键区别在于,所有线程必须都同时达到栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其它线程。Exchanger
是一种两方栅栏,各方在栅栏位置上交换数据。如果一方通过exchange方法申请交换,而另一方还没有来,则会一直阻塞直至对方也提出申请。最简单的方案是,当缓冲区被填满时,由填充任务进行交换,当缓冲区为空时,由清空任务进行交换。但是如果新数据的到达率不可预测,那么一些数据的处理过程就将延迟。另一个方法是,不仅当缓冲被填满时进行交换,并且当缓冲被填充到一定程序并保持一定时间后,也进行交换。
相关文章推荐
- JAVA并发编程学习笔记------基础构建模块
- java并发编程实战学习(3)--基础构建模块
- Java 并发编程实战之 基础构建模块
- Java并发读书学习笔记(四)——基础构建模块
- Java并发编程学习——基础构建模块
- 并发编程实战学习笔记(十)-构建自定义的同步工具
- Java并发编程实战:并发基础构建模块
- Java并发编程基础构建模块(06)——高效缓存总结示例
- 并发编程实战学习笔记(六)——线程池的使用
- [Java 并发] Java并发编程实践 思维导图 - 第五章 基础构建模块
- 并发编程实战学习笔记(九)-显式锁
- 并发编程实战学习笔记(十一)-原子变量与非阻塞同步机制
- Java并发编程学习——《Java Concurrency in Practice》学习笔记 5.基础构建模块
- Java 并发编程实战学习笔记——路径查找类型并行任务的终止
- Java 并发编程实战学习笔记——串行任务转并行任务
- Java 并发编程之基础构建模块 (二)
- Java并发学习笔记(1)——并发编程基础
- 并发编程实战学习笔记(二)——对象的共享
- 【Java并发编程的艺术】【学习笔记】并发基础
- 并发编程实战学习笔记(七)——避免活跃性问题