并发编程学习笔记 二 park/unpark

并发编程学习笔记 二

  • park / unpark 原理
  • java 线程状态之间的相互转换
  • 死锁 活锁 饥饿
  • ReentrantLock 可重入锁
  • JMM
    • volatile 的原理
  • 乐观锁 CAS
  • 享元模式
    • 线程池

park / unpark 原理

每个线程都有自己的一个 Parker 对象,由三部分组成 _counter , _cond 和 _mutex
(1)当线程调用 park 方法时,会检查 counter 是不是为 0 ,若为 0 ,则获得 mutex 互斥锁,进入 cond 条件变量等待。设置 counter=0 。若为 1 ,则不阻塞,只是置 counter 为 0。
(2)当线程调用 unpark 方法时,设置 counter 为 1,并唤醒 cond 条件变量中的线程,线程恢复运行,并置 counter 为 0。

java 线程状态之间的相互转换

  1. NEW --> RUNNABLE
    (1)调用线程的 start 方法。
  2. WAITING <–> RUNNABLE
    (1)wait / notify,当 notify 竞争失败后,会从 WAITING 变为 BLOCKED
    (2)join / join结束或interrupt
    (3)park / unpark 或interrupt
  3. BLOCKED <–> RUNNABLE
    (1)sychronized 等 锁竞争成功或失败
  4. RUNNABLE --> TERMINATED线程代码运行完了

死锁 活锁 饥饿

  1. 死锁的四个条件
    (1)资源独占
    (2)不可抢占
    (3)保持申请(hold and apply)
    (4)循环等待
  2. 活锁:活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束
  3. 饥饿:一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束

ReentrantLock 可重入锁

  1. 特点
    (1)可重入:一个线程获取锁后有权利再次获取。
    (2)可打断阻塞:调用 lock.lockInterruptibly() 方法,此时等待锁的阻塞线程可以被打断。
    (3)可设置超时时间:调用 lock.trylock() ,无参方法获取锁失败后不等,可设置等待超时时间。
    (4)可设置公平锁:在构造方法内传 true。
    (5)支持多个条件变量(WaitSet):可调用 lock.newCondition() 创建一个新的环境变量。

JMM

  1. JMM 的体现
    (1)原子性:保证指令不会受到线程上下文切换的影响
    (2)可见性:保证指令不会受 cpu 缓存的影响
    (3)有序性:保证指令不会受 cpu 指令并行优化的影响

  2. volatile (易变关键字)
    (1)被 volatile 修饰的变量,在线程取变量值时,必须要去主存中取,不可以使用线程缓存的值。这样解决了线程之间的可见性。
    (2)volatile 还可以组织指令重排。

volatile 的原理

  1. volatile 的底层原理时内存屏障
    (1)对 volatile 变量的写指令后会加入写屏障。
    (2)对 volatile 变量的读指令前会加入读屏障。
  2. 如何保证可见性
    (1)写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
    (2)而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
  3. 如何保证有序性
    (1)写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
    (2)读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前

乐观锁 CAS

  1. CAS 的原理
    调用的操作系统提供的原子指令,实现比较并交换,在单核或多核下都可以保证原子性。
  2. CAS 与 volatile
    在使用 CAS 时,必须保证共享变量在多个线程之间的可见性,所以共享变量必须使用 volatile 修饰,只有读取到最新值,才能完成比较并交换的操作。
  3. 为什么无锁效率更高
    线程都在占用 cpu 不断尝试获取锁,这期间不会发生线程的上下文切换,但是因为要保持线程的不断运行,需要多核 cpu 支持。
  4. CAS 的特点
    适用于线程数少,多核 cpu 的情况,因为没有使用 sychronized,所以不会陷入阻塞,但是如果竞争激烈,必然会导致重试,也会影响效率。
  5. CAS 在 java 中的实现
    (1)原子整数 (2)原子引用 (3)原子数组
  6. ABA 问题
    如果在 cas 过程中,共享变量在期间修改过,但是值没有发生变化,这样无法检测出是否改变,解决办法是加版本号,活着标记是否被修改,在 java 中都有实现 AtomicStampedReference AtomicMarkableReference。

享元模式

  1. 思想
    当需要重用数量有限的同一类对象,想需要最小化内存的使用。
  2. 体现
    (1)包装类:有缓存一部分数值,比如 Integer 缓存了【-127, 128】
    (2)StringTable 串池:缓存字符串常量。
    (3)线程池

线程池

  1. 线程池的状态
    ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量,因为一次 cas 操作就 ok。
状态名 高三位 接收新任务 处理阻塞队列任务 说明
RUNNING 111 Y Y
SHUTDOWN 000 N Y 不会接受新任务,但是会将阻塞队列的剩余任务
STOP 001 N N 会中断现在执行的任务,并抛弃阻塞队列的任务
TIDYING 010 - - 任务全部执行完毕,活动线程为0,即将进入终结状态
TERMINATED 011 - - 终结状态
  1. 线程池参数
    (1)线程数相关:corePoolSize 核心线程数目,maximumPoolSize 最大线程数目(救急线程数)
    (2)救急线程参数:keepAliveTime 生存时间 unit 时间单位
    (3)workQueue 阻塞队列
    (4)threadFactory 线程工厂 - 可以为线程创建时起个好名字
    (5)handler 拒绝策略

  2. shutdown() 与 shutdownnow() 的区别
    shutdown() 让线程池进入 SHUTDOWN 状态, shutdownnow() 让线程池进入 STOP 状态。

  3. 创建多少线程合适?
    (1)cpu 密集型运算: cpu 核数 + 1
    (2)I / O 密集型运算:线程数 = 核数 * 期望 CPU 利用率 * 总时间(CPU计算时间+等待时间) / CPU 计算时间

你可能感兴趣的:(java,java,开发语言,后端)