《JAVA并发编程实践》读书笔记(二)

第4章 对象的组合
在Java中,同步经常是与状态挂钩的,想要实现等待某个先验条件为真时才执行的操作,可以通过Java库中的一些阻塞类,例如BlockingQueue, Semaphore以及其他同步工具类来实现(待会会讲到)。同时,同步还涉及到一个状态所有权的问题,在大多数情况下,所有权是与封装性互相关联的:对象封装它拥有的状态。但是容器类表现出“所有权”分离的形式,容器类拥有其自身的状态,而客户代码则拥有容器类各个对象的状态(所以,和所有共享对象一样,这些对象应该是线程安全的对象)。
但是,如果某对象不是线程安全类的话,可以通过实例封闭实现(数据封装在对象内部,将数据访问限制在对象的方法上,从而确保线程在访问数据时总能持有正确的锁)。Java平台中有很多线程封闭的示例,将非线程安全的类转化为线程安全的类(例如ArrayList和HashMap本身非线程安全,但是类库提供了包装器工厂方法(例如Collections.synchronizedList及其类似方法),使得这些非线程安全的类可以安全使用。(这些工厂方法通过”装饰器“模式,将容器类封装在同步的包装器对象中,封闭机制也更易于构造线程安全的类,因为当封闭类的状态时,分析类的线程安全性就无须检查整个程序)。
从线程封闭原则可以得到Java监视器模式(通过对象的内置锁或者私有的锁对象来保护状态)。私有的锁对象可以把锁封装起来,客户代码无法得到锁,可以避免客户代码错误获得另外一个对象锁而导致的活跃性问题。
Java类库中包括了许多有用的”基础模块“类,倘若要增加功能,要么能修改原始类,要么需要扩展这个类。在客户端加锁要注意锁是使用容器的内置锁,不是客户端的内置锁。
同步策略文档化非常复杂,同时也难以令人满意,所以可以通过标注@GuardedBy和@ThreadSafe来标记。

第5章 基础构建模块
同步容易类包括Vector和Hashtable,两者是早期JDK的一部分,此外在JDK1.2以后还添加了同步封装类Collections.synchronizedXxx.注意,对于复合操作,要加容器的内置锁。
在设计同步容器类的时候,没有考虑到并发修改的问题,所以它们表现出来的是”及时失败“(fail-fast)现象。如果在迭代过程当中,容器被修改了,会抛出一个ConcurrentModificationException异常。所以为了避免这种情况发生,必须迭代过程中持有容器的锁。但是在迭代过程中加锁,很容易产生活跃性问题(死锁、饥饿),如果想避免这种情况,在迭代过程前,克隆容器。
Java5.0提供了并发容器去改善同步容器的性能。例如增加ConcurrentHashMap去替代同步且基于散列的Map,增加CopyOnWriteArrayList,用于在遍历操作为主要操作的情况下替代同步的List。Java5.0增加两种新的容器类型:Queue和BlockingQueue。Queue提供了几种实现(ConcurrentLinkedQueue,这是个传统的先进先出队列,以及PriorityQueue,这是个(非并发的)优先队列。BlockingQueue在”生产者-消费者“模式里当阻塞队列非常有用。
正如ConcurrentHashMap用于替代基于散列的同步Map, Java 6也引入了ConcurrentSkipListMap和CurrentSkipListSet,分别作为同步的SortedMap和SortedSet的并发替代品(例如synchronizedMap包装的TreeMap或TreeSet)。

5.1 ConcurrentHashMap
1. ConcurrentHashMap采用分段锁加锁,粒度更细,任意数量的读取进程可以并发访问Map。一定数量的写进程可以并发修改Map。
2. ConcurrentHashMap提供的迭代器不会抛出ConcurrentModificationException, 因此不需要在迭代过程中对容器加锁。ConcurrentHashMap返回的迭代器具有”弱一致性“,而并非”及时失败“。弱一致性容忍并发修改,当创建迭代器时会遍历已有的元素,并可以(但不保证)在迭代器修改后,将修改操作反映给容器。ConcurrentHashMap没有提供独占加锁的能力,因此一些常见的操作,例如“若没有则添加”,“若相等则移除”,”若相等则替换“,都已经实现为原子操作,并在ConcurrentMap的接口中声明。
5.2 CopyOnWriteArrayList
每次修改的时候,都会创建并重新发布一个新的容器副本。
5.3 阻塞队列与生产者-消费者模式
阻塞队列提供了可阻塞的put和take方法,以及支持定时的offer和poll方法。BlockingQueue有多种实现,其中, LinkedBlockingQueue和ArrayBlockingQueue是FIFO队列。PriorityBlockingQueue是优先级队列(可以根据元素的自然顺序比较【如果实现Comparable方法的话】,也可以使用Comparator来比较)。
最后一个BlockingQueue的实现是SynchronousQueue,实际上它不是一个真正的队列,它维护一组线程,这些线程在等待着把元素加入或者移出队列。
5.3.3 双端队列
Java 6增加了两种容器类型,Deque和BlockingDeque, 他们分别对Queue和BlockingQueue进行了扩展。
5.4 阻塞方法与中断方法
线程可以会阻塞或者暂停执行,当线程阻塞时,它通常会被挂起,并处于某种阻塞状态(BLOCKED, WAITING 或 TIMED_WAITING), 当某个外部事件发生时,线程会置回RUNNABLE状态,并可以再次被调度执行。
BlockingQueue的put和take方法会抛出受检查异常(Checked Exception) InterruptedException. Thread.sleep 也会抛出,当某方法抛出InterrupedException时,表明该方法是个阻塞方法,如果该方法被中断,他将努力提早结束阻塞状态。
Thread提供了interrupt方法,用于中断线程或者查询线程是否已经被中断。当代码中调用了一个将抛出InterruptedException异常的方法时候,你自己的方法也变成了阻塞方法。你有两种基本选择
1.传递InterruptedException
2.恢复中断 (在catch(InterruptedException e) {
Thread.currentThread().interrupt();
})

5.5 同步工具类
信号量(Semaphore)
- 表示一组虚拟的许可(permit)。执行中首先获得许可(只要还有剩余的许可),并在以后释放许可。(acquire将阻塞获得许可,release将返回一个许可给信号量)。
private final Semaphore sem;
sem = new Semaphore(bound);
sem.acquire();
sem.release();

栅栏(Barrier)
- 类似闭锁,能阻塞一组线程指导某件事情发生。栅栏与闭锁的关键区别在于,所有线程必须同事到达栅栏位置,才能继续执行。CyclicBarrier可以是一定数量的参与者反复地在栅栏位置汇集。
private final CyclicBarrier barrier;
this.barrier = new CyclicBarrier(count, new Runnable(){
public void run(){
mainBoard.commitNewValues();
}});
try{
barrier.await();
}catch (InterruptedException ex){
return;
}catch (BrokenBarrierException ex){
return;
}

闭锁(Latch)
-闭锁相当于一扇门,在闭锁达到状态结束以前,这扇门一直是闭的,并且没有任何线程可以通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。
final CountDownLatch endGate = new CountDownLatch(nThreads);
endGate.await();
FutureTask
- 通过Callable来实现,有三种状态:等待运行,正在运行以及运行完成。如果任务已经完成,那么get会立即返回结果,否则,get会阻塞直到任务完成状态,返回结果或者抛出异常。FutureTask在Executor框架中表示异步任务。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值