1. Java源程序运行步骤(半解释语言 semi-interpreted language)
      1. Java文件被编译器编译为字节码文件(JDK中的前端编译器:javac.exe)
        1. 词法分析
        2. 语法分析
        3. 语义分析
        4. 中间代码生成
      2. 加载字节码文件(ClassLoader 类加载器)
      3. 将字节码文件解释为汇编指令(JVM中的后端编译器:JIT即时编译,AOT静态编译;JVM中的解释器:java.exe)
      4. 执行(JRE;它包含了JVM不具有的运行字节码文件所需的Java类库)
    2. JVM中的所有线程共享堆内存、方法区,但每个线程都有各自的方法栈;PC寄存器(程序计数器)存放下一条待执行指令的地址,JVM会在方法栈中压入一个栈帧存放字节码操作数局部变量。入栈后,交由JVM解释执行。
      1. 每个线程都有自己对应的PC寄存器,每个线程只有当前方法(Java方法栈或本地方法栈)在执行
      2. Java字节码解释器(java.exe)是JVM的一部分,用于解释执行字节码文件
      3. Java解释器通过改变PC寄存器的值,来明确下一条待执行的字节码指令
    3. 进程(Process) v.s. 线程(Thread)
      1. 将程序读进内存并执行,叫做进程
        1. 进程是操作系统进行资源分配(包括CPU、内存、磁盘IO等)的最小单位
        2. 进程在执行过程中有独立内存单元
        3. 进程间通信需要借助操作系统
      2. 线程是比进程更小的,CPU调度和分配的基本单位
        1. 保证了多线程的高并发性
        2. 进程可以有多个线程,线程之间独立运行,共享进程资源;
        3. 创建线程时无须寻找内存空间,线程时运行在进程的地址空间中的;
        4. 不利于资源的管理和保护;
        5. 操作系统要为每个线程,在进程中的内存空间中分配一个栈,因为每个执行流都需要一个栈来保存各自信息;
    4. 线程调度
      1. 将进程分配给CPU,但是CPU在各个线程之间进行调度;
      2. 多个CPU也可以并行执行同一进程的多个线程——一个进程中有多个执行流;
      3. 在进程开启后,创建多个线程即可让所有CPU都慢起来,实现高并发;
      4. 操作系统的调度器只调度线程,而不调度进程
      5. 大量创建线程会消耗过多内存资源,创建销毁需要消耗系统时间;
      6. 通过线程池技术复用线程,可以避免频繁地创建、销毁线程,而避免内存开销以及时间开销。
    5. 并发(Concurrency) v.s. 并行(Parallelism) 线程与进程、并行与并发 - 图1
      1. 并发:同一时间段,两个任务都同时处于已开始-未结束的状态;
      2. 并行:多个CPU核心各自同时执行进程,两个线程不相互抢占CPU资源;
      3. 单CPU可实现并发,多CPU可实现并行;
      4. 通过CPU时间分片技术将CPU分配给各个线程用于执行,其他线程挂起;并发的线程对应的时间片是乱序的
      5. 并发执行多任务,程序之间相互依赖,并且切换程序费时;并行执行多任务,相互独立。
    6. 线程任务
      1. 生命周期
        1. Long-lived Task
        2. Short-lived Task
      2. 所需资源
        1. CPU-Intensive:花费大量时间进行计算,线程数约等于核数即可充分利用CPU资源
        2. I/O-Intensive:花费大量时间处理磁盘、网络I/O;要考虑IO上的等待时间(Waiting Time),以及CPU上的计算时间(Computing Time)
    7. 线程池
      1. 将需要数据和处理数据的函数交给线程池。此时,线程池中被阻塞在队列上的线程会被被唤醒,执行处理函数;
      2. 把不同类别的任务放到不同的线程池中;
      3. 包含I/o任务的线程池需要对任务进行超时设置,否则可能处于阻塞状态
    8. Java中的集合并发修改:一个线程遍历集合,另一个并发线程修改了集合
      1. Fail-Fast
        1. 并发修改的情况下,抛出异常ConcurrentModificationException
        2. 通过方法 checkForComodification 来检查变量 expectedModCount 和 modCount 是否相等,不等则抛出异常;
        3. ForEach增强for循环,也会被处理成迭代器(Iterator)来进行迭代;
      2. Fail-Safe
        1. 当对象发生结构型修改(遍历、添加、删除)时不抛出任何异常,仍然可以正常遍历;
        2. Fail-Safe类型的数据结构在遍历时会创建迭代器(Iterator);
        3. 执行的结构型修改不会作用到原集合类对象,因为遍历的是拷贝到迭代器中的副本(需要占用额外的内存空间);
        4. 可以通过 weakly consistent 来实现;
        5. java.util.concurrent 下的容器都是安全失败的。
    9. 操作系统中的并发工具
      1. 信号量(Semaphore)
        1. 整型变量来累计唤醒次数,为0的时候表示休眠(挂起);当其他进程挂起,则可以恢复运行。
        2. 针对其有两个操作:down,up;
          1. 值大于0,减1;
          2. 值等于0,挂起;
          3. 没有进程挂起,加1
      2. 互斥量(Mutex)
        1. 一位二进制数(布尔值),0代表解锁,1代表加锁
        2. 互斥锁处于锁定状态,调用线程会阻塞到线程执行结束,并执行 mutex_unlock 解锁;多个调用线程被阻塞,会随机选一个获得锁。
        3. 互斥锁处与解锁状态,会调用 mutex_lock 加锁,调用线程成功。
      3. 快速用户空间互斥(Fast User Space Mutex;Futex)
        1. 组成部分
          1. 内核服务:等待队列,允许多个在锁上排队的进程,除非内核解除阻塞
          2. 用户库
    10. 并发问题的两种锁
      1. 互斥锁
        线程之间共享资源是互斥的,线程获取资源使用权之后就会加锁,使用完之后解锁。这个过程中其他想要获取资源的线程会陷入阻塞睡眠状态,直到资源解锁后(所有被阻塞的线程都)会被唤醒,随机一个线程获得资源使用权后加锁,执行线程,其他的线程继续阻塞。
      2. 自旋锁
        1. 特殊的互斥锁。资源被占用后加锁,其他待获取资源的线程不会阻塞,而是陷入循环检测——循环检查资源状态直到资源被释放。
        2. 好处是减少了线程从阻塞到被唤醒得到资源消耗,缺点是循环检查的过程会一直占用CPU资源。
        3. 适用于不希望花费太多资源在线程的唤醒上的任务。
        4. 需要阻止代码运行中的并发干扰
          1. 中断:硬件中断,软件中断
          2. 内核抢占
    11. 参考资料
      1. 我工作三年了,该懂并发了
      2. 互斥锁、递归锁、读写锁和自旋锁区别