并发编程三要素?
- 三要素主要包括可见性、原子性和有序性
- 可见性:是指一个线程对共享变量的修改能够被其他线程立即看到的特性。
- 原子性:是指一个或多个操作要么全部执行成功,要么全部执行失败,不会被其他因素打断。
- 有序性:是指程序执行的顺序必须符合预期,不能出现乱序的情况。
同步方法和同步块哪个是更好的选择?
- 如果业务需求简单,可以倾向于使用同步方法,因为它更简单直观。但如果需要更细粒度的锁控制,或者想要提高性能和减少死锁风险,同步块是更好的选择。
谈谈原子性?哪些使用到了?
- 原子性:是指一个或多个操作要么全部执行成功,要么全部执行失败,且这个执行过程不会被其他线程打断或干扰。
- 实现方式:
- 使用synchronized关键字:
- 通过在方法或代码块上使用synchronized关键字,可以确保同一时间只有一个线程能够执行该方法或代码块,从而实现原子性。但这种方式可能会导致线程阻塞和性能下降。
- 使用原子类:
- Java提供了java.util.concurrent.atomic包中的一系列原子类,如AtomicInteger、AtomicLong、AtomicBoolean等。这些类中的方法都是原子操作,内部使用了CAS(Compare and Swap)等底层机制来确保操作的原子性。这种方式比使用synchronized关键字更高效,因为它不会导致线程阻塞。
- 使用Lock接口及其实现类:
- Java还提供了Lock接口及其实现类(如ReentrantLock)来提供比synchronized更灵活的锁机制。通过显式地加锁和解锁,可以确保操作的原子性。但这种方式需要程序员自己管理锁的生命周期,增加了代码的复杂性。
- 使用synchronized关键字:
谈谈可见性?哪些使用到了?
- 可见性:是指当一个线程修改了共享变量的值时,这个新值对其他线程来说是可以立即得知的。
- 可见性使用情况:
- volatile关键字
- synchronized关键字
- final关键字
- Locks和Condition
- 原子变量类
- 线程的启动和结束
谈谈有序性?举一个例子?
- 有序性:是指多线程环境下,程序执行顺序的一种保障机制。
什么是线程池?
- 线程池:是一种重用线程的机制,它可以在需要时创建线程,而不是每次都创建新的线程。
- 线程池核心参数:最大线程数、拒绝策略、核心线程数、任务队列、线程空闲时间
- 常见类型:固定大小线程池(FixedThreadPool)、缓存线程池(CachedThreadPool)、单线程池(SingleThreadPool)、定时线程池(ScheduledThreadPool)、工作窃取线程池
线程池有哪些创建方式?
- 使用Executors工厂类
- 直接使用ThreadPoolExecutor类
- 使用Executors工厂方法的变体
- 使用ForkJoinPool
谈谈四种线程池的创建?
- Executors.newCachedThreadPool:创建一个可根据需要创建新线程的线程池,并允许自定义线程工厂。
- Executors.newFixedThreadPool:创建一个可重用固定线程数的线程池,并允许自定义线程工厂。
- Executors.newSingleThreadExecutor:创建一个只有一个线程的线程池,并允许自定义线程工厂。
- Executors.newScheduledThreadPool:创建一个用于延迟执行或定期执行任务的线程池,并允许自定义线程工厂。
newCachedThreadPool?
- 缓存线程池:不固定线程数量,可以根据需要自动创建新线程,适用于短期异步任务。
newFixedThreadPool ?
- 固定大小线程池:包含固定数量的线程,适用于需要限制并发线程数量的场景。
newScheduledThreadPool ?
- 定时线程池:可以执行定时任务和周期性任务。
newSingleThreadExecutor ?
- 单线程池:只包含一个工作线程,保证所有任务按顺序执行,适用于需要保持任务顺序执行的场景。
多线程的优缺点?
- 优点:提高性能、改善响应性、资源利用率高、并行处理、代码模块化、提高吞吐量
- 缺点:复杂性增加、竞争条件、死锁、上下文切换开销、资源限制、调试困难、不可预测性、线程安全问题
创建线程的有哪些方式?
- 继承Thread类、实现Runnable接口、使用Callable和Future创建、使用线程池
谈谈各种创建线程的优缺点?
- 继承Thread类:
- 优点:编写简单,容易理解
- 缺点:由于单继承机制,如果类已经继承另一个类,则无法再继承Thread类
- 实现Runnable接口:
- 优点:避免了单继承限制,可实现多个接口。更加灵活,可使用lambda表达式、匿名内部类方式简化代码
- 缺点:需手动管理线程的生命周期
- 使用Callable和Future创建:
- 优点:可获取线程的返回值。可声明抛出异常
- 缺点:相比Runnable接口更加复杂,需配合FutureTask使用
- 使用线程池:
- 优点:提高线程的利用率和系统吞吐量。降低线程创建、销毁的开销。提供更好的线程管理策略,如线程服用、线程缓存等。
- 确定:需理解线程池的工作原理和配置参数。需注意线程池的资源管理和关闭问题。
对比下你应该选择哪种创建?
- 需要根据具体的应用场景和需求、编码规范来选择合适的方式。
Runnable和Callable的区别?
- 核心方法差异:Runnable接口核心方法是run,Callable接口核心方法是call。
- 使用场景差异:Runnable接口适用简单任务,这些任务不需要返回值,也不会抛出受检异常。Callable接口适用需要返回值的任务或需要抛出受检异常的任务。
- 异常处理差异:Runnable接口无法对异常进行显式的捕获和处理异常。Callable可以抛出受检异常,可以显式的捕获和处理异常。
- 与线程类的关系:Runnable接口可以作为Thread类的构造器参数,通过Thread类来启动线程。不能直接作为Thread类的构造器参数,需通过其他方式(FutureTask类)启动线程。
线程的状态流转图?有哪些状态?
- 线程状态:NEW(新建)、RUNNABLE(可运行)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(超时等待)、TERMINATED(终止)
线程池的优点?
- 资源管理
- 提高吞吐量
- 性能提升
- 减少上下文切换
- 灵活性
- 异常处理
- 任务调度
- 减少竞争
常用的并发集合类有哪些?
- ConcurrentHashMap
- ConcurrentLinkedQueue
- BlockingQueue接口及其实现
- CopyOnWriteArrayList
- CopyOnWriteArraySet
- ConcurrentSkipListMap
- ConcurrentSkipListSet
- AtomicReference
- Collections.synchronizedMap、Collections.synchronizedList、Collections.synchronizedSet
- Executor框架中的线程安全集合
ConcurrentHashMap实现?
- 分段锁、cas操作和节点级锁定、红黑树优化、弱一致性、容量和负载因子、并发性能
- 是在并发环境下提供高性能,通过减少锁的粒度来实现。
- 使用分段锁实现线程安全。使用cas操作来更新结构,减少锁的使用,提高性能。读操作不需要加锁,减少读操作的开销。写操作尝试使用cas操作更新,更新失败,则使用synchronized锁住当前链表或红黑树的节点。遵循内存一致性原则,确保写操作完成后,后续的读操作能看到最新的值。
CopyOnWriteArrayList实现?
- 主要用于读多写少的场景。
- 数据结构底层使用数组。读操作不加锁,直接操作数组的快照。写操作会创建数组的新副本,在副本上执行修改操作,然后将原数组引用指向新副本。
CopyOnWriteArraySet实现?
- 主要用于读多写少的场景。
- 内部使用一个CopyOnWriteArrayList来存储元素。
谈谈COW?
- COW(Copy-On-Write,写时复制)是一种用于优化并发访问的数据结构实现策略。
- COW的基本思想是在进行写操作时,不直接修改原数据,而是先复制一份副本,然后在副本上进行修改。写操作完成后,再将副本替换为原数据。
- COW实现的并发容器:CopyOnWriteArrayList和CopyOnWriteArraySet。
- 优势:读操作无需加锁,提高读取效率。避免锁竞争和死锁。适用读多写少的场景。
- 劣势:写操作内存开销大。极端情况下仍存在数据一致性问题。不适用写操作频繁的场景。
常用的并发工具类有哪些?
- Executor框架:Executor接口、Executors工厂类、ExecutorService接口、ScheduledExecutorService接口、ForkJoinPool
- 同步辅助工具:CountDownLatch、CyclicBarrier、Semaphore(控制同时访问特定资源的线程数量)、Exchanger(用于两个线程交换数据)、Phaser
- 并发集合:ConcurrentHashMap、ConcurrentLinkedQueue、BlockingQueue接口、CopyOnWriteArrayList、CopyOnWriteArraySet
- 原子变量类:AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference、AtomicStampedReference
- 并发工具类:Future、FutureTask、Callable、Runnable、Lock、ReadWriteLock
- 线程池管理工具:ThreadPoolExecutor
- 同步器:ReentrantLock、ReentrantReadWriteLock
CyclicBarrier和CountDownLatch的应用场景?
- CyclicBarrier:并行任务的等待、一次性事件触发、统计多个线程的执行时间。CyclicBarrier的计数器可以重置,因此可以在所有线程释放后,屏障再次用于下一轮的同步。它适用于需要多次等待多个线程完成任务的场景。
- CountDownLatch:并行计算后的汇总、阶段任务同步、游戏或模拟中的回合制同步、批量处理。CountDownLatch的计数器只能减少,且一旦减为0就不能再次使用。它适用于一次性等待多个线程完成任务的场景。
CyclicBarrier和CountDownLatch的区别?
- CyclicBarrier 适用于需要多个线程在某个点进行协作的场景,可以重复使用。
- CountDownLatch 适用于一个线程等待其他线程完成任务的场景,使用后不能重用。
Semaphore的应用场景?
- 适合于需要限制资源并发访问数量的场景。
- 应用场景:控制并发访问、资源池管理、限流、线程执行顺序控制、互斥锁、实现生产者-消费者模式、保护共享资源
synchronized的作用?底层如何实现?
- 作用:是一种同步机制,用于控制多个线程对共享资源的访问,以确保在任一时刻只有一个线程能够执行特定代码段,从而避免并发问题。
- 底层实现:synchronized是通过Monitor(监视器)来实现的,Monitor是依赖于底层操作系统的互斥锁实现的。
synchronized和ReentrantLock的区别?
- synchronized是java关键字,ReentrantLock是java的类。
- synchronized:
- 锁无需获取和释放
- 只支持非公平锁
- 可以修饰普通方法、静态方法、代码块
- 低并发情况下,性能比ReentrantLock好
- 底层是JVM层面通过Monitor(监视器)实现
- reentrantLock:
- 锁需手动获取和释放
- 支持公平锁和非公平锁
- 只能修饰代码块,但可以对代码块进行精细化的控制
- 高并发情况下,性能通常比synchronized好
- 底层基于AQS实现
volatile关键字的作用?底层如何实现?
- 作用:
- 用于确保变量的可见性和禁止指令重排。
- 当一个变量被声明为volatile时,JVM保证了对该变量的读操作总是返回最新的值,即在任意线程中读取该变量时,都能得到该变量的最新值。
- 写操作时,volatile变量的值会立即被更新到主内存中,且这个更新对所有线程都是可见的。
- 底层实现:
- volatile通过内存屏障实现内存可见性
- 读操作前,插入Load Barrier,使得屏障之前的所有读/写操作都完成后,才执行该读操作
- 写操作后,插入Store Barrier,使得屏障之后的写操作都完成后,才执行该操作
什么是CAS?底层如何实现?
- CAS是Java并发编程中一种重要的原子操作,用于实现无锁编程。
- 底层实现:
- CAS的底层实现主要依赖于硬件提供的原子性操作指令和JNI(Java Native Interface)技术。
- CAS操作通常通过Atomic类实现,类中的CAS方法底层用C语言编写,通过JNI技术调用本地方法实现
CAS有哪些问题?
- ABA问题
- 当一个线程准备用CAS更新一个变量的值时,另一个线程可能已经将该值从A改为了B,然后又改回了A。此时,第一个线程执行CAS操作时会认为值没有发生变化,从而成功更新值。
- 解决办法:使用版本号、使用带有标记的原子引用
- 循环时间长开销大
- CAS操作是基于自旋锁的一种实现方式。如果CAS操作一直不成功,会导致线程长时间处于忙等(Busy-Wait)状态,从而消耗大量的CPU资源。
- 只能保证一个共享变量的原子操作
synchronized、volatile、CAS比较?
- synchronized:保证代码执行的原子性、可见性和有序性
- volatile:用于修饰变量,确保变量的可见性,即一个线程修改了变量的值,新值对其他线程来说是立即可见的。
- CAS:通过比较内存中的值和预期值是否相等,来安全地更新变量的值,通常用于实现原子类,如AtomicInteger。
什么是Future?底层如何实现?
- Future是Java并发编程中的一个重要接口,它代表了异步计算的结果。
- 底层实现:
- Future是Java并发编程中的一个重要接口,它代表了异步计算的结果。
- 通过调用Future对象的get()方法来获取任务的结果。
- Future对象的状态管理通常依赖于AQS框架。
- Future对象会捕获这个异常并保存在内部状态中。
什么是FutureTask?
- FutureTask 是 Future 接口的一个实现类,同时也实现了 Runnable 接口,因此可以作为线程执行的任务。
- 既可以作为 Future 的实现类来获取任务的执行结果,也可以作为线程执行的任务来执行异步操作。
- FutureTask 允许在一个单独的线程中执行任务,并且可以获取任务的执行结果。
什么是AQS?底层如何实现?
- AQS是Java并发包中的一个核心组件,是构建锁和其他同步器的基础框架。它定义了一套多线程访问共享资源的同步器框架,为Java并发同步组件提供统一的底层支持。
- 底层实现:内部用一个volatile修饰的int类型的成员变量state来控制同步状态。state = 0表示没有线程正在独占共享资源的锁,state = 1表示有线程正在共享资源的锁
ReadWriteLock读写锁应用场景?
- 应用场景:缓存场景、配置文件修改、共享文档操作、游戏状态管理、社交软件场景
- ReadWriteLock读写锁适用于读多写少的并发场景,通过允许多个线程同时读取共享资源,可以显著提高系统的并发性能。
ReadWriteLock底层实现?
- ReentrantReadWriteLock的底层实现是通过AQS来管理锁的状态和同步操作的。
- 通过将state变量按位拆分来区分读锁和写锁的状态,并提供了读锁和写锁的获取与释放、公平性策略、锁降级等丰富的功能。
ThreadLocal是什么?底层如何实现?
- ThreadLocal是Java提供的一个用于创建线程局部变量的机制,它能够为每个使用该变量的线程提供一个独立的变量副本,从而实现了变量的线程隔离。
- 底层实现:
- 依赖于Thread类和ThreadLocalMap类。
- 通过ThreadLocalMap为每个线程提供了独立的变量副本,从而实现了变量的线程隔离。
死锁的常见原因有哪些?
- Java死锁的常见原因涉及多个方面,包括竞争系统资源、锁的嵌套与不当的申请顺序、线程间相互等待、锁失效、饥饿与资源分配不均等。
如何避免死锁?有哪些解决方案?
- 避免同时锁定多个资源
- 使用定时锁
- 使用超时锁定
- 顺序锁定资源
- 减少锁的粒度
- 使用并发控制工具
- 检测死锁
- 避免嵌套锁定
- 使用不可变对象
- 死锁恢复策略
怎么唤醒一个阻塞的线程?
- 使用wait()和notify()/notifyall()
- 使用Lock和Condition
- 改变线程等待的条件
- 使用中断机制
什么是多线程的上下文切换?
- 在多个线程之间进行切换的过程
- 切换原因:
- 线程阻塞:当一个线程等待I/O操作、获取同步锁或调用某些会挂起线程的方法时,操作系统可能会挂起该线程,并进行上下文切换以运行其他线程。
- 线程结束:当一个线程执行完毕时,操作系统会进行上下文切换,将CPU控制权交给其他线程。
- 时间片耗尽:在采用时间片轮转调度的系统中,当一个线程使用完其分配的时间片后,如果它还没有完成执行,操作系统会挂起该线程,并进行上下文切换以运行其他线程。
- 线程优先级改变:如果一个高优先级的线程变为可运行状态,操作系统可能会挂起当前线程,并进行上下文切换以运行高优先级的线程。
线程调度算法是什么?
- 是指Java虚拟机(JVM)用来决定哪个线程应该获得CPU资源以执行其任务的规则和策略。
- 主要调度算法:
- 时间片轮转调度:系统会为每个线程分配一个固定的时间片,在时间片内,线程可以正常执行。当时间片用完时,如果线程还没有执行完,CPU将把控制权交给下一个线程。
- 抢占式调度:抢占式调度意味着线程的执行顺序并不是按照先到先得的原则,而是由线程的优先级决定。
- 协同式调度
- 多级反馈队列调度
什么是线程调度器和时间分片?
- 线程调度器:是操作系统或Java虚拟机(JVM)的一部分,负责决定在多线程环境中,哪个线程应该获得CPU时间并执行。
- 时间分片:时间分片是线程调度中的一个关键概念,它指的是将CPU时间划分成若干个小时间段(即时间片),并轮流分配给每个线程执行。
单例模式的线程安全性?
- 在多线程环境中,如果多个线程同时访问单例类的实例化代码块,可能会创建多个实例,这违反了单例模式的原则。
- 单例模式的线程安全实现:
- 懒汉式:这种方式通过将getInstance方法声明为 synchronized来确保线程安全,但每次调用都会进行同步,性能较低。
- 双重检查锁定:这种方式只在实例化时进行同步,减少了同步的开销。volatile关键字确保了instance变量的可见性和禁止指令重排。
- 饿汉式:这种方式在类加载时就完成了实例化,因此是线程安全的,但可能会造成资源的浪费,因为实例可能一直未被使用。
- 静态内部类:这种方式利用了Java的类加载机制来保证线程安全。SingletonHolder类在第一次被访问时才加载,JVM保证了SingletonHolder类的加载过程是线程安全的。
- 枚举:枚举类型本身就是线程安全的,并且可以防止反序列化重新创建新的对象。
Executors是什么?
- Executors是一个工具类,提供一系列静态工厂方法,用于快速创建和管理线程池。
- 提供四种线程池类型:固定大小线程池、缓存线程池、单线程池、定时线程池
谈谈ExecutorService,ScheduledExecutorService?
- 是concurrent包中提供的两个接口,用于异步执行任务。
- ExecutorServivice:
- ExecutorService是一个执行器服务,允许你异步地执行任务。
- 特点:任务提交、线程池管理、关闭和终止、返回结果和异常处理。
- ScheduledExecutorService:
- ScheduledExecutorService是ExecutorService的子接口,它增加了定时任务和周期性任务的执行能力。
- 特点:定时任务、周期性任务、单次调度和固定频率调度。