画布 1
Chapter12.Java内存模型与线程
  文字版文档
    https://github.com/BingLau7/blog/issues/39
  效率问题
    现代计算机加入一层读写速度尽可能接近处理器运算速度的高速缓存（Cache）来作为内存与处理器之间的缓冲
    运算结束后再从缓存同步回内存之中
    简单说来就是：计算机计算太快了，等不及慢的主存了，加了 L3、L2、L1 以及寄存器4个高速缓存就是为了让 IO 能快点
    由此产生一个问题
      缓存一致性
        内存模型
          在特定的操作协议（上述协议）下，对特定的内存或高速缓存进行读写访问的过程抽象。
        重排序
          处理器可能会对输入代码进行乱序执行（Out-Of-Order Execution）优化，处理器会在计算之后将乱序执行的结果重组，保证该结果与顺序执行的结果是一致的，但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致，因此，如果存在一个计算任务依赖另外一个计算任务的中间结果，那么其顺序性并不能靠代码的先后顺序来保证。
  Java 内存模型
    存在的意义
      是为了屏蔽掉各种硬件和操作系统的内存访问差异，以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。
    主内存与工作内存
      所有的变量都存储在主内存（MainMemory）中（非系统主内存，虚拟机内存一部分）。
      每条线程还有自己的工作内存（Working Memory，可与前面讲的处理器高速缓存类比），线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝
      线程对变量的所有操作（读取、赋值等）都必须在工作内存中进行，而不能直接读写主内存中的变量。
      不同的线程之间也无法直接访问对方工作内存中的变量，线程间变量值的传递均需要通过主内存来完成
    volatile
      两个特性
        可见性
          保证此变量对所有线程的可见性
          是指当一条线程修改了这个变量的值，新值对于其他线程来说是可以立即得知的
          volatile 变量在各个线程的工作内存中不存在一致性问题，但是 Java 里面的运算并非原子操作，导致 volatile 变量的运算在并发下一样是不安全的
          结论：volatile变量只能保证可见性，多线程修改仍然要加锁
        禁止指令重排序优化
      结论
        volatile 的同步机制的性能确实要优于锁（使用 synchronized 关键字或 java.util.concurrent 包里面的锁）
        由于虚拟机对锁实行的许多消除和优化，使得我们很难量化地认为 volatile 就会比 synchronized 快多少
        大多数场景下 volatile 的总开销仍然要比锁低，我们在 volatile 与锁之中选择的唯一依据仅仅是 volatile 的语义能否满足使用场景的需求。
    原子性，可见性与有序性
      原子性
        原子性变量操作包括 read、load、assign、use、store 和 write
        lock 和 unlock 操作：更大范围的原子性保证（经常会遇到），Java提供了更高层次的字节码指令 monitorenter 和 monitorexit 来隐式地使用这两个操作，即synchronized 关键字
      可见性
        Java内存模型是通过在变量修改后将新值同步回主内存，在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的
        除了 volatile 之外，还有两个关键字可实现可见性
          synchronized。同步块的可见性是由『对一个变量执行unlock操作之前，必须先把此变量同步回主内存中（执行store、write操作）』这条规则获得的
          final。被final修饰的字段在构造器中一旦初始化完成，并且构造器没有把『this』的引用传递出去（this引用逃逸是一件很危险的事情，其他线程有可能通过这个引用访问到『初始化了一半』的对象），那在其他线程中就能看见 final 字段的值。
      有序性
        总结为一句话：如果在本线程内观察，所有的操作都是有序的；如果在一个线程中观察另一个线程，所有的操作都是无序的。
        Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性
          volatile 关键字本身就包含了禁止指令重排序的语义，而 synchronized 则是由『一个变量在同一个时刻只允许一条线程对其进行lock操作』这条规则获得的，这条规则决定了持有同一个锁的两个同步块只能串行地进入。
      Java 中重排序 happens-before 规则
        Happens-before 的前后两个操作不会被重排序且后者对前者的内存可见
        详细规则：P453
      synchronized 关键字在需要这 3 种特性的时候都可以作为其中一种的解决方案，但要注意性能与滥用
  详细PDF
    Chapter11.Java内存模型与线程.pdf
  Java与线程
    线程的实现
      使用内核线程实现
        内核线程（Kernel-Level Thread, KLT）就是直接由操作系统内核（Kernel，下称内核）支持的线程
        这种线程由内核来完成线程切换，内核通过操纵调度器（Scheduler）对线程进行调度，并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身。
        内核线程的一种高级接口——轻量级进程，每个轻量级进程都由一个内核线程支持。1:1的对比关系
        轻量级进程局限性
          首先，由于是基于内核线程实现的，所以各种线程操作，如创建、析构及同步，都需要进行系统调用。而系统调用的代价相对较高，需要在用户态（User Mode）和内核态（Kernel Mode）中来回切换。
          其次，每个轻量级进程都需要有一个内核线程的支持，因此轻量级进程要消耗一定的内核资源（如内核线程的栈空间），因此一个系统支持轻量级进程的数量是有限的
      使用用户线程实现
        狭义上的用户线程指的是完全建立在用户空间的线程库上。如果程序实现得当，这种线程不需要切换到内核态，因此操作可以是非常快速且低消耗的，也可以支持规模更大的线程数量
        优势在于不需要系统内核支援，劣势也在于没有系统内核的支援，所有的线程操作都需要用户程序自己处理。
      使用用户线程加轻量级进程混合实现
        既存在用户线程，也存在轻量级进程
        操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁，这样可以使用内核提供的线程调度功能及处理器映射，并且用户线程的系统调用要通过轻量级线程来完成，大大降低了整个进程被完全阻塞的风险。
    Java线程的实现
      常用的HotSpotVM使用的都是1:1线程模型，Solaris除外
      Java 线程调度
        协同式线程调度（协程）
          最大好处是实现简单
          坏处也很明显：线程执行时间不可控制，甚至如果一个线程编写有问题，一直不告知系统进行线程切换，那么程序就会一直阻塞在那里。
        抢占式线程调度（Java使用的）
          每个线程将由系统来分配执行时间，线程的切换不由线程本身来决定
      Java线程的运行状态
        New：创建后尚未启动的线程处于这种状态。
        Runable：Runable 包括了操作系统线程状态中的 Running 和 Ready，也就是处于此状态的线程有可能正在执行，也有可能正在等待着CPU为它分配执行时间。
        Waiting：**处于这种状态的线程不会被分配CPU执行时间，它们要等待被其他线程显式地唤醒。**以下方法会让线程陷入无限期的等待状态：
          没有设置Timeout参数的Object.wait()方法。
          没有设置Timeout参数的Thread.join()方法。
          LockSup-port.park()方法。
        Timed Waiting：处于这种状态的线程也不会被分配CPU执行时间，不过无须等待被其他线程显式地唤醒，在一定时间之后它们会由系统自动唤醒。以下方法会让线程进入限期等待状态：
          Thread.sleep()方法。
          设置了Timeout参数的Object.wait()方法。
          设置了Timeout参数的Thread.join()方法。
          LockSupport.parkNanos()方法。
          LockSupport.parkUntil()方法。
        Blocked：线程被阻塞了，『阻塞状态』与『等待状态』的区别是：『阻塞状态』在等待着获取到一个排他锁，这个事件将在另外一个线程放弃这个锁的时候发生；而『等待状态』则是在等待一段时间，或者唤醒动作的发生。在程序等待进入同步区域的时候，线程将进入这种状态。
        Terminated：已终止线程的线程状态，线程已经结束执行。
      协程
        内核线程弊端
          调度成本来自于用户态与核心态之间的状态切换
          开销主要是响应中断、保护和恢复执行现场的成本
        协程优点
          轻量，因为摆脱系统的限制，所以可以同时存在多达十多万个
        协程缺点
          应用层需要实现特别多的内容（调用栈、调度器这些）