Java并发编程实战:第16章 总结

  • Java 存储模型明确地规定了在什么时机下,操作存储器的线程的动作可以保证被另外的动作看到。
  • 规范还规定了要保证操作是按照一种偏序关系进行排序。这种关系称为 happens-before,它是规定在独立存储器和同步操作的级别之上的。
阅读更多

Java并发编程实战:第16章 存储模型

1. 什么是存储模型,要它何用

1
value = 100

在多线程环境中,要获取到 value 的正确值,需要用到同步。

  • 编译器生成指令的次序,可以不同于源代码的所暗示的”显然”版本,而且编译器还会把变量存储在寄存器,而不是内存中;
阅读更多

Java并发编程实战:第15章 总结

  • 比较并交换(compare-and-swap):当多个线程试图使用比较并交换(CAS),同时更新相同的变量时,其中一个线程会胜出,并且更新变量的值,而其它的线程都会失败。失败的线程允许尝试再次比较并交换操作。一个线程在竞争 CAS 时失败,不会被阻塞,它可以决定是否重试,这样的灵活性大大减少了锁相关的活跃度风险。
  • 原子变量类,提供了广义的 volatile 变量,以支持原子的、条件的读-写-改操作。原子变量是“更佳的volatile”
阅读更多

Java并发编程实战:第15章 原子变量与非阻塞同步机制

1. 锁的劣势

volatile 变量

  • volatile 变量与锁相比是更轻量的同步机制,因为它们不会引起上下文的切换和线程调度。
  • 然而,volatile 变量与锁相比有一些局限性:尽管它们提供了相似的可见性保证,但是它们不能用于构建原子化的复合操作。
  • 当一个变量依赖其他变量时,或者当变量的新值依赖于旧值时,是不能用 volatile 变量的。这些都限制了 volatile 变量的使用,因此它们不能用于实现可靠的通用工具,比如计数器,或互斥体(mutex)
阅读更多

Java并发编程实战:第14章 总结

  1. 使用 Object 超类中的 wait()、notify()、notifyAll()等方法管理条件队列
  2. 如果说 Lock 是显式的 Synchroinzed,那么也可以认为 Condition 也是一种显式的条件队列 —- Condition 提供了类似 wait()、notify()、notifyAll() 且更全面的方法
阅读更多

Java并发编程实战:第14章 构建自定义的同步工具

1. 管理状态依赖性

1.1 管理状态依赖性

  • 状态依赖性指某种操作必须依赖于指定的状态才可以执行。比如一个阻塞队列的take方法依赖于这个阻塞队列中有至少一个元素这个状态。
  • 如果一个状态依赖性操作所依赖的状态不满足,通常有几种处理办法:
    1. 抛出异常
    2. 使用某种约定的错误返回值
阅读更多

Java并发编程实战:第13章 总结

  • 显式的 Lock 与内部锁 synchronized 相比提供了一些扩展的特性,包括处理不可用的锁时更好的灵活性,以及对队列行为更好的控制。
  • ReentrantLock 与 synchronized各有优缺点。
阅读更多

Java并发编程实战:第13章 显式锁

1. Lock 和 ReentrantLock

Lock 接口:

1
2
3
4
5
6
7
8
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}

Lock 提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有加锁和解锁的方法都是显式的。

阅读更多

Java并发编程实战:第12章 测试并发程序

1. 测试正确性

1.1 测试并发程序

并发程序打造可以分为两类:

  1. 安全性测试
  2. 活跃性测试
    • 进展性测试
    • 无进展测试

与活跃性测试相关的事性能测试。性能的衡量:

  • 吞吐量
  • 响应性
  • 可伸缩性
阅读更多

Java并发编程实战:第11章 总结

  • 性能可伸缩性: 多线程能充分的发挥出闲置的处理能力,使现有任务正在运行的情况下立刻开始着手处理新的任务,提高系统的响应性。

  • Amdahl 定律:

    N 表示处理器数量,F表示串行化任务的比重。

阅读更多

Java并发编程实战:第11章 性能可伸缩性

1. 性能可伸缩性

  • 使用多线程可以使程序更充分的发挥出闲置的处理能力,从而更好地利用资源;
  • 使用多线程,还能够使程序在现有任务正在运行的情况下立刻开始着手处理新的任务,从而提高系统的响应性。
阅读更多

Java并发编程实战:第10章 总结

  • 死锁:静态的锁顺序死锁、动态的锁顺序死锁、协作对象之间发生的死锁、资源死锁。
  • 在发生死锁时,可以通过线程转储 — 如使用命令 jcmd PID号 Thread.print 输出线程和锁信息,分析死锁原因。
阅读更多

Java并发编程实战:第9章 总结

  • GUI框架几乎都是作为单线程化子系统实现的,所有与表现相关的代码都作为任务在一个事件线程中运行。
  • 因为只有唯一一个线程,耗时任务会损害响应性,所以它们应该在后台线程中运行。
阅读更多

Java并发编程实战:第9章 GUI应用程序

1. GUI 应用程序

  • 几乎所有的GUI工具集都是单线程化的子系统,意味着所有GUI的活动都被限制在一个单独的线程中。
  • 我们应该避免在事件线程中执行耗时操作,以免UI失去响应。
  • Swing 的数据结构不是线程安全的,所以在使用它时必须小心的把他们限制在事件线程中。
阅读更多

Java并发编程实战:第8章 总结

  • 对于并发执行的任务,Executor 框架是强大且灵活的。
  • ThreadPoolExecutor 提供了大量可调节的选项,比如创建和关闭线程的策略,处理队列任务的策略,处理过剩任务的策略,并且提供了几个钩子函数用于扩展它的行为。
    • 钩子函数:terminated()、afteExecute()、beforeExecute()
阅读更多

Java并发编程实战:第8章 应用线程池

1. 应用线程池

1.1 在任务与执行策略之间的隐性耦合

Executor 框架可以将任务的提交任务的执行策略解耦开来。Executor 框架为制定和修改执行策略提供了相当大的灵活性,但并非所有的任务都能适用所有的执行策略。 有些类型的任 务需要明确地指定执行策略, 包括:

  1. 依赖性任务:简单来说就是,提交的 task 是需要依赖 其它任务 的, task 就类似有某种枷锁一样,浑身不自在。

    • 大多数任务都是独立的,它们不依赖于其他任务的执行时序、 执行结果或其他效果。
    • 当在线程池中执行独立的任务时, 可以随意地改变线程池的大小和配置,这些修改只会对执行性能产生影响。
    • 如果提交给线程池的任务需要依赖其他的任务, 那么就隐含地给执行策略带来了约束, 此时必须小心地维持这些执行策略,以避免产生活跃性问题。
阅读更多

Java并发编程实战:第7章 总结

  1. 任务、线程、服务以及应用程序在生命周期结束时的问题,可能会导致向它们引入复杂的设计和实现。
  2. Java 没有提供具有明显优势的机制来取消活动或者终结线程。
阅读更多