画布 1
并发实战
  第二章 线程安全性
    第零节
      一、理解
        1）理解前提术语
          1、共享
            ①什么是状态共享
          2、可变
            ①什么是状态可变
          3、状态
            ①什么是状态变量
            ②有哪四种状态变量
        2）理解线程安全性
          1、出现线程安全问题
            ①本质原因
            ②如何判断线程是否安全
          2、三种解决线程安全问题的思路
          3、java中的几种同步机制
    第一节 线程安全性
      一、定义
        1）复杂的定义
        2）困惑的定义
          1、这些定义让人困惑的两个原因
          2、如何定义线程安全，才不会困惑
        3）理解前提
          1、理解『正确性』
            ①什么是『正确性』
        3）正确的定义
          1、简易版
          2、完全版
      二、举例
        1）因数分解 servlet
          1、功能说明
          2、代码举例
          3、此 servlet 线程安全的原因
          4、无状态对象，线程安全的原因
            ①只有局部变量
              A、如何理解局部变量，不会产生线程安全性问题
            ②没有共享变量
              A、如何理解无状态对象，没有共享状态？
            ③为什么说，多个线程访问同一个无状态对象，就好像访问不同的实例一样，也即好像没有访问共享的对象一样
    第二节 原子性
      一、新的需求
      二、解决方案
        1）叙述
        2）代码
        3）可能的错误
          1、起初
          2、过程
          3、最后
          4、错误原因
      三、竞态条件
        1）概念
          1、什么是『竞态条件』
            ①概念
            ②特点
            ③解决思路
          2、什么时候会出现『竞态条件』
          3、为什么竞态条件，会带来线程安全性问题
        2）分类
          1、『先检查后执行』类型竞态条件
            ①为什么『先检查后执行』类型是竞态条件中最常见的
            ②本质
            ③举例（没有做思维导图，今后做）
              现实生活中『先检查后执行』类型的竞态条件
              延迟初始化中的『先检查后执行』类型的竞态条件
          2、『读取-修改-写入』类型竞态条件（没有做思维导图，今后做）
        3）存在的问题
          执行时序、竞态条件与线程安全的关系
        4）解决方案
          1、解决『竞态条件』引起的线程安全性问题的『思路』
            ①思路叙述
            ②思路解释
              A、为什么，要解决『竞态条件』问题，就要解决执行时序不确定的问题？而不是把多线程变为单线程？
              B、使用什么方法，解决执行时序不正确的问题？
              C、什么是正确的执行时序？
              D、如何确保正确的执行时序
          2、复合操作
            ①严谨地描述复合操作
            ②什么是复合操作？
            ③如何定义复合操作
              A、方法一：加锁机制（下一节讲）
              B、方法二：使用一个现有的原子变量类
                a、代码
                b、解释
                c、结论
    第三节 加锁机制
      一、原子变量类的局限性
        1）当前的原子变量类，使用情况？
        2）当前的原子变量类，存在的问题？
      二、带有缓存的因式分解servlet
        1）目的
        2）如何实现
          1、思路
        3）疑问
  购买xmind pro
某个变量可以被多个线程访问
变量的值在其生命周期内可以发生变化
①既共享又可变（需要管理）
②共享但不可变（不需要管理）
③不共享但可变（不需要管理）
④既不共享又不可变（不需要管理）
要编写线程安全的代码, 其核心在于要对状态访问的操作进行管理,特别是对共享的(Shared) 和可变的(Mutable) 状态的访问
①情况：多线程对某个对象『可变状态』的访问，在这种情况下，这个数据既是共享的，又是可变的
②存在的问题？
对于共享且可变的数据进行访问的时候，如果无法对多个线程实现协同，也就是同步,那么可能会导致数据破坏以及其他不该出现的结果，也就是使得程序失去正确性，也就是带来线程安全性的问题
①和对象要实现的『功能无关』
②和程序中对象的『访问方式有关』
一个对象是否需要是线程安全的,取决于它是否被多个线程访问
如果当多个线程访问同一个可变的状态变量时，没有使用合适的同步那么程序就会出现错误。有三种方式可以修复这个问题
①不共享：不在线程之间共享该状态变量
②不可变：将状态变量修改为不可变的变量
③既共享又可变，但是需要同步
在访问状态变量时使用同步
①synchronized：Java中的主要同步机制是关键字synchronized,它提供了一种独占的加锁方式
②volatile类型的变量
③显式锁(Explicit Lock)
④原子变量
⑤注意：同步这个术语不仅仅包括synchronized
①可以在多个线程中调用,并且在线程之间不会出现错误的交互
②可以同时被多个线程调用,而调用者无须执行额外的动作
因为它们没有告诉我们——
①如何区分『线程安全的类』以及『非线程安全的类』
②定义中“安全”的含义是什么
在线程安全性的定义中,最核心的概念就是『正确性』。如果对线程安全性的定义是模糊的,那么就是因为缺乏对『正确性』的清晰定义
正确性的含义是,某个『类的行为』与『类的规范』完全一致。在良好的规范中通常会定义（但是通常我们自己不会定义）
①各种『不变性条件』(Invariant)来约束对象的状态
②定义各种『后验条件』(Postcondition) 来描述对象操作的结果
当多个线程访问某个类时,这个类始终都能表现出正确的行为（也即始终满足，类的不变性条件和后验性条件）,那么就称这个类是线程安全的
这个 Servlet 从请求中提取出数值,执行因数分解,然后将结果封装到该 Servlet 的响应中
@ThreadSafe
public class StatelessFactorizer implements Servlet {
public void service(ServletRequest req, ServletResponse resp) {
Biglnteger i = extractFromRequest(req);
Biglnteger[] factors = factor(i);
encodelntoResponse(resp, factors);
}
}
StatelessFactorizer 是无状态的:它既不包含任何域,也不包含任何对其他类中域的引用。（你可以点开 servlet 看一看，servlet 是一个接口，它没有任何属性）
局部变量因为其存储位置决定了，它永不共享：计算过程中的临时状态仅存在于线程栈上的局部变量中（我们知道静态变量、局部变量、属性都是存放在计算机中的不同的位置）,并且只能由正在执行的线程访问
多个线程访问同一个对象时，成员变量会被共享，但是无状态对象没有成员。别说共享状态了，无状态对象连成员变量都没有，连状态都没有，怎么可能有共享状态
访问 StatelessFactorizer 对象的线程不会影响,另一个访问同一个 StatelessFactorizer 对象的线程,的计算结果,因为这两个线程并没有共享状态（ servlet 根本没有任何状态，更别说共享状态了）,就好像它们都在访问不同的实例（虽然都是访问的同一个 servlet『 StatelessFactorizer 』实例）
假设我们希望为 servlet，增加一个『计数器』来统计所处理的请求数量
一种直观的方法是在 Servlet 中增加一个 long 类型的成员属性,并且每处理一个请求就将这个值加1
@NotThreadSafe
public class UnsafeCountingFactorizer implements Servlet {
private long count = 0;
public long getCount(){ return count; }
public void service(ServletRequest req, ServletResponse resp) {
Biglnteger i =extractFromRequest(req);
Biglnteger[] factors = factor(i);
++count;
encodelntoResponse(resp, factors) ;
}
}
如果计数器的初始值为9
那么在某些情况下,多个线程读到的值都为9（假如100核处器），同时读取了 count 属性,接着执行递增操作,并且都将计数器的值设为10
结果肯定是错误的，处理了多个请求，为什么 servlet 对象的 count 属性的值，都是（++操作中的写入操作）写入了10，也即最终结果只增加了1,显然,这并不是我们希望看到的情况
①原因：没有满足类的后验证性条件，也即破坏了类的正确性：存在多个线程访问后，访问次数只是加了1的情况
②结论：因而这个 servlet 就不是线程安全的
在并发编程中,可能会因为出现『不恰当的执行时序』而出现『不正确的结果』，『这样的情况』就被称作『竞态条件』。
对于『竞态条件』来说，是否执行正确全凭运气。如果执行时序正确，那么计算就正确，如果执行时序不正确，计算就错误。
为了防止『竞态条件』出现不正确的结果，就需要规避『不恰当的执行时序』，那么如何规避『不恰当的执行时序』就是我们需要研究的课题
当某个计算的正确性,取决于多个线程的『交替』『执行时序』时,那么就可能会发生竞态条件，因为存在线程的交替执行，因而可能会因为运气，产生不同的执行时序（根据线程的调度情况）。除非这多个线程的『交替』『执行时序』是正确的。
在多线程程序中，对于共享可变的状态进行访问时，由于执行时序的不同，可能会带来不同的结果——也就是说竞态条件，有一定的概率造成程序错误，影响程序的正确性（先验性条件、后验性条件），因而竞态条件会带来线程安全性的问题
①说法一：多个线程的执行时序，会因为线程调度的不确定性，得到不同的检测结果，而不同的检测结果又带来了不同的执行结果
②说法二：
﻿A、执行结果——依赖于——》检测的结果，而检测结果——依赖于——》多个线程的执行时序
B、多个线程的执行时序通常情况下是不固定不可判断的，从而导致执行结果出现各种情况（当然也就包括，错误的情况）
多线程由于执行时序的不确定性，导致多线程所访问的类，会基于一种『可能失效』的观察结果，进而做出判断或者执行某个计算（如果观察结果合理，就正确，如果观察结果失效，就错误）
①针对某一对象共享且可变的状态进行访问——》执行时序的不确定性——》存在竞态条件（有因为时序的不确定带来的错误的可能性）——》线程不安全
②如何保证线程安全？
要保证线程安全，就需要规避竞态条件，要规避竞态条件，就需要防止出现『执行时序的不确定性』。为了防止出现『执行时序的不确定性』，就需要确保『针对同一状态，进行改变』这一复合操作的原子性，要实现复合操作的原子性，就需要对多个线程『访问同一状态变量』实现协同
要解决线程安全性问题——》就要解决『竞态条件』问题——》要解决『竞态条件』问题——》就要解决执行时序不确定的问题——》要解决执行时序不确定的问题——》就要知道
A、什么是『正确的执行时序』
B、使用什么方法，保证『正确的执行时序』
——》使用复合操作，确保操作的原子性，可以保证正确的执行时序
①首先：你不能把多线程变成单线程
抓住问题的源头，我们知道多线程开发方式，是产生一切问题的根源。但是，多线程开发是必须要进行的。因为多线程开发具有很多优点。你不能因为多线程中程序会因为不当的编码带来『竞态条件』，就把多线程改为单线程。
②然后：你不能修改对象的『共享』『可变』的性质
多线程开发方式，又经常伴随着对『共享』『可变』状态的访问，你总不能让所有的多线程开发，都是访问不共享或者不可变的的对象状态吧
③最后：因此前面两个步骤，你都无法做文章。
所以，你必须在这样的情况下，去思考如何解决『竞态条件』问题——在多线程的情况下，多个线程会访问共享可变的状态变量。这两个因素是永远存在的，你只能着眼于其它地方，考虑如何解决『竞态条件』问题。于是，很自然地你就会考虑，通过解决执行时序不确定的问题，来规避『竞态条件』，从而避免出现线程安全性问题
在访问『共享』『可变』的状态时，你可以通过同步机制，实现原子操作，规避『执行时序』的不确定，从而规避竞态条件，避免线程不安全
多个线程，对于共享可变状态的『读取+修改操作』，以串行的方式执行，这样的执行时序就是正确的
使用复合操作——让多个线程间，在对某个状态变量进行『读取+修改操作』时，得以通过原子的方式执行，也即是以串行的方式进行，此时执行时序就一定是正确的了
假定有两个操作A和B，如果从执行A操作的线程来看，当另一个线程执行B时，要么将B全部执行完，要么完全不执行B，那么A和B对彼此来说是原子的。原子操作是指，对于『访问同一个状态的所有操作』（包括该操作本身）来说，这个操作是一个以原子方式执行的操作
包含了一组必须以原子方式执行的操作，从而确保线程安全性。由于『访问同一个状态的所有操作』必须以原子的方式执行，所以『访问同一个状态的所有操作』就必须组合在一起，作为一个复合操作
public class CountingFactorizer implements Servlet {
private final AtomicLong count = new AtomicLong(0);
public long getCount(){ return count.get(); }
public void service(ServletRequest req, ServletResponse resp) {
Biglnteger i = extractFromRequest(req) ;
Biglnteger [] factors = factor{i) ;
count.incrementAndGet();
encodelntoResponse(resp, factors);
}
}
A、原子变量类在什么包中？
在 java.util.concurrent.atomic 包中包含了一些原子变量类
B、原子变量类的作用？
用于实现在数值和对象引用上的『原子状态转换』，也即将『读取-修改-写入』等操作作为一个原子操作
C、作用举例
通过用 AtomicLong 来代替 long 类型的计数器,能够确保所有对计数器状态的访问操作都是原子的（用 count.incrementAndGet() 代替了 count++ ）
D、如果成员是线程安全的，那么对象就是状态安全的？
由于 Servlet 的状态就是计数器的状态（因为 servlet 只有计数器对象这一个属性）,并且计数器是线程安全的,因此这里的 Servlet 也是线程安全的。
结论——使用一个现有的原子变量类（线程安全类）确保原子性
我们在因数分解的 servlet 中增加了一个计数器,并通过使用线程安全类 AtomicLong 来管理计数器的状态,从而确保了代码的线程安全性。
A、当在无状态的类中添加一个状态时,如果该状态完全由线程安全的对象来管理,那么这个类仍然是线程安全的。
B、在实际情况中,应尽可能地使用现有的线程安全对象（例如AtomicLong）来管理类的状态。与非线程安全的对象相比,判断线程安全对象的可能状态及其状态转换情况要更为容易,从而也更容易维护和验证线程安全性。
当在 Servlet 中添加『一个状态变量』时,可以通过『线程安全的原子变量类实例对象』来管理『 Servlet 的计数器状态』（由于只有一个状态变量，此时 servlet 的状态就是这个对象的状态）以维护 Servlet 的线程安全性
但如果想在 Servlet 中添加更多的状态,那么是否只需添加更多的线程安全状态变量就足够了?
假设我们希望提升 Servlet 的性能:将最近的计算结果缓存起来,当『两个连续的请求』对『相同的数值』进行因数分解时,可以直接使用上一次的计算结果,而无须重新计算（这并非一种有效的缓存策略,5.6节将给出一种更好的策略）
要实现该缓存策略,需要保存两个状态:最近『执行因数分解』的数值,以及分解结果
A、我们曾通过『线程安全的类』 AtomicLong 以线程安全的方式来管理计数器的状态
B、那么,在这里是否可以使用类似的『线程安全的类』AtomicReferencee，来管理最近执行因数分解的数值及其分解结果