线程及相关问题

一、线程的基础知识

  1. 线程和进程的区别
    • 进程:正在运行程序的实例,用于加载指令、管理内存和 IO。
    • 线程:进程内的执行单元,一个进程可包含多个线程。
    • 区别:进程是资源分配的最小单位,线程是调度的最小单位;进程间内存独立,线程可共享进程内存;线程上下文切换成本通常低于进程。
  2. 并行和并发的区别
    • 并行:同一时间多个核同时执行多个线程。
    • 并发:同一时间多个线程轮流使用一个或多个 CPU。
  3. 创建线程的四种方式
    • 继承 Thread 类。
    • 实现 Runnable 接口。
    • 实现 Callable 接口配合 FutureTask
    • 使用线程池创建线程。
  4. Runnable 和 Callable 的区别
    • Runnable 的 run 方法无返回值,Callable 的 call 方法有返回值且可抛出异常。
    • Callable 配合 Future 和 FutureTask 可获取异步执行结果。
  5. 线程的 run() 和 start() 的区别
    • start() 用于启动线程,通过该线程调用 run 方法执行逻辑代码,且只能调用一次。
    • run() 方法封装了线程要执行的代码,可被多次调用。
  6. 线程的状态及状态转换
    • 状态包括:新建、可运行、阻塞、等待、有时限等待、终结。
    • 状态转换条件多样,如获取锁、释放锁、调用 wait()/notify() 等方法、超时等。
  7. 如何保证线程按顺序执行
    • 可使用线程的 join() 方法,在一个线程中启动另一个线程,确保被调用 join 的线程先执行完。
  8. notify() 和 notifyAll() 的区别
    • notifyAll() 唤醒所有等待的线程,notify() 只随机唤醒一个等待线程。
  9. 在 Java 中 wait 和 sleep 方法的不同
    • 共同点:都可让线程进入阻塞状态,暂时放弃 CPU 使用权。
    • 不同点:
      • 方法归属不同,sleep() 是 Thread 的静态方法,wait() 是 Object 的方法。
      • 醒来时机不同,sleep() 等待指定时间后自动醒来,wait() 需被 notify 唤醒,否则一直等待。
      • 锁特性不同,wait() 调用需先获取对象锁,执行后释放锁;sleep() 在同步代码块中不释放锁。

二、线程中并发锁

  1. synchronized 关键字的底层原理
    • 通过 monitor 实现互斥,同一时刻至多只有一个线程能持有对象锁。
    • monitor 是 JVM 级别的对象,由 C++ 实现,与对象关联。
    • monitor 内部有 Owner(持有锁的线程)、EntryList(阻塞线程列表)、WaitSet(等待线程列表)三个属性。
  2. synchronized 关键字的底层原理 - 进阶
    • synchronized 属于重量级锁,涉及用户态和内核态切换、进程上下文切换,成本高、性能低。
    • JDK 1.6 引入偏向锁、轻量级锁优化:
      • 偏向锁:适用于长时间只有一个线程使用锁的场景,第一次获取锁通过 CAS 设置线程 ID,后续判断线程 ID 即可,无需 CAS。
      • 轻量级锁:适用于线程加锁时间错开且竞争不激烈的场景,通过 CAS 修改对象头的锁标志实现加锁,解锁时若失败则膨胀为重量级锁。
  3. 对 JMM(Java 内存模型)的理解
    • 所有共享变量存储于主内存,每个线程有自己的工作内存。
    • 线程对变量的操作必须在工作内存中完成,不同线程间变量值传递通过主内存。
  4. CAS(Compare And Swap)
    • 一种乐观锁思想,体现无锁情况下保证线程操作共享数据的原子性。
    • 包含当前内存值 V、旧的预期值 A、即将更新的值 B,当且仅当 A 和 V 相同时,将 V 修改为 B 并返回 true,否则返回 false
    • CAS 底层依赖 Unsafe 类调用操作系统底层的 CAS 指令实现,常见于 ReentrantLock 和 AtomicXXX 类等。
  5. 对 volatile 的理解
    • 保证线程间的可见性,强制将修改的值立即写入主存。
    • 禁止进行指令重排序,通过添加内存屏障实现。
  6. AQS(AbstractQueuedSynchronizer)
    • 阻塞式锁和同步器工具的框架,是构建锁或其他同步组件的基础。
    • 维护一个用 volatile 修饰的 state 属性表示资源状态,提供基于 FIFO 的等待队列和条件变量实现等待、唤醒机制。
    • 常见实现类有 ReentrantLockSemaphoreCountDownLatch 等。
  7. ReentrantLock 的实现原理
    • 利用 CAS + AQS 队列实现,可重入。
    • 支持公平锁和非公平锁,默认非公平锁,公平锁效率通常低于非公平锁。
    • 构造方法可接受公平参数,通过修改 state 状态和控制线程在队列中的等待、唤醒实现加锁和解锁。
  8. synchronized 和 Lock 的区别
    • 语法层面:synchronized 是关键字,用 C++ 实现;Lock 是接口,用 Java 实现,且需要手动调用 unlock 方法释放锁。
    • 功能层面:都属于悲观锁,具备基本互斥、同步、锁重入功能,Lock 还提供获取等待状态、公平锁、可打断、可超时、多条件变量等功能。
    • 性能层面:无竞争时 synchronized 做了优化性能较好,竞争激烈时 Lock 通常性能更佳。
  9. 死锁产生的条件及诊断方法
    • 条件:一个线程需要同时获取多把锁,且多个线程相互等待对方持有的锁。
    • 诊断方法:使用 jps 查看运行的线程,jstack 查看线程运行情况定位死锁问题,还可使用可视化工具如 jconsoleVisualVM

三、线程池

  1. 线程池的核心参数及工作流程
    • 核心参数:
      • corePoolSize:核心线程数目。
      • maximumPoolSize:最大线程数目(核心线程 + 救急线程的最大数目)。
      • keepAliveTime:救急线程的生存时间。
      • unit:救急线程生存时间的单位。
      • workQueue:任务队列,当核心线程满时,新任务加入此队列排队。
      • threadFactory:线程工厂,可定制线程对象的创建。
      • handler:拒绝策略,当所有线程都繁忙且任务队列满时触发。
    • 工作流程:提交任务时,先判断核心线程数是否已满,未满则创建核心线程执行任务;核心线程满后判断任务队列是否已满,未满则将任务加入队列;队列满后判断线程数是否小于最大线程数,满足则创建救急线程执行任务;否则执行拒绝策略。
  2. 线程池中常见的阻塞队列
    • ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO,底层是数组,一把锁,提前初始化 Node 数组。
    • LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO,默认无界,支持有界,底层是链表,两把锁(头尾),创建节点时添加数据。
    • DelayedWorkQueue:优先级队列,保证每次出队的任务是执行时间最靠前的。
    • SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待一个移出操作。
  3. 如何确定核心线程数
    • IO 密集型任务:推荐核心线程数大小设置为 2N + 1N 为计算机的 CPU 核数)。
    • CPU 密集型任务:推荐核心线程数大小设置为 N + 1
  4. 线程池的种类
    • newFixedThreadPool:固定线程数的线程池,核心线程数与最大线程数相同,无救急线程,阻塞队列是无界的 LinkedBlockingQueue
    • newSingleThreadExecutor:单线程化的线程池,只用唯一工作线程执行任务,保证任务按顺序执行,阻塞队列是无界的 LinkedBlockingQueue
    • newCachedThreadPool:可缓存线程池,核心线程数为 0,最大线程数为 Integer.MAX_VALUE,阻塞队列为 SynchronousQueue
    • newScheduledThreadPool:具有延迟和周期执行功能的线程池,核心线程数可指定,最大线程数为 Integer.MAX_VALUE,阻塞队列为 DelayedWorkQueue
  5. 为什么不建议用 Executors 创建线程池
    • FixedThreadPool 和 SingleThreadPool 允许的请求队列长度为 Integer.MAX_VALUE,可能堆积大量请求导致 OOM。
    • CachedThreadPool 允许的创建线程数量为 Integer.MAX_VALUE,可能创建大量线程导致 OOM。

四、线程使用场景问题

  1. CountDownLatch 的使用场景
    • 用于线程同步协作,一个或多个线程等待其他多个线程完成某件事情之后才能执行。
    • 构造参数初始化等待计数值,通过 await() 等待计数归零,countDown() 让计数减一。
  2. Future 的使用场景
    • 在需要获取异步任务执行结果时使用,配合线程池可提升性能。
  3. Semaphore 的使用场景
    • 通过限制执行的线程数量达到限流效果,当线程执行时先获取许可,执行完成后释放许可。

五、其他

  1. 对 ThreadLocal 的理解
    • 为每个线程分配独立的线程副本,解决变量并发访问冲突问题,同时实现线程内的资源共享。
    • 主要方法有 set()(设置值)、get()(获取值)、remove()(清除值)。
    • 内部有一个 ThreadLocalMap 类型的成员变量,以 ThreadLocal 自己作为 key,存储线程的资源对象。
  2. ThreadLocal 的内存泄露问题
    • ThreadLocalMap 中的 Entry 继承自 WeakReferencekey 为弱引用的 ThreadLocal 实例,value 为线程变量副本。
    • 当 ThreadLocal 对象没有被外部强引用引用时,可能会被 GC 回收,但 value 不会,导致内存泄露。
    • 建议在使用完 ThreadLocal 后手动调用 remove() 方法释放资源。
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值