1 线程生命周期
图片来源于网络
2 死锁
- 造成死锁的条件
- 互斥条件:该资源任意时刻只能由一个线程占用;
- 请求与保持条件:某线程获取资源失败后仍不释放已获得的资源;
- 不剥夺条件:该资源被线程释放前,不能被其他线程剥夺;
- 循环等待条件:若干线程头尾相接地等待资源;
- 破坏死锁
- 互斥条件无法破坏,锁就是用于创造互斥条件的;
- 破坏请求与保持条件:一次性申请所有的资源;
- 破坏不剥夺条件:主动让出资源,如 ReentrantLock 的 tryLock() 失败后手动释放锁;
- 破坏循环等待条件:按顺序申请资源。
3 synchronized 关键字
- 什么是synchronized
被它修饰的方法或代码块同时只有一个线程进行访问。 - 主要使用方式
- 修饰实例方法:给当前对象加锁;
- 修饰静态方法:给当前类加锁;
- 修饰代码块:给某个指定对象加锁;
- 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)优点:避免重量级锁挂起/恢复线程时切换用户态与内核态。
- 偏向锁:
- synchronized 与 ReentrantLock
- 二者都是可重入锁;
- synchronized 是非公平锁,ReentrantLock 可以设置是否公平;
- synchronized 配合 wait() 与 notify() 实现等待唤醒机制,唤醒的线程由 JVM 决定,且 notifyAll() 会唤醒所有线程。ReentrantLock 配合 Condition 的 await() 和 signal() 实现等待唤醒机制,唤醒的线程由用户决定,且 notifyAll() 只会唤醒同一个 Condition 对象的线程。
4 volatile 关键字
- 作用
- 保证数据可见性。JDK 1.2 前线程直接使用主内存存取数据,1.2 之后线程通过工作内存(如寄存器)拷贝主内存的数据进行操作,会导致主内存与工作内存数据不一致;
- 防止指令重排。
- 并发编程三要素
- 原子性(synchronized 保证)
- 可见性(volatile 保证)
- 有序性(volatile 保证)
5 ThreadLocal
- 作用:ThreadLocal 用于存储线程私有的对象。
- 原理:
- Thread 类维护一个 ThreadLocalMap,每个 Thread 对象的 ThreadLocalMap 都不同;
- ThreadLocal 通过调用当前线程的 ThreadLocalMap 的存取元素方法进行元素的存取,实现每个线程的存储对象不同;
- ThreadLocal 内存泄漏问题
- ThreadLocalMap 中 Entry 的 key 为弱引用,如果栈中 ThreadLocal 设置为 null,则 key 只剩下弱引用将被 GC,而 value 是强引用,导致某些 Entry 永远无法被访问且留在内存中,导致内存溢出;
- 假设 Entry 的 key 为强引用,当 ThreadLocal 设置为 null,依然导致 Entry 无法被回收引起内存溢出;
- 解决方法:ThreadLocal 每次 get/set/remove 方法时都会清除 key 为 null 的 Entry。
6 线程池
- 线程池的优点
- 降低资源消耗:不需要重复创建于销毁线程;
- 提高响应速度:不需要等待线程创建;
- 提高可管理性;
- 创建线程池
- 使用 Executors 创建(不推荐):
- 因工作队列 WorkQueue 容量为 Integer.MAX_VALUE 可能导致 OOM 的线程池:
(1)SingleThreadExecutor:只存储一个线程;
(2)FixedThreadPool:存储指定线程数; - 因最大线程数 MaximumPoolSize 容量为 Integer.MAX_VALUE 可能导致 OOM 的线程池:
(1)CachedThreadPool:可伸缩长度;
(2)ScheduleThreadPool:固定长度;
- 因工作队列 WorkQueue 容量为 Integer.MAX_VALUE 可能导致 OOM 的线程池:
- 使用 ThreadPoolExecutor 构造:
- 使用 Executors 创建(不推荐):