【面试八股速通篇01】锁与并发工具
前言好久没有发文章了其实并不是没写而是最近在写自己的项目没有什么时间来写还有六七篇存稿没有整体都是好几万字的后续会慢慢整理主要是关于agent的相关内容同步进行的是面试的一些八股算法因此在这里给大家分享一些八股吧主要是自己一个人看也记不住多少自己写出来并且能讲明白印象更深一点。今天看的主要是java并发工具还有锁相关的知识这些都是面试的高频考点需要重点掌握。我们先整体的看一下先明确了整体的框架我们主要抓住重点的进行学习。实际最常用的是线程池、ConcurrentHashMap、CountDownLatch、CompletableFuture。synchronized 底层原理这是面试超高频题核心就一句话synchronized 的锁会根据竞争情况从轻到重逐级升级尽量少用重量级锁。一、先搞懂锁存在哪对象头每个 Java 对象在内存里都有一段对象头Mark Word锁的状态就记录在这里。64 位 JVM 下Mark Word 主要存这些信息不同锁状态存的不一样锁状态Mark Word 内容无锁hashcode 分代年龄 锁标志位(01)偏向锁线程ID 时间戳 分代年龄 标志位(01)轻量级锁指向栈中锁记录的指针 标志位(00)重量级锁指向 Monitor 的指针 标志位(10)关键锁的信息不在你的业务对象上而在对象头里。二、四种锁状态 升级过程无锁 → 偏向锁 → 轻量级锁 → 重量级锁 只有一个线程 少量竞争、自旋 竞争激烈注意锁升级是单向的不能降级偏向锁撤销除外。三、逐个拆解1. 偏向锁Biased Locking场景只有一个线程反复进入同步块。原理第一个线程来的时候把自己的线程 ID记到对象头的 Mark Word 里以后这个线程再来检查 Mark Word 里是不是自己的 ID是就直接进去连 CAS 都不用相当于这个锁偏向我我随便进为什么快几乎零开销就是一次比较。什么时候升级有第二个线程来竞争时偏向锁就要撤销升级为轻量级锁。补充JDK 15 之后默认关闭并废弃了偏向锁因为维护成本高、收益有限。但面试还是会问。2. 轻量级锁Lightweight Lock场景多个线程竞争但竞争不激烈锁持有时间短。原理CAS 自旋线程在自己的栈帧里创建一个锁记录Lock Record用CAS把对象头的 Mark Word 替换成指向自己锁记录的指针CAS 成功 → 拿到锁失败 → 说明有竞争自旋重试自旋一定次数还拿不到 → 升级为重量级锁为什么比重量级快不涉及操作系统在用户态用 CAS 自旋解决避免线程阻塞/唤醒的开销。自旋的代价自旋就是空转 CPU。如果锁一直被占用自旋就是白费 CPU所以不能无限自旋。JDK 6 后引入自适应自旋自旋次数根据上次成功情况动态调整。3. 重量级锁Heavyweight Lock场景竞争激烈或锁持有时间长。原理依赖操作系统的Monitor管程C 实现存在对象头指向的 ObjectMonitor 里抢不到锁的线程直接阻塞挂起进入等待队列涉及用户态 ↔ 内核态切换开销大ObjectMonitor 结构了解_owner当前持有锁的线程_EntryList等待锁的线程队列_WaitSet调用 wait() 后进入的等待队列_count重入次数为什么慢线程阻塞和唤醒需要操作系统介入上下文切换成本高。四、锁优化补充面试加分项除了锁升级JVM 还有这些优化优化说明锁消除逃逸分析发现锁对象不会被其他线程访问直接去掉锁锁粗化连续多次加锁解锁合并成一次大锁自适应自旋根据历史成功率动态决定自旋次数锁消除例子java public String test() { StringBuffer sb new StringBuffer(); // 局部变量不会逃逸 sb.append(a); return sb.toString(); } // StringBuffer 方法有 synchronized但对象是局部的JVM 直接消除锁五、一句话总结synchronized 通过对象头 Mark Word记录锁状态采用锁升级策略单线程用偏向锁记线程ID零开销少量竞争用轻量级锁CAS 自旋用户态解决激烈竞争用重量级锁操作系统 Monitor阻塞排队。目的是尽量把锁控制在用户态减少内核态切换开销。此外还有锁消除、锁粗化、自适应自旋等优化。六、常见追问Q偏向锁为什么被废弃A撤销偏向锁需要 STWStop The World在高并发场景下维护成本大于收益JDK 15 后默认关闭。Q轻量级锁自旋多少次升级A不是固定值JDK 6 后是自适应自旋根据上次在该锁上自旋的成功率动态调整。Qsynchronized 和 ReentrantLock 底层区别Asynchronized 是 JVM 层面MonitorReentrantLock 是 JDK 层面基于 AQS CASReentrantLock是什么JDK 提供的可重入锁基于 AQS 实现手动加锁解锁。java Lock lock new ReentrantLock(); lock.lock(); try { i; } finally { lock.unlock(); // 必须手动释放 }比 synchronized 多的能力能力方法场景可中断lockInterruptibly()防止死等可超时tryLock(3, SECONDS)抢不到就放弃公平锁new ReentrantLock(true)需要按顺序多条件Condition精确唤醒使用场景java // 场景1尝试获取锁超时就放弃防止死等 if (lock.tryLock(3, TimeUnit.SECONDS)) { try { /* 干活 */ } finally { lock.unlock(); } } else { // 3秒没抢到走别的逻辑 } // 场景2公平锁按排队顺序拿锁 Lock fairLock new ReentrantLock(true);什么时候用 ReentrantLock 而不是 synchronized需要超时、可中断、公平、多条件这些高级功能时。ReentrantReadWriteLock —— 读多写少是什么一把锁拆成读锁 写锁。核心规则读锁 读锁 → 可以共存多个线程同时读 读锁 写锁 → 互斥 写锁 写锁 → 互斥为什么需要普通锁读读也互斥太浪费。但读操作不会改数据多个线程一起读完全没问题。java ReentrantReadWriteLock rwLock new ReentrantReadWriteLock(); Lock readLock rwLock.readLock(); Lock writeLock rwLock.writeLock(); // 读操作多个线程可同时进行 readLock.lock(); try { return map.get(key); } finally { readLock.unlock(); } // 写操作独占 writeLock.lock(); try { map.put(key, value); } finally { writeLock.unlock(); }使用场景读多写少比如本地缓存。text缓存读 100 次写 1 次 → 用读写锁100 个读可以并行注意如果读写差不多甚至写多读写锁反而更慢维护两把锁有开销。StampedLock —— 读写锁的升级版JDK8是什么比读写锁更快的锁支持乐观读。java StampedLock lock new StampedLock(); // 乐观读不加锁读完检查有没有被改 long stamp lock.tryOptimisticRead(); int data this.data; if (!lock.validate(stamp)) { // 读期间被改了 // 升级为悲观读 stamp lock.readLock(); try { data this.data; } finally { lock.unlockRead(stamp); } }使用场景读极多、写极少追求极致性能。实际项目用得很少面试会问。CAS / 原子类 —— 无锁是什么不加锁靠 CPU 的 CAS 指令保证原子性。java AtomicInteger i new AtomicInteger(0); i.incrementAndGet(); // 无锁自增比 synchronized 快使用场景简单计数访问量、库存状态标记AtomicBoolean无锁数据结构ConcurrentHashMap底层java // 典型库存扣减 AtomicInteger stock new AtomicInteger(100); if (stock.decrementAndGet() 0) { // 扣减成功 }注意高并发下 CAS 自旋会浪费 CPU此时用LongAdder更好。ConcurrentHashMap一句话定位线程安全的 HashMap。多线程环境下替代HashMap性能又远好于Hashtable。为什么不用 HashMap / Hashtable集合线程安全性能问题HashMap不安全高并发 put 会丢数据、死循环JDK7、数据错乱Hashtable安全低所有方法加synchronized全表一把锁并发差ConcurrentHashMap安全高锁粒度细并发好Hashtable 的问题一个线程 put其他所有线程连 get 都被阻塞。太粗暴。ConcurrentHashMap 怎么做到又安全又快JDK 7分段锁Segment把整个表分成 16 个 Segment每个 Segment 一把锁。ConcurrentHashMap ├── Segment[0] ← 锁A ├── Segment[1] ← 锁B ├── ... └── Segment[15] ← 锁P不同 Segment 可以并发写锁冲突概率降到 1/16。JDK 8CAS synchronized锁桶头节点放弃分段锁改成数组 链表 红黑树和 HashMap 一样锁的粒度是单个桶数组的某个下标桶为空时用CAS放第一个节点无锁桶非空时用synchronized 锁住桶的头节点text数组 table [0] → null [1] → A → B → C ← 锁这个头节点 A [2] → null [3] → D → E ← 锁这个头节点 D [4] → 红黑树...为什么快不同桶可以真正并行写空桶用 CAS完全无锁只有哈希冲突到同一个桶才用 synchronized且只锁那一个桶4. 核心操作原理put 流程JDK 8text1. 计算 key 的 hash定位到数组下标 i 2. 如果 table[i] null → CAS 直接放入新节点无锁成功就完事 3. 如果 table[i] ! null → synchronized 锁住 table[i] 这个头节点 → 遍历链表/红黑树找到就覆盖找不到就尾插 4. 链表长度 ≥ 8 且数组长度 ≥ 64 → 转红黑树 5. 元素个数 1用 CAS 更新 sizebaseCount CounterCell 分段计数get 流程text1. 算 hash 定位下标 2. 遍历链表/红黑树找 key 3. 全程不加锁节点 value 和 next 用 volatile 保证可见性get 为什么不用加锁因为节点的val和next是volatile的改了别的线程立刻能看到不会读到脏数据常见追问QConcurrentHashMap 允许 null 键值吗A不允许。因为 get 返回 null 时无法区分key 不存在还是value 就是 null并发下有二义性。Qsize() 是精确的吗AJDK 8 用 baseCount CounterCell 累加并发下是近似值不是强一致。Q扩容时其他线程能操作吗A能。扩容是多线程协助迁移其他线程 put 时如果发现正在扩容会帮忙搬一部分数据。补充ConcurrentHashMap的扩容机制设计目的多线程协助避免一个线程搬、其他人干等CAS 抢任务无锁分配搬迁任务高效ForwardingNode标记搬完的桶转发请求锁桶头保证正在搬的桶不出错sizeCtl 负数标识扩容状态让其他线程感知目标扩容期间读写操作尽量不被长时间阻塞。JDK 8 采用多线程协助扩容触发扩容时把 sizeCtl 设为负数标识扩容中创建新数组 nextTable每个线程用 CAS 抢 transferIndex领取一段桶进行搬迁搬某个桶时用 synchronized 锁住桶头节点把元素重新 hash 到新数组搬完在旧桶放 ForwardingNode 标记其他线程 put/get 遇到 ForwardingNode会转发到新数组操作其他线程 put 时发现正在扩容也会主动来帮忙搬。相比 JDK 7 的单线程扩容JDK 8 多线程协助扩容期间尽量不阻塞读写。CountDownLatch1. 作用一个线程等 N 个线程完成。2. 原理基于 AQS 的共享锁state 初始化为 NcountDown()就是 state-1await()就是等 state0。3. 代码java CountDownLatch latch new CountDownLatch(3); for (int i 0; i 3; i) { new Thread(() - { try { // 干活 } finally { latch.countDown(); } }).start(); } latch.await(); // 主线程阻塞直到计数为 0 System.out.println(全部完成);4. 使用场景主线程等所有子任务完成并发汇总模拟并发测试让多个线程同时开始服务启动等待组件初始化5. 面试追问QCountDownLatch 能重用吗不能计数到 0 就废了。要重用用 CyclicBarrier。Qawait() 能超时吗能await(3, TimeUnit.SECONDS)返回 false 表示超时。Q底层原理AQS 共享模式state 为计数countDown 用 CAS 递减减到 0 唤醒所有等待线程。AQS 是什么AQS 是面试锁话题的终极考点。ReentrantLock、CountDownLatch、Semaphore、读写锁全都基于它。搞懂它前面所有锁的底层就串起来了。一、一句话定义AQS AbstractQueuedSynchronizer抽象队列同步器是 JDK 提供的一个构建锁和同步器的框架。它把抢锁、排队、阻塞、唤醒这套通用逻辑都写好了你只需要告诉它能不能拿到锁。打个比方AQS 就像一栋毛坯房框架、水电、墙体排队、阻塞、唤醒它都建好了你只需要装修实现能不能拿锁的逻辑ReentrantLock、CountDownLatch、Semaphore 都是基于这套毛坯房装修出来的成品。二、AQS 的核心两个东西AQS 内部就靠两样东西运转javapublic abstract class AbstractQueuedSynchronizer { // 1. 同步状态核心 private volatile int state; // 2. 等待队列双向链表 private transient volatile Node head; private transient volatile Node tail; }核心1state同步状态state 是一个 int不同子类给它不同含义子类state 表示什么ReentrantLock锁被重入几次0没锁1拿1次2重入CountDownLatch还剩几个任务没完成Semaphore还剩几个许可证state 的修改用 CAS 保证原子性。核心2等待队列CLH 变体的双向链表抢不到锁的线程包装成 Node排进这个队列。texthead tail │ │ ▼ ▼ [Node] ⇄ [Node] ⇄ [Node] ⇄ [Node] 线程B 线程C 线程D 线程E 正在等等待中等待中等待中每个 Node 装一个等待的线程前驱释放锁后唤醒后继双向链表方便节点取消、找前驱三、AQS 的精髓模板方法模式AQS 把抢锁分成两部分text┌─────────────────────────────────────┐ │ AQS 负责的通用逻辑写好了 │ │ ├── 维护 state │ │ ├── 维护等待队列 │ │ ├── 线程排队、阻塞park │ │ └── 唤醒后继unpark │ ├─────────────────────────────────────┤ │ 子类负责的业务逻辑你来写 │ │ └── tryAcquire能不能拿到锁 │ └─────────────────────────────────────┘AQS 定义了几个留给子类实现的方法java// 独占模式能不能获取 protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); // 空实现子类必须重写 } // 独占模式能不能释放 protected boolean tryRelease(int arg) { ... } // 共享模式能不能获取 protected int tryAcquireShared(int arg) { ... } // 共享模式能不能释放 protected boolean tryReleaseShared(int arg) { ... }你只需要重写这几个方法队列、阻塞、唤醒 AQS 全帮你搞定。四、两种模式独占 vs 共享模式含义实现独占模式Exclusive同一时刻只有一个线程能拿到ReentrantLock共享模式Shared多个线程可以同时拿到CountDownLatch、Semaphore、读写锁的读锁独占像厕所单间一个人进去锁门别人等。共享像公园长椅可以坐好几个人坐满了才需要等。五、加锁流程独占模式重点以ReentrantLock.lock()为例textlock() │ ▼ AQS.acquire(1) │ ▼ ① tryAcquire(1) —— 子类实现尝试拿锁 │ ├── 成功 → 拿到锁直接返回干活 │ └── 失败 ↓ ▼ ② addWaiter() —— 把当前线程包装成 Node加入队列尾部 │ ▼ ③ acquireQueued() —— 在队列里自旋 阻塞 │ ├── 如果自己是队首head.next再试 tryAcquire │ 成功 → 出队拿到锁 │ 失败 → 继续 │ └── 不是队首或试失败 → LockSupport.park() 阻塞 │ ▼ ④ 前驱节点释放锁时unpark 唤醒自己 │ ▼ 回到 ③ 再试简化记忆text尝试拿锁 → 失败入队 → 自旋/阻塞 → 被唤醒 → 再尝试 → 成功出队六、ReentrantLock 怎么用 AQS 实现加锁java// ReentrantLock 的 tryAcquire非公平版简化 protected boolean tryAcquire(int acquires) { int c getState(); if (c 0) { // 没人拿锁CAS 抢 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } } else if (current getExclusiveOwnerThread()) { // 是当前线程拿的 → 可重入state1 setState(c acquires); return true; } return false; // 抢不到 }看ReentrantLock 只做了两件事state 为 0 → CAS 抢state 不为 0 且是自己 → state1重入剩下的排队、阻塞、唤醒全是 AQS 干的。解锁javaprotected boolean tryRelease(int releases) { int c getState() - releases; // state - 1 if (c 0) { setExclusiveOwnerThread(null); // 真正释放 setState(c); return true; // 返回 true 才会唤醒后继 } setState(c); // 还没释放完重入返回 false return false; }只有 state 减到 0 才算真正释放才唤醒队列里的下一个。七、CountDownLatch 怎么用 AQS共享模式java// 初始化state 3 new CountDownLatch(3); // countDownstate - 1 countDown(); → tryReleaseShared(1) → CAS 把 state 减 1 → state 减到 0 → 返回 true → 唤醒所有等待线程 // await等 state 0 await(); → tryAcquireShared(1) → state 0 ? 返回 1成功: 返回 -1失败入队阻塞state 就是还剩几个任务。减到 0所有人放行。八、公平锁 vs 非公平锁AQS 里怎么体现区别就在tryAcquire里加不加检查队列这一步。非公平锁默认javaprotected boolean tryAcquire(int acquires) { // 直接 CAS 抢不管队列里有没有人排队 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } // ... }新来的线程可以直接插队抢锁。公平锁javaprotected boolean tryAcquire(int acquires) { // 先检查队列里有没有人排队 if (!hasQueuedPredecessors() // ← 关键有前驱就不抢 compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } // ... }公平锁多了一句hasQueuedPredecessors()如果前面有人在等我就乖乖排队不抢。公平锁非公平锁抢锁前检查队列有没有前驱直接 CAS 抢性能慢上下文切换多快减少唤醒饥饿不会可能为什么默认非公平因为非公平可以减少线程唤醒吞吐量更高。九、AQS 里几个关键方法面试可能问方法作用acquire(int)独占获取含入队阻塞acquireShared(int)共享获取release(int)独占释放releaseShared(int)共享释放hasQueuedPredecessors()队列里有没有前驱公平锁用LockSupport.park()阻塞当前线程LockSupport.unpark(t)唤醒指定线程park/unpark和wait/notify的区别常问park/unparkwait/notify是否需要持有锁不需要必须在 synchronized 里唤醒精度可以精确唤醒指定线程notify 随机唤醒一个顺序unpark 可以先于 parknotify 先于 wait 会丢失AQS 是什么AQS 是 AbstractQueuedSynchronizer是 JUC 里构建锁和同步器的基础框架。ReentrantLock、CountDownLatch、Semaphore 都基于它。核心是一个 volatile 的 state 变量 一个双向等待队列state 表示同步状态不同子类含义不同ReentrantLock 里是重入次数CountDownLatch 里是剩余计数抢不到锁的线程包装成 Node进入队列排队用 LockSupport.park 阻塞它用了模板方法模式AQS 负责队列、阻塞、唤醒这些通用逻辑子类只需实现tryAcquire/tryRelease等方法告诉 AQS能不能拿到锁。AQS 支持两种模式独占ReentrantLock和共享CountDownLatch、Semaphore。公平锁和非公平锁的区别就在于tryAcquire里是否检查队列前驱。AQS 里的 Condition条件队列Condition 是 AQS 里除了等待队列之外的第二个队列用来实现await/signal对应synchronized里的wait/notify。一、先搞懂 Condition 是什么Condition 条件变量让线程在某个条件下等待条件满足时被唤醒。类比synchronizedwait/notify只有一个休息室所有等的线程都挤在一起ReentrantLockCondition可以开多个休息室不同条件去不同休息室等java// synchronized 版本 synchronized (lock) { while (条件不满足) lock.wait(); // 干活 lock.notifyAll(); }java// Condition 版本更灵活 lock.lock(); try { while (条件不满足) condition.await(); // 干活 condition.signal(); } finally { lock.unlock(); }核心区别一个 Lock 可以创建多个 Condition精确唤醒。二、为什么需要 Condition对比 wait/notify问题wait/notify 只能有一个等待队列经典例子阻塞队列生产者-消费者java// 用 synchronized wait/notify 的问题 synchronized (lock) { // 生产者队列满了要等 while (queue.size() MAX) lock.wait(); // 消费者队列空了要等 while (queue.size() 0) lock.wait(); lock.notifyAll(); // 只能全部唤醒分不清唤醒谁 }问题生产者和消费者在同一个等待队列里。生产者唤醒时可能唤醒的也是生产者白唤醒还得继续等。解决用两个 Condition 分开等java// 用 Condition生产者、消费者各一个队列 private final Condition notFull lock.newCondition(); // 生产者在这等 private final Condition notEmpty lock.newCondition(); // 消费者在这等 // 生产者 lock.lock(); try { while (queue.size() MAX) notFull.await(); // 满了在 notFull 等 queue.add(e); notEmpty.signal(); // 精确唤醒消费者 } finally { lock.unlock(); } // 消费者 lock.lock(); try { while (queue.size() 0) notEmpty.await(); // 空了在 notEmpty 等 queue.poll(); notFull.signal(); // 精确唤醒生产者 } finally { lock.unlock(); }这就是 ArrayBlockingQueue 的实现原理三、Condition 的核心方法方法作用对应 wait/notifyawait()释放锁进入等待被唤醒后重新拿锁wait()await(time, unit)带超时的等待wait(timeout)signal()唤醒一个等待线程notify()signalAll()唤醒所有等待线程notifyAll()注意调用这些方法前必须持有锁javalock.lock(); // 必须先拿锁 try { condition.await(); // 才能 await } finally { lock.unlock(); }和wait/notify必须在 synchronized 里一样。四、await/signal 的完整流程await() 干了什么text线程A 调用 condition.await() │ ▼ ① 释放锁state 归 0唤醒等待队列的下一个 │ ▼ ② 把自己包装成 Node加入 Condition 的【条件队列】 │ ▼ ③ 阻塞park等待被 signal │ ▼ ④ 被 signal 后从条件队列移到【等待队列】 │ ▼ ⑤ 重新竞争锁acquire │ ▼ ⑥ 拿到锁await 返回继续执行关键点await 会释放锁不然别人没法 signal被唤醒后要重新抢锁所以不是立刻返回signal() 干了什么text线程B 调用 condition.signal() │ ▼ ① 从条件队列取出第一个 Node │ ▼ ② 把它移到等待队列不是直接唤醒 │ ▼ ③ 等它前面的线程释放锁后才会被唤醒去抢锁关键signal 只是搬家从条件队列搬到等待队列真正唤醒要等锁释放。五、两个队列的关系核心图textAQS 内部有两个队列 ① 等待队列同步队列 ┌──────────────────────────────────┐ head ⇄ Node ⇄ Node ⇄ Node ⇄ tail 抢锁失败的线程在这排队 └──────────────────────────────────┘ ② 条件队列Condition Queue ┌──────────────────────────────────┐ firstNode → Node → Node → lastNode 调用 await 的线程在这等 └──────────────────────────────────┘await 和 signal 就是在这两个队列之间搬家text调用 await 线程从【运行】→ 释放锁 → 进入【条件队列】 调用 signal 线程从【条件队列】→ 移到【等待队列】→ 等锁 → 拿到锁 → 继续运行画个流程text线程运行 │ │ await() ← 释放锁进条件队列 ▼ 条件队列等待 signal │ │ signal() ← 被移到等待队列 ▼ 等待队列重新抢锁 │ │ 抢到锁 ▼ 线程继续运行六、完整代码示例手写阻塞队列javapublic class MyBlockingQueueT { private final Object[] items; private int count, putIndex, takeIndex; private final ReentrantLock lock new ReentrantLock(); private final Condition notFull lock.newCondition(); // 生产者等 private final Condition notEmpty lock.newCondition(); // 消费者等 public MyBlockingQueue(int capacity) { items new Object[capacity]; } // 生产者 public void put(T x) throws InterruptedException { lock.lock(); try { while (count items.length) notFull.await(); // 满了生产者在这等 items[putIndex] x; if (putIndex items.length) putIndex 0; count; notEmpty.signal(); // 唤醒消费者 } finally { lock.unlock(); } } // 消费者 public T take() throws InterruptedException { lock.lock(); try { while (count 0) notEmpty.await(); // 空了消费者在这等 T x (T) items[takeIndex]; if (takeIndex items.length) takeIndex 0; count--; notFull.signal(); // 唤醒生产者 return x; } finally { lock.unlock(); } } }这就是 ArrayBlockingQueue 的核心逻辑。七、面试必问的几个点Q1await 和 wait 的区别awaitwait所属ConditionObject必须持有Locksynchronized队列可以有多个 Condition只有一个唤醒signal 精确唤醒notify 随机Q2为什么 await 必须重新抢锁因为await会释放锁别的线程可能在此期间改了状态。被唤醒后必须重新拿锁重新检查条件所以要用while而不是ifjavawhile (条件不满足) condition.await(); // 必须 while不能 if用 if 的坑被唤醒后不重新检查可能条件已经又不满足了虚假唤醒。Q3什么是虚假唤醒线程可能在没有 signal 的情况下被唤醒操作系统层面。所以必须用while循环重新检查条件。Q4signal 和 signalAll 区别signal唤醒条件队列第一个signalAll唤醒所有一般用 signal 更高效但如果多个线程等不同条件得用 signalAll。Q5Condition 队列和等待队列的区别等待队列条件队列触发抢锁失败调用 await目的等锁等条件出队拿到锁被 signal结构双向链表单向链表八、一句话总结Condition 是 AQS 的条件队列配合 Lock 实现 await/signal对应 wait/notify。一个 Lock 可以有多个 Condition实现精确唤醒生产者、消费者各一个await 流程释放锁 → 进条件队列 → 阻塞 → 被 signal 移到等待队列 → 重新抢锁 → 继续两个队列等待队列等锁、条件队列等条件signal 就是搬家必须用 while 检查条件防止虚假唤醒

相关新闻

微信小程序问卷系统毕业设计实战:联调、权限与幂等性闭环指南

微信小程序问卷系统毕业设计实战:联调、权限与幂等性闭环指南

简介:本资源是一套面向本科毕业设计的微信小程序问卷调查系统完整实现方案,适用于Java后端开发、Vue前端及小程序全栈学习者,解决校园调研、课程实践、小型组织数据采集等场景中的问卷发布、填写、统计与管理需求。项目采用SpringBoot构建RES…

2026/10/14 3:33:36 阅读更多 →
Unity中Tag与Layer的代码管理:编辑器脚本实现自动化配置

Unity中Tag与Layer的代码管理:编辑器脚本实现自动化配置

开篇就直接说结论吧:在Unity里,Tag和Layer用编辑器面板拖一拖、填一填确实很直观,但只要项目开始多人协作、频繁并行分支,或者你在做自动化打包、程序化生成场景,光靠手工维护这两个配置就很容易出问题。同一台机器上明…

2026/10/14 3:33:36 阅读更多 →
【单片机毕业设计】基于STM32的婴儿床哭泣尿床PM2.5监测与手机端报警装置设计 基于物联网的婴儿监护尿床哭泣检测与空气监测系统设计(032706)

【单片机毕业设计】基于STM32的婴儿床哭泣尿床PM2.5监测与手机端报警装置设计 基于物联网的婴儿监护尿床哭泣检测与空气监测系统设计(032706)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/10/14 3:33:36 阅读更多 →

最新新闻

计及需求侧响应的配电网供电能力综合评估Matlab实现

计及需求侧响应的配电网供电能力综合评估Matlab实现

供电能力评估这个方向,做配电网规划的人都不陌生。传统做法很简单:把负荷当成一个给定的“硬条件”,算主变、算线路、算N-1通过率,能带多少就是多少。但问题是,现在的负荷已经不再“硬”了——分时电价、可中断负荷、车…

2026/10/14 6:02:23 阅读更多 →
AI协作者实战指南:从提示词工程师到流程编排师

AI协作者实战指南:从提示词工程师到流程编排师

1. 这不是“自动印钞机”,而是AI时代的职业重构现场“自动赚钱时代来了?”——这个标题一出来,朋友圈就炸了。有人截图转发配文“终于等到这一天”,也有人冷笑评论“又一个割韭菜的标题党”。但真正让我坐下来认真拆解它的&#x…

2026/10/14 6:02:23 阅读更多 →
ZeroTermux 内置命令手册:tcpreplay 报文重放与双网卡网络测试实战

ZeroTermux 内置命令手册:tcpreplay 报文重放与双网卡网络测试实战

移动开发开发工具 【免费下载链接】ZeroTermux 项目地址: https://gitcode.com/GitHub_Trending/ze/ZeroTermux 点击查看 免费下载 导读 tcpreplay 是一款基于 PCAP 文件的报文重放工具,它能把用 Wireshark(及其前身 Ethereal)、…

2026/10/14 6:02:23 阅读更多 →
AnyPS5:一套对任意PS5都适用的折腾与优化方案

AnyPS5:一套对任意PS5都适用的折腾与优化方案

看到“AnyPS5”这个名字,估计不少人会误会:是破解方案?是某个新机型?其实都不是。我更愿意把它理解为一套“对任意一台PS5都适用的折腾与优化方案”。这些年帮朋友处理过不少台PS5主机,有的是刚拆封的新机,…

2026/10/14 6:02:23 阅读更多 →
轻资产酒店集团财报解读:现金流、分红与CFO变动背后的逻辑

轻资产酒店集团财报解读:现金流、分红与CFO变动背后的逻辑

最近酒店行业的头条,大概率是这家头部连锁酒店集团的年度成绩单:营收253亿,净利51亿,同时宣布派发约4亿美元股息,同一时间,任职多年的CFO卸任。几个消息堆在一起,很多人第一反应是"业绩不错…

2026/10/14 6:02:23 阅读更多 →
高校农业病虫害智能监测系统设计与实现

高校农业病虫害智能监测系统设计与实现

1. 项目概述农业病虫害监测与预测一直是农业生产中的关键环节。传统的人工巡查方式效率低下且容易遗漏,而基于大数据的智能监测系统正在改变这一现状。这个项目构建了一个面向高校场景的农业病虫害监测与趋势预测系统,整合了数据采集、存储、分析和可视化…

2026/10/14 6:01:22 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →