委托是创建线程安全类的一个最有效的策略:只需让现有的线程安全类管理所有的状态即可。

一、同步容器类

1、同步容器类的问题

  同步容器类都是线程安全的,容器本身内置的复合操作能够保证原子性,但是当在其上进行客户端复合操作则需要额外加锁保护其安全性

  由于同步容器类要遵守同步策略,即支持客户端加锁,但必须清楚加入同一个锁

2、迭代器与ConcurrentModificationException

  及时失败机制:容器在迭代过程中被修改时 ,就会抛出一个ConcurrentModificationException异常

  解决方法:加锁或创建副本

3、隐藏迭代器

  一些隐藏的迭代操作:hashCode, equals, containsAll, removeAll, retainAll等

二、并发容器

同步容器对容器状态访问实现串行化以保证线程安全,但这种方法严重降低了并发性。通过并发容器来代替同步容器,可以极大地提高伸缩性并降低风险。

BlockingQueue扩展了Queue,实现了可阻塞的插入和获取等操作ConcurrentHashMap代替HashMap

1、ConcurrentHashMap

  并不是在每个方法上都在锁使得只有一个线程可以访问容器,即没有实现独占访问。而是使用一种粒度更细的加锁机制来实现大程度的共享,这种机制称为分段锁(Lock Striping)

  ConcurrentHashMap的迭代器不会抛出ConcurrentModificationException,因此不需要在迭代过程中加锁,因为其返回的迭代器具有弱一致性,而非"及时失败"。

  ConcurrentHashMap对一些操作进行了弱化,如size(计算的是近似值,而不是精确值), isEmpty等

2、额外的原子Map操作

  ConcurrentHashMap实现了若没有则添加、若有则删除、映射则替换等操作的接口

3、CopyOnWriteArrayList

  特点:写入时复制,即每当修改容器时都会复制底层数组产生开销,只要发布一个事实不可变的对象,那么在访问该对象时就不需要进一步同步

  不会抛出ConcurrentModificationException,不用加锁,性能更好

  仅当迭代操作远远多于修改操作时,才应该使用"写入时复制"容器

三、阻塞队列和生产者消费者模式

阻塞队列提供了可阻塞的put和take方法,以及支持定时的offer和poll方法。(offer方法如果数据不能添加到队列则返回一个失败状态)

可有界也可无界

在构建高可靠的应用程序时,有界队列是一种强大的资源管理工具;它们能够意志或防止产生过多的工作项,使应用程序在负荷过载的情况下变得更加健壮。

实现:LinkedBlockingQueue, ArrayBlockingQueue, PriorityBlockingQueue(可实现comparable方法比较排序),SynchronousQueue(维护一组工作线程,而不是维护队列元素的存储空间)

1、串行线程封闭

  对于可变对象,生产者--消费者这种设计与阻塞队列一起,促进了串行线程封闭,从而将对象所有权从生产者交付给消费者。

  线程封闭对象由单个线程所有,但通过生产者消费者模式安全的转移了对象的所有权,转移后只有接受的线程获得该对象的所有权,而发布者放弃了所有权,并不会在访问他。

2、双端队列适用于工作密取

  Deque和BlockingDeque对Queue进行了拓展,实现了双端队列,即从头尾皆可插入删除。(实现:ArrayDeque,LinkedBlockingDeque)

  每个消费者有各自的双端队列,当消费者自己的双端队列为空时,它会从其他消费者队列末尾中密取任务。优点:大大减少了竞争,保证线程均出于忙碌状态

四、阻塞方法和中断方法

阻塞的原因:等待I/O操作结束,等待获得一个锁,等待从Thread.sleep方法中醒来,或是等待另一个线程的计算结果等

线程阻塞时会被挂起,处于某种阻塞状态(BLOCKED,WAITING,TIMED_WAITING),并且必须等待某个不受他控制的事件完成

抛出InterruptedException的方法叫做阻塞方法

中断是一种协作机制,一个线程不能强制要求其他线程停止正在执行的操作而去执行其他操作。

处理对中断的响应:传递InterreuptedException,抛出异常给方法调用者,或捕获异常,做一些清理工作再抛出抛出异常;恢复中断:有时不能抛出InterruptedException, 比如在Runnable中,则可以恢复中断

五、同步工具类

1、闭锁:确保某些活动直到其他活动都完成后才继续执行

  闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。当闭锁到达结束状态后,将不会再改变状态。

  CountDownLatch:一个或多个线程等待一组事件发生。闭锁状态包括一个计数器,该计数器被初始化为一个正数,表示需要等待的事件数量。countDown方法递减计数器,表示有一个事件已经发生了,而await方法阻塞直到计数器到达零

 1 public long timeTasks(int nThreads, final Runnable task) throws InterruptedException{
2 final CountDownLatch startGate = new CountDownLatch(1); //所有线程同时开始执行task的阀门
3 final CountDownLatch endGate = new CountDownLatch(nThreads); //所有线程结束的阀门
4
5 for (int i=0; i<nThreads; i++){
6 Thread t = new Thread(){
7 @Override
8 public void run() {
9 try {
10 startGate.await(); //等待startGate值减为0
11 try {
12 task.run();
13 } finally{
14 endGate.countDown(); //一个线程运行结束,值减1
15 }
16 } catch (InterruptedException e) {
17 e.printStackTrace();
18 }
19 }
20 };
21 t.start();
22 }
23 long start = System.nanoTime();
24 startGate.countDown(); //所有线程开始执行task
25 endGate.await(); //等待所有线程执行结束
26 long end = System.nanoTime();
27 return end - start;
28 }

2、FutureTask

  FutureTask是通过 Callable 来实现的,相当于一种可生成结果的 Runnable,并且可处于以下三种状态:等待运行,正在运行,运行完成(正常完成、取消、异常结束)。当FutureTask进入完成状态后,它会停留在这个状态上。

  Future.get 用来获取计算结果,如果FutureTask还未运行完成,则会阻塞。FutureTask 将计算结果从执行计算的线程传递到获取这个结果的线程,而FutureTask 的规范确保了这种传递过程能实现结果的安全发布

 1 import java.util.concurrent.Callable;
2 import java.util.concurrent.ExecutionException;
3 import java.util.concurrent.FutureTask;
4
5 public class Preloader {
6 private final FutureTask<Integer> future = new FutureTask<>(new Callable() {
7 public Integer call() throws Exception {
8 return 969*99*99;
9 }
10 });
11
12 private final Thread thread = new Thread(future);
13
14 public void start() {
15 thread.start();
16 }
17
18 public Integer get() throws Exception {
19 try {
20 return (Integer) future.get();
21 } catch (ExecutionException e) {
22 Throwable cause = e.getCause();
23 throw launderThrowable(cause);
24 }
25 }
26
27 private static Exception launderThrowable(Throwable cause) {
28 if (cause instanceof RuntimeException)
29 return (RuntimeException) cause;
30 else if (cause instanceof Error)
31 throw (Error) cause;
32 else
33 throw new IllegalStateException("Not Checked", cause);
34 }
35
36 public static void main(String[] args) throws Exception {
37 Preloader p = new Preloader();
38 p.start();
39 long start = System.currentTimeMillis();
40 System.out.println(p.get());
41 System.out.println(System.currentTimeMillis() - start);
42 }
43 }

3、信号量

计数信号量用来控制同时访问某个特定资源的操作数量,或同时执行某个指定操作的数量。或者可以用来实现某种资源池,或者对容器施加边界。

Semaphore管理一组虚拟许可,有构造函数指定数量(数量为1即为互斥锁),acquire请求许可(阻塞直到获得,或中断,或超时),release释放一个许可

使用Semaphore实现有界阻塞容器

 1 public class BoundedList<T> {
2
3 private final List<T> list;
4 private final Semaphore semaphore;
5
6 public BoundedList(int bound) {
7 list = Collections.synchronizedList(new LinkedList<T>());
8 semaphore = new Semaphore(bound);
9 }
10
11 public boolean add(T obj) throws InterruptedException {
12 semaphore.acquire();
13 boolean addedFlag = false;
14 try {
15 addedFlag = list.add(obj);
16 }
17 finally {
18 if (!addedFlag) {
19 semaphore.release();
20 }
21 }
22 return addedFlag;
23 }
24
25 public boolean remove(Object obj) {
26 boolean removedFlag = list.remove(obj);
27 if (removedFlag) {
28 semaphore.release();
29 }
30 return removedFlag;
31 }
32 }

4、栅栏

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

闭锁用于等待事件,而栅栏用于等待其他线程。闭锁是一次性对象,一旦进入终止状态,就不能被重置

六、构建高效可伸缩的结果缓存

首先考虑采用HashMap,通过sychronized方法满足原子性

–> 性能较差,同一时间只有一个线程进行计算操作,使用ConcurrentHashMap改善性能,无需使用同步方法,但是可能导致很多线程在计算同样的值

–> 考虑阻塞方法,使用基于FutureTask的ConcurrentHashMap,Future.get实现阻塞知道结果返回,减少了多次计算,但仍然不是原子性的

–> 使用ConcurrentHashMap中的 putifAbsent()

–> 继续解决缓存污染问题,当缓存结果失效时移除,解决缓存逾期,缓存清理等等问题

 1 public class Memoizer <A, V> implements Computable<A, V> {
2 private final ConcurrentMap<A, Future<V>> cache
3 = new ConcurrentHashMap<A, Future<V>>();
4 private final Computable<A, V> c;
5
6 public Memoizer(Computable<A, V> c) {
7 this.c = c;
8 }
9
10 public V compute(final A arg) throws InterruptedException {
11 while (true) {
12 Future<V> f = cache.get(arg);
13 if (f == null) {
14 Callable<V> eval = new Callable<V>() {
15 public V call() throws InterruptedException {
16 return c.compute(arg);
17 }
18 };
19 FutureTask<V> ft = new FutureTask<V>(eval);
20 f = cache.putIfAbsent(arg, ft);
21 if (f == null) {
22 f = ft;
23 ft.run();
24 }
25 }
26 try {
27 return f.get();
28 } catch (CancellationException e) {
29 cache.remove(arg, f);
30 } catch (ExecutionException e) {
31 throw LaunderThrowable.launderThrowable(e.getCause());
32 }
33 }
34 }
35 }

java并发编程实战:第五章----基础构建模块的更多相关文章

  1. Java并发编程实战---第六章:任务执行

    废话开篇 今天开始学习Java并发编程实战,很多大牛都推荐,所以为了能在并发编程的道路上留下点书本上的知识,所以也就有了这篇博文.今天主要学习的是任务执行章节,主要讲了任务执行定义.Executor. ...

  2. Java并发编程实战 第16章 Java内存模型

    什么是内存模型 JMM(Java内存模型)规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对其他线程可见. JMM为程序中所有的操作定义了一个偏序关系,称为Happens-Be ...

  3. 【java并发编程实战】第一章笔记

    1.线程安全的定义 当多个线程访问某个类时,不管允许环境采用何种调度方式或者这些线程如何交替执行,这个类都能表现出正确的行为 如果一个类既不包含任何域,也不包含任何对其他类中域的引用.则它一定是无状态 ...

  4. Java并发编程实战 第8章 线程池的使用

    合理的控制线程池的大小: 下面内容来自网络.不过跟作者说的一致.不想自己敲了.留个记录. 要想合理的配置线程池的大小,首先得分析任务的特性,可以从以下几个角度分析: 任务的性质:CPU密集型任务.IO ...

  5. Java并发编程实战 第5章 构建基础模块

    同步容器类 Vector和HashTable和Collections.synchronizedXXX 都是使用监视器模式实现的. 暂且不考虑性能问题,使用同步容器类要注意: 只能保证单个操作的同步. ...

  6. java并发编程实战《五》死锁

    一不小心就死锁了,怎么办? 在上一篇文章中,我们用 Account.class 作为互斥锁,来解决银行业务里面的转账问题,虽然这个方案不存在并发问题,但是所有账户的转账操作都是串行的,性能太差. 向现 ...

  7. java并发编程实战:第二章----线程安全性

    一个对象是否需要是线程安全的取决于它是否被多个线程访问. 当多个线程访问同一个可变状态量时如果没有使用正确的同步规则,就有可能出错.解决办法: 不在线程之间共享该变量 将状态变量修改为不可变的 在访问 ...

  8. JAVA并发编程实战---第三章:对象的共享(2)

    线程封闭 如果仅仅在单线程内访问数据,就不需要同步,这种技术被称为线程封闭,它是实现线程安全性的最简单的方式之一.当某个对象封闭在一个线程中时,这种方法将自动实现线程安全性,即使被封闭的对象本生不是线 ...

  9. 《Java并发编程实战》第九章 图形用户界面应用程序界面 读书笔记

    一.为什么GUI是单线程化 传统的GUI应用程序通常都是单线程的. 1. 在代码的各个位置都须要调用poll方法来获得输入事件(这样的方式将给代码带来极大的混乱) 2. 通过一个"主事件循环 ...

随机推荐

  1. jvm jconsole

    JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.port=60001 -Djava.rmi.server.hostname=192. ...

  2. [转]无网络环境,在Windows Server 2008 R2和SQL Server 2008R2环境安装SharePoint2013 RT

    无网络环境,在Windows Server 2008 R2和SQL Server 2008R2环境安装SharePoint2013 RT,这个还有点麻烦,所以记录一下,下次遇到省得绕弯路.进入正题: ...

  3. Python 迭代对象、迭代器、生成器

    原文出处: liuzhijun 本文源自RQ作者的一篇博文,原文是Iterables vs. Iterators vs. Generators,俺写的这篇文章是按照自己的理解做的参考翻译,算不上是原文 ...

  4. 黄聪:国内com域名转移到Godaddy详细教程(转)

    原文:http://www.cnblogs.com/hsapphire/archive/2010/01/16/1649743.html 最近CCTV进行大量报道色情新闻,还举报CNNIC监管CN域名不 ...

  5. 阿里云VPS(win系统)装ROS教程

    以下方法是VPS下的WIN系统下安装ROS的方法,LINUX暂时没有 VPS系统装2003或2008 ,建议2008 启动快,安全,但以下内容是在2003上测试的, 2003系统,2003设置开机自动 ...

  6. 原生态JDBC问题的总结

    package com.js.ai.modules.aiyq.testf; import java.sql.Connection; import java.sql.DriverManager; imp ...

  7. scrapy与redis实战

    从零搭建Redis-Scrapy分布式爬虫 Scrapy-Redis分布式策略: 假设有四台电脑:Windows 10.Mac OS X.Ubuntu 16.04.CentOS 7.2,任意一台电脑都 ...

  8. OpenCL 图像卷积 2

    ▶ 上一篇图像卷积 http://www.cnblogs.com/cuancuancuanhao/p/8535569.html.这篇使用了 OpenCV 从文件读取彩色的 jpeg 图像,进行边缘检测 ...

  9. 迷你MVVM框架 avalonjs 1.3发布

    性能得到大幅改良的avalon1.3发布了. 修复$outer BUG 修复IE6-8下扫描加载Flash资源的OBJECT标签时,遇到它既没有innerHTML也没有getAttributeNode ...

  10. EL 和 JSTL

    EL 什么是EL表达式 EL(Express Lanuage) 表达式可以嵌入在jsp页面内部 减少jsp脚本的编写 EL出现的目的是要替代jsp页面中脚本的编写 作用区间 EL最主要的作用是获取四大 ...