Java 并发编程读书笔记(基础篇)

基础篇,从各种概念开始。

线程安全

  • 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步货协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
  • 在线程安全类中封装来必要的同步机制,因此客户端无须进一步采取同步措施。
  • 无状态对象一定是线程安全的。
    在并发编程中,这种由于不恰当的执行时序而出现的不正确的结果是一种非常重要的情况,它有一个正式的名字:竞态条件

原子操作

作为一个不可分割的操作来执行。我们所熟知的 ++i 并不是一个原子操作,它实际上是一个 *¨读取 i 值 – 修改 i 值 – 写入 i 值¨ 的操作序列,并且其结果依赖于之前的状态。

内置锁

每个 Java 对象都可以用做一个实现同步的锁,这些锁被称为内置锁(Inteinsic Lock)或者监视器锁(Monitor Lock)。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。Java 的内置锁相当于一种互斥体(互斥锁),这意味着最多只有一个线程能持有这种锁。

重入

当某个线程请求一个由其他线程持有的锁是,发出请求的线程就会阻塞。¨重入¨ 意味着获取锁的操作的粒度是¨线程¨,而不是¨调用¨。重入的一种实现方法是,为每个锁关联一个获取计数值和一个所有者线程。

Volatile 关键字

Java 语言提供来一种弱同步机制,即 volatile 变量,用来确保变量的更新操作通知到其他线程。
仅当 volatile 变量能简化代码的实现以及对同步策略的验证时,才应该使用它们。如果在验证正确性时需要对可见性进行复杂的判断,那么就不要使用 volatile 变量。volatile 变量的正确使用方式包括:确保它们自身状态的可见性,确保它们锁引用对象的状态的可见性,以及标识一些重要的程序生命周期事件的发生。

发布与逸出

发布一个对象的意思是指,使对象能够在当前作用域之外的代码中使用。一般来说,如果一个已经发布的对象能够通过非私有的变量引用和方法调用到达其他对象,那么这些对象也都会被发布。
当某个不应该发布的对象被发布时,就称为逸出。

线程封闭

线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由该线程修改。

Ad-hoc 线程封闭

Ad-hoc 线程封闭是指,维护线程封闭性的职责完全由程序实现来承担。

栈封闭

栈封闭是线程封闭的一种特例,在栈封闭中,只能通过局部变量才能访问对象。栈封闭比 Ad-hoc 线程封闭更易于维护,也更健壮。

ThreadLocal 类

维持线程封闭性的一种更规范方法是使用 ThreadLocal,这个类能使线程中的某个值与保存值的对象关联起来。ThreadLocal 对象通常用于防止对可变的单实例变量或全局变量进行共享。

不变性

如果某个对象在被创建后其状态就不能被修改,那么这个对象就称为不可变对象。
不可变对象一定是线程安全的

当满足一下条件时,对象才是不可变的:

  • 对象创建以后其状态就不能被修改。
  • 对象的所有域都是 final 类型。
  • 对象是正确创建的(在对象创建期间,this 引用没有逸出)。

Final 域

final 域能确保初始化过程的安全性,从而可以不受限制的访问不可变对象,并在共享这些对象时无须同步。

要安全地发布一个对象,对象的引用以及对象的状态必须同事对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布:

  • 在静态初始化函数中初始化一个对象引用。
  • 将对象的引用保存到 olatile 类型的域或者 AtomicReferance 对象中。
  • 将对象的引用保存到某个正确构造对象的 final 类型域中。
  • 将对象的引用保存到一个由锁保护的域中。

线程安全库中的容器类提供以下安全发布保证:

  • 通过将一个键或者值放入 Hashtable、sychronziedMap 或者 ConcurrentMap 中,可以安全地将它发布给任何从这些容器中访问它的线程(无论是直接访问还是通过迭代器访问)。
  • 通过将某个元素放入 Vector、CopyOnWriteArrayList、CopyWriteArraySet、synchronizedist 或 synchroizedSet 中,可以将该元素安全地发布到任何从这些容器中访问该元素的线程。
  • 通过将某个元素放入 BlockingQueue 或者 ConcurrentLinkedQueue 中,可以将该元素安全地发布到任何从这些队列中访问该元素的线程。

对象的发布需求取决于它的可变性:

  • 不可变对象可以通过任意机制来发布。
  • 事实不可变对象必须通过安全方式来发布。
  • 可变对象必须通过安全方式来发布,并且必须是线程安全的或者由某个锁保护起来。

在并发程序中使用和共享对象时,可以使用一些实用的策略,包括:
线封闭 线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改。
只读共享 在没有额外同步的情况下,共享的只对对象可以由多个线程并发访问,但任何线程都不能修改它。共享只读对象包括不可变对象和事实不可变对象。
线程安全共享 线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步。
保护对象 被保护的对象只能通过持有特定的锁来访问。保护对象包括封装在其他线程安全对象中的对象,以及已发布的并且有某个特定锁保护的对象。

在设计线程安全类的过程中,需要包含一下三个基本要素:

  • 找出构成对象状态的所有变量。
  • 找出约束状态变量的不变性条件。
  • 建立对象状态的并发访问管理策略。

小结

  • 可变状态是至关重要的。
    所有的并发问题都可以归结为如何协调对并发状态的访问。可变状态越少,就越容易确保线程的安全性。
  • 尽量将域声明为 final 类型,除非需要它们是可变的。
  • 不可变对象一定是线程阿安全的。
    不可变对象能极大地降低并发编程到的复杂性。它们更为简单而且安全,可以任意共享而无须使用加锁或保护性复制等机制。
  • 封装有助于管理复杂性。
    将数据封装在对象中,更易于维持不变性条件,将同步机制封装在对象中,更易于遵循同步策略。
  • 用锁来保护每个可变变量。
  • 当保护用一个不变性条件中的所有变量时,要使用同一个锁。
  • 在执行符合操作期间,要持有锁。
  • 如果从多线程中访问同一个可变变量时没有同步机制,那么程序会出现问题。
  • 不要故作聪明地推断出不需要使用同步。
  • 在设计过程中考虑线程安全,或者文档中明确指出它不是线程安全的。
  • 将同步策略文档化。
文章目录
  1. 1. 基础篇,从各种概念开始。
    1. 1.1. 线程安全
    2. 1.2. 原子操作
    3. 1.3. 内置锁
    4. 1.4. 重入
    5. 1.5. Volatile 关键字
    6. 1.6. 发布与逸出
    7. 1.7. 线程封闭
      1. 1.7.1. Ad-hoc 线程封闭
      2. 1.7.2. 栈封闭
      3. 1.7.3. ThreadLocal 类
    8. 1.8. 不变性
      1. 1.8.1. Final 域
    9. 1.9. 小结
,