【学习笔记-并发编程实战】第5章 基础构建模块

同步容器类

将状态封装起来,对公共方法都进行同步,确保一次只有一个线程可是访问容器。

同步容器类的问题

在组合同步容器如Vector时,如果发布含有Vector的复合操作,多个复合操作可能不会有线程安全方面的问题,但是会出现不希望的结果,比如P67所示的抛出异常(但是这个结果又与Vector规范所一致)。所以可使用客户端加锁的方式,来保证复合操作符合使用预期。

迭代器与ConcurrentModificationException

“现代”同步容器的迭代仍然没有考虑到并发修改的问题,通过一个计数器的改变,来判断是否有其他线程进行了修改,“及时失败”的抛出异常(计数器无法保证一加一减保持不变的情况)。如果直接加锁迭代,迭代量很大,影响效率,通过复制一份的方式,再进行操作,再复制的过程中,如果量大,仍然有显著的性能开销。

隐藏迭代器

迭代会出现问题,我们可以将其加锁,但是有很多地方有隐式的迭代,却很容易让人疏忽。书上一句话,需要理解,正如封装对象的状态有助于维持不变性条件一样,封装对象的同步机制同样有助于确保实施同步策略。

 

并发容器

同步容器保证了线程的安全性,但是直接加锁的这种方式导致并发性很低,于是有了并发容器,可以极大的提高伸缩性并降低风险。ConcurrenttHashMap/CopyOnWriteArrayList,支持常见的复合操作。Queue、BlockingQueue,ConcurrentLinkedQueue、PriorityQueue.

ConcurrentHashMap

通过分段锁来实现,它的迭代器不需要加锁,是不会抛出ConcurrentModifactionException的弱一致性,而非快速失败。由于分段及弱一致性,size和isEmpty便是估计值,不过一般也不需要准确值。

额外的原子Map操作

“没有则添加”,“相等则替换”,“相等则移除”等原子操作不能在客户端加锁,但是在ConcurrentHashMap已经内置。

CopyOnWriteArrayList

用于替代同步的List,在修改容器时,会先复制一份,作“写入时复制”,在同时写入和迭代时,会迭代复制之前的内容。由于复制有性能消耗,所以当修改操作远远大于迭代操作时使用该容器较好。

 

阻塞队列和生产者-消费者

很简单,put和take或者offer和poll,如果队列为空,take或poll将阻塞,直到队列有东西可用,或队列满了,put或offer将阻塞,直到可以再添加元素,当然无界队列除外。

BlockQueue包含多种实现,LinkedBlockingQueue和ArrayBlockingQueue是FIFO队列,分别于LinkedList与ArrayList对应,另外PriorityBlockingQueue按优先级排列的队列,和SynchronousQueue维护一组线程。

桌面搜索

主要是文件遍历和建立索引两个功能。文件遍历为生产者,建立索引为消费者,使用LinkedBlockingQueue作为功能队列。

串行线程封闭

线程封闭一个可变对象,在一个线程中,这个对象可以安全的发布,将所有权转移到其他线程,必须确保只有一个线程进行接收,相当于对象在生产者和消费者之间转移。

双端队列与工作密取

接口Deque和BlockingDeque的实现是ArrayDeque和LinkedBlockingDeque,每个消费者有一个自己的双端队列,可以进行任务的获取,当自己的队列为空时,可以秘密的在其他双端队列的末尾获取任务(其他队列对应的消费者从头部获取),这样的消费者模式叫做工作密取,相对来说它的效率更高。常见的应用有搜索图算法,和垃圾回收阶段对堆进行标记。

 

阻塞方法与中断方法

线程阻塞时,就是被挂起了,被挂起的原因有多种,但是阻塞的状态只有几种:BLOCKED,WAITING或TIMED_WAITING,线程阻塞后,需要等待外部线程的条件改变,线程再次被置位RUNNABLE状态,再次被调度。

中断的意思是根据状态标志,判断当前线程是否被中断,一个线程不能强制使另外一个线程在任意时刻中断,只要在另一个线程的指定地方中断。

中断后一般有两种处理方式,1是传递InterruptedException,不捕获该异常或者以另外一种形式的异常抛出,2是恢复中断,这里的恢复中断并不是指将中断状态恢复,个人感觉是指被中断的线程将中断标志位置位true方便自己或其他线程观察到此线程已经被中断。

 

同步工具类

根据自身的状态类协调线程的控制流,主要包含了一些结构化的属性,1封装的状态,2提供了一些对状态操作的方法,3另外一个些方法高效地使同步类进入预期状态。

闭锁

设置一个有数量的闭锁,countDown表示等待的事件已经发生,而await方法会一直阻塞计数器到达零。一般使用起始门和结束门两个闭锁来控制等待线程一起开始。闭锁是不可逆的一种同步机制。

FutureTask

异步的执行任务,等待获取结果。有三种状态,等待运行、正式运行、运行完成,“执行完成”表示所有可能的结束状态,正常结束、由于取消而结束、由于异常而结束。

信号量

相当于一组虚拟的许可,用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定的操作的数量。通过acquire获取许可,release释放许可,如果没有许可可以获取,线程将阻塞直到有新的许可。

栅栏

与闭锁类似,闭锁是一次性的对象,栅栏是可以重置的多次使用对象。闭锁是等待某个时间发生,而栅栏是用于等待所有线程。如果await的调用超时,或者await阻塞的线程被中断,那么栅栏被认为是打破,所有线程都将抛出异常BrokenBarrierException。如果栅栏成功打卡,那么await将为每个线程返回到达索引号。

 

构建高效且可伸缩的结果缓冲

HashMap->ConcurrentHashMap->在计算之间进行缓存,缓存Future,通过Future.get来获取值->在put的是否检查是否已经有存在的键,避免重复计算

你可能感兴趣的:(java基础)