多线程

yxalkaid

多线程

线程

  • 进程与线程
    进程包含了线程。线程的上下文切换成本比进程的低。
    不同进程使用不同的内存空间,同一个进程下的所有线程共享内存空间。

  • 并行与并发
    并行:同一时间内多个任务同时进行。
    并发:同一时间内多个任务交替进行。

  • 线程同步

    • 目的:保护共享资源 ,防止多个线程同时修改数据导致的竞态条件
    • 核心思想:通过限制对共享资源的并发访问,确保线程安全
    • 常见实现方式:synchronized、Lock、Semaphore、CountDownLatch、CyclicBarrier、原子操作
  • 线程通信

    • 目的:协调线程间的执行顺序 ,让线程根据特定条件暂停或唤醒
    • 核心思想 :通过传递信号或数据,让线程在特定条件下等待或继续执行
    • 常见实现方式:wait/notify、Condition、BlockingQueue
  • 创建线程的方式

    • 继承Thread类
    • 实现Runnable接口
      • 实现run()方法,无返回值。
      • 不能抛出异常(检查型异常)。
    • 实现Callable接口
      • 实现call()方法,有返回值。
      • 配合FutureTask使用。
      • 可以抛出异常。
    • 使用线程池
  • 线程状态

    • 新建
    • 就绪
    • 运行
    • 阻塞:等待资源、等待信号、等待IO操作完成。
    • 等待:线程调用wait()方法。
    • 睡眠:线程调用sleep()方法。
    • 终止
  • notify和notifyAll的区别

    • notify:随机唤醒一个正在等待的线程。
    • notifyAll:唤醒所有正在等待的线程。
  • wait和sleep的区别

    • wait
      • Object的成员方法。
      • 需要先获得目标对象的锁。
      • 执行后会释放锁。
    • sleep
      • Thread的静态方法。
      • 不需要获得锁。
      • 不释放锁。
  • 如果停止一个正在运行的线程?
    interrupt()方法

    • 如果线程处于阻塞状态,则抛出InterruptedException异常。
    • 如果线程处于运行状态,则设置中断标志。
  • synchronized关键字的底层原理

    • 由Monitor实现,实现的锁属于重量级锁。
    • 在同步块前后插入了monitorentermonitorexit
    • Monitor结构
      • WaitSet:关联了调用了wait()方法的线程。
      • EntryList:关联了执行到synchronized代码块的线程(没有持有锁的线程)
      • Owner:当前持有锁的线程。
    • 锁升级:对象头中的MarkWord部分
      • 无锁
      • 偏向锁
      • 轻量级锁
      • 重量级锁:使用Monitor实现,性能较低。
  • JMM(Java内存模型)
    JMM将内存分为主内存和工作内存。线程与线程之间是隔离的,交互需要通过主内存。

  • CAS
    CAS(比较再交换),体现的是乐观锁的思想,在无锁状态下保证线程操作数据的原子性。

  • volatile

    • 禁止JIT对volatile修饰的变量进行优化,让一个线程对共享变量的修改对另一个线程可见
    • 禁止指令重排
  • AQS
    AQS(抽象队列同步器)是多线程中的队列同步器,是一种锁机制,作为一个基础框架使用,ReentrantLock通过AQS实现。
    内部维护了一个先进先出的双向队列,存储的是排队的线程。
    对state属性进行CAS操作,保证线程操作数据的原子性。

  • ReentrantLock
    主要使用CAS和AQS实现,默认为非公平锁。
    特点:可中断、可以设置超时、可以设置公平锁、可重入、支持多个条件变量。

  • synchronized和Lock的区别

    • synchronized
      • 关键字,源码jvm中,使用C++实现
      • 自动释放
      • 悲观锁、互斥、同步、可重入
    • Lock
      • 接口,源码在jdk中,使用Java实现
      • 需要手动释放
      • 悲观锁、互斥、同步、可重入
      • 提供公平锁、可打断、可超时、多条件变量
  • 死锁的产生条件

    • 互斥、不剥夺、请求和保持、循环等待
    • 死锁诊断:jps、jstack
  • 怎么保证多线程的执行安全?

    • 原子性:synchronized、Lock
    • 内存可见性:volatile、synchronized、Lock
    • 有序性:volatile

线程池

  • 线程池的作用
    线程池可以减少频繁的创建线程和销毁线程带来的性能损耗。

  • 线程池的核心参数

    • corePoolSize:核心线程数。
    • maximumPoolSize:最大线程数。
    • keepAliveTime:线程空闲时间。
    • unit:时间单位。
    • workQueue:工作队列
    • threadFactory:线程工厂。
    • handler:拒绝策略。
  • 线程池的执行原理

    • 如果核心线程数未满,则创建核心线程。
    • 如果任务队列未满,则添加到任务队列中。
    • 如果总线程数小于maximumPoolSize,则创建非核心线程。
    • 如果总线程数大于或等于maximumPoolSize,则执行拒绝策略。
      • AbortPolicy:直接抛出异常。(默认策略)
      • DiscardPolicy:直接丢弃。
      • DiscardOldestPolicy:丢弃队列中最旧的任务。
      • CallerRunsPolicy:由调用者线程处理该任务。
  • 线程池的阻塞队列

    • ArrayBlockingQueue:基于数组的先进先出队列。
      • 强制有界
      • 提前初始化
      • 使用一个锁
    • LinkedBlockingQueue:基于链表的先进先出队列。
      • 无界,可以有界。
      • 插入时初始化
      • 首尾共两个锁
    • SynchronousQueue:
  • 如何确定核心线程数?

    • 高并发、任务执行时间短
      • 核心线程数:N+1。减少线程上下文切换
    • IO密集型
      • 核心线程数:2N+1
    • CPU密集型
      • 核心线程数:N+1
  • 线程池的种类
    Executors类的静态函数。
    不建议使用,因为等待队列长度默认为Integer.MAX_VALUE,可能会堆积大量请求,导致内存溢出。

    • newFixedThreadPool:固定大小线程池。
    • newSingleThreadExecutor:单线程线程池。
    • newCachedThreadPool:可变大小线程池。
    • newScheduledThreadPool:定时线程池。
  • 线程池的使用场景

    • 批量导入
    • 数据汇总
      • 多线程获取不同数据
    • 异步任务
      • 统计数据
      • 保存日志
  • CountDownLatch

    • 设置目标数量,主线程调用await()方法,等待其他线程。
    • 其他线程执行完,调用countDown()方法。
    • 数量为0时,主线程继续执行。
    • 不可重复使用。
  • CyclicBarrier

    • 设置目标数量。
    • 其他线程执行到某个阶段,调用await()方法,等待其他线程。
    • 数量到达目标数量时,等待的线程继续执行。
    • 可以重复使用。
  • semaphore信号量

    • acquire()
    • release()
  • ThreadLocal

    • 线程内部存储类,实现资源对象的线程隔离,避免线程争用引发的安全问题。
    • 每个线程维持一个ThreadLocalMap对象,以ThreadLocal对象为key,保存数据。
    • 内存泄露问题:ThreadLocalMap中的key是对ThreadLocal的弱引用。而value是强引用,若未调用remove(),不会被GC回收。

锁的类型

  • 乐观锁和悲观锁
  • 公平锁和非公平锁
  • 互斥锁和共享锁
  • 可重入锁和非可重入锁
  • 自旋锁、分段锁、死锁
On this page
多线程