2. 安全性
竟态条件(Race Condition);计算结果正确性取决于多个线程交替执行时序时,那么就会发生竟态条件;java
- 安全性 不发生任何错误的行为
- 活跃性 某个正确的事情最终会发生
如何修复线程安全问题:web
- 1.不在线程间共享该状态变量
- 2.将状态变量改成不可修改
- 3.在访问状态变量是使用同步机制;
关于对象安全性总结算法
- 1.无状态的对象必定是线程安全的;
- 2.在并发线程中,保持状态的一致性,就是原子性;
- 3.每一个共享和可变的变量都应该由锁来保护,维护人员应该知道是哪个锁;
- 4.涉及到包含多个变量的不变性条件,其中涉及的全部变量都须要同一个锁来保护.
不良并发数据库
3.对象的共享
3.1 可见性
重排序windows
3.1.1 失效数据
3.1.2 非原子的64位操做
3.1.3 加锁与可见性
加锁不只保证互斥还保证可见性数组
3.1.4 volatile变量
volatile仅能保证可见性,不能保证原子性; 知足如下条件:缓存
- 1.对变量的写入操做不依赖变量的当前值,或者只有单个线程更新变量的值;
- 2.该变量不会与其余变量一块儿归入不变性条件中
3.2发布和溢出;
- 发布:对象能在当前做用域以外的代码中使用
- 溢出:不该该发布的对象发布就叫溢出;
安全发布安全
- 1.将对象保存到共有的静态变量中
- 2.从非私有方法返回一个引用
- 3.发布一个内部类的实例 需防止对象的this没有溢出;
3.3线程封闭
3.3.1 Ad-hoc线程封闭
线程封闭的职责由线程实现来承担;啥都不加;服务器
3.3.2 栈封闭
局部变量网络
3.3.3 ThreadLocal
线程的全局变量,在线程内部共享,线程结束后回收;
3.3.4 不变对象
1.建立后状态不能被修改 2.对象全部域都是final类型 3.对象正确建立.()
3.4 不变性
不可变的对象必定是线程安全的
3.4.1 final域
3.4.2 使用volatile发布不可变域
3.5 安全发布
3.5.1 不正确的发布:正确的对象被破坏
3.5.2 不可变对象与初始化安全性
3.5.3 安全发布的经常使用模式
- 1.在静态初始化函数中初始化一个对象;
- 2.将对象保存到volatile类型的域中或AtomaticReference对象中
- 3.将对象的引用保存到某个正确构造对象的final类型域中
- 4.将对象的引用保存到一个由锁保护的域中 共享对象的策略:线程封闭,只读共享,线程安全共享,保护对象;
4 对象的组合
经过对象组合设计出更大的组件或程序
4.1 设计线程安全的类
包含如下几点:
- 1.找出构成对象状态的全部变量;
- 2.找出约束状态变量的不变性条件.
- 3.创建对象状态的并发访问管理策略. 同步策略:在不违背对象不变条件和后验条件性的前提对其状态访问进行协同
4.1.1 收集同步需求
4.1.2 依赖状态的操做
须要加锁
4.1.3 状态全部权
通常对象,容器类对象;发布对象;
4.2线程封闭
封闭在做用域,线程,私有成员;
4.3线程安全性的委托
即便都是线程安全的对象,组成在一块儿也不必定是线程安全的对象组合;
4.3.1 在容器里的变量
4.3.2 独立的状态变量
4.3.3 委托失效
不符合先验条件,错误使用了volatile
4.3.4 发布底层的状态变量
4.4在现有的线程安全类中添加功能
4.4.1 客户端加锁机制
不推荐
4.4.2 组合
推荐
4.5将同步策略文档化
将哪些变量声明为volatile类型,哪些变量用锁保护,哪些锁保护哪些变量,那些变量必须是不可变的或者被封闭到线程中的,那些操做必须是原子操做; 最好在接口上书写线程安全的文档
5.基础构建模式
5.1 同步容器类
5.1.1 同步容器类的问题
迭代,跳转,条件计算都会出现相似问题
5.1.2 迭代器与concurrentModificationException
避免该状况需对容器加锁
5.1.3 隐藏迭代器
5.2 并发容器
- concurrentHashMap;
- queue:concurrentLinkedQueue,PriorityQueue;
- concurrentSkipListMap(替代sortMap),ConcurrentSkipListSet(替代sortSet); blockingQueue;
5.2.1 ConcurrentHashMap
5.2.2 concurrentHashMap扩展的接口
- V putIfAbsent(K key,V value); 没有值才能插入
- boolean remove(K key,V value); 仅当k被V映射时才能移除
- boolean replace(K key,V oldValue,V newValue); 仅当K被映射到oldValue时才替换为newValue
- V replace(K key,V newValue);
5.2.3 CopyOnWriteArrayList
写入时复制;适用于写入少读取多的状况
5.3 阻塞队列和生产者-消费者模式
put,get;定时的offer和poll, 相似线程池和工做队列的关系; LinkedBlockingQueue,ArrayBlockingQueue:FIFO; PriorityBlockingQueue; 足够多的的消费者,有一个生产者时,才能使用同步队列;
5.3.2 串行线程封闭
确保对象只有一个线程获取;
5.3.3 双端队列和工做密取
deque,blockingDeque;密取时从队尾去获取线程;
5.4 阻塞方法与中断方法
传递或恢复中断
5.5 同步工具类
5.5.1 闭锁(CountDownLatch)
await(),countDown();
5.5.2 futureTask(必须执行完成)
futureTask.get()会抛出异常Throwable,须要处理;
5.5.3 信号量
Semaphore; acquire()获取信号,release()释放信号
5.5.4 栅栏
Barrier,CyclicBarrier; await();当栅栏剩余数量达到0时,会通知主线程执行;
6.任务执行
6.1在线程中执行任务
清晰的任务边界,明确的任务执行策略
6.1.1 串行的执行任务
6.1.2 显式的为任务建立线程
6.1.3 无限制建立线程的不足
- 1.线程生命周期的开销很是高.
- 2.资源消耗.线程闲置,占用内存.
- 3.稳定性.易被攻击;
6.2 Executor框架
基于简单的生产者消费者框架;使用Runnable执行任务;
6.2.1 示例:基于Executor的web服务器
6.2.2 执行策略
包括:什么线程,执行顺序,并发数量,等待数量,拒绝方案;任务完成先后的事件
6.2.3 线程池
好处:
- 1.节省线程建立销毁的开销;
- 2.提升线程响应
- 3.防止线程相互竞争致使oom
建立方法: newFixedThreadPool,newCachedThreadPool,newSingleThreadExecutor,newScheduledThreadPool;
6.2.4 关闭任务池
shutdown(),isShutdown();shutdownNow();isTerminated(),awaitTermination; ExecutorService的生命周期:运行,关闭,终止;
6.2.5 延迟任务与周期任务
使用ScheduledThreadPool来代替Timer执行
6.3 找出可利用的并行性
6.3.1 实例:串行的页面渲染器
6.3.2 携带结果的任务callable与Future
- Future能够查看任务状态,取消任务,获取任务结果;
- boolean cancel(boolean mayInterruptRnning);
- boolean isCancelled();
- boolean isDone();
- v get();
- v get(long timeout,TimeUnit,unit);
6.3.3 使用future实现页面渲染器
6.3.4 在异构任务并行化中存在的局限
当大量异构任务能够并发处理时,才能带来性能真正的提高;
6.3.5 CompletionService: Executor与BlockingQueue
多个completionService可共享一个Executor;将结果存入blockingQueue
6.3.7 为任务设置时限
若是超过期限,报错TimeOutException,则应该当即终止或取消该任务;
6.3.8 示例:旅行预订门户网站
executorService.invokeAll(List<Callable>,time,unit); 所有定时执行
7.取消和关闭
运行良好的软件能处理好失败,关闭,取消等过程;
7.1 任务取消
用户请求取消,有时间限制的操做,应用程序事件,错误,关闭;
7.1.1 中断
使用interrupt中断
7.1.2 中断策略
须要捕获了InterruptedException以后恢复中断状态
7.1.3 响应中断
7.1.5 经过future来实现取消
future.cancel();
7.1.6 处理不可中断的阻塞
同步socket I/O,同步I/O;
7.2 中止基于线程的服务
若是服务的时间大于线程存在的时间,那就应该提供关闭服务的方法
7.2.1 示例:日志服务
7.2.2 关闭ExecutorServie
shutdown()关闭,shutdownNow
7.2.3 毒丸对象
7.2.4 只执行一次的服务
shutdown(),awaitTermination(timeout,unit);
7.2.5 shutdownNow的局限性
7.3 处理非正常的线程终止
未捕获的异常;只有execute()执行中抛出的异常才是未捕获异常;
7.4 jvm关闭
7.4.1 关闭钩子
正常的关闭:关闭钩子->if(finalizer),执行finalizer; Runtime.getRuntime().addShutdownHook(Thread);注册关闭钩子
7.4.2 守护线程
守护线程在jvm中止时,会直接抛弃,不会执行finally代码块
7.4.3 终结器
只适用于管理那些资源是经过本地方法获取的对象;
8 线程池的使用
8.1 在任务与执行策略之间的隐性耦合
依赖性任务;线程封闭的任务;对响应时间敏感的任务,使用ThreadLocal的任务;
8.1.1 线程饥饿死锁
依赖性任务,依赖相关任务的完成
8.1.2 运行时间较长的任务
可堵塞方法的现时不限时版本:Thread.join,BlockingQueue.put,CountDownLatch.await,Selector.select;
8.2 设置线程池的大小
- 过大会竞争,太小会闲置;
- 计算密集型:通用:cpu个数+1;
- 更精细的判断: cpu个数cpu利用率(1+计算等待时间/计算时间)
8.3 配置ThreadPoolExecutor
new ThreadPoolExecutor(args...)
8.3.1 线程的建立和销毁
- 基本容量
- 最大容量
- 存活时间; 超过存活时间,会被回收;
fixedThreadPool线程池基本容量大小和最大容量同样大; newCachedThreadPool的线程池基本容量为0,最大容量为Integer.MAX_VALUE;
8.3.2 管理队列任务
-
用blockingQueue来保存等待任务;包括:无界队列,有界队列,同步移交
-
fixedThreadPool,singleThreadExecutor使用无界的LinkedBlockingQueue;
-
synchronousQueue 适合无界的线程池,好比cachedThreadPool
-
PriorityBlockingQueue,根据优先级排序;
-
线程依赖性,则选择CachedThreadPool;
8.3.3 饱和策略
经过setRejectedExecutionHandler(ThreadPoolExecutor.CallerRunsPolicy)来修改
-
终止(abort);默认饱和策略;抛出RejectExecutionException;注意抛弃最旧的策略与优先级队列(Discaord-Oldest)
-
调用者运行策略; 超出后在主线程执行该任务,这段时间不接受新的任务,其余任务提交到tcp层的队列中;
8.3.4 线程工厂
- 实现ThreadFactory接口
- 设计本身的Thread类; extend Thread;
8.3.5 在调用构造函数后再定制 ThreadPoolExecutor
- 将ExecutorService转型为ThreadPoolExecutor进行设置;
- unconfigurableExecutorService;返回一个代理,且不能对他设置
8.4 扩展 ThreadPoolExecutor
可重写的方法:beforeExecute,afterExecute(任务完成带有Error,则不执行),terminated;
8.5 递归算法的并行化
- 循环算法可使用并行化
- 递归循环也可使用并行化技术
- 使用Latch(闭锁)来接受答案,控制整个流程;使用concurrentHashMap来控制保存遍历印记避免重复循环;
9 GUI使用 略;
第三部分 活跃性,性能与测试
10.避免活跃性的危险
- 顺序死锁(Lock-Ordering deadlock)
- 资源死锁(Resource deadlock)
10.1 死锁
死锁状况:每一个人都拥有其余人须要的资源,同时又等待其余人已经拥有的资源,而且每一个人在获取全部须要的资源以前都不会放弃已经拥有的资源;
10.1.1 锁顺序死锁
两个线程以不一样的顺序获取相同的锁
10.1.2 动态锁的顺序死锁
- 两个锁,可能会相互交换.须要有序的调用锁.
- System.identityHashCode(Object)获取hash值.
- 比较hash值大小,肯定加锁顺序
- 若是相等,则先获取第三方变量(中立者)的锁;
10.1.3 在协助对象之间发生的死锁
若是持锁时调用外部方法,那么将出现活跃性问题.若是外部方法须要获取其余的锁,就可能致使死锁产生.
10.1.4 开放调用
- 在调用某方法时不须要持有锁,那么就叫开放调用;
- 收缩同步代码块的保护范围
- 服务关闭;服务关闭期间一直持有服务的锁,直到关闭完成才释放锁;
10.1.5 资源死锁
- 资源相互等待死锁
- 线程饥饿死锁; 线程A依赖线程B的执行结果
10.2 死锁的避免和诊断
- 尽可能减小潜在的加锁交互的数量,将获取锁时须要遵循的协议写入正式文档并始终遵循这些协议
- 两阶段策略:找出获取多个锁的地方,对这些实例进行全局分析,确保他们在整个程序中获取锁的顺序都保持一致;
10.2.1 支持定时的锁
10.2.2 经过线程转储信息来分析死锁
10.3 其余活跃性危险
饥饿,丢失信号,活锁
饥饿
糟糕的响应性
后台任务与前台任务的竞争;大容器的迭代
10.3.3 活锁
- 重复执行,排在消息队列的头部,但没法完成执行
- 过分的代码恢复机制
- 须要在重试机制添加随机性
11.性能和可伸缩性
应该保证程序正确运行,在提高程序性能
11.1对性能的思考
- 受限:cpu,io,磁盘,内存,带宽,数据库请求,
- 线程额外开销:线程之间协调(加锁,触发信号,内存同步),上下文切换,线程建立和销毁,线程调度;
11.1.1 性能与可伸缩性
性能:
- 运行速度(服务时间,等待时间)
- 处理能力(生产量,吞吐量)
可伸缩性:增长计算资源时,程序吞吐量或处理能力相应的增长;
11.1.2 评估各类性能权衡因素
Amdahl 定律
按母达尔定律,增长处理器得到的最高加速比,这个值取决于程序中可并行组件与串行组件所占的比例;
Speedup<= 1/(F+(1-F)/N)
F指必须串行程序占的比例,N指处理器数量;理论最大值是,1/F
- 全部的并发程序都包含串行的部分,若是你的程序没有,则须要仔细检查一下;
11.2.2 Amdahl定律的应用
找到加速比;串行代码
线程引入的开销
11.3.1 上下文切换
unix系统的vmstat命令和windows系统的perfmon工具能够报告上下文切换次数及在内核中执行时间所在比例;
11.3.2 内存同步
- 内存栅栏会刷新缓存,使缓存无效,刷新硬件的写缓存 jvm会进行一些优化,去掉无用的同步;
- 对synchronzed对无竞争的同步作了优化
- 略过不会发生竞争的锁
- 经过溢出分析找出线程本地变量,他们不会进行同步加锁操做;
- 锁粒度粗化操做,把临近的同步代码块用一个锁合并起来
11.3.3 阻塞
- 竞争锁失败会自旋或挂起
- 挂起包含两次上下文切换开销,必要的操做系统操做和缓存操做
11.4 减小锁的竞争
- 减小锁的持有时间
- 下降锁的请求频率
- 使用带有协调机制的独占锁,这些机制容许更高的并发性'
11.4.1 缩小锁的范围(快进快出)
- 把一个同步代码块分解为多个时,并不能提高性能,jvm优化
- 只有把一些大量的计算和阻塞IO移出才能够
11.4.2 锁分解
11.4.3 锁分段
11.4.4 避免热点域
- 以计数器为例,每一个元素发生变化都会访问,这就是一个热点域.
- 经过计数分段的方法实现锁分段
11.4.5 一些替代独占锁的方法
- readWriteLock;读取多,写入少
- 原子变量下降热点域; 静态计数器,序列发生器,链表头节点引用
11.4.6 检测cpu的利用率
负载不均匀的缘由
- 负载不充足
- I/O 密集
- 外部限制,网络限制
- 锁竞争
11.4.7 向对象池说不
11.5 实例:比较map的性能
11.6 减小上下文切换的开销
当日志消息转到另外一个线程进行处理
12 并发程序的测试
- 安全性,活跃性两点;
- 活跃性,包括进展测试,无进展测试两点
- 性能测试:吞吐量,响应性,可伸缩性
12.1 正确性测试
12.1.1 基本的单元测试
12.1.4 资源管理的测试
容器应该及时清除无用的对象;
12.1.5 使用回调
12.1.6 产生更多的交替操做
12.2 性能测试
12.2.1 在putTakeTest中增长计时功能
12.3 避免性能测试的陷阱
12.3.1 垃圾回收
12.3.2 动态编译
12.3.3 对代码路径的不真实采样
12.3.4 不真实的竞争程度
12.3.5 无用代码的消除
12.4 其余测试方式
12.4.1 代码审查
12.4.2 静态分析工具
findbugs的一些检查模块:
- 不一致的同步
- 调用Thread.run
- 未被释放的锁
- 空的同步块
- 双重检查加锁
- 在构造函数中启动一个线程
- 通知错误
- 条件等待中的错误
- 对Lock和Condition的误用
- 在休眠或者等待的同时持有一个锁
- 自旋循环
12.4.3 面向切面的测试技术
12.4.4 分析与检测工具
13.显式锁
13.1 Lock与ReentrantLock
Lock提供了更灵活的加锁机制,
- void lock()
- void lockInterruptibly()
- boolean tryLock()
- boolean tryLock(long timeout,TimeUnit unit)
- void unlock()
- Condition newCondition();
13.1.1 轮询锁与定时锁
经过tryLock()实现
可中断的锁获取操做
lockInterruptibly()在中断的同时保持对中断的响应
13.1.3 非块结构的加锁
锁分解,锁分段
13.2 性能考虑因素
性能不是一成不变的,受cpu,处理器数量,缓存大小以及jvm特性等的影响
13.3 公平性
- 大部分时间,公平性的锁性能要小于非公平性的锁
- 缘由在于,线程唤醒不能当即运行,会形成锁获取的延迟和堵塞
13.4 在synchronized和ReentrantLock之间的进行选择
通常默认选择synchronized,除非用你要使用ReentranceLock的新特性
13.5 读-写锁
- 读写锁可使多个线程并发的访问被保护对象,以读取操做为主时,能够提升程序的可伸缩性
- 获取写入锁时,其余线程没法获取读取锁
- 写入锁线程能够降级,但读取锁线程没法升级;
14 构建自定义的同步工具
14.1 状态依赖性的管理
14.1.1 实例:将前提条件的失败传递给调用者
14.1.2 示例:经过轮询与休眠来实现简单的堵塞
14.1.3 条件队列
14.2 使用条件队列
14.2.1 条件谓词
- 条件谓词是使某个操做成为状态依赖操做的前提条件
- 将在条件队列相关联的条件谓词以及相关操做写入文档
14.2.2 过早唤醒
notify,notifyAll,没法判断是哪一个线程发出的唤醒标志;当使用条件等待时(Object.wait或Condition.await)
- 一般有一个条件谓词-包括对一些对象状态的测试,线程在执行前必须首先经过这些测试
- 在调用wait前测试条件谓词,而且从wait中返回时再次进行测试
- 在一个循环中使用wait
- 确保使用与条件队列相关的锁来保护构成条件谓词的各个状态变量
- 当调用wait,notify或notifyAll等方法时,必定要持有与条件队列相关的锁
- 在检查条件谓语以后以及开始执行相应的操做以前,不要释放锁
14.2.3 丢失的信号
信号可能会丢失,处理方案同14.2.2便可;
14.2.4 通知
- 每当在等待一个条件时,必定要确保在条件谓词变为真时经过某种方式发出通知.
- 应该优先使用notifyAll()
只有知足如下条件,才使用notify();
- 全部等待线程的类型都相同; 只有一个条件谓词和相关条件队列,每一个线程从wait返回后执行相同的操做
- 单进单出
单次通知和条件通知都属于优化措施;
14.2.5 示例:阀门类
在条件变化时,必须发起通知;
14.2.6 子类的安全问题
- 若是违背了条件通知或者单次通知的某个需求,那么在子类中能够增长合适的通知机制来表明基类
- 等待和通知等协议彻底向子类公开;公开条件队列和锁,状态变量,将条件谓词和同步策略都写入文档
- 彻底禁止子类化;
14.2.7 封装条件队列
有时条件队列也是锁的一种
14.2.8 入口协议和出口协议
- 入口协议:该操做的条件谓词
- 出口协议:检查该操做修改的全部状态变量,并确认他们是否使某个其余的条件谓词变成真,若是是,则通知相关的条件队列
14.3 显示的Condition对象
condition是一种广义的内置条件队列
- void await()
- boolean await(long time,TimeUnit unit)
- long awaitNanos(long nanosTimeout)
- void awaitUninterruptibly()
- boolean awaitUntil(date deadline)
- void signal()
- void signalAll()
Condition的功能:
14.4 Synchronizer剖析
都是基于AQS构建的
14.5 AbstractQueuedSynchronizer
-
getState,setState,compareAndSetState
-
acquire,release,isHeldExclusively
-
acquireShared,relseShared,
14.6 java.util.concurrnt同步器类中的AQS
14.6.1 ReentrantLock
tryAcquire为例:
- 初始化,1.持有者owner;2.state(也是被持有的数量)为0;
- 判断:state==0,先尝试用cas更新state,成功则设置owner,返回true
- status!=0,则判断当前线程==ownner?state++:return false;
14.6.2 Semaphore与CountDownLatch
114.6.3 FutureTask
14.6.4 ReentrantReadWriteLock
15 原子变量和非阻塞同步机制
原子类不只支持可见性,还支持原子性操做;
15.1 锁的劣势
- 竞争激烈,调度消耗会很是大,调度开销和工做开销的比例会很是高
- 若是持有锁的线程,有活跃性问题(死锁,活锁,无限等待),那么全部锁相关的线程都将阻塞
15.2 硬件对并发的支持
处理器直接支持,好比原子的测试并设置,比较并递增,比较并交换
15.2.1 比较并交换
- 首先从V中读取值A,并根据A计算新值B
- 经过cas以原子的方式直接将V中值由A变成B;
- 若是有竞争,其余线程竞争失败,则告知他们.并让他们决定是否重试
15.2.2 非阻塞的计数器
-
使用锁,须要jvm去遍历代码路径;甚至操做系统级的锁定,线程挂起,切换上下文
-
使用cas,能省去这步骤
-
缺点:须要调用者处理竞争问题(经过重试,回退,放弃),在锁中能自动处理;
15.2.3 JVM对cas的支持
提供了不少类型的原子类型支持;
15.3 原子变量类
- 四组: 标量类,更新器类,数组类,复合变量类
- 标量类:AtomicLong,AtomicInteger,AtomicBoolean,AtomicReference
- 原子数组类(只支持Integer,Long,Reference),对立面的元素可见
- 原子标量类不该该用作散列容器中的key
15.3.1 原子变量是一种"更好的volatile"
15.3.2 性能比较:锁与原子变量
- 在中低强度的竞争下,原子变量提供更高的可伸缩性
- 在高强度的竞争下,锁可以更有效的避免竞争;
15.4 非堵塞算法
- 非阻塞算法;在某种算法中,一个线程的失败或挂起不会致使其余线程也失败或挂起;技巧在于,将原子修改的范围缩小到单个变量上
- 无锁算法(lock-free):算法的每一个步骤都存在在每一个线程可以执行下去;
- 非阻塞算法不会出现死锁和优先级反转的问题
15.4.1 非阻塞的栈
- top做为头元素,其为原子类
- push,pop都将会更新top,若是不成功,则重试;
15.4.2 非阻塞的链表
-
dummy节点,初始时做为头节点head和尾节点tail
-
循环:1.判断tail.next!=null,则将tail使用cas设置为tail.next;这是一个清理工做;
-
2.判断tail.next==null,则cas(tail.next)成功,则cas(tail)返回true
-
3.即便2中cas(tail)错误,但下次put操做,会执行1步骤,会成功更新tail;并发中快速实现更新;
15.4.3 原子的域更新器
- AtomicReferenceFieldUpdater;
- 执行原子更新的同时还须要维持现有类的串行化形式那么原子的更新器将很是有用
15.4.4 ABA问题
- AtomicStampedReference,AtomicMarkableReference
- 在引用上加版本号,从而避免ABA问题.
16 java内存模型
16.1 什么是内存模型,为何须要它
16.1.1 平台的内存模型
jvm须要在内存插入内存栅栏屏蔽在JMM与底层平台内存模型之间的差别,才能获得该变量正确的值
16.1.2 重排序
16.1.3 java内存模型简介
Happens-Before的规则(偏序关系):
- 程序顺序规则;
- 监视器锁规则;获取以前必须限释放
- volatile变量规则;写入在读取以前
- 线程启动规则; 线程先启动,里面的代码才能执行
- 线程结束规则;对线程的任何操做,要在线程结束以前完成;
- 中断规则
- 终结器规则,构造函数在终结器以前执行完成.
- 传递性, A>B,B>C,A>C
16.1.4 借助同步
各类同步工具都借用了Happer_before的原则;
- 队列
- semaphore ,acquire,release()
- countDownLatch ,await,countDown()
- futureTask
- executor.execute
- cyclicBarrier
16.2 发布
16.2.1 不安全的发布
16.2.2 安全的发布
16.2.3 安全初始化模式
16.2.4 双重检查加锁
DCL已被抛弃,无竞争获取锁时速度已很是快
16.3 安全初始化
初始化安全性只能保证final域可达到的值从构造过程完成时开始的可见性.其余的非final域可达的值,不能保证;