第一章
- 线程会共享进程范围内的资源,例如内存句柄和文件句柄,但每个线程都有各自的程序计数器(Program Counter)、栈以及局部变量。
- 通过使用线程,可以将复杂并异步的工作进一步分解为一组简单并且同步的工作流,每个工作流在一个单独的线程中运行,并在特定的同步位置进行交互。现有的一些框架可以实现上述目标,例如Servlet和RMI(Remote Mothod Invocation,远程方法调用)。
- 安全性的含义是“永远不发生糟糕的事情”,而活跃性则关注另一个目标,即“某件正确的事情最终会发生”。当某个操作无法继续执行下去时,就会发生活跃性问题。在串行程序中,活跃性问题的形式之一就是无意中造成的无限循环。
- 与活跃性问题密切相关的是性能问题。活跃性意味着某件正确的事情最终会发送,但却不够好,因为我们希望正确的事情尽快发送。
第二章
- 要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问。
- 如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误,有3中方式可以修复这个问题:
i. 不在线程之间共享该变量。
ii. 将状态变量修改为不可变的变量。
iii. 在访问状态变量时使用同步。 - 在编写并发代码时,应始终遵循一个原则:首先使代码正确运行,然后才提高代码的运行速度。即便如此,最好也是当性能测试结果和应用需求告诉你必须提高性能,以及测量结果表明这种优化在实际环境中确实能带来性能提升时,才进行优化。
- 在线程安全性的含义中,最核心的概念就是正确性,而正确性的含义是:某个类的行为与其规范完全一致。在良好的规范中通常会定义各种不变性条件(Invariant)来约束对象的状态,以及定义各种后验条件(Postcondition)来描述对象操作的结果。
- 当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。
- 无状态对象一定是线程安全的。无状态:既不包含任何域,也不包含任何对其他类中域的引用。
- 竞态条件:在并发编程中,这种由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况。
- 假定有两个操作A和B,如果从执行A的线程来看,当另一个线程执行B时,要么将B全部执行完,要么完全不执行B,那么A和B对彼此来说都是原子的。原子操作是指,对于访问同一个状态的所有操作(包括该操作本身)来说,这个操作是一个以原子方式执行的操作。
- 复合操作:包含了一组必须以原子方式执行的操作以确保线程安全性。
- 在实际情况中,应尽可能使用现有的线程安全对象(例如AtomicLong)来管理类的状态。与非线程安全的对象相比,判断线程安全对象的可能状态及其状态转换情况要更为容易,从而也更容易验证和维护线程安全性。
- 每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock)。
- Java的内置锁相当于一种互斥体(或互斥锁),这意味着最多只有一个线程能持有这个锁。
- 内置锁是可以重入的,因此如果某个线程试图获得一个已经有它自己持有的锁,那么这个请求就会成功。
- 对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,我们称状态变量是由这个锁保护的。
- 每个共享的和可变的变量都应该只由一个锁来保护,从而使维护人员知道是哪一个锁。一种常见的加锁约定是,将所有的可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码路径进行同步,使得在该对象上不会发生并发访问。
- 在获取与释放锁等操作上都需要一定的开销,因此如果将同步代码块分解的过细,那么通常并不好,尽管这样不会破坏原子性。
- 当执行时间较长的计算或者可能无法快速完成的操作时,一定不要持有锁。
第三章
- Synchronized关键字不止能用于实现原子性或者确定“临界区(Critical Section)”,还有另一个重要的方面:内存可见性(Memory Visibility)。
- 仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它们。如果在验证正确性时需要对可见性进行复杂的判断,那么就不要使用volatile变量。Volatile变量的正确使用方式包括:确保它们自身状态的可见性,确保它们所引用对象的状态的可见性,以及标识一些重要的程序生命周期事件的发生。
- 加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性。
- 当且仅当满足以下所有条件时,才应该使用volatile变量:
i. 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
ii. 该变量不会与其他状态变量一起纳入不变性条件中。
iii. 在访问变量时不需要加锁。 - 发布(publish)一个对象的意思是指,是对象能够在当前作用域之外的代码中使用。
- 逸出(Escape):当某个不应该发布的对象被发布时。
- 线程封闭:如果仅在单线程内访问数据,就不需要同步。它是实现线程安全性的最简单方式之一。
- Ad-hoc线程封闭:指维护线程封闭性的职责完全由程序实现来承担。非常脆弱。程序中尽量少用。
- 栈封闭:它是线程封闭的一种特例,在栈封闭中,只能通过局部变量才能访问对象。局部变量的固有属性之一就是封闭在执行线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。
- ThreadLocal类:维护线程封闭性的一种更规范的方法是使用ThreadLocal类,这个类能使线程中的某个值与保存值的对象关联起来。ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。
- ThreadLocal对象通常用于防止对可变的单实例变量或全局变量进行共享。从概念上看,你可以将ThreadLocal视为包含了Map