并发编程实战-阅读笔记

本书分为4部分:

  • 基础
  • 结构化并发(并发程序构造理论)
  • 性能调优
  • 高级主题

1.0 并发简史

  1. OS作用?
  • 提高资源利用率: IO等待时也可以运行其他程序。
  • 公平性: 粗粒度时间分片
  • 便利性: 用多个程序执行一个任务,必要时通信,比一个程序执行所有任务更容易实现
  1. 线程优势?
  • 发挥多处理器强大能力
  • 建模简单性(同上述便利性)
  • 异步事件的简化处理
  • 响应更灵敏的用户界面 事件分发线程替代主事件循环,线程中调用应用程序的主事件处理器
  1. 风险
  • 安全性:“永远不发生糟糕的事情” 如 a++

    1
    2
    3
    4
    5
    6
    7
    public class Sequence{
    private int value;
    public synchronized int getNext(){
    return value++
    }
    }
  • 活跃性:“某件正确的事情最终会发生”

    • 死锁问题
    • 饥饿问题
    • 活锁问题
  • 性能问题
    • 频繁上下文切换
    • 同步机制抑制编辑器优化
  • 无处无在
    • 必须熟悉

2.0 基础知识


1. 线程安全性

  • 核心: 对状态的访问操作进行管理: 特别是对共享的和可变状态的访问。
  • 状态: 存储在状态变量中的数据(实例域+静态域)。
  • 同步:
    • synchronized
    • volatile 内存可见性
    • 显示锁 Explicit Lock
    • 原子变量
  • 如果没有合适的同步?
    • 不共享
    • 变量设为不可变
    • 访问状态变量时使用同步
  • 正确编程
    • 先正确,再提高速度
  • 安全性定义
    • 正确性 : 当多个线程访问某个类时,这个类始终都能表现出正确的行为。
  • 无状态的对象 一定是安全的
    • 不包含任何域
    • 不包含任何对其他类中域的引用
    • 计算过程中的临时状态仅存在于线程栈上的局部变量中,且只能由正在执行的线程访问
  • 原子性

    • 竞态条件 :由于不恰当的执行时序而出现的不正确的结果 如 一地两咖啡店2人相约情景 - 先检查后执行的单例模式
    • 复合操作:

      • 如 先检查后执行 读-改-写
        java.util.concurrent.atomic包中包含一些原子变量类,用于实现在数值和对象引用上的原子状态转换。

        1
        2
        3
        4
        5
        6
        private final AtomicLong count = new AtomicLong(0);
        public void service(){
        anyOperation();
        count.incrementAndGet();
        }
  • 加锁机制

    • 内置锁 synchronized 代码块
      • synchronized方法每次都只由一个线程可以访问
      • synchronized变量每次只能由一个线程访问
    • 重入 内置锁是可重入的,获取锁操作的粒度是“线程” 如子类调用父类的方法,两个类都上锁了,可以调用成功。
  • 用锁来保护状态

    • 为什么不所有方法都上锁呢?

      1. 导致活跃性问题,如互相持有锁导致死锁
      2. 导致性能问题
    • Tips

      1. 可能被多个线程同访的可变状态变量,访问它时都需要持有同一个锁。
      2. 每个共享的和可变的变量都应该只由一个锁来保护。
      3. 对于每个包含多个变量的不变性条件,其中涉及的所有变量都要由同一个锁来保护。
  • 活跃性与性能
    • 当执行时间较长的计算或者可能无法快速完成的操作时,一定不要持有锁。 如: IO - 网络IO/控制IO
    • 不要用多重同步机制。 一来造成混乱,二来性能和安全性有影响

2. 对象的共享

  • 调试小tip:服务器应用程序,无论开发还是测试,启动JVM时,指定-server命令行选项。
  • 只要有数据在多个线程间共享,就使用正确的同步
  • 加锁的含义不仅仅是局限于互斥行为,还包括内存可见性。为了确保所有线程都能正看到共享变量的最新值,所有执行读操作或写操作的线程都必须在同一个锁上同步。
  • volatile变量

    • 确保它们自身状态的可见性
    • 确保它们所引用对象的状态的可见性
    • 标识一些重要的程序生命周期事件的发生

      1
      2
      3
      4
      5
      6
      7
      8
      /**
      *典型用法
      **/
      volatile boolean asleep;
      while(!asleep){
      countSomeSheep();
      }
    • 当且仅当满足以下所有条件时,才应该使用volatile变量

      • 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值
      • 该变量不会与其他状态变量一起纳入不变性条件中
      • 在访问变量时不需要加锁
  • 发布与溢出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    /**
    *3种溢出方式
    **/
    public static Set<Secret> knowSecrets;
    public void initialize(){
    knowSecrets = new HashSet<Secret>();
    }
    //第二种
    class UnsafeStates(){
    private String[] state = new String[] {
    "AS","AL" ...
    }
    public String[] getState(){
    return state;
    }
    }
    //第三种
    public class ThisEscape(){
    public ThisEscape(EventSource source){
    source.registerListener(
    new EventListener(){
    public void onEvent(Event e){
    doSometing();
    }
    }
    )
    }
    }
    //第三种正确方式
    public class SafeListener(){
    private final EventListener listener;
    private SafeListener(){
    listener = new EventListener(){
    public void onEvent(Event e){
    doSomething(e);
    }
    }
    }
    public static SafeListener newInstance(EventSource source){
    SafeListener safe = new SafeListener();
    source.registerListener(safe.listener);
    return safe;
    }
    }
  • 线程封闭

    • 应用场景: Swing,可视化组件和数据模型对象封闭到Swing的事件分发线程中来实现线程安全性。 JDBC不要求Connection对象必须是线程安全的,但是服务器提供的线程池是线程安全的,要不然无意义。 隐含式地将Connection对象封闭在线程中。

    • Ad-hoc线程封闭: 维护线程封闭性的职责完全由程序实现来承担 ???什么鬼,反正尽量少用它

    • 栈封闭:

    • ThreadLocal类:

  • 不变性

    • 不可变的对象一定安全
    • 条件
      • 对象创建后其状态不可修改
      • 对象的所有域都是final的
      • 对象是正确创建的(创建期间 this引用 没有逸出)
    • 对于在访问和更新多个相关变量时出现的竞争条件问题,可以通过将这些变量全部保存在一个不可变对象中来消除。
  • 安全发布

    • 常用模式
      • 在静态初始化函数中初始化一个对象引用
      • 在对象的引用保存到volatile类型的域或者AtomicReferance对象中
      • 将对象的引用保存到某个正确构造对象的final域中
      • 将对象的易用保存到一个由锁保护的域中
    • Tips
      • 事实不可变对象 安全发布足矣
      • 不可变对象,可以通过任意机制来发布
      • 可变对象必须通过安全发布方式来发布,而且必须是线程安全的或者有锁保护