java并发实战读书笔记

2. 安全性

竟态条件(Race Condition);计算结果正确性取决于多个线程交替执行时序时,那么就会发生竟态条件;

  • 安全性 不发生任何错误的行为
  • 活跃性 某个正确的事情最终会发生

如何修复线程安全问题:

  • 1.不在线程间共享该状态变量
  • 2.将状态变量改为不可修改
  • 3.在访问状态变量是使用同步机制;

关于对象安全性总结

  • 1.无状态的对象一定是线程安全的;
  • 2.在并发线程中,保持状态的一致性,就是原子性;
  • 3.每个共享和可变的变量都应该由锁来保护,维护人员应该知道是哪一个锁;
  • 4.涉及到包含多个变量的不变性条件,其中涉及的所有变量都需要同一个锁来保护.

不良并发

3.对象的共享

3.1 可见性

重排序

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的功能:

  • 每个锁上存在多个等待,条件等待可以是可中断的或不可中断的,基于时限的等待

  • 公平的或非公平的队列操作

  • 当你选择lock时,必须选择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

  • AQS的状态
  • 维护指向线程的引用

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域可达的值,不能保证;

转载于:https://my.oschina.net/u/1590027/blog/3051866

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值