Java并发编程面试汇总

Java并发编程

  • 一、 基础概念
    • 1. 进程与线程的区别是什么?
    • 2. 创建线程的几种方式?
    • 3. 线程的生命周期(状态)有哪些?
    • 4. 什么是守护线程(Daemon Thread)?
    • 5. 线程优先级(Priority)的作用及问题?
  • 二、线程安全与锁机制
    • 1. 什么是线程安全?如何实现线程安全?
    • 2. synchronized 关键字的底层实现原理(对象头、Monitor 机制)?
    • 3. volatile 关键字的作用和实现原理(内存屏障、可见性、禁止指令重排)?
    • 4. 什么是 CAS(Compare-And-Swap)?其优缺点?
    • 5. synchronized 和 ReentrantLock 的区别?
    • 6. 可重入锁(ReentrantLock)的实现原理?
    • 7. 公平锁与非公平锁的区别?
    • 8. 什么是死锁?如何避免或检测死锁?
    • 9. 偏向锁、轻量级锁、重量级锁的升级过程?
    • 10. 锁消除(Lock Elimination)和锁粗化(Lock Coarsening)的原理?
  • 三、线程协作与通信
    • 1. `wait()`、`notify()`、`notifyAll()` 的使用场景和注意事项?
    • 2. `sleep()` 和 `wait()` 的区别?
    • 3. 如何实现线程间通信(如生产者-消费者模型)?
    • 4. `Condition` 接口的作用及与 `wait/notify` 的区别?
  • 四、并发工具类(JUC)
    • 1. `CountDownLatch`、`CyclicBarrier`、`Semaphore` 的使用场景和区别
      • **1.1 `CountDownLatch`**
      • **1.2 `CyclicBarrier`**
      • **1.3 `Semaphore`**
    • 2. `Exchanger` 的作用
    • 3. `Phaser` 的使用场景
    • 4. `Future` 和 `CompletableFuture` 的区别
    • 5. `StampedLock` 的优化点及使用场景
      • **优化点**
      • **使用场景**
  • 五、 线程池相关问题解析
    • 1. 线程池的核心参数及作用
    • 2. 线程池的工作流程(任务提交后的处理逻辑)
    • 3. 常见的线程池类型及其问题
    • 4. 线程池的拒绝策略
    • 5. 如何合理配置线程池参数
    • 6. 线程池中线程复用(Thread Reuse)的原理
    • 示例代码
  • 六、 高级并发特性解析
    • 1. Java 内存模型(JMM)的核心概念
    • 2. 原子性、可见性、有序性及其保证方法
    • 3. 指令重排序及其避免方法
    • 4. ThreadLocal 的原理、使用场景及内存泄漏问题
    • 5. Fork/Join 框架与工作窃取机制
    • 示例代码
  • 七、 并发容器解析
    • 1. ConcurrentHashMap 的实现原理(JDK7 vs JDK8)
    • 2. CopyOnWriteArrayList 的适用场景及优缺点
    • 3. BlockingQueue 的实现类及使用场景
    • 4. ConcurrentLinkedQueue 的无锁实现原理
  • 八、 并发设计模式与实战
    • 1. 生产者-消费者模式的实现方式
    • 2. 如何实现线程安全的单例模式
    • 3. 如何排查和解决死锁问题
      • 排查方法
      • 解决措施
    • 4. 如何避免竞态条件(Race Condition)
      • 加锁同步
      • 原子操作
      • 不可变对象设计
      • 线程安全的集合
    • 5. 如何设计高并发场景下的计数器
      • 原子变量
      • LongAdder/LongAccumulator
      • 分段计数器
  • 九、底层原理与扩展
    • 1. Java 线程与操作系统线程的关系
    • 2. 什么是协程(Coroutine)?Java 中的虚拟线程(Loom 项目)
    • 3. 如何通过 jstack 分析线程状态
    • 4. 什么是伪共享(False Sharing)?如何避免?
  • 十、其他扩展问题
    • 1. 如何实现异步编程(如 CompletableFuture、Reactive Streams)?
      • CompletableFuture
      • Reactive Streams
    • 2. 分布式锁与单机锁的区别
      • **单机锁**
      • **分布式锁**
    • 3. 无锁编程(Lock-Free)的实现思路
      • **CAS(Compare-And-Swap)**
      • **乐观锁**
      • **无锁数据结构**
    • 4. 高并发场景下常见的性能优化手段
      • **1. 减少锁竞争**
      • **2. 线程池优化**
      • **3. 缓存机制**
      • **4. 异步 & 批量处理**
      • **5. 数据结构优化**

一、 基础概念

1. 进程与线程的区别是什么?

进程与线程的主要区别如下:

  1. 定义
    • 进程(Process)是操作系统分配资源的基本单位。
    • 线程(Thread)是 CPU 调度的基本单位。
  2. 资源分配
    • 进程拥有独立的内存空间和系统资源。
    • 线程共享进程的资源,如内存和文件句柄。
  3. 通信方式
    • 进程间通信(IPC)方式复杂,如管道、共享内存、消息队列等。
    • 线程间通信更简单,可通过共享变量直接通信。
  4. 开销
    • 进程创建和切换开销较大。
    • 线程切换成本较小,效率更高。
  5. 崩溃影响
    • 进程崩溃不会影响其他进程。
    • 线程崩溃可能影响整个进程。

2. 创建线程的几种方式?

在 Java 中,创建线程主要有以下几种方式:

  1. 继承 Thread

    class MyThread extends Thread {public void run() {System.out.println("线程执行");}
    }public class Main {public static void main(String[] args) {MyThread thread = new MyThread();thread.start();}
    }
    
  2. 实现 Runnable 接口

    class MyRunnable implements Runnable {public void run() {System.out.println("线程执行");}
    }public class Main {public static void main(String[] args) {Thread thread = new Thread(new MyRunnable());thread.start();}
    }
    
  3. 使用 CallableFutureTask(可以返回值和抛出异常)

    import java.util.concurrent.*;class MyCallable implements Callable<String> {public String call() throws Exception {return "线程执行完成";}
    }public class Main {public static void main(String[] args) throws Exception {FutureTask<String> task = new FutureTask<>(new MyCallable());Thread thread = new Thread(task);thread.start();System.out.println(task.get());}
    }
    
  4. 使用线程池 ExecutorService

    import java.util.concurrent.*;public class Main {public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(2);executor.execute(() -> System.out.println("线程执行"));executor.shutdown();}
    }
    

3. 线程的生命周期(状态)有哪些?

线程的生命周期可分为以下几种状态:

  1. NEW(新建状态):线程对象被创建,但未调用 start()
  2. RUNNABLE(就绪/运行状态):调用 start() 方法后,等待 CPU 调度。
  3. BLOCKED(阻塞状态):线程试图获取锁但被阻塞。
  4. WAITING(无限等待状态):线程调用 wait()join(),需显式唤醒。
  5. TIMED_WAITING(计时等待状态):线程调用 sleep(time)wait(time) 等方法。
  6. TERMINATED(终止状态):线程执行完成或被异常终止。

4. 什么是守护线程(Daemon Thread)?

守护线程是后台运行的线程,主要用于执行后台任务,如垃圾回收。

  • 特点
    • 守护线程在所有非守护线程结束后,自动终止。
    • setDaemon(true) 方法可以设置线程为守护线程。

示例:

public class DaemonThreadExample {public static void main(String[] args) {Thread daemonThread = new Thread(() -> {while (true) {System.out.println("守护线程运行中...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});daemonThread.setDaemon(true);daemonThread.start();try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主线程结束");}
}

5. 线程优先级(Priority)的作用及问题?

  • Java 线程优先级范围:1(最低)~ 10(最高)。
  • 线程默认优先级是 5
  • 使用 setPriority(int newPriority) 设置优先级。

示例:

Thread thread = new Thread(() -> System.out.println("线程运行"));
thread.setPriority(Thread.MAX_PRIORITY); // 设置最高优先级
thread.start();

问题

  1. 不一定生效:线程调度由操作系统决定,Java 只是建议。
  2. 可能导致线程饥饿:高优先级线程可能长时间占用 CPU,低优先级线程无法执行。
  3. 平台相关:不同操作系统对线程优先级的实现可能不同。

二、线程安全与锁机制

1. 什么是线程安全?如何实现线程安全?

线程安全指的是多个线程同时访问共享数据时,不会导致数据的不一致性或错误。

实现线程安全的方法:

  1. synchronized 关键字:对方法或代码块加锁,确保同一时间只有一个线程访问。
  2. volatile 关键字:保证变量的可见性,防止指令重排序。
  3. ReentrantLock:可重入锁,提供更灵活的锁控制。
  4. CAS(Compare-And-Swap):无锁并发编程,保证变量的原子性更新。
  5. 线程安全的集合类:如 ConcurrentHashMapCopyOnWriteArrayList
  6. ThreadLocal:为每个线程提供独立变量,防止共享数据冲突。
  7. 原子操作类:如 AtomicIntegerAtomicReference

2. synchronized 关键字的底层实现原理(对象头、Monitor 机制)?

synchronized 通过对象头(Mark Word)中的锁标志位和 Monitor 机制 实现。

  • 对象头(Mark Word):存储锁信息,如偏向锁、轻量级锁、重量级锁。
  • Monitor 机制:由操作系统的 互斥量(Mutex) 实现,线程竞争锁时可能进入阻塞状态。
  • 锁升级过程
    1. 偏向锁(Biased Locking)
    2. 轻量级锁(Lightweight Locking)
    3. 重量级锁(Heavyweight Locking)

示例:

synchronized (this) {System.out.println("同步代码块");
}

3. volatile 关键字的作用和实现原理(内存屏障、可见性、禁止指令重排)?

volatile 关键字作用:

  1. 保证可见性:修改后的值会立即刷新到主内存。
  2. 禁止指令重排序:防止 CPU 优化导致的顺序问题。
  3. 不保证原子性:多个线程修改 volatile 变量仍可能导致竞态条件。

底层实现:

  • 通过 内存屏障(Memory Barrier) 确保可见性。
  • 使用 MESI 缓存一致性协议 保证 CPU 缓存一致性。

示例:

private volatile boolean flag = true;

4. 什么是 CAS(Compare-And-Swap)?其优缺点?

CAS(比较并交换) 是一种无锁并发机制,核心思想是:

  1. 读取变量的当前值 V
  2. 如果 V 等于期望值 E,则更新为新值 N
  3. V 发生变化,则重新尝试。

优点:

  • 无需加锁,性能高。

缺点:

  • ABA 问题:可使用 AtomicStampedReference 解决。
  • 自旋消耗 CPU:长时间自旋可能降低性能。

示例:

AtomicInteger atomicInteger = new AtomicInteger(0);
atomicInteger.compareAndSet(0, 1);

5. synchronized 和 ReentrantLock 的区别?

对比项synchronizedReentrantLock
锁的类型内置锁显式锁
可重入性支持支持
公平锁不支持支持
中断响应不支持支持
条件变量不支持支持 Condition

6. 可重入锁(ReentrantLock)的实现原理?

可重入锁 允许同一线程多次获取锁。

  • ReentrantLock 通过 AQS(AbstractQueuedSynchronizer) 维护一个 state 变量,记录获取次数。
  • 线程释放锁时,state 递减,直到 state == 0 时真正释放锁。

示例:

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {System.out.println("执行任务");
} finally {lock.unlock();
}

7. 公平锁与非公平锁的区别?

  • 公平锁:线程按顺序获取锁,避免线程饥饿。
  • 非公平锁:线程可以插队获取锁,提高性能。

示例:

ReentrantLock lock = new ReentrantLock(true); // 公平锁

8. 什么是死锁?如何避免或检测死锁?

死锁 是指多个线程相互等待对方释放资源,导致程序无法继续。

避免方法:

  1. 避免嵌套锁
  2. 资源分配有序
  3. 超时机制
  4. 死锁检测工具(jstack、jconsole)

9. 偏向锁、轻量级锁、重量级锁的升级过程?

锁的升级流程:

  1. 无锁状态
  2. 偏向锁:只有一个线程访问。
  3. 轻量级锁:多个线程竞争但无阻塞。
  4. 重量级锁:多个线程竞争,阻塞等待。

10. 锁消除(Lock Elimination)和锁粗化(Lock Coarsening)的原理?

  • 锁消除:JIT 编译时,发现局部变量不逃逸,移除锁。
  • 锁粗化:多个连续加锁的操作合并,减少锁的频繁释放与获取。

示例:

public void test() {StringBuilder sb = new StringBuilder(); // JIT 可能优化掉锁sb.append("Hello");sb.append("World");
}

三、线程协作与通信

1. wait()notify()notifyAll() 的使用场景和注意事项?

  • 使用场景:
    wait()notify()notifyAll() 主要用于线程间的同步,适用于生产者-消费者模型或多个线程共享资源的情况。

    • wait(): 让当前线程进入等待状态,释放锁,让其他线程可以获取锁并执行。
    • notify(): 唤醒一个wait() 状态的线程,但不会立即释放锁,需等待当前线程执行完毕。
    • notifyAll(): 唤醒所有等待的线程,但只有一个线程能获取锁执行,其他线程仍需等待。
  • 注意事项:

    1. wait()notify()notifyAll() 必须在同步代码块或同步方法中使用,否则会抛 IllegalMonitorStateException
    2. 调用 wait() 方法后,线程会释放锁,进入等待队列,直到被 notify()notifyAll() 唤醒。
    3. notify() 只是通知一个等待线程,并不会立即释放锁,必须等待持有锁的线程执行完毕后才能释放。

2. sleep()wait() 的区别?

对比项sleep()wait()
作用让当前线程休眠一段时间让当前线程等待,直到被 notify() 唤醒
释放锁不释放锁释放锁
使用位置可以在任何地方使用必须在同步代码块同步方法中使用
恢复方式时间到后自动恢复运行需要 notify()notifyAll() 唤醒
抛出异常InterruptedExceptionInterruptedException

3. 如何实现线程间通信(如生产者-消费者模型)?

示例:使用 wait()notify() 实现生产者-消费者模式

class SharedResource {private int data;private boolean available = false;public synchronized void produce(int value) {while (available) {try {wait(); // 生产者等待} catch (InterruptedException e) {e.printStackTrace();}}data = value;available = true;System.out.println("生产者生产: " + value);notify(); // 唤醒消费者}public synchronized int consume() {while (!available) {try {wait(); // 消费者等待} catch (InterruptedException e) {e.printStackTrace();}}available = false;System.out.println("消费者消费: " + data);notify(); // 唤醒生产者return data;}
}

4. Condition 接口的作用及与 wait/notify 的区别?

  • 作用:

    • Condition 提供了比 wait()notify() 更强大的线程间通信机制。
    • 允许多个等待队列,可更精细地控制线程的唤醒。
  • 区别:
    | 对比项 | wait()/notify() | Condition |
    |------------|-----------------|------------|
    | 依赖的锁 | 必须配合 synchronized 关键字使用 | 需要 Lock 对象 |
    | 等待方法 | wait() | await() |
    | 通知方法 | notify() / notifyAll() | signal() / signalAll() |
    | 多个等待队列 | 只有一个等待队列,notify() 可能会误唤醒非目标线程 | 可以创建多个 Condition,更精准唤醒特定线程 |

  • 示例:使用 Condition 实现生产者-消费者模型

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class SharedResourceWithCondition {private int data;private boolean available = false;private final Lock lock = new ReentrantLock();private final Condition condition = lock.newCondition();public void produce(int value) {lock.lock();try {while (available) {condition.await(); // 生产者等待}data = value;available = true;System.out.println("生产者生产: " + value);condition.signal(); // 唤醒消费者} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public int consume() {lock.lock();try {while (!available) {condition.await(); // 消费者等待}available = false;System.out.println("消费者消费: " + data);condition.signal(); // 唤醒生产者return data;} catch (InterruptedException e) {e.printStackTrace();return -1;} finally {lock.unlock();}}
}

四、并发工具类(JUC)

1. CountDownLatchCyclicBarrierSemaphore 的使用场景和区别

1.1 CountDownLatch

  • 作用:用于 等待多个线程执行完毕后再继续计数器只能减不能加
  • 使用场景
    • 任务分解:主线程等待多个子线程完成后再继续。
    • 并发测试:多个线程同时执行某个任务,主线程等待所有线程完成。
    • 系统启动依赖:等待多个资源加载完毕后再启动服务。
import java.util.concurrent.CountDownLatch;public class CountDownLatchExample {public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(3);for (int i = 0; i < 3; i++) {new Thread(() -> {System.out.println(Thread.currentThread().getName() + " 完成任务");latch.countDown();}).start();}latch.await(); // 等待所有子线程执行完毕System.out.println("所有任务完成,继续执行主线程");}
}

1.2 CyclicBarrier

  • 作用:用于 让一组线程等待彼此到达某个同步点,并在到达后执行某个 回调任务
  • CountDownLatch 的区别
    • CountDownLatch 只能使用一次,CyclicBarrier重复使用
    • CyclicBarrier 允许在所有线程到达屏障时执行额外任务
  • 使用场景
    • 多人游戏:等待所有玩家加载完毕后开始游戏。
    • 并行计算:多线程分块计算,待所有线程计算完成后合并结果。
import java.util.concurrent.CyclicBarrier;public class CyclicBarrierExample {public static void main(String[] args) {CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("所有线程到达屏障点"));for (int i = 0; i < 3; i++) {new Thread(() -> {System.out.println(Thread.currentThread().getName() + " 到达屏障");try {barrier.await();} catch (Exception e) {e.printStackTrace();}}).start();}}
}

1.3 Semaphore

  • 作用限流,用于控制并发线程数,类似停车场的车位管理。
  • 使用场景
    • 限制数据库连接数
    • 控制接口并发访问,防止系统崩溃。
    • 线程池限流,限制任务提交速率。
import java.util.concurrent.Semaphore;public class SemaphoreExample {public static void main(String[] args) {Semaphore semaphore = new Semaphore(2);for (int i = 0; i < 5; i++) {new Thread(() -> {try {semaphore.acquire();System.out.println(Thread.currentThread().getName() + " 获取资源");Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + " 释放资源");semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}}).start();}}
}

2. Exchanger 的作用

  • 作用两个线程间的数据交换工具,用于在两个线程之间安全地交换对象

  • 特点

    • 线程 A 调用 exchange() 方法后会阻塞,直到线程 B 也调用 exchange() 交换数据。
    • 适用于 生产者-消费者模型,当生产者产生数据,消费者等待数据时,Exchanger 可用作数据缓冲区
  • 使用场景

    • 数据处理:一个线程生产数据,另一个线程处理数据。
    • 大数据计算:并行计算任务的阶段性交换。

3. Phaser 的使用场景

  • 作用可变并发任务同步工具,类似 CyclicBarrier,但支持 动态注册/注销线程

  • 区别

    • CyclicBarrier 需要指定固定数量的线程,Phaser 支持动态增减线程。
    • Phaser 适用于多阶段任务,而 CyclicBarrier 适用于单次屏障
  • 使用场景

    • 分阶段任务:如多轮竞赛,每轮线程数不同。
    • 递归任务:动态管理并发线程的执行。

4. FutureCompletableFuture 的区别

对比项FutureCompletableFuture
异步能力只能获取异步任务结果,不支持回调支持回调和流式操作,能更好地管理异步流程
阻塞获取结果需要 get() 方法,可能会阻塞线程支持 thenApply() 等非阻塞操作
支持组合任务不支持多个 Future 组合thenCombine() 支持多个任务组合
异常处理只能手动 try-catch 处理exceptionally() 处理异常更方便
取消任务cancel() 方法cancel() 也可用
  • CompletableFuture 适用于更复杂的异步任务链,能提高代码可读性和效率。

5. StampedLock 的优化点及使用场景

优化点

  1. 支持三种模式

    • 写锁 (writeLock()):独占锁,类似 ReentrantReadWriteLock 的写锁。
    • 读锁 (readLock()):共享锁,多个线程可同时读。
    • 乐观读 (tryOptimisticRead()):不阻塞写入,提高性能,适用于读多写少场景。
  2. 性能优势

    • 避免读锁竞争:乐观读锁不会阻塞写操作,适用于高读低写的情况。
    • 降低锁的升级开销:先尝试乐观读,再回退为悲观锁,减少锁冲突。

使用场景

  • 缓存系统:大多数操作是读取,写入较少的情况,避免 ReentrantReadWriteLock 造成的读锁开销。
  • 多线程计数器:多个线程同时读计数值,少数线程写入。
  • 金融交易系统:读取账户余额时使用乐观锁,只有在余额变更时才获取写锁。
import java.util.concurrent.locks.StampedLock;public class StampedLockExample {private int count = 0;private final StampedLock lock = new StampedLock();public int optimisticRead() {long stamp = lock.tryOptimisticRead();int current = count;if (!lock.validate(stamp)) {stamp = lock.readLock();try {current = count;} finally {lock.unlockRead(stamp);}}return current;}public void write(int value) {long stamp = lock.writeLock();try {count = value;} finally {lock.unlockWrite(stamp);}}
}

五、 线程池相关问题解析

1. 线程池的核心参数及作用

  • 核心线程数 (corePoolSize)
    指定了线程池中始终保持运行的最小线程数。当有新任务到达时,线程池会优先创建新线程直到达到核心线程数,之后任务才会进入队列等待执行。

  • 最大线程数 (maximumPoolSize)
    表示线程池允许创建的最大线程数。当队列已满且核心线程都在忙碌时,线程池会创建新线程直至达到最大线程数。

  • 任务队列 (workQueue)
    用于存放等待执行的任务。常用队列类型有有界队列(如 ArrayBlockingQueue)和无界队列(如 LinkedBlockingQueue)。队列的选择会影响任务调度的策略和系统资源使用。

  • 线程空闲时间 (keepAliveTime)
    当线程数超过核心线程数时,多余线程在空闲状态下等待新任务的最长时间,超时后这些线程会被终止,以节省资源。

  • 线程工厂 (threadFactory)
    用于创建新线程,可以通过自定义线程工厂设置线程名称、优先级、守护状态等,从而便于线程管理和调试。

  • 拒绝策略 (rejectedExecutionHandler)
    当线程池饱和(即线程数已达最大值且任务队列也满)时,对新提交任务的处理策略,如抛异常、调用者运行、丢弃任务或丢弃队列中最老任务等。

2. 线程池的工作流程(任务提交后的处理逻辑)

  1. 任务提交
    当调用 execute()submit() 方法提交任务时,线程池首先判断当前线程数是否小于核心线程数。

  2. 创建核心线程
    如果当前线程数小于核心线程数,则立即创建新线程执行任务,而不是将任务放入队列。

  3. 任务入队
    当核心线程数已满,新的任务将被放入任务队列中等待执行。

  4. 创建非核心线程
    如果任务队列已满,并且当前线程数小于最大线程数,则线程池会创建新线程来处理额外的任务。

  5. 拒绝策略触发
    当任务队列已满且线程数已经达到最大线程数时,线程池根据配置的拒绝策略处理新提交的任务。

  6. 线程复用与销毁
    完成任务后,线程不会立即销毁,而是进入等待状态以便复用。当线程超过核心线程数且长时间空闲,则会被回收。

3. 常见的线程池类型及其问题

  • FixedThreadPool

    • 特点:固定线程数,不会动态增减。
    • 问题:若任务执行缓慢或任务积压,可能导致队列中任务长时间等待,无法利用资源动态扩展。
  • CachedThreadPool

    • 特点:根据任务需要动态创建线程,线程空闲时会被回收,适用于任务执行时间较短的场景。
    • 问题:在任务激增时可能会创建大量线程,导致系统资源耗尽,甚至引发性能问题。
  • SingleThreadExecutor

    • 特点:单线程顺序执行所有任务,适用于需要保证任务顺序的场景。
    • 问题:单线程可能成为性能瓶颈,且任务过多时容易造成任务堆积。

4. 线程池的拒绝策略

  • AbortPolicy (默认策略)
    当任务无法执行时,直接抛出 RejectedExecutionException 异常,中断任务提交流程。

  • CallerRunsPolicy
    由任务提交者所在的线程执行该任务,从而降低新任务的提交速率,缓解线程池压力。

  • DiscardPolicy
    直接丢弃新提交的任务,不会抛出异常,但可能导致部分任务未被执行。

  • DiscardOldestPolicy
    丢弃任务队列中等待最久的任务,然后尝试提交当前任务,这样可以为新的任务腾出空间。

5. 如何合理配置线程池参数

  • 根据任务性质配置线程数

    • CPU密集型任务:通常将线程数设置为 CPU 核心数或略微超出。
    • IO密集型任务:线程数可以设置为 CPU 核心数的多倍,因为等待时间较长。
  • 任务队列的选择
    根据任务特性选择合适的队列类型和容量。无界队列虽能避免拒绝任务,但可能导致任务堆积;有界队列则能限制任务数量,但在高负载时容易触发拒绝策略。

  • 合理设置 keepAliveTime
    防止非核心线程在任务较少时占用系统资源,确保线程在空闲超时后被回收。

  • 拒绝策略的选取
    根据系统对任务丢失或延迟的容忍度选择合适的拒绝策略,保障系统稳定性。

  • 动态调整策略
    根据实际运行情况,监控任务执行情况和系统资源使用情况,动态调整线程池参数以达到最佳性能。

6. 线程池中线程复用(Thread Reuse)的原理

  • 预先创建与等待
    线程池在初始化或任务提交时创建一定数量的核心线程,这些线程在完成任务后不会被销毁,而是进入等待状态,以便立即处理后续任务。

  • 任务队列
    线程通过不断从任务队列中取任务来执行,实现线程的重复利用,避免频繁创建和销毁线程带来的开销。

  • KeepAlive 机制
    对于非核心线程,在任务执行完毕后,如果在指定的空闲时间内没有新任务到达,则会被回收。这样既能保证高负载时的扩展能力,也能在低负载时节省资源。

示例代码

下面是一个简单的 Java 示例,展示如何使用线程池提交任务。注意,Java 代码已做适当转义:

// 示例:使用线程池提交任务的简单代码
import java.util.concurrent.*;public class ThreadPoolExample {public static void main(String[] args) {// 创建一个固定大小的线程池,核心线程数和最大线程数都为4ExecutorService executor = Executors.newFixedThreadPool(4);for (int i = 0; i < 10; i++) {executor.submit(new Runnable() {@Overridepublic void run() {System.out.println("执行任务 - " + Thread.currentThread().getName());}});}// 关闭线程池,等待所有任务执行完毕后退出executor.shutdown();}
}

六、 高级并发特性解析

1. Java 内存模型(JMM)的核心概念

  • 主内存与工作内存
    每个线程都有自己的工作内存,用于保存共享变量的副本,而共享变量本身存储在主内存中。线程对变量的所有操作都必须先在工作内存中进行,再同步到主内存,从而保证了数据的一致性和线程间的可见性。

  • happens-before 原则
    规定了操作之间的先后关系,确保一个操作的结果在另一个操作执行前是可见的。典型规则包括:

    • 程序顺序规则:单线程中代码按照顺序执行。
    • 锁规则:解锁操作 happens-before 后续对同一锁的加锁操作。
    • volatile 规则:volatile 写操作 happens-before 随后的 volatile 读操作。
    • 线程启动规则:线程启动前的操作 happens-before 线程内的所有操作。

2. 原子性、可见性、有序性及其保证方法

  • 原子性
    一个操作是不可分割的,要么全部执行,要么全部不执行。
    保证方法

    • 使用 synchronized 关键字或显示锁(如 ReentrantLock)来保证复合操作的原子性。
    • 利用 Java 的原子类(如 AtomicInteger)实现无锁的原子操作。
  • 可见性
    确保当一个线程修改了共享变量,其他线程能够及时看到这个修改。
    保证方法

    • 使用 volatile 关键字,确保变量的修改立即刷新到主内存。
    • 使用锁(synchronizedLock)来保证操作的可见性。
  • 有序性
    保证程序中指令按照代码顺序执行(在单线程环境下成立)。
    保证方法

    • 使用 synchronizedvolatile 提供的内存屏障,防止指令重排序。
    • JMM 中的 happens-before 规则确保了操作的有序性。

3. 指令重排序及其避免方法

  • 指令重排序原理
    为了优化性能,编译器和处理器可能会重新排序指令。虽然在单线程环境中不会改变程序的正确性,但在多线程环境中可能会导致数据不一致的问题。

  • 避免方法

    • 内存屏障
      利用内存屏障指令保证某些操作在执行顺序上的严格顺序,从而防止重排序问题。
    • volatile 关键字
      使用 volatile 修饰的变量能确保写操作不会被重排序到读操作之后,并保证了可见性。

4. ThreadLocal 的原理、使用场景及内存泄漏问题

  • 原理
    每个线程内部都有一个 ThreadLocalMap,其中存储了 ThreadLocal 对象和对应的线程局部变量。这样,每个线程都可以独立访问和修改自己的变量副本,而不会影响其他线程。

  • 使用场景

    • 需要线程隔离的变量,如用户会话、数据库连接等。
    • 避免在多线程环境中共享数据而导致的同步问题。
  • 内存泄漏问题
    当线程使用完 ThreadLocal 后,如果没有调用 remove() 方法清理 ThreadLocalMap,可能会导致内存泄漏,特别是在使用线程池等长生命周期线程时。
    解决方法

    • 在任务执行结束后主动调用 ThreadLocal.remove() 清理数据。
    • 注意设计长生命周期线程中 ThreadLocal 的使用,避免不必要的数据驻留。

5. Fork/Join 框架与工作窃取机制

  • Fork/Join 框架
    是 Java 7 引入的一个并行计算框架,适用于将大任务拆分成多个小任务(Fork),并行执行后将结果合并(Join)。核心组件包括 ForkJoinPoolForkJoinTask 以及其子类 RecursiveTask(有返回值)和 RecursiveAction(无返回值)。

  • 工作窃取机制 (Work-Stealing)
    每个工作线程维护一个双端队列,当线程完成了自己队列中的任务时,会尝试从其他线程队列的尾部“窃取”任务以保持高效的资源利用和负载均衡。

示例代码

以下是一个使用 Fork/Join 框架计算数组求和的示例代码,注意 Java 代码中的特殊字符均已转义:

// 示例:使用 Fork/Join 框架计算数组求和
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;public class ForkJoinSum extends RecursiveTask<Long> {private static final int THRESHOLD = 1000;private long[] arr;private int start;private int end;public ForkJoinSum(long[] arr, int start, int end) {this.arr = arr;this.start = start;this.end = end;}@Overrideprotected Long compute() {if (end - start <= THRESHOLD) {long sum = 0;for (int i = start; i < end; i++) {sum += arr[i];}return sum;} else {int mid = (start + end) / 2;ForkJoinSum leftTask = new ForkJoinSum(arr, start, mid);ForkJoinSum rightTask = new ForkJoinSum(arr, mid, end);leftTask.fork(); // 异步执行左边任务long rightResult = rightTask.compute(); // 同步执行右边任务long leftResult = leftTask.join(); // 等待左边任务完成return leftResult + rightResult;}}public static void main(String[] args) {long[] array = new long[10000];// 初始化数组for (int i = 0; i < array.length; i++) {array[i] = i;}ForkJoinPool pool = new ForkJoinPool();ForkJoinSum task = new ForkJoinSum(array, 0, array.length);long result = pool.invoke(task);System.out.println("Sum: " + result);}
}

该示例展示了如何将一个大任务(数组求和)拆分成多个小任务,通过 Fork/Join 框架的工作窃取机制并行处理任务,并最终合并结果。

七、 并发容器解析

1. ConcurrentHashMap 的实现原理(JDK7 vs JDK8)

  • JDK7 实现原理

    • 分段锁(Segment)机制:将整个 Map 划分为多个 Segment,每个 Segment 内部维护一部分哈希桶。
    • 锁定粒度较小:操作某个键值对时,仅锁定所在的 Segment,读操作大多无锁,从而提升并发度。
    • 局限性:Segment 数量在创建时固定,可能存在热点 Segment 导致争用。
  • JDK8 实现原理

    • 取消分段锁:直接使用一个 Node 数组作为底层结构,通过 CAS 操作实现无锁更新。
    • 细粒度锁:在发生冲突时,仅对链表或红黑树的某个桶加锁,而非整个 Segment。
    • 链表转红黑树:当单个桶中链表长度超过阈值时,转换为红黑树以降低查找时间。
    • 优势:读操作完全无锁,写操作在冲突较少时可通过 CAS 完成,提高了整体性能和扩展性。

2. CopyOnWriteArrayList 的适用场景及优缺点

  • 适用场景

    • 读操作远远多于写操作的场景,如缓存、事件监听器列表等。
    • 需要保证在迭代时数据不被修改,避免 ConcurrentModificationException 的情况。
  • 优点

    • 无锁读操作:迭代时不需要加锁,性能高且线程安全。
    • 简单安全:每次写操作都会复制整个底层数组,保证了数据的一致性。
  • 缺点

    • 写操作开销大:每次更新都会复制整个数组,对于写密集型应用效率低下。
    • 内存占用增加:复制操作会导致额外的内存消耗,适用于数据量较小且变更较少的场景。

3. BlockingQueue 的实现类及使用场景

  • ArrayBlockingQueue

    • 实现原理:基于数组实现的有界阻塞队列,内部通过 ReentrantLock 来保证线程安全。
    • 使用场景:适用于固定容量的缓冲区,如生产者消费者模式中限制任务数量,防止过载。
  • LinkedBlockingQueue

    • 实现原理:基于链表实现,可设置有界或无界队列,采用分离的 putLocktakeLock 提高并发性能。
    • 使用场景:适用于需要较大容量缓冲,或不希望受到固定数组大小限制的场景。
  • 其他实现类

    • PriorityBlockingQueue:基于优先级的无界阻塞队列,适用于需要按照优先级处理任务的场景。
    • DelayQueue:基于延时策略的队列,用于任务调度。
    • SynchronousQueue:每个插入操作必须等待对应的移除操作,适合于任务直接交给消费者处理的场景。

4. ConcurrentLinkedQueue 的无锁实现原理

  • 基于链表的无锁队列:采用链表结构作为底层数据结构。
  • CAS 操作:利用 CAS(Compare-And-Swap)原子操作来保证在高并发环境下的线程安全,无需显式锁。
  • 头尾指针维护:通过原子变量维护头结点和尾结点,入队和出队操作通过 CAS 更新指针,保证数据一致性。
  • 优点:高并发情况下性能优越,避免了锁竞争;
  • 缺点:在极端情况下可能存在短暂的不一致状态,但总体上能够保证最终一致性。

八、 并发设计模式与实战

1. 生产者-消费者模式的实现方式

  • 阻塞队列实现
    利用阻塞队列(如 ArrayBlockingQueueLinkedBlockingQueue)实现任务缓冲区,生产者调用 put() 将任务放入队列,消费者调用 take() 从队列中获取任务。
  • 等待/通知机制
    在共享缓冲区上使用 wait()notify()/notifyAll() 实现线程间通信,但需要小心处理虚假唤醒和同步问题。
  • 信号量机制
    通过 Semaphore 控制缓冲区中可用资源数量,协调生产者和消费者的访问。

2. 如何实现线程安全的单例模式

  • 双重检查锁定(DCL)
    在实例为空时进入同步代码块,再次检查后创建实例。需要将实例声明为 volatile,防止指令重排序导致问题。
  • 静态内部类
    利用 JVM 类加载机制,在第一次使用时创建单例,既保证线程安全,又实现延迟加载。
  • 枚举实现
    使用枚举类型,JVM 会保证枚举实例的唯一性和线程安全。

示例:使用双重检查锁定实现线程安全的单例模式
(注意:Java代码中的特殊字符已转义)

public class Singleton {private static volatile Singleton instance;private Singleton() {// 防止反射调用if (instance != null) {throw new IllegalStateException("Already initialized");}}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

3. 如何排查和解决死锁问题

排查方法

  • 使用线程转储(Thread Dump)工具分析线程的锁持有情况;
  • 利用 JDK 内置的监控工具(如 VisualVM、JConsole、ThreadMXBean)检测死锁;
  • 检查代码中锁的获取顺序是否一致,是否存在资源循环等待。

解决措施

  • 统一锁顺序:确保所有线程按照相同顺序获取多个锁;
  • 使用超时锁:采用 tryLock() 设置超时,避免长时间等待;
  • 细化锁粒度:减少单个锁保护的资源范围,或采用无锁算法与乐观锁。

4. 如何避免竞态条件(Race Condition)

加锁同步

  • 使用 synchronizedReentrantLock 等互斥机制,确保同一时间只有一个线程访问共享数据。

原子操作

  • 利用原子类(如 AtomicIntegerAtomicLong)保证对单个变量的原子更新。

不可变对象设计

  • 使用不可变对象,避免在并发场景下对对象状态的修改。

线程安全的集合

  • 采用并发容器(如 ConcurrentHashMapCopyOnWriteArrayList)管理共享数据,避免并发修改问题。

5. 如何设计高并发场景下的计数器

原子变量

  • 使用 AtomicIntegerAtomicLong 提供无锁计数操作,适用于简单的计数场景。

LongAdder/LongAccumulator

  • 在高并发场景下,使用 LongAdder(JDK8及以上)分散热点,多个计数单元并行累加,最后合并结果。

分段计数器

  • 将计数器分为多个独立部分,每个线程或线程组维护各自的局部计数,定期合并成全局计数,降低锁竞争。

九、底层原理与扩展

1. Java 线程与操作系统线程的关系

  • 映射关系:Java 线程是由 JVM 映射到底层操作系统线程,通常采用 1:1 模型,即每个 Java 线程对应一个操作系统线程。
  • 操作系统调度:操作系统负责线程的调度、上下文切换等底层管理,JVM 则在此基础上提供高级的线程管理功能,如线程池和并发工具。
  • 资源管理:底层线程由操作系统管理资源,Java 线程的创建、销毁和调度都依赖于操作系统的支持。

2. 什么是协程(Coroutine)?Java 中的虚拟线程(Loom 项目)

  • 协程(Coroutine)

    • 协程是一种轻量级的线程实现,可以在单个线程中实现多个任务之间的切换。
    • 它通过保存和恢复执行上下文来实现并发执行,切换开销远低于操作系统线程的上下文切换。
    • 协程通常由用户级库调度,不依赖于操作系统的线程管理。
  • Java 中的虚拟线程(Loom 项目)

    • Project Loom 是 Java 的一个实验性项目,旨在引入虚拟线程这一概念,使得创建成千上万的线程成为可能。
    • 虚拟线程基于协程思想,能够在用户空间内高效调度,极大降低线程切换和资源消耗。
    • 该项目有望简化并发编程模型,使得编写高并发应用程序更加直观和高效。

3. 如何通过 jstack 分析线程状态

  • 生成线程堆栈信息:使用命令 jstack <pid>(其中 <pid> 是 Java 进程的标识)获取当前线程的状态及堆栈信息。
  • 分析线程状态
    • 查看各线程的状态,如 RUNNABLEWAITINGBLOCKED 等。
    • 检查线程等待锁和持有锁的信息,确定是否存在死锁或锁竞争问题。
    • 根据堆栈信息定位长时间阻塞或运行缓慢的线程,帮助分析性能瓶颈或故障原因。

4. 什么是伪共享(False Sharing)?如何避免?

  • 伪共享定义

    • 当多个线程在不同的变量上操作时,如果这些变量恰好位于同一个 CPU 缓存行中,会引起频繁的缓存行失效和重载,进而影响性能。
  • 避免方法

    • 内存填充(Padding):通过在变量之间添加无用的数据字段(例如额外的 long 型字段)来确保它们不在同一缓存行上。
    • 使用 @Contended 注解:在 Java 8 及以上版本中,可以使用 @Contended 注解标记容易发生伪共享的变量(需在 JVM 启动参数中启用 -XX:-RestrictContended)。
    • 合理的数据结构设计:在设计并发数据结构时,考虑将易冲突的变量分散到不同的缓存行中,减少互相影响。

十、其他扩展问题

1. 如何实现异步编程(如 CompletableFuture、Reactive Streams)?

CompletableFuture

  • CompletableFuture 是 Java 8 引入的异步编程工具,它支持链式操作,可以避免传统回调地狱(Callback Hell)。
  • 主要方法包括 supplyAsync()thenApply()thenAccept()thenCompose()handle() 等。
import java.util.concurrent.CompletableFuture;public class AsyncExample {public static void main(String[] args) {CompletableFuture.supplyAsync(() -> {return "Hello, World!";}).thenApply(result -> {return result + " - Processed";}).thenAccept(System.out::println);}
}

Reactive Streams

  • Reactive Streams 是一种基于发布-订阅模式的异步处理标准,支持 非阻塞背压(Back Pressure) 机制。
  • 常见实现有 Project Reactor(Spring WebFlux)和 RxJava
  • 核心组件:
    • Publisher(发布者):生产数据流。
    • Subscriber(订阅者):消费数据流。
    • Subscription(订阅):连接 PublisherSubscriber,管理数据传输速率。
    • Processor(处理器):既是 Publisher 也是 Subscriber,可对流数据进行处理。

2. 分布式锁与单机锁的区别

单机锁

  • 仅用于单个 JVM 内部,保证线程间同步
  • 实现方式
    • synchronized 关键字
    • ReentrantLock 可重入锁
  • 优点:实现简单、开销低。
  • 缺点:无法跨进程或跨服务器同步。

分布式锁

  • 用于多个进程或服务器之间的同步控制。
  • 常见实现
    • 基于 Redis:如 SET NX EX 方式。
    • 基于 ZooKeeper:利用 EPHEMERAL(临时节点)实现锁。
    • 基于数据库:使用数据库表中的唯一字段实现锁(效率较低)。
  • 优点:支持跨服务同步,防止分布式环境下的数据竞争。
  • 缺点:实现复杂,需要考虑网络延迟、锁超时、锁丢失等问题。
import redis.clients.jedis.Jedis;public class RedisLock {private Jedis jedis;private String lockKey = "distributed_lock";public RedisLock(Jedis jedis) {this.jedis = jedis;}public boolean lock() {return "OK".equals(jedis.set(lockKey, "locked", "NX", "EX", 10));}public void unlock() {jedis.del(lockKey);}
}

3. 无锁编程(Lock-Free)的实现思路

CAS(Compare-And-Swap)

  • CAS 操作:比较并交换,保证多个线程同时更新时的原子性。
  • Java 中的无锁实现
    • AtomicInteger
    • AtomicLong
    • AtomicReference
  • 缺点
    • 可能出现 ABA 问题(即值改变了但看起来未变)。
    • 高并发下可能导致 自旋过多,消耗 CPU
import java.util.concurrent.atomic.AtomicInteger;public class CASExample {private static AtomicInteger count = new AtomicInteger(0);public static void main(String[] args) {count.incrementAndGet(); // 使用 CAS 增加计数System.out.println("Counter: " + count.get());}
}

乐观锁

  • 原理:不主动加锁,而是在更新前检查数据版本,若检测到冲突则重试。
  • 实现方式
    • CAS(Compare-And-Swap) 操作
    • 版本号机制(如数据库的 version 字段)
  • 适用于:高并发、冲突较少的场景。

无锁数据结构

  • 设计时避免使用锁,如 ConcurrentLinkedQueue 采用 CAS + 链表的方式更新数据。
  • 示例
    • ConcurrentLinkedQueue
    • LongAdder(分段计数,减少锁竞争)
    • CopyOnWriteArrayList(写时复制)

4. 高并发场景下常见的性能优化手段

1. 减少锁竞争

  • 使用 细粒度锁分段锁 降低锁竞争。
  • 无锁数据结构 代替同步容器(如 ConcurrentHashMap)。
  • 读写锁 代替互斥锁(ReentrantReadWriteLock)。

2. 线程池优化

  • 合理配置线程池
    • CPU 密集型任务:线程数 = CPU 核心数 + 1
    • IO 密集型任务:线程数 = CPU 核心数 * 2
  • 避免频繁创建/销毁线程,实现线程复用

3. 缓存机制

  • 本地缓存(如 CaffeineGuava Cache)。
  • 分布式缓存(如 RedisMemcached)。
  • 读写分离数据预热,减少数据库压力。

4. 异步 & 批量处理

  • 采用消息队列(MQ),如 KafkaRabbitMQ 进行异步处理。
  • 批量操作(如数据库批量插入 batch insert)。

5. 数据结构优化

  • 选择合适的数据结构,避免不必要的锁竞争:
    • ConcurrentHashMap 代替 Hashtable
    • CopyOnWriteArrayList 代替 ArrayList(适用于读多写少的场景)
  • 避免伪共享(False Sharing),通过 缓存行填充 提高 CPU 缓存命中率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/39342.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue中keep-alive组件的使用

keep-alive是vue的内置组件&#xff0c;它的主要作用是对组件进行缓存&#xff0c;避免组件在切换时被重复创建和销毁&#xff0c;从而提高应用的性能和用户体验。它自身不会渲染一个 DOM 元素&#xff0c;也不会出现在父组件链中。使用时&#xff0c;只需要将需要缓存的组件包…

Python Excel表格数据对比工具

【Excel对比工具】提升工作效率的神奇助手&#xff1a;基于PyQt5和Pandas的文件数据对比应用 相关资源文件已经打包成EXE文件&#xff0c;可双击直接运行程序&#xff0c;且文章末尾已附上相关源码&#xff0c;以供大家学习交流&#xff0c;博主主页还有更多Python相关程序案例…

注册登录表单

html登录页面&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>创建一个登录页面</t…

JAVA:Spring Boot @Conditional 注解详解及实践

1、简述 在 Spring Boot 中&#xff0c;Conditional 注解用于实现 条件化 Bean 装配&#xff0c;即根据特定的条件来决定是否加载某个 Bean。它是 Spring 框架中的一个扩展机制&#xff0c;常用于实现模块化、可配置的组件加载。 本文将详细介绍 Conditional 相关的注解&…

Java高频面试之集合-17

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天来报道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面试官&#xff1a;JDK 8 对 HashMap 主要做了哪些优化呢&#xff1f;为什么要这么做&#xff1f; JDK 8 对 HashMap 的主要优化及原因 JDK…

力扣DAY24 | 热100 | 回文链表

前言 简单 √ 是反转链表的衍生题&#xff0c;很快写完了。不过没考虑到恢复链表结构的问题。 题目 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输…

Unity跨平台构建快速回顾

知识点来源&#xff1a;人间自有韬哥在&#xff0c;豆包 目录 一、发布应用程序1. 修改发布必备设置1.1 打开设置面板1.2 修改公司名、游戏项目名、版本号和默认图标1.3 修改 Package Name 和 Minimum API Level 2. 发布应用程序2.1 配置 Build Settings2.2 选择发布选项2.3 构…

手敲NLP相关神经网络,熟悉神经网络的结构与实现!

一、NNLM 二、word2vec 三、TextCNN 四、TextRNN 五、TextLSTM 六、Bi-LSTM 七、seq2seq 八、seq2seq&#xff08;attention&#xff09;

Spring MVC 拦截器使用

javaweb过滤器和springmvc拦截器&#xff1a; 拦截器的概念 拦截器使用 1/创建拦截器类&#xff0c;类中实现 handler执行前&#xff0c;执行后与渲染视图后的具体实现方法 public class GlobalExceptionHandler implements HandlerInterceptor {// if( ! preHandler()){re…

数据库分类、存储引擎、介绍、Mysql、SQL分类

DAY17.1 Java核心基础 数据库 关系型数据库&#xff08;传统数据库&#xff0c;安全可靠&#xff0c;数据量大&#xff09;&#xff1a;Mysql、Oracle、SQLServer 非关系型数据库nosql&#xff08;缓存数据库&#xff0c;高并发项目中&#xff0c;存储热点数据&#xff0c;短信…

Extend module 01:Keyboard

目录 一、Keyboard &#xff08;1&#xff09;资源介绍 &#x1f505;原理图 &#x1f505;扫描原理 &#xff08;2&#xff09;STM32CubeMX 软件配置 &#xff08;3&#xff09;代码编写 &#xff08;4&#xff09;实验现象 二、Keyboard接口函数封装 三、踩坑日记 &a…

【机器人】复现 GrainGrasp 精细指导的灵巧手抓取

GrainGrasp为每个手指提供细粒度的接触指导&#xff0c;为灵巧手生成精细的抓取策略。 通过单独调整每个手指的接触来实现更稳定的抓取&#xff0c;从而提供了更接近人类能力的抓取指导。 论文地址&#xff1a;GrainGrasp: Dexterous Grasp Generation with Fine-grained Con…

解锁 AWX+Ansible 自动化运维新体验:快速部署实战

Ansible 和 AWX 是自动化运维领域的强大工具组合。Ansible 是一个简单高效的 IT 自动化工具&#xff0c;而 AWX 则是 Ansible 的开源 Web 管理平台&#xff0c;提供图形化界面来管理 Ansible 任务。本指南将带你一步步在 Ubuntu 22.04 上安装 Ansible 和 AWX&#xff0c;使用 M…

Vulhub-jangow-01-1.0.1通关攻略

第0步&#xff1a; 打开靶机&#xff0c;按下shift&#xff0c;出现下图界面 在此页面按下e键&#xff0c;进入如下界面&#xff0c; 将ro 替换为 rw signie init/bin/bash 替换完毕后&#xff0c;按下Ctrl键X键&#xff0c;进入如下页面 ip a查看网卡信息 编辑配置文件网卡信…

默克生命科学 | ProClin™安全、高效防腐剂

ProClin™防腐剂是水溶性抗菌剂&#xff0c;是体外诊断(IVD)行业中最有效的抗菌剂之一&#xff0c;广泛应用于行业领先的诊断生产商的1,000多种FAD注册IVD试剂盒。低工作浓度下&#xff0c;ProClin™产品有效快速地抑制广谱微生物&#xff0c;有助于延长IVD试剂的保质期。 有别…

const应用

最近学校的花开了&#xff0c;选了一张三号楼窗前的白玉兰&#xff0c;(#^.^#) 1.修饰普通变量 当 const 用于修饰普通变量时&#xff0c;该变量的值在初始化之后就不能再改变。 #include <stdio.h>int main() {const int num 10;// num 20; // 错误&#xff0c;不…

FastStoneCapture下载安装教程(附安装包)专业截图工具

文章目录 前言FastStoneCapture下载FastStoneCapture安装步骤FastStoneCapture使用步骤 前言 在日常工作与学习里&#xff0c;高效截图工具至关重要。本教程将为你呈现FastStoneCapture下载安装教程&#xff0c;助你轻松拥有。 FastStoneCapture下载 FastStone Capture 是一款…

3. 轴指令(omron 机器自动化控制器)——>MC_ResetFollowingError

机器自动化控制器——第三章 轴指令 13 MC_ResetFollowingError变量▶输入变量▶输出变量▶输入输出变量 功能说明▶指令详情▶时序图▶重启动运动指令▶多重启运动指令▶异常 MC_ResetFollowingError 对指令当前位置和反馈当前位置的偏差进行复位。 指令名称FB/FUN图形表现S…

【HTML5游戏开发教程】零基础入门合成大西瓜游戏实战 | JS物理引擎+Canvas动画+完整源码详解

《从咖啡杯到财务自由&#xff1a;一个程序员的合成之旅——当代码遇上物理引擎的匠心之作》 &#x1f31f; 这是小游戏开发系列的第四篇送福利文章&#xff0c;感谢一路以来支持和关注这个项目的每一位朋友&#xff01; &#x1f4a1; 文章力求严谨&#xff0c;但难免有疏漏之…

举例说明自然语言处理(NLP)技术

当我们使用智能助手或社交媒体平台时&#xff0c;就会接触到自然语言处理&#xff08;NLP&#xff09;技术的应用。以下是一些常见的例子&#xff1a; 语音识别&#xff1a;当我们与智能助手如Siri、Alexa或Google Assistant交互时&#xff0c;我们说出语音命令&#xff0c;系统…