Java开发笔记_多线程
线程和进程?
进程(Process):
- 进程是操作系统分配资源的基本单位,是程序的一个执行实例
- 每个进程都有自己独立的内存空间,包括代码、数据、堆栈等
- 进程之间相互独立,一个进程的崩溃不会影响其他进程
- 进程间通信需要使用操作系统提供的机制,如管道、消息队列、共享内存等
线程(Thread):
- 线程是进程的一个执行流,是进程的一部分
- 线程共享进程的内存空间,因此线程间通信相对更容易
- 线程可以更轻量级地创建和销毁,开销小
联系与区别:
- 线程是进程的一部分,一个进程可以包含多个线程
- 进程是独立的执行环境,拥有独立的内存空间,而线程共享进程的内存空间
- 进程之间的切换开销较大,涉及上下文切换。线程切换开销相对较小
- 进程间通信通常需要操作系统提供的机制,而线程间通信相对更容易,可以通过共享内存等方式实现
说说线程有几种创建方式?
- 继承 Thread 类:重写 run() 方法,调用 start() 方法启动线程
- 实现 Runnable 接口:重写 run() 方法,调用 start() 方法启动线程
- 实现 Callable 接口:重写 call() 方法
- 使用匿名类或 Lambda 表达式
- 使用线程池
为什么调用 start() 方法时会执行 run() 方法,那怎么不直接调用 run() 方法?
- 调用 start() 方法会创建一个新线程,然后新线程执行 run() 方法,从而实现多线程并发运行
- 如果直接调用 run() 方法,就会在当前线程执行,不会启动新线程,就不会实现多线程并发执行
线程有哪些常用的调度方法?
- wait():使线程进入等待状态
- join():让线程等待另一个线程执行完成
- notify():唤醒等待中的一个线程
- notifyAll():唤醒所有等待中的线程
- yield():让当前线程让出 CPU 资源
- interrupt():中断线程
- isInterrupted():检查线程的中断状态
- sleep():使线程进入休眠状态
线程有几种状态?
![[image-20230329090159344.png]]
- 初始状态
- 运行状态
- 阻塞状态:需要等待锁释放
- 等待状态:线程需要等待其他线程做出一些特定动作 (通知或中断)
- 超时等待状态:可以在指定时间后自行返回
- 终止状态:线程已经运行完毕
守护线程了解吗?
Java 线程分为两类,分为守护线程和用户线程
- 守护线程:服务性质的线程,在所有用户线程结束时会自动结束。通常用来执行后台任务,比如垃圾回收、资源管理等。不影响 JVM 的退出
- 用户线程:用户手动创建的线程,在程序执行期间一直存在,直到主线程执行完成或者被手动终止。用户线程会影响 JVM 的退出
线程间有哪些通信方式?
- 共享变量:线程通过读写共享内存中的变量进行通信
- 锁机制:使用锁来限制对共享资源的访问,从而实现线程的同步和互斥
- wait-notify 机制:一个线程可以通过 wait 方法进入等待状态,等待其他线程调用 notify/notifyAll 来唤醒
- 使用管道流:管道流可以在两个线程之间进行数据传输
- 阻塞队列:一个线程可以通过向队列中添加元素来与其他线程进行通信
并发和并行?
- 并发:系统能够同时执行多个任务,不一定是同时执行,而是通过任务的切换和调度来实现多个任务交替执行
- 并行:系统中同时执行多个任务,每个任务在独立的处理器上执行,各个任务之间相互独立
同步和异步?
- 同步:调用一个函数或方法后,必须等待完成并返回结果,才能继续执行
- 异步:调用一个函数或方法后,不用等待完成,会通过回调函数、事件触发等机制来处理最终的结果
为什么使用多线程?
- 提高程序性能:在多核 CPU 上,多线程可以充分利用多核来并行处理任务,加快程序的执行速度
- 提高资源利用率:多线程可以充分利用 CPU 和内存等资源
- 异步处理:多线程可以实现异步操作,例如在后台加载数据、文件下载等,提高系统的响应性
使用多线程可能带来的问题?
- 内存泄露
- 死锁
- 线程不安全
- 性能问题
什么是线程上下文切换?
- 线程上下文切换是指在多线程环境中,由于多个线程共享 CPU 的执行时间
- 当一个线程的执行时间片用完或者需要切换到另一个线程时,操作系统会暂停当前线程的执行,保存其上下文,然后加载另一个线程的上下文,执行线程
什么是线程死锁?
- 死锁是多个线程因相互等待对方持有的资源而无法继续执行的僵局状态
死锁产生的条件?
- 互斥条件:资源只能被一个线程占用
- 请求并保持条件:已经获得资源的进程请求其他资源,同时不释放已有资源
- 不剥夺条件:已经占有的资源不能其他的线程抢占
- 循环等待条件:多个进程之间形成环路,每个进程都在等待下一个进程所占有的资源
如何避免死锁?
- 破坏死锁的四个必要条件
- 互斥条件:使用资源分级,避免资源互斥
- 请求与保持条件:线程在获取资源前释放已持有的资源
- 不剥夺条件:允许抢占已持有的资源
- 循环等待条件:强制线程按照一定的顺序获取资源
- 避免无限期等待
- 设置资源的超时时间,如果超时未获取,线程放弃或者重试
- 使用死锁检测和解除机制
- 定期检测系统中是否存在死锁
- 使用锁的顺序
- 确定锁的获取顺序,所有线程按照相同的顺序获取锁
那死锁问题怎么排查呢?
- 确认是否发生了死锁:查看日志,如果出现线程堵塞的异常情况可能是死锁问题
- 定位死锁位置:通过分析线程堆栈信息,定位出现死锁的代码位置
- 确认锁的粒度:确认锁的粒度是否过大,如果锁的粒度过大,容易出现死锁问题
- 检查锁的维护方式:检查锁是否正常释放等
- 分析并发访问情况:了解多线程请求锁的顺序和时间
- 制定解决方案:根据以上情况制定解决方案,如调整锁的粒度、优化代码逻辑、增加重试机制
- 测试验证:在实际系统中测试解决方案是否有效,如果没有解决问题,按照以上步骤继续排查
活锁和饥饿锁了解吗?
- 活锁:两个或多个线程都在互相等待对方执行某个操作,形成死循环
- 饥饿锁:某个线程一直无法获取需要的资源
线程间如何同步?
线程间的同步需要通过互斥锁、条件变量和信号量等机制来实现
- 互斥锁:
- 互斥锁是一种最基本的同步机制,可以实现对共享资源的互斥访问。
- 当某个线程进入临界区进行访问时,其他线程需要等待,直到该线程离开临界区,释放锁资源,才能再继续访问共享资源
- 条件变量:
- 条件变量是一种用于线程间等待和通知的机制
- 当某个线程进入临界区后,如果发现共享资源不满足当前需求,那它就可以将自己休眠,释放锁资源,等待其他线程改变共享资源状态并发送通知以后,再重新唤醒
- 信号量:
- 信号量是一种更高级的线程同步机制,可以实现多个线程之间的同步操作。
- 当某个线程需要访问共享资源时,请求获取信号量。如果有其他线程正在使用该共享资源,则该线程进入休眠状态等待信号量被释放。当共享资源被释放时,信号量发出通知,并将等待资源的线程唤醒
sleep() 和 wait()?
在多线程中,sleep() 和 wait() 方法都可以暂停线程的执行
- sleep() 方法不会释放锁,wait() 会释放锁
- sleep() 方法通常用于暂停执行,wait() 通常用于线程间通信
- sleep() 方法在暂停指定时间后,自动恢复运行;wait() 方法调用后,需要其他线程调用 notify/notifyAll 方法唤醒
- sleep() 方法是 Thread 类方法,wait() 是 Object 类方法
为什么 wait() 方法不定义在 Thread 中?
- wait() 方法用于线程间通信,通过释放对象的锁并让线程进入等待状态,每个对象都拥有对象锁,因此定义在 Object 类
- sleep() 方法是让当前线程暂停执行,不涉及对象类,因此定义在 Thread 类中
说说你对原子性、可见性、有序性的理解?
- 原子性:
- 原子性指一个操作不可分割的,要么完全执行,要么完全不执行
- 原子性确保了操作的不可分割性
- 可见性:
- 可见性指一个线程对共享数据进行修改后,其他线程立即能知道这个修改
- 可见性确保了线程间共享数据的同步性
- 有序性:
- 有序性指程序执行的顺序和代码顺序一致
如何保证变量的可见性?
- 使用 volatile 关键字:将变量声明为 volatile,每次访问该变量时都会从主存中读取最新的值,而不是使用线程的缓存
- 使用 synchronized 或 Lock:使用同步机制来确保对变量的访问时互斥的
- 使用原子类:原子类的操作是原子的,可以保证对变量的修改可见
- 使用并发集合:使用线程安全的并发集合类,如 ConcurrentHashMap
- 使用线程间通信:可以使用线程间通信机制,确保线程间的操作顺序和可见性
那说说什么是指令重排?
- 指令重排是处理器和编译器的一种优化手段
- 通过优化指令的执行顺序,充分利用处理器的执行单元,减少指令的等待时间,提高程序的执行效率
指令重排有限制吗?happens-before 了解吗?
- 指令重排有限制,有两个规则 happens-before 和 as-if-serial
- happens-before 规则用来定义多线程间操作的执行顺序
如果操作 A happens-before 操作 B,那么在程序执行中,A 的效果对 B 可见
as-if-serial 又是什么?单线程的程序一定是顺序的吗?
- as-if-serial 规则是 Java 内存模型的另一个重要原则
允许虚拟机在保证多线程正确性的前提下进行指令重排,只要重排后的执行结果和原始执行顺序一致即可 - 单线程的程序一定是顺序执行的
volatile 关键字?
volatile 关键字可以保证可见性和有序性
- 保证可见性:线程读取变量的值都从主存中读取
- 保证有序性:volatile 关键字可以禁止指令重排
volatile 实现原理了解吗?
- volatile 实现可见性和有序性的关键在于内存屏障
- 内存屏障是一种硬件或软件层面的机制,可以确保在特定位置的操作不会发生在内存屏障之前或之后
- volatile 变量的读写操作会插入内存屏障,保证读写 volatile 变量时不会指令重排
解释双重校验锁实现单例的原理?
双重校验锁是一种常用于实现线程安全的懒汉式单例模式的优化方式
- 首次检查:在双重校验锁中,首先检查对象实例是否被创建,如果没有才进行同步
- 同步块:在单例对象还没有创建的时候,使用synchronized对代码块加锁,并通过两次检查对象是否被实例化,保证只有一个线程执行了同步块内的代码
- volatile 关键字:使用 volatile 关键字确保线程之间的可见性和禁止指令重排序,避免出现另一个线程获得不完全初始化的实例
//双重校验锁实现对象单例(线程安全)
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}} }
return uniqueInstance;}}
volatile 可以保证原子性吗?
- volatile 关键字不能保证对变量的操作是原子性的
synchronized?作用?
- synchronized 是 Java 用于实现线程同步的关键字
- 被修饰的代码在同一时刻只能被一个线程执行,从而实现线程的互斥访问,保证操作的原子性和线程安全
synchronized 怎么使用?
synchronized 用来保证代码的原子性和线程安全
- 修饰实例方法(锁当前对象实例):当方法被 synchronized 修饰时,该方法在同一时刻只能被一个线程执行,其他线程需要等待
- 修饰静态方法(锁当前类):类级别的锁,修饰的静态方法在同一时刻只能被一个线程执行,其他线程需要等待,这个锁是当前类的锁,会作用于这个类的所有对象实例
- 修饰代码块(锁指定对象或类):对括号中指定的对象或类加锁,进入同步代码前要获得给定对象或给定类的锁
构造方法可以用 synchronized 修饰吗?
- 构造方法不能用 synchronized 修饰
- 构造方法本身就是线程安全的,不需要同步
synchronized 底层原理了解吗?
- synchronized 底层原理是基于对象监视器 monitor 对象实现的
- 每个对象都有一个 monitor 对象和一个关联的等待队列,当线程进入 sychronized 代码块时,它会获取 monitor 对象的锁,其他线程必须等待锁被释放才能执行
除了原子性,synchronized 可见性,有序性,可重入性怎么实现?
- 可见性:synchronized 关键字在释放锁之前,会将当前线程对共享变量的修改刷新到主存中,保证其他线程可以看到最新的值
- 有序性:synchronized 同步的代码块,一次只能被一个线程访问,也就是同一时刻,代码是单线程执行的
- 可重入性:线程要再次执行 synchronized 代码块的时候,线程不会被堵塞,而是直接进入该代码块,这是通过给每个锁添加一个计时器来实现的
JDK1.6 后对 synchronized 的优化?
增加了适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁等优化策略,提高了同步性能
- 适应性自旋:当线程发现被阻塞的时间很短时,采用自旋,持续尝试获取锁,避免线程进入阻塞状态
- 锁消除:编译器会分析代码,判断是否可能存在共享数据共享,如果不存在,会消除不必要的锁
- 锁粗化:将连续的同步操作合并为一个锁的范围
- 轻量级锁:当前线程争夺锁的时候,使用 CAS 操作进行锁竞争
- 偏向锁:程序进入同步代码块时自动获取锁
锁升级?
锁的升级过程时为了在不同的竞争情况下,提供不同的锁状态,以达到更好的性能和线程安全,锁只会升级不会降级,所以当锁从重量级锁状态升级到其他状态时,会先解锁再升级
- 无锁状态:当没有线程占有锁时,对象处于无锁状态
- 偏向锁状态:只有一个线程访问对象时,会将对象标记为偏向锁状态,线程再次访问时,不需要重新获取锁
- 轻量级锁状态:当多个线程竞争同一个锁时,会将对象标记为轻量级锁状态,在轻量级锁状态,线程会通过 CAS 操作来竞争锁
- 重量级锁状态:当多个线程竞争同一个锁时,无法通过轻量级锁来解决竞争,锁会升级为重量级锁状态,此时线程会进入阻塞状态,通过操作系统的互斥量来保持同步
synchronized 和 volatile 有什么区别?
- volatile 关键字
- 主要用来解决变量在多线程之间的可见性
- 保证当一个线程修改变量时,其他线程能够立即看到变量的最新值
- 适用于简单的场景,可以用于修饰共享变量以保证可见性
- sychronized 关键字
- 不仅保证了可见性,还保证原子性
- 用来实现线程之间的同步,可以修饰方法和代码块
- 适用于多线程同步的场景
AQS 了解吗?
- 抽象队列同步器,是 Java 中一个抽象类
- 提供用于实现同步器的框架,为构建基于锁或其他同步器的大部分实现提供了底层的支持
- Java 中的很多同步工具,如 ReentrantLock、CountDownLatch、Semaphore 等都是基于 AQS 实现的
讲讲对 AQS 原理的理解?
- 状态变量:AQS 使用一个状态变量来表示资源的可用性
- 等待队列:当一个线程想要访问资源但资源已经被其他线程占用时,该线程会被放入一个等待队列,这个队列按照先进先出管理
- 获取和释放:当资源被释放时,AQS 会从等待队列中唤醒一个或多个线程,让它们继续执行
- 同步模式:AQS 支持独占模式和共享模式
- 独占模式:一次只有一个线程能获取锁,比如独占锁
- 共享模式:允许多个线程同时访问资源,比如读写锁
- CAS 操作:AQS 使用 CAS 操作来维护状态变量和等待队列,确保线程安全的队列操作
讲讲 CAS?
- CAS:CompareAndSwap,比较并交换,是一种并发编程中的原子操作
- 用于实现多线程环境下的同步和数据更新
- 基本思想:检查某个内存位置的值是否和预期值相等,如果相等,则将新的值写入该内存位置,否则不做任何操作
CAS 有什么问题?如何解决?
- ABA 问题:添加版本号和时间戳
- 循环性能开销:限制自旋次数
- 只能保证一个变量的原子操作:结合锁机制
Java 有哪些保证原子性的方法?如何保证多线程下 i++ 结果正确?
- 使用 synchronized 关键字
- 使用原子类
- 使用 Lock 接口中的锁
Atomic 原子类?
- Atomic 原子类提供简单高效的方式来实现线程安全的操作
- 避免了使用显式的锁机制
- 在低并发的情况下,性能优于传统的锁机制
AtomicInteger 的原理?
- 原子类主要利用 CAS 来实现原子性操作
什么是乐观锁?什么是悲观锁?使用场景?
- 乐观锁:
- 乐观锁认为数据在多线程之间不会产生冲突
- 因此在读取数据时不会加锁,而是在更新数据时检查数据版本或者使用 CAS 操作来保证数据的一致性
- 适用于读多写少,且数据冲突概率低的情况
- 悲观锁:
- 悲观锁认为并发访问的时候数据冲突的概率较大
- 因此在读写数据的时候会加锁,阻止其他线程对数据的访问
- 适用于读写操作频繁,数据冲突概率高的情况
如何实现乐观锁?
-
乐观锁使用版本号机制或 CAS 算法实现
公平锁和非公平锁的区别?
-
公平锁:锁被释放的时候,先等待的线程先得到锁
-
非公平锁:锁被释放的时候,线程随机获取锁
可中断锁和不可中断锁的区别?
- 可中断锁:获得锁的过程可以被中断,不需要等到获取锁之后才能进行逻辑处理,如 ReentrantLock
- 不可中断锁: 一旦线程申请了锁,就只有拿到锁以后才能进行逻辑处理,如 synchronized
共享锁和独占锁有什么区别?
- 共享锁:一把锁可以被多个线程同时获得
- 独占锁:一把锁只能被一个线程获得
线程持有读锁还能获取写锁吗?
- 线程持有读锁的情况下,线程不能获取写锁
- 线程持有写锁的情况下,线程可以继续获得读锁
读锁为什么不能升级为写锁?
-
写锁可以降级为读锁,读锁不能升级为写锁,这是因为写锁是独占锁,读锁升级为写锁会引起线程的争夺
CountDownLatch(倒计数器)了解吗?
-
CountDownLatch,倒计时器,是 Java 并发包的一个同步辅助类
-
主要用于等待一组线程的执行完成
-
工作方式:通过一个初始计数器,然后在主线程中等待,直到计数器减少到零
-
两个常见的使用场景
- 等待一组线程完成
- 统一各线程动作开始的时机
CyclicBarrier(同步屏障)了解吗?
- CyclicBarrier,可循环使用的屏障,是 Java 并发包中的同步辅助类
- 用于让一组线程等待,直到所有线程到达共同的屏障点,然后在屏障点继续执行
CyclicBarrier 和 CountDownLatch 有什么区别?
- 可循环性:
- CountDownLatch 是一次性的
- CyclicBarrier 可以多次设置屏障,实现循环使用
- 线程等待方式:
- CountDownLatch 中的每个线程在到达指定计数前不需要等待其他线程
- CylicBarrier 中的所有线程需要等待其他线程到达屏障
Semaphore(信号量)了解吗?
- Semaphore,信号量,用来控制同时访问特定资源的线程数量,从而对线程的并发访问
- 适用于资源有限的场景,比如数据库连接池、线程池、网络连接等
- 还可以实现流量控制
Exchanger 了解吗?
- Exchanger,是 Java 并发编程的同步工具
- 提供一个同步点,让两个线程可以交换彼此的数据
- 每个线程在交换数据时都会被阻塞,直到另一个线程也准备好交换数据
- 可以用于多线程间的数据交换,适用于需要线程间协作的场景
ThreadLocal 是什么?
- ThreadLocal 是一个创建线程局部变量的类
- 线程局部变量:每个线程都拥有自己独立的变量副本,在不同线程之间互不干扰
- 通过 ThreadLocal,每个线程可以独立访问和修改自己的变量副本,不会影响其他线程的副本
你在工作中用到过 ThreadLocal 吗?
- 用户会话管理
- 数据库连接管理
- 事务管理
- 线程上下文信息传递
ThreadLocal 怎么实现的呢?
- ThreadLocal 通过在每一个线程中维护一个 ThreadLocalMap 来实现
- 在存储值时,实际上是将这个值存储在当前线程的 Map 中
- ThreadLocalMap 是一个类似于哈希表的结构,key 是 ThreadLocal 实例的弱引用,value 是需要存储的值
ThreadLocal 内存泄露是怎么回事?
- ThreadLocalMap 中的 key 是 ThreasLocal 的弱引用,而 value 是强引用
在垃圾回收的时候,会出现 ThreadLocal 对象被回收,但是 value 还在 ThreadLocalMap 中的情况,就会导致内存泄露 - 解决方法:使用完 ThreadLocal 后,调用 remove() 方法释放内存空间
ThreadLocalMap 的结构了解吗?
ThreadLocalMap 是一个类似于哈希表的结构,key 是 ThreadLocal 实例的弱引用,value 是需要存储的值
父子线程怎么共享数据?
- 使用 InheritableThreadLocal 类
为什么要用线程池/线程池的好处?
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁的开销
- 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行
- 提高线程的可管理性:使用线程池可以对线程进行统一的分配
能简单说一下线程池的工作流程吗?
- 创建线程池:初始化阶段创建一定数量的线程
- 将任务加入任务队列:当一个任务请求到达时,将其加入任务队列
- 线程执行任务:空闲的线程从消息队列中获取任务,执行任务
- 继续获取任务执行:任务完成后,线程并不终止,而是回到线程池中,准备获取新的任务继续执行
- 线程销毁:系统关闭时会回收所有线程资源
线程池常见参数有哪些?如何解释?
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- workQueue:等待队列
- handler:饱和拒绝策略
线程池的饱和策略有哪些?/线程池的拒绝策略有哪些?
- AbortPolicy:直接抛出异常
- CallerRunsPolicy:用调用者所在的线程执行任务
- DiscardOldestPollicy:丢弃队列最老的任务
- DiscardPolicy:直接丢弃任务
线程池处理任务的流程了解吗?/线程池原理分析?

- 核心线程池:当提任务到达线程池时,线程池判断核心线程池是否已满,如果没有,则创建线程,执行任务
- 队列:如果核心线程池已满,线程池判断工作队列是否已满,如果没有,则将新的任务存储在队列中
- 线程池:如果队列已满,线程池判断判断线程池是否已满,如果没有,创建新的线程,执行任务
- 饱和策略:如果线程池已满,则交给饱和策略来处理这个任务
线程池常用的阻塞队列有哪些?
- ArrayBlockingQueue:基于数组实现的堵塞队列
- LinkedBlockingQueue:基于链表实现的阻塞队列
- SynchronousQueue:同步队列,不存储元素
- DelayQueue:延迟队列
线程池提交 execute 和 submit 有什么区别?/execute() vs submit()?
- execute:用于提交不需要返回值的任务
- submit:用于提交需要返回值的任务
线程池怎么关闭知道吗?
- 可以调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池
- 原理:遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程
- shutdown:关闭提交通道,等已有线程执行完,关闭线程池
- shutdownNow:立即关闭线程池
线程池的线程数应该怎么配置?/ 配置线程池的参数?/ 如何设定线程池的大小?
- 计算密集型的线程池:一般是 CPU 数 +1,+1 是因为可能存在页缺失
- IO 密集型的线程池:一般是 CPU 数 *2
Executor 框架?
- Executor 框架是 JDK5 引进的并发编程工具
- 使用 Executor 框架替代 Thread 的方法来启动线程 ,可以更好管理线程资源
创建线程池的两种方式?
- 通过 ThreadPoolExecutor 构造函数创建
- 通过 Executor 框架的工具类创建
有哪几种常见的线程池?
- FixedThreadPool:固定数据线程的线程池
- SingleThreadExecutor:单线程的线程池
- CachedThreadPool:可缓存线程的线程池
- ScheduleThreadPool:定时执行的线程池
线程池异常怎么处理知道吗?
- try-catch 捕获异常
- Future 类的 get 方法接收异常
- 自定义处理未检测的异常
能说一下线程池有几种状态吗?
- RUNNING:运行状态
- SHUTDOWN:不接受新任务,但处理已有任务状态
- STOP:不接受新任务,中断所有任务
- TIDYING:所有任务已停止的状态
- TERMINATED:线程池终止状态
Fork/Join 框架了解吗?
- Fork/Join 框架是用于实现任务并行的框架
- 基于分治的思想,把大任务拆分成若干个小任务,最终并行执行小任务,最后将结果合并后得到大任务的结果
Runnable vs Callable?
- Runnable 接口不会返回结果或抛出检查异常
- Callable 接口可以通过 Future 类的 get 方法来获取返回结果和抛出异常
Thread、Runnable、Callable、Future、FutureTask 关系?
- Thread:Java 中的线程类,通过继承 Thread 类,重写 run 方法创建线程
- Runnable:一个接口,通过实现 Runnable 接口,实现 run 方法可以创建线程
- Callable:类似于 Runnable,但是可以返回执行结果或抛出异常
- Future:表示异步计算的结果,可以用来检查任务是否完成、获取任务的执行结果
- FutureTask:实现 Future 接口,可以接受 Runnable 或 Callable 实例作为参数,能够获取任务的执行结果
Runnable 和 Callable 是为了将任务逻辑和线程分离,提高代码的灵活性
Future 和 FutureTask 提供一种处理异步任务结果的机制,使得主线程可以等待异步任务的完成并获取结果
CompletableFuture 类有什么用?
- CompletableFuture 是 Java8 引入的一个类,用于处理异步编程
- 针对 Future 类做了改进,可以方便进行异步任务的组合和转换
ReentrantLock?
- ReentrantLock 是 Java并发包中的一个可重入的互斥锁
- 实现 Lock 接口
- 提供比使用 synchronized 关键字更高级别的同步控制机制
ReentrantLock 实现原理?
-
ReentrantLock 的核心实现是通过一个 AQS 同步器来实现锁的获取和释放
-
获取锁的过程:
- 调用 ReentrantLock 的 lock() 方法
- 调用 AQS 的方法尝试获取锁
- 锁是空闲的,获取锁成功
- 锁已经被持有,就将线程加入到等待队列中
- 当锁被释放后,AQS 会从等待队列中唤醒一个线程,将锁分配给该线程
-
释放锁的过程:
- 调用 ReentrantLock 的 unlock() 方法
- 调用 AQS 的方法释放锁
ReentrantLock 怎么实现公平锁?
- new ReentrantLock() 构造函数默认创建的是非公平锁,非公平锁调用 lock 后,会调用 CAS 进行抢锁
- 可以在创建锁构造函数中传入具体参数创建公平锁,公平锁会判断等待队列是否有线程处于等待状态,如果有就不进行抢锁
synchronized 和 ReentrantLock 的区别?
- 锁的实现:
- synchronized 关键字,基于 JVM 实现
- ReentrantLock 基于 JDK 的 API 实现
- 功能特点:相比较 synchronized,ReentrantLock 增加了高级功能,比如等待可中断、可实现公平锁和可实现选择性通知
- 等待可中断:ReentrantLock 提供能够中断等待锁的线程的机制
- 可实现公平锁:ReentrantLock 可以指定是公平锁还是非公平锁,而 synchronized 只能是非公平锁
- 可实现选择性通知:用 ReentrantLock 结合 Condition 实例可以实现选择性通知
ReentrantReadWriteLock 是什么?适合什么场景?
- ReentrantReadWriteLock:是一个可重入的读写锁
读锁是共享锁,写锁是独占锁,底层也是基于 AQS 实现的 - ReentrantReadWriteLock 能够保证多个线程同时读的效率,也能保证写入操作的线程安全,适合读多写少情况
