让我来康康Java并发编程的套路。(然后在cpp里自己造轮子,wdnmd)

第1章 并发编程现成基础

  • 在Java中有三种创建线程的方式

    • Runnable
    • 继承Thread类
      • java不能多继承,所以这个方法会导致不能继承其他类
    • FutureTask
      • 方便获取返回值。
      • 有点像js的await。
  • 紧接着是一堆线程常用操作。

    • wait, sleep, notify, join, yield
    • java里的线程中断简直迷惑行为
      • interrupted()里用了currentThread(),始终是对主线程起作用。
  • 守护线程与用户线程

    • JVM不等待守护线程退出,gc就属于守护线程
    • 用户可通过setDaemon(true)将线程设置为守护线程
    • tomcat中的服务线程也都默认是守护线程
  • ThreadLocal

    • 线程私有变量
    • 具体是每个线程维护一个私有的map,访问时先找到map,再找变量
    • 不支持继承
  • InheritableThreadLocal

    • 可被继承
    • 像Web server可能需要继承用户的token

第2章 并发编程其他基础知识

  • 线程安全,指多个线程同时读写一个共享资源,并且没有任何同步措施时,导致出现脏数据或者其他不可预见的结果的问题。

  • 内存可见性问题,书中的意思是L1 cache不同步,但根据我在体系结构和编译原理中所学,我以为问题是寄存器不同步。

    • 计算机在操作变量时,总是在寄存器里操作的,这样速度也最快。
    • 编译器有时会做一些优化,使得对某一个变量的操作总是在寄存器中进行。如果该变量是一个局部变量,这就是一个非常完美的优化。但有时,编译器根据上下文,也可能将某个变量一直存在寄存器中,直到函数结束再写回主存。
    • 在多核cpu中,寄存器是肯定不共享的。L1 cache这一层,应当有cache一致性算法提供保证。(这是我的理解,这些都可能随着计算机发展而变化,具体情况具体分析)
  • 原子性操作

    • syncronized,java关键字,被修饰的代码块,执行时需要先获取独占锁。
  • Unsafe类,java中玩转指针

    • 操纵对象属性
    • 操纵数组元素
    • 线程挂起与恢复、CAS(硬件级别的原子性保证)
  • ABA问题

  • 代码重排序问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    thread-1
    {
    num = 2
    ready = true
    }
    thread-2
    {
    if ready:
    print(num)
    }
    • 对num和ready的赋值会被编译器识别为,无依赖性,有可能会重排汇编指令执行顺序。
    • java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad
      • LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
      • StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
      • LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
      • StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
    • 屏障的作用
      • 阻止屏障两侧的指令重排序;
      • 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
    • volatile修饰的变量。
      • 在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障。
      • 在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障。
    • 悲观锁,整个操作过程都使用排他锁。
    • 乐观锁,通常在本地(对线程来说就是单独的栈,对分布式来说是不同的进程)修改,最后update的时候获取锁。
    • 公平锁,拿锁遵循先到先得。可以避免某个线程饥饿。
    • 共享锁,通常用于实现读写锁,读锁是共享的。写锁则是独占的。不过在《Linux多线程服务端编程:使用muduo C++网络库》中,作者提到,实践中从来不用读写锁。真正解决问题还是靠减小critical block。
    • 可重入锁,指的是同一线程重复获取同一个锁。synchronized的锁是实例级别的,可重入实现,使得可以在synchronized方法中,调用自己的另一个sync方法。
    • 自旋锁,获取锁失败时,不suspend,也就是不放弃cpu时间,认为很快可以获得锁。内核代码大量使用自旋锁,因为计算机正常工作时,处于内核态的时间应该不多,而且context switch开销很大。

第3章 Java并发包中ThreadLocalRandom类原理剖析

  • 第4章 Java并发包中原子操作类原理剖析

  • AtomicLong、LongAdder、LongAccumulator。

  • 用到Unsafe,JUC。

  • 主要是看代码,不赘述了。

第5章 Java并发包中的并发List源码剖析