并发编程实战 笔记

来源:互联网 发布:基因编辑 知乎 编辑:程序博客网 时间:2024/05/04 07:02

同步: sychronized,volatile ,显式锁(Explicit Lock),原子变量。

竞态条件(Race Condition):不恰当的执行时序,导致出现不正确的结果,这种情况称之为。

先检查后执行(check-then-act)

复合操作:“读取-修改-写入”、先检查后执行。

concurrent.atomic包,原子操作


正确性: 保证更新操作的原子性。

内置锁(Intrinsic Lock / 监视锁Monitor Lock)

重入:重入意味着,获取锁的操作的粒度是“线程”,而不是“调用”。例子:子类父类都有sychronized方法,子类中调父类的同步方法,是可以正常执行的。


安全性:程序不会出错

活跃性:程序按正常流程执行成功,注重程序执行完整性。

锁中代码如果是执行密集运算,或比较耗时操作时(IO)会影响性能,尽量不要在锁中执行此类程序。

性能和简单性有冲突。


3对象的共享

3.1可见性

失效数据:对同一对象,同一值进行修改与读取,要做到线程安全,修改与读取都要是同步方法。

非原子的64位操作:java内存模型要求,变量的写入和读取必须是原子操作。但是非volatile类型的long double等类型,JVM允许将64位变量的写入和读取分成两个32位的操作。多线程并发情况下,此变量可能会读取到一个值的高32位和另一个值的低32位,导致数据错误。

加锁的含义,不仅局限于互斥行为,还包括内存可见性。为了多线程看到共享变量的最新值,读写操作须在同一个锁上同步。

volatile变量保证可见性。加锁机制保证原子性与可见性。

3.2发布与逸出(Publish and Escape)

发布:使对象能够在当前作用域之外的代码中使用。

逸出:不应该被发布的对象,被发布了就叫逸出。

发布方式:公有静态变量,公有方法,内部类实例。

线程封闭(Thread confinement)

单线程内访问数据,不共享,称之为~。

3.3线程封闭

3.3.1Ad-hoc线程封闭

维护线程封闭性由程序实现来承担。

3.3.2栈封闭

只通过局部变量来访问对象,来达到栈封闭。局部变量固有属性之一就是封闭在执行线程当中。

3.3.3ThreadLocal

此类使某个值与保存此值的对象关联起来。每个使用此变量的线程都存有一份独立的副本。

3.4不变性

不可变对象是线程安全的

3.4.1 Final

3-12、3-13 volatile与对象引用例子

3.5安全发布

安全发布常用模式:

在静态初始化函数中,初始化一个对象的引用;

将对象的引用保存到volatile类型的域中,或是AtomicReference变量中;

将对象的引用保存到某个正确构造对象的final域中;

将对象的引用保存到一个由锁保护的域中;

4对象的组合

4.1设计线程安全的类

方法:找出构成对象状态的所有变量;

找出约束状态变量的不变性条件;

建立对象状态的并发访问管理策略;

4.2实例封闭

4.2.1Java监视器模式

私有锁而不是内置锁

4.3线程安全性的委托

4.4在现有的线程安全类中添加功能

4.4.1 客户端加锁机制
4.4.2组合 -委托

5 基础构建模块

5.1 同步容器类

5.1.1 同步容器类的问题

同步容器类是线程安全的,但在执行一些复合型操作时,需要客户端加锁来保护。如:迭代,若没有则添加等

5.1.2 迭代器与ConcurrentModificationException
5.1.3 隐藏的迭代操作

5.2 并发容器

concurrentHashMap/CopyOnWriteArrayList 迭代操作为主;

Queue PriorityQueue(优先队列) BlockingQueue(阻塞队列) 在生产者 - 消费者模式中应用

5.2.1 ConcurrentHashMap

Locking striping 分段锁

与Hashtable和 SynchronizedMap相比,ConcurrentHashMap有更多的优势 更少的劣势。大多数情况应使用ConcurrentHashMap

5.2.2 本身提供了 额外的原子操作函数
5.2.3 CopyOnWriteArrayList

用于替代同步list,仅当迭代操作远大于修改操作时用此类。

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

BlockingQueue  场景:洗盘子、盘架、烘盘子

5.3.1 桌面搜索
5.3.3 双端队列与工作密取 Deque,BlockingDeque

5.5 同步工具类

5.5.1 闭锁(Latch)CountDownLatch
5.5.2 FutureTask
5.5.3 信号量(Semaphore)

用来控制同时访问某一资源的操作数量,或是同时执行某个操作的数量。

5.5.4 栅栏(Barrier) 

阻塞一组线程,直到某个事件发生。

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

5.6 的缓存实现例子很经典,值得一看

二 结构化并发应用程序

6 任务执行

6.1在线程中执行任务

6.1.1 串行地执行任务


线程栈 地址空间。一个线程两个执行栈,一个执行java代码,一个执行原生代码。通常,jvm会生成一个复合的栈,大约0.5MB。32位机器,2的32次方 除以线程栈大小,线程个数会被限制在几千到几万。

6.1.2 为任务创建线程
6.1.3 无限制创建线程的不足

6.2 Executor框架

6.2.1 基于Executor的Web服务器
6.2.2 执行策略
6.2.3 线程池
6.2.4 Executor生命周期
6.2.5 延迟任务与周期任务
java 5.0以后 尽量不要用Timer。缺点:基于绝对时间,对系统时钟变化很敏感。

ScheduledThreadPoolExecutor代替Timer,基于相对时间的调度。
构建自己的调度服务,可以使用DelayQueue。

6.3 找出可利用的并行性

6.3.2 携带结果的任务callable 与 future
6.3.4 异构任务并行化中存在的局限
6.3.5 CompletionService : Executor 与 BlockingQueue
6.3.6 CompletionService页面渲染器
6.3.7 为任务设置时限 
future.get(timeout)
future.cancel()
6.3.8 eg.旅行预订门户网站

ExecutorService.invokeAll()

清晰的任务边界,或是同构任务比较容易实现并发。

复杂任务及异构任务需要分析任务的并行性,设计并行方案。

7 任务的取消与关闭

7.1 任务取消

任务取消策略(Cancellation Policy)、How,when,do what

7.1.1 中断

线程的中断是为了做取消任务使用的
阻塞库方法:Thread.sleep  obj.wait
interrupt并不会立即停止目标线程的运行,而只是发出中断的请求,由线程在下一个合适的时刻(取消点)中断自己。
通常,中断是实现取消最合理的方式。

7.1.2 中断策略

7.2 停止基于线程的服务
7.3 处理非正常的线程终止
7.4 JVM关闭

8 线程池的使用

8.1 在任务与策略间的隐含耦合
8.2 设置线程池的大小
8.3 配置ThreadPoolExecutor
8.4 扩展ThreadPoolExecutor
8.5 递归算法的并行化


9 图形用户界面应用程序

9.1 为什么GUI是单线程的
9.2 短时间GUI任务
9.3 长时间GUI任务
9.4 共享数据模型
8.5 其他形式的单线程子系统








0 0
原创粉丝点击