java并发编程实战读书笔记--第五章

java高并发读书笔记——第五章

5.2:并发容器

5.2.1:ConcurrentHashMap

1、每个操作都是线程安全的 get、contains等 包含大量工作,但加锁了所以安全2、基于散列的Map
3、粒度更细的加锁机制:分段锁---->高并发时吞吐量更高,单线程时性能损失小
4、弱一致性:size和isEmpty等操作可以返回结果已经过期,实际只是一个估计值
5、没有对Map加锁提供独占访问,而Hashtable和synchronizedMap可以防止其他线程访问该Map。有好有坏吧。好处是不是独占锁,速度快,坏处是无法实现独占功能访问。

5.2.2:额外的院子Map操作

ConcurrentMap接口已经声明了“若没有则添加”、“若相等则移除”、“若相等则替换”等原子操作。

5.2.3:CopyOnWriteArrayList

1、线程安全:读不加锁,写加锁
2、适用于读操作远大于写操作的情景,因为写操作需要复制,会很占空间
3、无法保证实时性,可能读的过程中,数据被修改了,读到的就是旧数据。但不会出现脏读现象,只是读到的数据可能不是最新的。

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

5.3.1:各种队列简介

队列满了:put方法将阻塞到有空间可用
队列为空:take方法将阻塞到有元素可用
1、ArrayBlockingQueue:类似ArrayList,有界队列,FIFO
2、LinkedBlockingQueue:类似于LinkedList,无界队列,FIFO
3、PriorityBlockingQueue:按优先级排序的队列,可以自然排序,也可以使用 Comparator来比较
4、SynchronousQueue:没有存储功能,生产者生产之后,必须有消费者消费,不然会一直阻塞。消费者多时使用。

5.3.2:串行线程封闭

线程封闭对象只能有单个线程拥有,但可以安全的发布出去,转移所有权。转移之后,新的线程拥有该封闭对象的访问权,之前线程不会再访问它。保证了可见性,同时保证了封闭性。

5.3.3:双端队列与工作密取

1、Deque双端队列,为Queue的扩展版本
2、具体实现:ArrayDeque和LinkedDeque
3、适用场景:工作密取
4、生产者消费者模式:所有的消费者有一个共享的队列
5、工作密取模式:每个消费者都有自己的双端队列,当自己的双端队列消费完了之后,会从其他消费者的双端队列的尾部获取工作。(秘密的获取->密取)
6、密取模式优点:更好的伸缩性。不会在共享队列上发生竞争,因为每个消费者一般只在自己的队列中消费。当消费完自己的队列去访问其他消费者队列时,也是从尾部获取的,进一步降低了队列上的竞争程度。

5.4:阻塞方法与中断方法

1、线程可能在执行过程中阻塞或者暂停执行,例如等待IO结束,等待获得一个锁,等待从Thread.sleep方法中醒来,或者等待另一个线程的计算结果。
2、中断是一种协作机制,一个线程不能强制要求其他线程停止正在执行的操作而去执行其他操作。当线程A中断线程B时,A只是要求B在执行到某个可以暂停的地方停止正在执行的操作。但是实际怎样处理中断是由线程B自己决定的。所以在,类中调用阻塞方法时,需要添加中断处理。
3、中断处理策略:

  • 传递InterruptedException:将中断处理交给方法的调用者,包括根本不捕获异常或者捕获异常后进行简单的处理之后再抛出异常。
  • 恢复中断:有时候不能抛出InterruptedException,例如在Runnable中,这时需要捕获异常并且恢复异常。

5.5:同步工具类

5.5.1:闭锁

1、闭锁:CountDownLatch是其中一种(马老师所说的门闩)
2、功能:相当于一扇门,门上有n个门闩,没调用一次countDown就会减少一个门闩,只有当门闩减少为0时,await才会执行。否则await会一直等待。可以保证某些活动指导其他活动都完成后才继续执行。

5.5.2:FutureTask

1、FutureTask也可用做闭锁
2、可用做闭锁的原因:Future.get取决于任务的状态。

  • 如果任务已完成,那么get会立即返回结果
  • 否则get将阻塞直到任务进入完成状态,然后返回结果或者抛出异常

3、FutureTask其实是Future接口的唯一实现
4、FutureTask既实现了Runable接口也实现了Future接口

5.5.3:信号量

Semaphore可以控制同时访问的线程个数,功能有点类似于锁

  • 通过acquire()获取一个许可,如果没有就等待。
  • release()释放一个许可。
  • 通过availablePermits()方法得到可用的许可数目。

5.5.4栅栏

栅栏:类似于闭锁,阻塞一组线程直到某个事件发生
区别:

  • 闭锁:用于等待事件,一次性对象,一旦终止,就不能重置。
  • 栅栏:用于等待线程,可以循环使用。

当线程到达栅栏位置时,将调用await方法,该方法将阻塞直到所有的线程都到达栅栏位置。

5.6:构建高效且可伸缩的结果缓存

主要讲述了使用缓存来避免重复计算的工作

  • 第一版本:使用HashMap在计算的时候,为了保证同步,使用了synchronized对整个计算过程加锁,导致并发性很低。
  • 第二版本:使用ConcurrentHashMap保证了并发性问题,但会出现可能计算出相同值的情况,原因是虽然ConcurrentHashMap是同步的,但计算过程不是原子性的,因此会出现多次计算的情况。有点类似于“若没有则添加”。
  • 第三版本:使用FutureTask,如果其他线程正在计算结果,则通过get方法等待计算结果。但还是会出现计算出相同结果的情况,原因还是因为计算过程中,设置缓存时“先检查再执行”的操作不是原子性的,使用ConcurrentHashMap中的PutIfAbsent即可解决。

你可能感兴趣的:(java并发编程实战,java)