当前位置: 首页 > web >正文

多线程八股

多线程八股

1.ArrayList的底层原理

  1. Array List底层是用动态扩展的数组实现的;

  2. ArrayList初始容量为0,当第一次添加数据的时候才初始容量为10;

  3. 在进行扩展时容量是原来的1.5倍,每次扩展都需要拷贝数据;

  4. 在添加数据的时候有以下情况:首先判断当前数组是否有足够容量存储新数据,如果容量不足,将调用grow方法进行扩容(原来的1.5倍),确保新增数据有地方存储后,将新元素添加到位于size的位置上,返回添加成功布尔值。

为什么ArrayList的插入有时比LinkedList快?
A:当插入位置靠近尾部且不需要扩容时,ArrayList的System.arraycopy()在现代CPU上的效率可能高于LinkedList的内存分配和指针操作,尤其在小数据集时。

何时LinkedList实际插入复杂度不是O(1)?
A:当需要先定位插入位置时(如list.add(index, element)),需要O(n)遍历时间,实际操作为O(n)+O(1)

2.多线程

核心思想:多线程就是让程序能“同时”干好几件事(虽然CPU可能在快速切换)。关键问题在于怎么让它们别打架(线程安全),怎么排队(锁),怎么互相打招呼(协作),怎么高效干活(线程池)。

1. 线程安全:为啥要“锁”?

  • 问题本质: 想象一个公共厕所(共享资源),很多人(线程)想用。如果大家一窝蜂冲进去,或者不锁门,会发生啥?混乱!程序里就是数据被改得乱七八糟(脏数据)。
  • 经典例子: i++ 这个操作。它看起来简单,实际是三步:读i的值 -> 加1 -> 写回i。如果两个线程同时做这个操作,可能它们都读到同一个值(比如10),都加1变成11,然后都写回去。结果应该是12,但实际只有11!这就是竞态条件
  • 面试官爱问: HashMap 为啥线程不安全?ConcurrentHashMap 怎么解决的?
    • 大白话: HashMap 就像个没管理员也没排队规则的菜市场摊位,一群人(线程)同时抢着往本子上记东西(修改内部结构),很容易把本子撕坏(内部链表环化导致死循环)或者记错(数据丢失/错误)。ConcurrentHashMap 聪明多了:它给菜市场分了很多小隔间(分段锁/JDK8后的Node+CAS),大家只在抢同一个隔间的东西时才需要排队,抢不同隔间的可以同时进行,效率高多了。或者用更精细的“无锁”方式(CAS)来记账。
  • 核心考点: 理解多个线程同时修改共享数据会出乱子。怎么解决?用“锁”或者“原子操作”。

2. 锁:怎么“排队”和“管理钥匙”?

  • synchronized (内置锁/监视器锁):
    • 比喻: 就像厕所门上的一把钥匙。谁想进去,必须拿到这把钥匙,用完出来再把钥匙挂回去(释放锁)。别人(其他线程)想进去?只能等着(阻塞)。
    • 怎么用?
      • 锁方法: 在方法前加 synchronized -> 钥匙就是这个方法所属的对象本身(对于实例方法) 或 这个类的Class对象(对于静态方法)。
      • 锁代码块: synchronized(某个对象) { ... } -> 钥匙就是你指定的那个对象(锁对象)。
    • 特性:
      • 可重入: 同一个线程拿到钥匙后,可以再进这个锁保护的另一个门(调用另一个synchronized方法或代码块),不会被自己卡在外面。就像你有钥匙,进大门后进里面的小门不需要再找钥匙。
      • 非公平: 默认情况下,外面等钥匙的线程不是严格按先来后到的顺序拿钥匙。谁抢到算谁的(虽然JVM有优化,但本质非公平)。
  • ReentrantLock (可重入锁):
    • 比喻: synchronized 的升级版。还是那把钥匙,但功能更强:
      • 公平锁: 可以设置成严格排队,先来的线程先拿钥匙。避免“饥饿”(老实的线程永远抢不到)。
      • 可中断: 等钥匙等烦了(阻塞状态),可以喊一声“不等了!” (lockInterruptibly()) 然后去做别的事。
      • 超时等待: 可以设定等钥匙的最长时间,时间到了还没拿到就走人 (tryLock(timeout))。
      • 条件变量 (Condition): 更精细的“等待室”。比如生产者线程发现仓库满了,就去“满仓等待室”等着;消费者拿走东西后,可以去那个“满仓等待室”喊一声“有位置了!”,只唤醒在等仓库空位的生产者,而不是乱喊把所有人都吵醒(wait/notify 是唤醒所有在同一个锁上等待的线程)。
    • 注意:ReentrantLock 必须手动解锁!通常放在 finally 块里。synchronized 是自动释放的。
  • 核心考点: 理解 synchronizedReentrantLock 的基本用法、区别(公平性、灵活性、需手动释放)、可重入性。知道 ReentrantLock 的高级功能(公平、中断、超时、条件)。

3. volatile:这个变量大家都能看见最新版!

  • 问题本质: CPU有高速缓存。线程A修改了一个变量,可能先存在自己的缓存里,还没写回主内存。线程B去读主内存,读到的还是旧值!这就不一致了。
  • volatile 的作用:
    • 可见性: 保证一个线程修改了 volatile 变量,新值立刻对其他所有线程可见(强制写回主存,其他线程读时强制从主存读)。
    • 禁止指令重排序: 编译器/JVM为了优化,可能会调整代码执行顺序。volatile 能防止这种重排序跨越它(建立内存屏障)。
  • 大白话: 给变量贴了个“公告栏”。谁改了它,必须把新值写在公告栏上(主存)。谁要看它,必须去看公告栏,不能看自己小本本(缓存)上的旧记录。同时告诉编译器和CPU:“这个变量周围的代码顺序别乱动!”
  • 注意: volatile 不保证原子性! 它只保证单次读/写是原子的(比如读一个long)。像 i++(读+改+写)这种复合操作,volatile 管不了。要保证 i++ 原子性,得用 synchronizedAtomicInteger
  • 典型场景: 状态标志位(如 boolean running = true; 需要 volatile),单例模式双重检查锁定(DCL)里的实例引用。
  • 核心考点: 理解 volatile 解决了什么问题(可见性、有序性),不解决什么问题(原子性)。常见使用场景。

4. 线程间通信:你干完了叫我一声!

  • wait(), notify(), notifyAll() (必须在 synchronized 块里用):
    • 比喻: 还是那个公共厕所(锁对象)。线程A拿到钥匙进去后发现没纸了(条件不满足)。
      • wait():A把钥匙挂回去(释放锁),然后去厕所旁边的“等待室”坐着睡觉。
      • notify():线程B(比如送纸工)拿到钥匙进去送完纸,出来时可以喊一声“纸来啦!”(notify()),这会随机唤醒等待室里的一个线程(比如A)。
      • notifyAll():喊“纸来啦!”,唤醒等待室里所有线程。被唤醒的线程会重新竞争钥匙。
    • 要点:
      • 调用 wait()必须持有锁(在 synchronized 块里)。
      • wait() 会释放锁
      • 被唤醒的线程在从 wait() 返回前必须重新拿到锁
      • 通常用 while(条件不满足) { wait(); } 来防止虚假唤醒(没被 notify 也可能醒来)。
  • Condition (配合 ReentrantLock 使用):
    • 比喻: ReentrantLock 可以有多个独立的“等待室”(Condition)。
      • 生产者:notFull.await() (仓库满了,去“不满等待室”等着) -> 消费者取货后:notFull.signal() (喊“不满啦!”唤醒生产者)。
      • 消费者:notEmpty.await() (仓库空了,去“不空等待室”等着) -> 生产者放货后:notEmpty.signal() (喊“不空啦!”唤醒消费者)。
    • 优点:wait/notify 更精细,能定向唤醒特定条件的线程。
  • 核心考点: 理解 wait/notify 机制(为什么要在 synchronized 里?为什么 wait 会释放锁?虚假唤醒?)。知道 Condition 提供了更灵活的等待/通知方式。

5. 原子类 (java.util.concurrent.atomic):不用锁也能安全加减?

  • 问题: i++ 不安全,用 synchronized 太重(排队慢)。
  • 解决: AtomicInteger, AtomicLong 等。
  • 原理: 利用CPU底层的 CAS (Compare And Swap) 指令。
    • CAS比喻: 想象一个值V在内存里。线程A想把它从10改成11:
      1. 读当前值 (10 - 期望值)。
      2. 计算新值 (11)。
      3. 进行CAS操作:检查内存里的值现在还是不是10?如果是,说明没人改过,放心改成11!如果不是,说明被别人改过了(比如变成了12),那这次修改失败,重新再试(读新值12 -> 计算13 -> 再次尝试CAS…)。
    • 这个过程是硬件级别保证原子性的。
  • 优点: 无锁(或乐观锁),性能通常比 synchronized 高很多,尤其在低竞争场景。
  • 方法: get(), set(), getAndIncrement() (i++), incrementAndGet() (++i), compareAndSet(expect, update) (核心CAS) 等。
  • 核心考点: 知道原子类存在(解决计数器等简单共享变量的原子操作),理解其底层原理是CAS,知道CAS是什么(比较并交换)。了解ABA问题(虽然不常深究)。

6. 线程池:别老创建销毁线程,太浪费!

  • 为什么需要? 创建和销毁线程开销大。线程池预先创建好一些线程放着(核心线程),来任务了就让这些线程去执行。任务太多时,新任务排队(工作队列)。排队也排满了?就创建新线程(直到最大线程数)。最大线程数也满了?就拒绝新任务(拒绝策略)。
  • 核心参数 (ThreadPoolExecutor):
    • corePoolSize (核心线程数): 池子里长期保留的工人数,即使他们闲着。
    • maximumPoolSize (最大线程数): 池子最多能容纳的工人数(核心 + 临时工)。
    • workQueue (工作队列): 任务太多时,排队的队伍。常用 LinkedBlockingQueue(无界/有界), ArrayBlockingQueue(有界), SynchronousQueue(直接交接,不排队)。
    • keepAliveTime (空闲线程存活时间): 临时工(超出核心线程数的那些)如果闲着超过这个时间,就被解雇(销毁)。
    • threadFactory (线程工厂): 用来创建新线程(可以设置线程名、优先级等)。
    • RejectedExecutionHandler (拒绝策略): 当池子满了(线程数达max且队列也满了),新任务咋办?常见策略:
      • AbortPolicy (默认):直接抛异常 RejectedExecutionException
      • CallerRunsPolicy:让提交任务的线程(比如main线程)自己来执行这个任务。
      • DiscardPolicy:默默丢掉新任务,不通知。
      • DiscardOldestPolicy:丢掉队列里最老的任务,然后尝试把新任务加进去。
  • 常见线程池 (Executors 工厂创建,但一般推荐手动配置 ThreadPoolExecutor):
    • FixedThreadPool: 固定大小线程池。核心=最大线程数,队列无界。可能导致OOM。
    • CachedThreadPool: 核心线程数=0,最大线程数巨大(几乎无限制),队列是 SynchronousQueue。来一个任务,如果有空闲线程就用,没有就创建新线程。线程空闲60秒后被回收。适合大量短生命周期的异步任务。可能导致创建过多线程。
    • SingleThreadExecutor: 只有一个线程的池子。保证任务按提交顺序串行执行。如果这个线程挂了,会创建一个新的。
    • ScheduledThreadPool: 能执行定时或周期性任务的线程池。
  • 核心考点: 为什么用线程池?线程池的核心参数有哪些?各自代表什么?常见的线程池类型(Fixed, Cached, Single)的特点和潜在风险?如何配置一个合理的线程池?常见的拒绝策略?

7. 其他高频点:

  • Thread vs Runnable vs Callable
    • Thread:代表一个线程对象。可以直接继承 Thread 并重写 run(),但Java是单继承,不推荐。
    • Runnable最常用。定义一个任务(run()方法),没有返回值,不能抛受检异常。任务可以被提交给 Thread 或线程池执行。
    • Callable:类似 Runnable,但它的 call() 方法有返回值,并且可以抛出受检异常。通常配合 Future/FutureTask 使用,由线程池 (ExecutorService.submit()) 执行。
  • Future / FutureTask 代表一个异步计算的结果。你可以用它来:
    • 查询计算是否完成 (isDone())。
    • 尝试取消计算 (cancel())。
    • 获取计算结果 (get())。注意 get() 会阻塞,直到计算完成或超时。
  • synchronizedReentrantLock 的区别: 上面锁的部分已经讲了(公平性、灵活性、手动释放、条件变量)。
  • 死锁:
    • 条件: 互斥、持有并等待、不可剥夺、循环等待。
    • 避免:
      • 按固定顺序获取锁(所有线程都按A->B->C的顺序申请锁)。
      • 使用带超时的锁(tryLock(timeout))。
      • 避免嵌套锁。
  • ThreadLocal 给每个线程提供了一个变量的独立副本。线程内部任何地方都可以访问到这个副本。常用于保存线程上下文信息(如用户会话、数据库连接),避免参数传递。
    • 注意: 使用不当(比如在线程池环境)可能导致内存泄漏!用完记得 remove()

8. ThreadLocal 能给子线程传值吗?

  • 核心答案:默认情况下,不能!

  • 通俗解释:

    • 想象 ThreadLocal 是每个线程自己专属的“储物柜”(ThreadLocalMap)。父线程往自己的储物柜里放的东西(设置值),子线程是完全看不到、拿不到的。子线程有自己的、全新的、空空的储物柜。
    • 为什么? ThreadLocal 的设计核心就是线程隔离,保证每个线程操作自己的变量副本,互不干扰。这是它的核心价值所在(避免同步,提高性能)。
  • 特殊情况:InheritableThreadLocal

    • 可以! 这是 ThreadLocal 的一个特殊子类。
    • 原理: 当父线程创建一个子线程时,InheritableThreadLocal 会把它父线程中当前的值拷贝一份给新创建的子线程的 InheritableThreadLocal。之后,父子线程各自修改自己的副本,互不影响。
    • 关键限制:
      • 仅适用于“创建时”传递: 只在 new Thread() 创建子线程的那一刻进行值拷贝。之后父线程再修改自己的值,子线程不会跟着变。
      • 线程池大坑! 线程池的核心在于复用线程。当一个线程被线程池创建出来时,它从父任务(可能是主线程,也可能是其他线程)那里继承了当时的 InheritableThreadLocal 值。但是,当这个线程执行完一个任务,回到线程池待命,再被分配执行下一个任务时:
        • 它之前执行任务时修改的 InheritableThreadLocal还在
        • 下一个任务提交者(可能是完全不同的调用者)设置的 InheritableThreadLocal不会自动覆盖线程池线程已有的值。
        • 这会导致严重的数据错乱内存泄漏(旧任务的值一直残留在复用线程中)。
  • 线程池中如何“传值”?

    • 避免使用 InheritableThreadLocal 因为上述问题,在线程池场景下基本不可用。
    • 推荐方法:
      1. 显式传递参数: 把需要传递的值作为 RunnableCallable 任务的构造参数传入。这是最清晰、最安全的方式。
      2. 使用第三方库 (如阿里 TransmittableThreadLocal - TTL): 专门为解决线程池上下文传递设计。它通过包装 Runnable/Callable 在任务提交时捕获当前值,在任务执行时恢复值,并在执行后清理,完美适配线程池复用机制。(面试加分项!)
  • 总结: 普通 ThreadLocal 绝对不行。InheritableThreadLocal 只在 new Thread() 创建子线程时有效,严禁用于线程池。线程池传值首选显式参数传递,复杂场景考虑 TTL 等方案。

  1. 线程池怎么创建?**
  • 核心答案:强烈推荐使用 ThreadPoolExecutor 构造器手动创建!

  • 为什么不用 Executors 工厂方法?

    • Executors.newFixedThreadPool()Executors.newSingleThreadExecutor(): 使用无界队列 (LinkedBlockingQueue)。如果任务提交速度持续远大于处理速度,队列会无限增长,最终导致 OutOfMemoryError (OOM)。
    • Executors.newCachedThreadPool(): 核心线程数为0,最大线程数是 Integer.MAX_VALUE。如果任务提交过多且都是长任务,可能瞬间创建海量线程,耗尽系统资源 (CPU, 内存,线程数限制),同样可能导致崩溃。
  • 正确创建姿势:

    import java.util.concurrent.*;public class CustomThreadPool {public static void main(String[] args) {// 1. 定义核心参数 (下面会详细讲每个参数)int corePoolSize = 5; // 核心线程数int maximumPoolSize = 10; // 最大线程数long keepAliveTime = 60L; // 空闲线程存活时间 (单位)TimeUnit unit = TimeUnit.SECONDS; // 存活时间单位 (秒)BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100); // 工作队列 (有界队列,容量100)ThreadFactory threadFactory = Executors.defaultThreadFactory(); // 线程工厂 (可自定义)RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); // 拒绝策略 (默认抛异常)// 2. 使用构造器创建线程池ExecutorService threadPool = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);// 3. 提交任务threadPool.execute(() -> {System.out.println("执行任务...");});// ... 提交更多任务// 4. 优雅关闭 (重要!)threadPool.shutdown(); // 停止接收新任务,等待已提交任务执行完成// threadPool.shutdownNow(); // 尝试立即停止所有正在执行的任务,返回未执行任务列表}
    }
    

10. 线程池有哪些参数呢?

ThreadPoolExecutor 构造器的 7 个核心参数 (非常重要!):

  1. corePoolSize (核心线程数):
    • 池中长期保持存活的线程数量,即使它们是空闲状态。
    • 相当于公司里的“正式编制员工”。
  2. maximumPoolSize (最大线程数):
    • 池中允许存在的最大线程数量。
    • 当任务非常多,队列也满了,线程池会创建新线程来处理任务,直到达到这个数量。
    • 相当于“正式员工” + “临时工”的总人数上限。
  3. keepAliveTime (空闲线程存活时间):
    • 当线程池中的线程数量超过 corePoolSize 时,那些多余的空闲线程在等待新任务时的最长存活时间
    • 如果超过这个时间还没有新任务,这些多余的线程就会被终止销毁。
    • 只对 超过 corePoolSize 的那些线程生效。核心线程默认一直存活 (allowCoreThreadTimeOut 可设置核心线程超时)。
    • 单位: 需要配合下一个参数 unit
    • 相当于“临时工”如果闲着没事干超过一定时间,就被解雇。
  4. unit (存活时间单位):
    • 指定 keepAliveTime 参数的时间单位。
    • 常用 TimeUnit.SECONDS (秒)、TimeUnit.MILLISECONDS (毫秒) 等。
  5. workQueue (工作队列):
    • 用于存放被提交但尚未被执行的任务的阻塞队列 (BlockingQueue)。
    • 关键选择:
      • ArrayBlockingQueue: 基于数组的有界队列。需要指定容量。任务数超过容量且线程数达max,触发拒绝策略。推荐!防止OOM。
      • LinkedBlockingQueue: 基于链表的队列。默认构造是无界队列 (Integer.MAX_VALUE),容易OOM。也可指定容量变成有界。
      • SynchronousQueue: 一个不存储元素的队列。每个 put 操作必须等待一个 take 操作,反之亦然。相当于直接交接。通常要求 maximumPoolSize 足够大,否则容易触发拒绝策略。Executors.newCachedThreadPool() 使用它。
      • PriorityBlockingQueue: 具有优先级的无界队列。按优先级出队。也有OOM风险。
    • 相当于“任务待办事项清单”或“排队等候区”。
  6. threadFactory (线程工厂):
    • 用于创建新线程的工厂。可以实现 ThreadFactory 接口来自定义线程的名称、是否是守护线程、优先级等。
    • 如果不指定,使用 Executors.defaultThreadFactory(),创建的就是普通的、同组的、非守护线程。
    • 相当于“人力资源部”,负责按照要求“招聘”(创建)新员工(线程)。
  7. handler (拒绝策略 - RejectedExecutionHandler):
    • 当线程池已经关闭,或者线程池饱和(达到 maximumPoolSizeworkQueue 已满)时,对新提交的任务采取的处理策略
    • 内置策略:
      • AbortPolicy (默认): 直接抛出 RejectedExecutionException 异常。最常用,明确知道任务被拒绝了。
      • CallerRunsPolicy: 将任务退回给调用者线程(提交任务的线程,比如 main 线程)去执行。这样提交任务的速度会下降,给线程池喘息时间。
      • DiscardPolicy: 默默丢弃新提交的任务,不做任何通知。
      • DiscardOldestPolicy: 丢弃工作队列中排队时间最久(队列头部)的那个任务,然后尝试重新提交当前这个新任务。(不保证成功,因为队列可能还是满的)。
    • 相当于当“待办事项清单”满了,且“员工+临时工”都用满了,再有新任务来时的“处理办法”。

11. 线程数怎么设置呢?

  • 核心答案:没有绝对标准答案!需要根据具体业务场景、服务器资源进行压测和调整。但有一些指导原则:
  • 考虑因素:
    1. 任务类型 (最关键!):
      • CPU 密集型任务 (计算为主,如复杂算法、视频编码): 线程数 ≈ CPU 核心数 (或 CPU 核心数 + 1)。设置过多会导致大量线程上下文切换,反而降低性能。Runtime.getRuntime().availableProcessors() 获取逻辑核心数。
      • I/O 密集型任务 (等待为主,如网络请求、数据库操作、文件读写): 线程数可以设置得远大于 CPU 核心数。因为线程在等待 I/O 时 CPU 是空闲的,可以处理其他线程的任务。经验公式:
        • 线程数 ≈ CPU 核心数 * (1 + 平均等待时间 / 平均计算时间)
        • 例如:4核CPU,任务50%时间计算,50%时间等待I/O:线程数 ≈ 4 * (1 + 0.5 / 0.5) = 4 * 2 = 8
        • 实际中等待时间很难精确计算,通常需要压测。可以从 CPU核心数 * 2 开始测试,逐步增加,观察 CPU 利用率、响应时间、吞吐量变化,找到性能拐点。
    2. 系统资源限制:
      • CPU 核心数: 是硬限制。过多的 CPU 密集型线程只会导致争抢和切换。
      • 内存: 每个线程都需要栈内存(默认约1MB,可通过 -Xss 调整)。大量线程会消耗可观的内存。
      • 操作系统/文件句柄/数据库连接池限制: 线程数不能超过这些外部资源的限制。
    3. 任务特性:
      • 任务优先级: 是否需要优先级队列?PriorityBlockingQueue
      • 任务依赖: 复杂的依赖关系可能需要更复杂的线程池组合或任务编排 (如 CompletableFuture)。
      • 任务执行时间: 长短任务混合?可能需要隔离不同的线程池处理。
    4. 业务目标:
      • 吞吐量优先: 在资源允许范围内,适当增加线程数可能提升吞吐量。
      • 响应时间优先: 需要控制线程数避免过多排队。可能需要设置合理的队列大小和拒绝策略保证核心请求响应。
    5. 突发流量:
      • 考虑系统是否能承受短时间的高峰流量。可以通过 maximumPoolSize 和合适的队列大小 (ArrayBlockingQueue) 以及拒绝策略 (CallerRunsPolicy 或自定义降级) 来应对。
  • 动态调整:
    • 理想情况下,线程池大小应该能根据负载动态调整。虽然 ThreadPoolExecutor 提供了 setCorePoolSize()setMaximumPoolSize() 方法,但动态调整逻辑需要自己实现(例如基于监控指标)。
    • 很多成熟的框架(如 Spring Cloud Netflix Hystrix, 阿里 Sentinel)或服务网格(如 Istio)提供了更高级的流量控制、熔断和线程池隔离策略。
  • 实践建议:
    1. 优先使用有界队列 (ArrayBlockingQueue) 并设置合理的容量。 这是防止 OOM 的关键。
    2. 设置明确的拒绝策略。 AbortPolicyCallerRunsPolicy 通常是好的选择,让调用者感知到系统压力。
    3. I/O 密集型任务:CPU核心数 * 2 开始压测调整。压测!压测!压测! 是找到最佳线程数的唯一可靠方法。
    4. CPU 密集型任务: 设置为 CPU核心数CPU核心数 + 1
    5. 监控: 使用 JMX、Micrometer 等工具监控线程池的关键指标(活动线程数、队列大小、任务完成数、拒绝任务数等),以便及时调整配置。

总结关键点:

  • ThreadLocal: 默认不能传值给子线程。InheritableThreadLocal 只在新线程创建时有效,绝对不要用于线程池。线程池传值用参数传递TTL
  • 线程池创建: ThreadPoolExecutor 手动创建,避免 Executors 的潜在 OOM 风险。
  • 线程池参数: 牢记 7 大金刚 (corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler) 的含义和作用,特别是有界队列合适的拒绝策略
  • 线程数设置: 看任务类型 (CPU/IO)看系统资源看业务目标。CPU 密集型 ≈ 核心数;IO 密集型 ≈ 核心数 * N (N>1,需压测确定)。压测是王道!

一句话总结核心: **多线程要安全,共享数据要保护(锁/原子类/volatile)。线程之间要协作(wait/notify/Condition)。线程创建销毁太贵,用池子管理(线程池)。

http://www.xdnf.cn/news/14663.html

相关文章:

  • Shell脚本中和||语法解析
  • tkinter Text 组件学习指南
  • 创业知识概论
  • 机器学习流量识别(pytorch+NSL-KDD+多分类建模)
  • 深入解析BERT:语言分类任务的革命性引擎
  • 5G 浪潮:发展全景、困境突围与未来航向
  • 目标检测新升级:用YOLOv8打造密度视频热力图可视化
  • Agent轻松通-P3:分析我们的Agent
  • LeetCode 680.验证回文串 II
  • PowerShell批量处理文件名称/内容的修改
  • 大模型在肺癌预测及个性化诊疗方案中的应用研究
  • Git——分布式版本控制工具
  • NVIDIA开源Fast-dLLM!解析分块KV缓存与置信度感知并行解码技术
  • android gradle的优化
  • uni-app-配合iOS App项目开发apple watch app
  • 【大模型学习】项目练习:知乎文本生成器
  • RIP路由协议实验任务八:RIPv1配置与分析
  • Seata的总结
  • Kafka协议开发总踩坑?3步拆解二进制协议核心
  • 领码 SPARK 融合平台赋能工程建设行业物资管理革新——数智赋能,重塑中国模式新范式
  • NestJS中实现动态Cron任务管理
  • 【 感知集群】大规模分布式基础设施的AI赋能蓝图
  • JS红宝书笔记 8.2 创建对象
  • IPv4编址及IPv4路由基础
  • 73、MYSQL ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin含义
  • Transformer结构介绍
  • 记录存储的使用
  • uni-app项目实战笔记16--实现头部导航栏效果
  • 优化TCP/IP协议栈与网络层
  • 工程师生活:清除电热水壶(锅)水垢方法