Java并发编程实战~笔记~章五

原创 2013年12月05日 00:23:26

2013 1204:

5.1 同步容器类

同步容器类是指使用这种策略进行多线程保护的容器类:访问操作被synchronized修饰,被严格串行化。

比如Vector类和用Collections.synchronizedmap得到的Map。

Vector

Vector类的串行化极其严格,无论是修改动作的add方法,还是读取动作的get、size、elementAt等等方法,都直接由synchronized修饰,并且连迭代器也是如此,迭代方法next、remove都会锁住Vector对象。

Vector的迭代器还有修改动作检查,如果迭代期间有任何其他地方的修改动作,那么就会抛出异常。

get或其他访问方法在越界时,还会抛出越界异常。

为了能够让程序正常运行,就不得不在整个迭代期间上锁,比如: Vector v; synchronized(v) { // 迭代},又或者取对象时进行这样的动作: synchronized(v) {if (a < v.size()) get(a);}

因此这个同步容器在多线程下的功能十分有限。

Collections.synchronizedmap

这个方法得到的Map,会在除迭代以外的其他访问方法上上锁。也就是说,它没有解决迭代期间被修改的问题,一旦被修改就会抛出ConcurrentModificationException。甚至它还不提供客户端去为整个迭代上锁的方式。

可以说,这个方法得到的Map不应该使用迭代。

可是实际上迭代动作,即使不是显示调用,也可能出现。

比如for循环,又比如基本上所有容器的toString方法里面,都会用迭代器去遍历元素

这种隐藏迭代器,使得Collections.synchronizedmap非常不安全。

5.2 并发容器

并发容器的代表ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentLinkedQueue和LinkedBlockingQueue

分段锁:ConcurrentHashMap

迭代时对写动作不敏感,分段存储数据,每次读写只在某个段上进行上锁,降低了锁冲突,适合修改和迭代都很频繁的情况。

事实不可变:CopyOnWriteArrayList

这是一个很特别的实现,它里面使用一个对象数组存储数据。每次有修改动作的时候,都会把原来的数组拷贝到新的数组上面,把新的元素添加或者修改到新的数组里面,然后舍弃掉旧的数组。这样的做法显然增加了修改时的性能损耗,但是它有着很强大的作用——它的迭代都会保存当时的数组(快照),只在这个数组上进行,由于这个数组在形成以后就不会再发生任何变化,所以不会有任何多线程问题。而下一次迭代,又会在新的数组上进行。

总而言之,就是一次迭代保证不变性,多次迭代保证可变性

这个容器用于修改动作远远少于迭代动作的情况,比如监听器存储。注册和销毁监听器的动作,远远少于事件的数量,迭代上不需要任何锁,性能十分优越。

原子操作委托:ConcurrentLinkedQueue

这个类使用UNSAFE来串行化读写操作,迭代时同样对写动作不敏感。

应用同步工具类:LinkedBlockingQueue

这个类使用了ReentrantLock和Condition来进行offer和poll的控制。

5.3 阻塞队列和生产者-消费者模式

BlockingQueue

BlockingQueue有许多实现,LinkedBlockingQueue、ArrayBlockingQueue和PriorityBlockingQueue,这种阻塞队列使用时,需要注意生产者和消费者的处理速率。开发人员经常会假设消费者的速率会跟得上生产者的,但实际上这常常不被保证。如果生产者的效率更高,那么数据就会堆积在队列中越来越多,导致耗尽内存。

有界队列是一种强大的资源管理工具,它们能通过阻塞生产者的方式,抑制并防止产生过多的生产项,使应用程序在负荷过载的情况下更加健壮。所以习惯性的在LinkedBlockingQueue构造时,加上容量参数吧。
阻塞队列在多线程方面的模式,其实是串行线程封闭,也就是说,一个对象通过一次同步处理(阻塞队列),被一个线程转移到另一个线程里面去。两个线程不存在同时操作该对象的场景。对象一开始封闭在生产者线程中,后来被封闭在消费者线程中。

双端队列与工作密取

双端队列是支持在尾部添加对象,在首尾两端都可以取走对象的队列。工作密取的一个典型例子是多处理器,当一个处理器的任务队列空了以后,它主动出击,从其他处理器的队列尾部获取任务来执行(work stealing)。

5.4 阻塞与中断

阻塞上升:一个方法调用了阻塞方法,那么本身就变成了阻塞方法。

当某个方法抛出InterruptedException时,表示该方法是一个阻塞方法(比如Thread.sleep),如果这个方法被中断,那么它将努力提前结束阻塞状态。

中断是一种协作机制,一个线程在阻塞状态不能自己中断自己,它只能被其他线程中断。因此,当一个阻塞方法产生InterruptedException时,它需要决定如何应对其他线程的中断。处理方式一般就这么几种:

1、自己处理,继续流程。

2、不做异常处理继续流程,但是调用Thread.interrupt方法恢复中断标志位,使得高层知晓。

3、把异常抛出去让高层处理。

错误的处理方式是:捕获了异常,但是不做任何响应。

5.5 同步工具类

闭锁 CountDownLatch

闭锁使多个线程等待一组事件发生,当这组事件全部发生以后,所有等待的线程全部结束阻塞,继续执行。它可以用于如下场景:

所有资源都被初始化之后才继续执行。

所有依赖的服务都启动后才启动。

某个操作的参与者都就绪才开始进行。

CountDownLatch的原理和ReentrantReadWriteLock是一样的,事实上他们都是基于AbstractQueuedSynchronizer实现的。

读写锁的特性是,读写互斥、写写互斥,但是读读不互斥。所以可能出现这种场景,一堆读操作,等待一个写操作结束。这就是闭锁。

CountDownLatch构造函数接受一个int型参数,设定它需要等待的资源数,如果一个资源就绪,就调用countDown方法,将等待数减1。如果等待数不是0,那么await方法将被阻塞,无论多少个线程。当等待数到了0,这些被阻塞的线程,就会被全部释放开来。并且这把闭锁以后就失效了,无论是countDown方法还是await方法,都不再有任何控制。

这里一个典型的应用场景就是,在osgi架构中,某个操作可能需要多个osgi服务配合,但是ogsi服务又不一定及时绑定上了,就可以用闭锁阻塞。

FutureTask

FutureTask会指定一段需要被执行的代码(一个call方法,返回一个指定类型的变量),当这个call方法被执行完以后,其他线程调用FutureTask的get方法,就能得到返回值(或者接收一个异常),否则将被阻塞住。它比较适合某个资源的初始化,初始化线程和使用线程不是同一个的情况(比如osgi的bundle start里面产生FutureTask初始化资源,而业务代码get资源)。

信号量Semaphore和栅栏CyclicBarrier

前者太简单,后者太复杂,就不看了。

《java并发编程实战》读书笔记——并发应用

1.使用线程池 当应用需要处理多个任务时,例如一个Web服务器处理它接收到的请求,可以使用线程池。 通过重用现有的线程而不是创建新的线程,可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销。...
  • Great_Smile
  • Great_Smile
  • 2016年01月06日 22:11
  • 1041

《Java并发编程实战》读书笔记

Subsections  线程安全(Thread safety) 锁(lock) 共享对象 对象组合 基础构建模块 任务执行 取消和关闭 线程池的使用 性能与可伸缩性 并发程序的测试 显示锁 原子变量...
  • cdl2008sky
  • cdl2008sky
  • 2014年05月20日 17:02
  • 24720

《Java并发编程实战》读书笔记一:基础知识

一、线程安全性一个对象是否是需要是线性安全的,取决于它是否需要被多个线程访问 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要额外的同步,这个...
  • jeffleo
  • jeffleo
  • 2016年12月20日 19:47
  • 418

《Java并发编程实战》第五章 同步容器类 读书笔记

一、同步容器类 1. 同步容器类的问题 线程容器类都是线程安全的,但是当在其上进行符合操作则需要而外加锁保护其安全性。 常见符合操作包含: . 迭代 . 跳转(根据指定顺序找到当前元...
  • love_world_
  • love_world_
  • 2014年05月25日 08:04
  • 1258

《Java并发编程实战》第六章 任务执行 读书笔记

一、 在线程中执行任务 无限制创建线程的不足 .线程生命周期的开销非常高 .资源消耗 .稳定性 二、Executor框架 Executor基于生产者-消费者模式,提交任务的...
  • love_world_
  • love_world_
  • 2014年05月26日 07:43
  • 1578

【Java并发】JAVA并发编程实战-读书笔记2

当多个线程访问同一个类时,如果不考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方代码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。 一个无状态的Ser...
  • kingdz618
  • kingdz618
  • 2016年10月12日 21:02
  • 237

java并发编程实战阅读笔记(第四章)对象的组合

一、设计线程安全的类在设计线程安全类的过程中,需要包含三个步骤: 1)找出构成对象状态的所有变量。 2)找出约束状态变量的不变形条件。 3)建立对象状态的并发访问管理策略。对象的域:是指对象中的...
  • wee616
  • wee616
  • 2017年05月12日 17:16
  • 190

【Java并发】JAVA并发编程实战-读书笔记4

无论是什么原因保证线程安全的实际要求,都只是存在于一线开发人员编码的那一刻。如果线程内部用法的设定没有清楚地文档化,那么后期维护人员会错误放任对象的逸出。 一种维护线程限制的更加规范的方式是使用Thr...
  • kingdz618
  • kingdz618
  • 2016年10月17日 21:02
  • 249

《Java并发编程实战》第十六章 Java内存模型 读书笔记

Java内存模型是保障多线程安全的根基,这里仅仅是认识型的理解总结并未深入研究。 一、什么是内存模型,为什么需要它 Java内存模型(Java Memory Model)并发相关的安全...
  • love_world_
  • love_world_
  • 2014年06月05日 07:58
  • 1542

《Java并发编程实战》第四章 对象的组合 读书笔记

一、设计线程安全的类 在设计线程安全类的过程中,需要包含以下三个基本要素:  . 找出构成对象状态的所有变量。  . 找出约束状态变量的不变性条件。  . 建立对象状态的并发访问管理策略。 ...
  • love_world_
  • love_world_
  • 2014年05月25日 07:28
  • 1484
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Java并发编程实战~笔记~章五
举报原因:
原因补充:

(最多只允许输入30个字)