《java并发编程实战》读书笔记(一)

2019, Apr 03    

第1章 java多线程简介

  • 无论何时,只要有多于一个的线程访问给定的状态变量,而且其中某个线程会写入该变量,此时必须使用同步来协调线程对该变量的访问。

第2章 线程安全

  • 一个类是线程安全的,是指在被多个线程访问时,类可以持续进行正确的行为。
  • 因为线程访问无状态对象的行为,不会影响其他线程访问该对象时的正确性,所以无状态对象是线程安全的。
  • 每个Java对象都可以隐式地扮演一个用于同步的锁的角色;这些内置的锁被称作内部锁(intrinsiclocks)或监视器锁(monitorlocks)。执行线程进入synchronized块之前会自动获得锁;而无论通过正常控制路径退出,还是从块中抛出异常,线程都在放弃对synchronized块的控制时自动释放锁。获得内部锁的唯一途径是:进入这个内部锁保护的同步块或方法。
  • 当一个线程请求其他线程己经占有的锁时,请求线程将被阻塞。然而内部锁是可重进入的,因此线程在试图获得它自己占有的锁时,请求会成功。重进入意味着锁的请求是基于“每线程(per-thread)”,而不是基于“每调用(per-invocation)”的。重进入的实现是通过为每个锁关联一个请求计数(acquisition count)和一个占有它的线程。当计数为0时,认为锁是未被占有的。线程请求一个未被占有的锁时,JVM将记录锁的占有者,并且将请求计数置为1。如果同一线程再次请求这个锁,计数将递增:每次占用线程退出同步块,计数器值将递减。直到计数器达到0时,锁被释放。

第3章共享对象

  • 主线程就己经写入ready并使之对读取线程可见,这是一种“重排序(reordering)”现象。在单个线程中,只要重排序不会对结果产生影响,那么就不能保证其中的操作一定按照程序写定的顺序执行——即使重排序对于其他线程来说会产生明显的影响。
  • 当一个线程在没有同步的情况下读取变量,它可能会得到一个过期值。但是至少它可以看到某个线程在那里设定的一个真实数值,而不是一个凭空而来的值。这样的安全保证被称为是最低限的安全性(out-of-thin-airsafety)。
  • 当访问一个共享的可变变量时,为什么要求所有线程由同一个锁进行同步,我们现在可以给出另一个理由——为了保证一个线程对数值进行的写入,其他线程也都可见。另一方面,如果一个线程在没有恰当地使用锁的情况下读取了变量,那么这个变量很可能是一个过期的数据。
  • 锁不仅仅是关于同步与互斥的,也是关于内存可见的。为了保证所有线程都能够看到共享的、可变变量的最新值,读取和写入线程必须使用公共的锁进行同步。
  • Java语言也提供了其他的选择,即一种同步的弱形式:volatile变量。它确保对一个变量的更新以可预见的方式告知其他的线程。当一个域声明为volatile类型后,编译器与运行时会监视这个变量:它是共享的,而且对它的操作不会与其他的内存操作一起被重排序。volatice变量不会缓存在寄存器或者缓存在对其他处理器隐藏的地方。所以,读一个volatile类型的变量时,总会返回由某一线程所写入的最新值。

  • volatile变量对可见性的影响所产生的价值远远高于变量本身。线程向volatile变量写入值,随后线程B读取该变量,所有执行写操作前可见的变量的值,在B读取了volatile变量后,成为对B也是可见的。所以从内存可见性的角度看,写入volatile变量就像退出同步块,读取volatile变量就像进入同步块。但是我们并不推荐过度依赖volatile变量所提供的可见性。依赖volatile变量来控制状态可见性的代码,比使用锁的代码更脆弱,更难以理解。
  • 加锁可以保证可见性与原子性;volatile变量只能保证可见性
  • 不可变对象可以在没有額外同步的情况下,安全地用于任意线程;甚至发布它们时亦不需要同步。
  • 置入Hashtable、synchronizedMap、ConcurrentMap中的主键以及键值,会安全地发布到可以从Map获得它们的任意线程中,无论是直接获得还是通过迭代器(iterator)获得。
  • 置入Vector CopyOnWriteArrayList CopyOnWriteArraySet synchronizedList 或者synchronizedSet中的元素,会安全地发布到可以从容器中获得它的任意线程中;
  • 置入BlockingQueue或者concurrentLinkedQueue的元素,会安全地发布到可以从队列中获得它的任意线程中。

第4章 组合对象

  • 设计线程安全类的过程应该包括下面3个基本要素:

    1. 确定对象状态是由哪些变量构成的;
    2. 确定限制状态变量的不变约束;
    3. 制定一个管理并发访问对象状态的策略。
  • 同步策略(synchronizationpolicy)定义了对象如何协调对其状态的访问,并且不会违反它的不变约束或后验条件。它规定了如何把不可变性、线程限制和锁结合起来,从而维护线程的安全性,还指明了哪些锁保护哪些变量。为了保证开发者与维护者可以分析并维护类,应该将类的同步策略写入文档。

  • 虽然Java监视器模式与Hoare关于监视器(monitor)的研究(Hoare,1974)之间存在重要的不同,但是该模式的确从Hoare的工作中获得了灵感·进入和退出同步块的字节码指令甚至叫做monitorenter和monitorexit,而且Java的内置(内部)锁有时也被叫做监视器锁(monitorlocks)或监视器(monitors)。

  • 如果一个类由多个彼此独立的线程安全的状态变量组成,并且类的操作不包含任何无效状态转换时,可以将线程安全委托给这些状态变量。

  • 如果一个状态变量是线程安全的,没有任何不变约束限制它的值,并且没有任何状态转换限制它的操作,那么它可以被安全发布。

第5章 构建块