学习笔记 - 多线程

1 线程生命周期

图片说明
图片来源于网络

2 死锁

  1. 造成死锁的条件
    • 互斥条件:该资源任意时刻只能由一个线程占用;
    • 请求与保持条件:某线程获取资源失败后仍不释放已获得的资源;
    • 不剥夺条件:该资源被线程释放前,不能被其他线程剥夺;
    • 循环等待条件:若干线程头尾相接地等待资源;
  2. 破坏死锁
    • 互斥条件无法破坏,锁就是用于创造互斥条件的;
    • 破坏请求与保持条件:一次性申请所有的资源;
    • 破坏不剥夺条件:主动让出资源,如 ReentrantLock 的 tryLock() 失败后手动释放锁;
    • 破坏循环等待条件:按顺序申请资源。

3 synchronized 关键字

  1. 什么是synchronized
    被它修饰的方法或代码块同时只有一个线程进行访问。
  2. 主要使用方式
    • 修饰实例方法:给当前对象加锁;
    • 修饰静态方法:给当前类加锁;
    • 修饰代码块:给某个指定对象加锁;
  3. synchronized 优化
    • 概述:JDK 1.6 之后,锁出现了四个状态,分别是 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。当出现锁竞争的情况时,锁可能会升级,且不会降级;
    • 锁状态:
      • 偏向锁:
        (1)概述:偏向锁和轻量级锁目的是,在没有线程竞争的状态下,减小重量级锁使用系统互斥量带来的性能损耗;
        (2)加锁:
          a. 第一个线程进入同步代码块后,在锁对象的对象头里的 MarkWord 记录线程 ID,并将锁状态设置为偏向锁;
          b. 之后的线程进入同步代码块后,检查并发现 MarkWord 记录的线程 ID 与当前线程 ID 不一致,尝试使用 CAS 修改线程 ID;
          c. 使用 CAS 修改成功,执行同步代码块,否则执行锁撤销流程;
        (3)锁撤销:
          a. JVM 进入全局安全点,暂停所有拥有偏向锁的线程,并检查这些线程是否存活;
          b. 若已消亡,设置为无锁状态;
          c. 若仍然存活,判断该线程是否竞争此锁。若不竞争,则线程 ID 改为其他竞争的线程,否则升级锁。若撤销次数超过 40 次,仍然会强制升级锁;
      • 轻量级锁:
        (1)锁撤销:通过 CAS 尝试修改 MarkWord,若失败则升级锁;
      • CAS 自旋锁:
        (1)概述:在 JDK 1.6 后默认开启自旋锁,轻量级锁升级后不会直接升级为重量级锁。在 JDK 1.6 前自旋 10 次后自动挂起线程,之后则自适应自旋次数;
        (2)优点:避免重量级锁挂起/恢复线程时切换用户态与内核态。
  4. synchronized 与 ReentrantLock
    • 二者都是可重入锁;
    • synchronized 是非公平锁,ReentrantLock 可以设置是否公平;
    • synchronized 配合 wait() 与 notify() 实现等待唤醒机制,唤醒的线程由 JVM 决定,且 notifyAll() 会唤醒所有线程。ReentrantLock 配合 Condition 的 await() 和 signal() 实现等待唤醒机制,唤醒的线程由用户决定,且 notifyAll() 只会唤醒同一个 Condition 对象的线程。

4 volatile 关键字

  1. 作用
    • 保证数据可见性。JDK 1.2 前线程直接使用主内存存取数据,1.2 之后线程通过工作内存(如寄存器)拷贝主内存的数据进行操作,会导致主内存与工作内存数据不一致;
    • 防止指令重排。
  2. 并发编程三要素
    • 原子性(synchronized 保证)
    • 可见性(volatile 保证)
    • 有序性(volatile 保证)

5 ThreadLocal

  1. 作用:ThreadLocal 用于存储线程私有的对象。
  2. 原理:
    • Thread 类维护一个 ThreadLocalMap,每个 Thread 对象的 ThreadLocalMap 都不同;
    • ThreadLocal 通过调用当前线程的 ThreadLocalMap 的存取元素方法进行元素的存取,实现每个线程的存储对象不同;
  3. ThreadLocal 内存泄漏问题
    • ThreadLocalMap 中 Entry 的 key 为弱引用,如果栈中 ThreadLocal 设置为 null,则 key 只剩下弱引用将被 GC,而 value 是强引用,导致某些 Entry 永远无法被访问且留在内存中,导致内存溢出;
    • 假设 Entry 的 key 为强引用,当 ThreadLocal 设置为 null,依然导致 Entry 无法被回收引起内存溢出;
    • 解决方法:ThreadLocal 每次 get/set/remove 方法时都会清除 key 为 null 的 Entry。

6 线程池

  1. 线程池的优点
    • 降低资源消耗:不需要重复创建于销毁线程;
    • 提高响应速度:不需要等待线程创建;
    • 提高可管理性;
  2. 创建线程池
    • 使用 Executors 创建(不推荐):
      • 因工作队列 WorkQueue 容量为 Integer.MAX_VALUE 可能导致 OOM 的线程池:
        (1)SingleThreadExecutor:只存储一个线程;
        (2)FixedThreadPool:存储指定线程数;
      • 因最大线程数 MaximumPoolSize 容量为 Integer.MAX_VALUE 可能导致 OOM 的线程池:
        (1)CachedThreadPool:可伸缩长度;
        (2)ScheduleThreadPool:固定长度;
    • 使用 ThreadPoolExecutor 构造:
      图片说明
全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务