对象是过程的抽象,线程是调度的抽象。
编写整洁的并发程序很难
1. 为什么要并发
并发是一种解耦策略。帮助我们把做什么(目的)和何时(时机)做分
解开。 解耦目的与时机能明显地改善应用程序的吞吐量和结构。
并发:
并发会在性能和编写额外代码上添加一些开销
正确的并发是复杂的,即便对于简单的问题也是如此。
并发缺陷并非总能重现
并发常常需要对设计策略的根本性修改
2 并发防御原则:
- 2.1 单一权责原则(SRP):
方法/类/组件应当只有一个修改的理由
并发设计自身足够复杂到成为修改的理由,所以也该从其他代码中分离出来。
考虑问题:
并发相关代码有自己的开发、修改和调优生命周期
开发相关代码有自己要对付的挑战,和非并发相关代码不同,会更困难
没有外在影响,写的不好的并发代码出错也会很多。
建议:
分离并发相关代码与其他代码。 - 2.2 限制数据作用域
采用synchronized关键字在代码中保护一块使用共享对象的临界区 - 2.3 使用数据复本
避免共享数据的好方法之一避免共享数据。 只读方式创建复本,然后收集复本合并。 - 2.4 线程应尽可能地独立
建议:
尝试将数据分解到可被独立线程(可能在不同处理器上)操作的独立子集。
3 使用java 线程库。
使用executor框架执行无关任务。 尽可能使用非锁定解决方案
建议:
检读可用的类: 对于Java,掌握java.util.concurrent、 java.util.concurrent.atomic、 java.util.concurrent.locks,
ReentrantLock 可在一个方法中获取、在另一个方法中释放的锁
Semaphore 经典的“信号”的一种实现,有计数器的锁
CountDownLatch 在释放所有等待的线程之前,等待指定数量事件发生的锁,这样,所有线程都平等地几乎同时启动。
4 了解执行模型
基础定义:
限定资源, 互斥, 线程饥饿, 死锁, 活锁
执行模型:
生产者-消费者模型 (之间的队列就是一种限定资源)
读者-作者模型 宴席哲学家
如何找到必须锁定的代码区域并锁定
5 警惕同步方法之间的依赖
避免使用一个共享对象的多个方法。但有时必须使用一个共享代码的多个方法,有三种写对代码的手段:
- 基于客户端的锁定
客户端代码在调用第一个方法前锁定服务器,确保锁的范围覆盖了调用使用最后一个方法的代码
- 基于服务端的锁定
在服务器内创建锁定服务器端的方法,调用所有方法,然后解锁。让客户端调用新方法。
- 适配服务端
创建执行锁定的中间层。这是一种基于服务器的锁定的例子,但不修改原始服务端代码。
6. 保持同步区域微小
7. 尽早考虑关闭问题,尽早令其工作正常。
8. 测试线程代码。
建议:
不要将系统错误归咎于偶发事件
不要同时追踪非线程缺陷和线程缺陷。确保代码在线程之外可工作。
编写可拔插的线程代码,这样就能在不同配置环境下运行
尽早并经常地在所有目标平台上运行程序代码
两种装置代码的方式: 硬编码 , 自动化(使用Aspect-Oriented Framework, CGLIB, ASM等工具来编程来装置代码)