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

每日学习Java之一万个为什么(JUC)

文章目录

  • Git复习
  • synchronized
    • 介绍
      • 基本概念
      • 特点
    • 使用模板
      • 1. 同步方法
        • 格式
        • 特点
      • 2. 同步代码块
        • 格式
        • 特点
    • 常见面试题
      • 1. `synchronized`的实现原理?
      • 2. `synchronized`与`ReentrantLock`的区别?
      • 3. `synchronized`的缺点?
      • 4. 死锁的四个必要条件?
      • 5. 如何避免死锁?
      • 6. `synchronized`是重量级锁吗?
      • 7. `synchronized`修饰静态方法时锁对象是什么?
  • ReentrantLock
    • 介绍
      • 基本概念
      • 实现原理
      • 特点
    • 使用模板
      • 1. 基础用法(非公平锁)
      • 2. 公平锁
      • 3. 尝试获取锁(非阻塞)
      • 4. 带超时的尝试获取锁
    • 常用方法
    • 常见面试题
      • 1. `ReentrantLock`与`synchronized`的区别?
      • 2. `ReentrantLock`的可重入性如何实现?
      • 3. `ReentrantLock`的公平锁与非公平锁有何区别?
      • 4. 如何避免`ReentrantLock`的锁泄漏?
      • 5. `Condition`的作用是什么?如何使用?
      • 6. `ReentrantLock`的锁降级是否可行?
      • 7. `ReentrantLock`的`tryLock()`与`synchronized`的区别?
    • 总结
  • Condition实现指定唤醒线程 以及代替wait/notify在Synchronized中的地位
    • Condition介绍
    • 使用模板
    • demo:指定唤醒线程(ABC轮流使用资源类)
    • demo:代替wait/notify在Synchronized中的地位(包括synchronized使用)
  • Condition
    • 常用方法
      • 核心方法
      • 其他方法
    • 常见面试题
      • 1. **Condition 的作用是什么?与 `wait/notify` 有何区别?**
      • 2. **为什么 `await()` 必须在持有锁时调用?**
      • 3. **`signal()` 和 `signalAll()` 的区别?**
      • 4. **`Condition` 如何实现精准唤醒?**
      • 5. **`Condition` 的实现原理?**
      • 6. **使用 `Condition` 时的注意事项?**
      • 7. **`Condition` 的典型应用场景?**
      • 8. **`Condition` 与 `synchronized` 的 `wait/notify` 的兼容性?**
      • 9. **`await()` 可能抛出的异常?**
      • 10. **如何实现带超时的条件等待?**
    • 总结
  • JUC 容器类 CopyOnWriteXXX / ConcurrentHashMap
    • 常见面试题
    • **1. JUC中常见的线程安全容器有哪些?**
    • **2. ConcurrentHashMap与Hashtable的区别?**
    • **3. 为什么JDK1.8后ConcurrentHashMap舍弃分段锁?**
    • **4. CopyOnWriteArrayList的适用场景?**
    • **5. BlockingQueue的实现类有哪些?分别适用于什么场景?**
    • **6. 为什么JUC的ConcurrentHashMap要用synchronized而不是ReentrantLock?**
    • **7. 如何确保一个集合不能被修改?**
    • **8. 什么是fail-fast和fail-safe?JUC容器如何实现?**
    • **9. 说一下ConcurrentHashMap的实现原理(JDK1.8)?**
    • **10. BlockingQueue的take()和poll()有什么区别?**
    • **11. 如何选择线程安全的集合类?**
    • **12. 为什么Vector是遗留类?如何替代?**
    • **总结**
  • JUC 辅助类 CountDownLatch / CyclicBarrier / Semophore
    • CountDownLatchDemo(倒计时)
    • CyclicBarrierDemo(收集器)
    • SemaphoreDemo (信号量)可以代替锁
  • **CountDownLatch**
    • **常用方法**
    • **使用场景**
    • **常见面试题**
  • **CyclicBarrier**
    • **常用方法**
    • **使用场景**
    • **常见面试题**
  • **Semaphore**
    • **常用方法**
    • **使用场景**
    • **常见面试题**
    • **总结**

Git复习

在这里插入图片描述

以下是补充后的笔记内容,结构清晰,涵盖核心知识点和常见面试题:


synchronized

介绍

基本概念

  • 作用:Java中的synchronized关键字用于实现线程同步,确保同一时间只有一个线程访问被其修饰的代码块或方法,避免线程安全问题(如竞态条件、脏读等)。
  • 实现机制:基于Java内存模型(JMM)的监视器锁(Monitor),通过对象头中的Mark Word记录锁状态,支持偏向锁、轻量级锁、重量级锁的升级机制。
  • 适用场景:简单同步需求,代码简洁,但性能可能受限于锁升级后的重量级锁。

特点

  • 原子性:保证代码块或方法的原子操作。
  • 可见性:线程间共享变量的修改对其他线程可见。
  • 阻塞式同步:线程获取锁失败时会阻塞等待。

使用模板

1. 同步方法

格式
// 实例方法(锁对象为this)
public synchronized void method() { ... }// 静态方法(锁对象为类的Class对象)
public static synchronized void staticMethod() { ... }
特点
  • 锁对象
    • 实例方法:当前对象实例(this)。
    • 静态方法:类的Class对象(ClassName.class)。

2. 同步代码块

格式
// 对象锁(锁对象为obj)
synchronized(obj) {// 同步代码块
}// 示例:使用this作为锁
synchronized(this) { ... }// 示例:使用类锁(静态方法等效)
synchronized(ClassName.class) { ... }
特点
  • 灵活性:可自定义锁对象,避免全类或全实例的锁竞争。
  • 推荐实践:锁对象应为私有且不可变,避免被外部误用。

常见面试题

1. synchronized的实现原理?

  • Monitor对象:JVM通过对象头中的Mark Word记录锁状态。
  • 锁状态升级
    1. 偏向锁:默认为线程分配偏向锁,减少同步开销(JDK6引入)。
    2. 轻量级锁:通过CAS尝试获取锁,失败则升级为重量级锁。
    3. 重量级锁:阻塞线程,依赖操作系统互斥量(性能开销大)。

2. synchronizedReentrantLock的区别?

特性synchronizedReentrantLock
中断性不支持支持(tryLock()可中断)
超时不支持支持(tryLock(long time)
公平性非公平可配置公平或非公平
功能简单易用更灵活(如条件变量Condition
性能重量级锁时性能较差通常更高(但需合理使用)

3. synchronized的缺点?

  • 不可中断:线程获取锁时无法中断。
  • 死锁风险:若多个线程互相等待锁,可能引发死锁。
  • 性能问题:重量级锁可能导致线程阻塞开销大。
  • 粒度控制不足:同步方法无法灵活控制锁范围。

4. 死锁的四个必要条件?

  1. 互斥:资源不可共享,同一时间只能被一个线程占用。
  2. 请求与保持:线程持有已分配资源,仍请求新资源。
  3. 不剥夺:资源无法被强制剥夺,只能由持有者释放。
  4. 循环等待:存在线程循环等待链(如A等待B的资源,B等待A的资源)。

5. 如何避免死锁?

  • 按顺序获取锁:所有线程按固定顺序申请锁。
  • 超时机制:使用tryLock()并设置超时时间。
  • 尽早释放锁:减少锁持有时间,避免在锁内执行耗时操作。

6. synchronized是重量级锁吗?

  • 早期JVM:是,依赖操作系统互斥量,开销大。
  • JDK6+:引入锁升级机制,优先使用偏向锁和轻量级锁,性能显著提升。

7. synchronized修饰静态方法时锁对象是什么?

  • 锁对象为类的Class对象(即ClassName.class)。

以下是补充后的 ReentrantLock 笔记,结构清晰且覆盖核心知识点和常见面试题:


ReentrantLock

介绍

基本概念

  • 作用:Java中用于实现线程同步的显式锁,需手动获取和释放,属于 java.util.concurrent.locks 包。
  • 可重入性:支持同一线程多次获取同一锁(递归锁),避免死锁。
  • 公平性:可选择公平模式(线程按排队顺序获取锁)或非公平模式(允许插队,性能更高)。

实现原理

  • 底层机制:基于**AQS(AbstractQueuedSynchronizer)**实现,通过CAS操作竞争锁。
  • 锁状态:通过内部计数器记录锁的持有次数,线程释放锁时需计数归零。

特点

  • 灵活性:支持尝试获取锁(tryLock())、超时获取锁、条件变量(Condition)等高级功能。
  • 需手动管理:必须显式调用 lock()unlock(),否则可能导致死锁或资源泄漏。

使用模板

1. 基础用法(非公平锁)

ReentrantLock lock = new ReentrantLock();// 获取锁
lock.lock();
try {// 同步代码块
} finally {lock.unlock(); // 必须确保释放锁
}

2. 公平锁

ReentrantLock fairLock = new ReentrantLock(true); // true表示公平模式

3. 尝试获取锁(非阻塞)

// 尝试立即获取锁,失败返回false
if (lock.tryLock()) {try {// 同步代码块} finally {lock.unlock();}
}

4. 带超时的尝试获取锁

// 尝试在1秒内获取锁,超时返回false
if (lock.tryLock(1, TimeUnit.SECONDS)) {try {// 同步代码块} finally {lock.unlock();}
}

常用方法

方法说明
lock()阻塞等待获取锁,直到成功或被中断。
unlock()释放锁,需确保在finally块中调用。
tryLock()非阻塞尝试获取锁,成功返回true,失败返回false。
tryLock(long time)在指定时间内尝试获取锁,超时返回false。
isHeldByCurrentThread()判断当前线程是否持有该锁。
getHoldCount()返回当前线程持有的锁次数(可重入计数)。
newCondition()创建与该锁绑定的Condition对象,用于线程间协作。

常见面试题

1. ReentrantLocksynchronized的区别?

特性ReentrantLocksynchronized
中断性支持(lockInterruptibly()不支持
超时支持(tryLock()不支持
公平性可配置(公平/非公平)默认非公平
功能支持Condition,更灵活内置锁,无需手动释放
性能通常更高(无锁升级机制)JDK6+优化后性能接近
语法显式调用lock()/unlock()隐式管理

2. ReentrantLock的可重入性如何实现?

  • 通过内部计数器记录锁的持有次数:
    • 线程首次获取锁时计数器设为1。
    • 再次获取锁时计数器递增。
    • 每次unlock()计数器递减,归零时释放锁。

3. ReentrantLock的公平锁与非公平锁有何区别?

  • 公平锁:线程按排队顺序获取锁,避免“饥饿”但性能稍差。
  • 非公平锁:允许新线程插队尝试获取锁(CAS竞争),性能更高但可能引发饥饿。

4. 如何避免ReentrantLock的锁泄漏?

  • 必须确保unlock()在finally块中调用,即使发生异常也要释放锁:
    lock.lock();
    try {// 业务逻辑
    } finally {lock.unlock();
    }
    

5. Condition的作用是什么?如何使用?

  • 作用:提供线程等待/唤醒机制,替代synchronizedwait()/notify()
  • 使用示例
    Condition condition = lock.newCondition();// 等待条件
    condition.await();// 唤醒一个等待线程
    condition.signal();// 唤醒所有等待线程
    condition.signalAll();
    

6. ReentrantLock的锁降级是否可行?

  • 不可直接实现:需通过二级锁(如ReentrantReadWriteLock)实现读写锁的降级。

7. ReentrantLocktryLock()synchronized的区别?

  • tryLock()是非阻塞的,失败直接返回false,而synchronized会阻塞等待。
  • tryLock()需配合unlock()手动释放锁。

总结

  • 适用场景:需要高级功能(如超时、条件变量)或对性能要求高时。
  • 注意事项:必须确保锁的释放,避免泄漏;合理选择公平性模式。
  • 替代方案synchronized适合简单场景,ReentrantLock适合复杂需求。

Condition实现指定唤醒线程 以及代替wait/notify在Synchronized中的地位

Condition介绍

使用模板

demo:指定唤醒线程(ABC轮流使用资源类)

package com.qxy.practice.concurrent;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** @author : 戚欣扬* @Description : //      v4   对Condtion的灵活运用,指定唤醒线程   A 5->B 10->C 15 loop 10*/
public class ThreadOrder {private int flag  = 1;private Lock lock = new ReentrantLock();private Condition key1 = lock.newCondition();private Condition key2 = lock.newCondition();private Condition key3 = lock.newCondition();/*** print 1-5*/public void print5(){lock.lock();try {while(flag!=1){key1.await();}for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+":"+(i+1));}flag = 2;key2.signal();} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}}/*** print 1-10*/public void print10(){lock.lock();try {while(flag!=2){key1.await();}for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+":"+(i+1));}flag = 3;key2.signal();} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}}/*** print 1-15*/public void print15(){lock.lock();try {while(flag!=3){key1.await();}for (int i = 0; i < 15; i++) {System.out.println(Thread.currentThread().getName()+":"+(i+1));}flag = 1;key2.signal();} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}}/*** main*/public static void main(String[] args) {ThreadOrder threadOrder = new ThreadOrder();for (int i = 0; i < 10; i++) {new Thread(()->{threadOrder.print5();},"A").start();new Thread(()->{threadOrder.print10();},"B").start();new Thread(()->{threadOrder.print15();},"C").start();}}}

demo:代替wait/notify在Synchronized中的地位(包括synchronized使用)

package com.qxy.practice.concurrent;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** @author : 戚欣扬* @code :*/
public class ThreadTransmit {private int num = 0 ;private Lock lock = new ReentrantLock();Condition condition1 = lock.newCondition();Condition condition2 = lock.newCondition();Condition condition3 = lock.newCondition();
//    V1 使用公平锁 / 睡眠实现 1+ 1-
/*    private Lock lock = new ReentrantLock();public void increase(){lock.lock();try{num++;System.out.println(Thread.currentThread().getName()+"num ="+num);}finally {lock.unlock();}}public void decrease(){lock.lock();try{num--;System.out.println(Thread.currentThread().getName()+"num ="+num);}finally {lock.unlock();}}public static void main(String[] args) throws InterruptedException {ThreadTransmit threadTransmit = new ThreadTransmit();for (int i = 0; i < 10; i++) {new Thread(()->{threadTransmit.increase();},"A").start();new Thread(()->{threadTransmit.decrease();},"B").start();}}*/
//      V2 使用synchronized+wait+notify 实现 虚假唤醒问题 死锁问题
/*        public synchronized void increase(){//判断while(num!=0){try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}//执行System.out.println(Thread.currentThread().getName()+":num = " + (++num));//通知this.notifyAll();}public synchronized void decrease(){//判断while(num!=1){try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}//执行System.out.println(Thread.currentThread().getName()+":num = " + (--num));//通知this.notifyAll();}public static void main(String[] args) {ThreadTransmit threadTransmit = new ThreadTransmit();new Thread(()->{for (int i = 0; i < 10; i++) {threadTransmit.increase();}},"A").start();new Thread(()->{for (int i = 0; i < 10; i++) {threadTransmit.decrease();}},"B").start();new Thread(()->{for (int i = 0; i < 10; i++) {threadTransmit.increase();}},"C").start();new Thread(()->{for (int i = 0; i < 10; i++) {threadTransmit.decrease();}},"D").start();}*/
//      v3      lock.lock() +condition.await() + condition.signalAll()
public  void increase(){lock.lock();try {//判断while(num!=0){try {condition1.await();} catch (InterruptedException e) {throw new RuntimeException(e);}}//执行System.out.println(Thread.currentThread().getName()+":num = " + (++num));//通知condition1.signalAll();} finally {lock.unlock();}
}public  void decrease(){lock.lock();try {//判断while(num!=1){try {condition1.await();} catch (InterruptedException e) {throw new RuntimeException(e);}}//执行System.out.println(Thread.currentThread().getName()+":num = " + (--num));//通知condition1.signalAll();} finally {lock.unlock();}}public static void main(String[] args) {ThreadTransmit threadTransmit = new ThreadTransmit();new Thread(()->{for (int i = 0; i < 10; i++) {threadTransmit.increase();}},"A").start();new Thread(()->{for (int i = 0; i < 10; i++) {threadTransmit.decrease();}},"B").start();new Thread(()->{for (int i = 0; i < 10; i++) {threadTransmit.increase();}},"C").start();new Thread(()->{for (int i = 0; i < 10; i++) {threadTransmit.decrease();}},"D").start();}
//      v4      A 5->B 10->C 15 loop 10// concurrentModifyException
}

以下是对 Condition 的补充笔记,涵盖 常用方法常见面试题,结合知识库内容整理:


Condition

常用方法

核心方法

方法说明注意事项
await()使当前线程进入等待状态,直到被通知或中断。必须在持有锁时调用,否则抛出 IllegalMonitorStateException
signal()唤醒一个等待在该 Condition 上的线程。必须在持有锁时调用,唤醒的线程会进入同步队列竞争锁。
signalAll()唤醒所有等待在该 Condition 上的线程。适用于需要通知所有等待线程的场景(如资源充足时唤醒所有消费者)。
await(long time, TimeUnit unit)在指定时间内等待,超时返回 false超时后线程会自动释放锁并返回。
awaitUninterruptibly()忽略中断,等待直到被通知。即使线程被中断也不会抛出异常。
awaitNanos(long nanosTimeout)基于纳秒时间等待,返回剩余时间(若超时返回负数)。精度高但需注意时间单位转换。

其他方法

方法说明
awaitUntil(Date deadline)在指定截止时间前等待,超时返回 false
isAlive()检查线程是否存活(实际由线程自身判断,Condition不直接提供此方法)。

常见面试题

1. Condition 的作用是什么?与 wait/notify 有何区别?

  • 作用
    Condition 是 Java 并发包中用于线程间协作的接口,提供更灵活的条件等待和通知机制,需与 Lock 配合使用。
  • wait/notify 的区别
    • 多条件支持:一个 Lock 可创建多个 Condition 对象,每个条件对应独立的等待队列(如生产者-消费者中的“队列满”和“队列空”)。
    • 精准唤醒signal() 可精准唤醒特定条件的线程,而 notify() 可能随机唤醒线程;signalAll() 仅唤醒当前 Condition 的队列,比 notifyAll() 更高效。
    • 显式绑定:Condition 需显式绑定到 Lock,而 wait/notify 依赖隐式 Object 的监视器锁。

2. 为什么 await() 必须在持有锁时调用?

  • 原因
    1. 调用 await() 会释放当前持有的锁,允许其他线程获取锁。
    2. 若未持有锁就调用,会抛出 IllegalMonitorStateException
  • 流程
    lock.lock();
    try {while (conditionNotMet) {condition.await(); // 释放锁并等待}// 执行业务逻辑condition.signal(); // 唤醒其他线程
    } finally {lock.unlock(); // 重新获取锁后释放
    }
    

3. signal()signalAll() 的区别?

  • signal()
    随机唤醒等待队列中的一个线程,使其竞争锁。适用于“只需一个线程响应”的场景(如消费者消费一个商品)。
  • signalAll()
    唤醒所有等待线程,它们会竞争锁。适用于“所有线程都需要处理变化”的场景(如资源充足时唤醒所有消费者)。
  • 注意
    唤醒后线程需重新竞争锁,最终只有一个线程能执行临界区代码。

4. Condition 如何实现精准唤醒?

  • 多条件队列
    一个 Lock 可创建多个 Condition,每个 Condition 对应独立的等待队列。例如:
    Condition notFull = lock.newCondition(); // 生产者等待条件
    Condition notEmpty = lock.newCondition(); // 消费者等待条件
    
  • 选择性通知
    生产者在添加元素后调用 notEmpty.signal(),仅唤醒消费者队列中的线程;消费者在移除元素后调用 notFull.signal(),唤醒生产者队列中的线程。

5. Condition 的实现原理?

  • 底层机制
    基于 AQS(AbstractQueuedSynchronizer) 实现,每个 Condition 对象维护一个 等待队列
    • await()
      1. 释放锁,当前线程加入 Condition 的等待队列。
      2. 线程被阻塞,直到被信号唤醒或超时。
    • signal()
      1. 将等待队列中的线程移至 AQS 的同步队列,使其有机会重新竞争锁。
      2. 线程获取锁后继续执行。

6. 使用 Condition 时的注意事项?

  1. 必须在锁保护下调用
    await()signal() 必须在 lock 保护的代码块中调用。
  2. 避免虚假唤醒
    使用 循环检查条件,而非单一判断(如生产者-消费者中需检查队列是否满/空)。
  3. 避免死锁
    确保 unlock()finally 块中调用,避免因异常导致锁未释放。
  4. 谨慎使用 awaitUninterruptibly()
    可能忽略中断信号,需根据业务场景权衡。

7. Condition 的典型应用场景?

  • 生产者-消费者模式
    // 生产者
    lock.lock();
    try {while (queue.isFull()) {notFull.await(); // 队列满时等待}queue.put(item);notEmpty.signal(); // 唤醒消费者
    } finally {lock.unlock();
    }
    
  • 读写锁:通过多个 Condition 实现读写线程的协调。
  • 线程池任务调度:根据任务队列状态通知空闲线程。

8. Conditionsynchronizedwait/notify 的兼容性?

  • 不可混用
    • Condition 必须与 Lock 配合使用,不能与 synchronizedwait/notify 混合。
    • 若混用会导致锁状态混乱,可能抛出 IllegalMonitorStateException

9. await() 可能抛出的异常?

  • InterruptedException
    当线程在等待时被中断,会抛出此异常,需在 catch 块中处理。
  • IllegalMonitorStateException
    未持有锁时调用 await()signal() 会抛出此异常。

10. 如何实现带超时的条件等待?

  • 使用 await(long time, TimeUnit unit)awaitNanos()
    if (condition.await(1, TimeUnit.SECONDS)) {// 在1秒内被唤醒,继续执行
    } else {// 超时处理
    }
    

总结

  • 核心价值:Condition 提供了比 wait/notify 更灵活的线程协作能力,支持多条件管理和精准唤醒。
  • 使用要点:必须与 Lock 配合,确保锁的正确释放和获取,避免死锁和资源泄漏。
  • 替代方案:简单场景可使用 synchronized,复杂场景推荐 ReentrantLock + Condition

JUC 容器类 CopyOnWriteXXX / ConcurrentHashMap

package com.qxy.practice.concurrent;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;/*** @author : 戚欣扬* @Description : 线程同步队列的两种使用*/
public class SyncListDemo {/*** 如果我们使用ArrayList造100个线程去读写同一个list,会报ConcurrentModifyException :*         if (to > es.length) {*             throw new ConcurrentModificationException();*         }* 1.使用工具类转换为线程安全集合 Collections. 2.使用 CopyOnWirte 读写分离,写时复制*/public static void main(String[] args) {//   List<String> list =  Collections.synchronizedList(new ArrayList<>());//new ArrayList<>();List<String> list = new CopyOnWriteArrayList<>();// HashSet 同理 HashMap 采用 ConcurrentHashMapfor (int i = 0; i < 100; i++) {new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,6));System.out.println(list);},String.valueOf(i)).start();}}
}

常见面试题


1. JUC中常见的线程安全容器有哪些?

答案:

  • ConcurrentHashMap:线程安全的哈希表,替代Hashtable,支持部分并发操作。
  • CopyOnWriteArrayList:线程安全的动态数组,写时复制(Read-Copy-Update)实现。
  • CopyOnWriteArraySet:基于CopyOnWriteArrayList的线程安全Set实现。
  • ConcurrentSkipListMap/Set:基于跳表(SkipList)的有序线程安全集合。
  • BlockingQueue:阻塞队列(如ArrayBlockingQueue、LinkedBlockingQueue等),支持生产者-消费者模式。
  • ConcurrentLinkedQueue:非阻塞的线程安全队列,基于链表实现。

2. ConcurrentHashMap与Hashtable的区别?

答案:

特性ConcurrentHashMapHashtable
线程安全部分线程安全(分段锁/JDK1.8后CAS实现)全局锁(所有方法同步)
性能高并发下性能更好(分段锁/JDK1.8后无锁化)低效(单线程操作)
null值支持允许null键和值(JDK8+)不允许null键或值
迭代器失败为安全(fail-safe)失败快速(fail-fast)
实现原理分段锁(JDK1.7)或CAS+锁(JDK1.8+)全局synchronized

3. 为什么JDK1.8后ConcurrentHashMap舍弃分段锁?

答案:

  • 分段锁(Segment)的问题
    多个Segment锁仍存在竞争,且扩容时需锁所有Segment,性能受限。
  • JDK1.8的改进
    1. 基于CAS和锁:每个桶(Bucket)使用CAS操作,仅在哈希冲突时加锁单个桶。
    2. 无锁化设计:大部分读操作无需锁,写操作通过CAS和自旋实现。
    3. 数组+链表/红黑树:与HashMap结构一致,支持快速扩容和冲突处理。
  • 优势
    显著提升写操作的并发性能,同时保持读操作的高效性。

4. CopyOnWriteArrayList的适用场景?

答案:

  • 读多写少:写操作会复制整个数组,但读操作无需锁,适合读频繁场景。
  • 批量操作:如日志记录、配置列表等,写操作不频繁。
  • 避免迭代中断:迭代器是快照(fail-safe),不会抛出ConcurrentModificationException

缺点

  • 内存开销大(写操作复制数组)。
  • 写操作性能低,不适合频繁写入。

5. BlockingQueue的实现类有哪些?分别适用于什么场景?

答案:

实现类特点适用场景
ArrayBlockingQueue基于数组的有界队列,FIFO。固定容量的场景,需控制生产者速度。
LinkedBlockingQueue基于链表的可选有界队列,默认无界。无界队列或需要缓存的场景。
SynchronousQueue不存储元素,每个put必须等待一个take线程间直接交换数据(如线程池)。
PriorityBlockingQueue基于优先级堆的无界队列,按优先级排序。需按优先级处理任务的场景。
DelayQueue基于时间的延迟队列,元素只有在延迟时间到达后才能被取出。定时任务或延迟任务队列。
LinkedTransferQueue支持无界队列和扩展操作(如转移、批量操作)。需高效生产和消费的高并发场景。

6. 为什么JUC的ConcurrentHashMap要用synchronized而不是ReentrantLock?

答案:

  • 兼容性和性能
    • synchronized在JVM层面优化更好,JDK1.6后引入偏向锁、轻量级锁等机制,性能接近ReentrantLock
    • ConcurrentHashMap的锁粒度较小(如分段锁或单桶锁),同步块较短,synchronized的语法简洁性更优。
  • 代码简洁性
    synchronized的语法糖(自动释放锁)减少了代码复杂度,避免ReentrantLock需要显式unlock()的风险。

7. 如何确保一个集合不能被修改?

答案:

  • 使用不可变集合
    Collections.unmodifiableCollection()包装集合,返回只读视图。
  • 使用CopyOnWrite容器
    CopyOnWriteArrayList,写操作会抛出UnsupportedOperationException
  • 自定义实现
    继承集合类并重写修改方法,抛出异常或忽略操作。

8. 什么是fail-fast和fail-safe?JUC容器如何实现?

答案:

  • fail-fast
    • 机制:迭代过程中检测到结构修改(如并发修改),抛出ConcurrentModificationException
    • 实现:通过modCount计数器,迭代器维护expectedModCount,若不一致则抛异常。
    • 容器ArrayListHashMap等非线程安全集合。
  • fail-safe
    • 机制:迭代器基于快照(快照时的数据),忽略后续修改,不会抛异常。
    • 实现:基于写时复制(如CopyOnWriteArrayList)或版本控制(如ConcurrentHashMap)。
    • 容器CopyOnWriteArrayListConcurrentHashMap等线程安全集合。

9. 说一下ConcurrentHashMap的实现原理(JDK1.8)?

答案:

  1. 结构
    • 基于数组+链表/红黑树结构,与HashMap一致。
    • 数组元素为Node对象,每个节点包含键值对、哈希值、指针等。
  2. 并发控制
    • CAS操作:大部分操作通过CAS(compareAndSwap)无锁化完成。
    • 锁粒度:仅在哈希冲突时锁单个桶(synchronized)。
  3. 扩容机制
    • 扩容时分段进行,新旧表交替迁移数据,避免全局锁。
  4. 性能优化
    • 链表长度超过8转为红黑树,降低查找时间。
    • 读操作无需锁,写操作通过CAS和局部锁实现。

10. BlockingQueue的take()和poll()有什么区别?

答案:

方法行为
take()阻塞等待,直到有元素可用或被中断。
poll()非阻塞,若队列为空立即返回null(或指定超时时间后返回)。

11. 如何选择线程安全的集合类?

答案:

  • 读多写少CopyOnWriteArrayListConcurrentHashMap
  • 频繁写操作ConcurrentHashMap(哈希表)或ConcurrentSkipListMap(有序)。
  • 队列需求:根据是否阻塞选择BlockingQueueConcurrentLinkedQueue
  • 迭代安全:优先使用fail-safe容器(如CopyOnWriteArrayList)。

12. 为什么Vector是遗留类?如何替代?

答案:

  • Vector的问题
    • 全局synchronized锁,性能低。
    • 扩容为双倍容量,内存浪费。
  • 替代方案
    • 线程安全CopyOnWriteArrayList(读多写少)或ConcurrentLinkedQueue
    • 非线程安全ArrayList(手动加锁)。

总结

  • 核心思想:JUC容器通过减少锁粒度无锁化操作(CAS)、写时复制等技术,在保证线程安全的同时提升性能。
  • 设计模式
    • 分段锁(旧版ConcurrentHashMap)。
    • 快照迭代(CopyOnWrite)。
    • 阻塞队列(生产者-消费者模式)。
  • 高频考点:ConcurrentHashMap的实现、CopyOnWrite的适用场景、BlockingQueue的类型选择。

JUC 辅助类 CountDownLatch / CyclicBarrier / Semophore

CountDownLatchDemo(倒计时)

package com.qxy.practice.concurrent;import java.util.concurrent.CountDownLatch;/*** @author : 戚欣扬* @Description :*/
public class CountDownLatchDemo {public static void main(String[] args) {CountDownLatch clock = new CountDownLatch(3);for (int i = 3; i >0; i--) {int finalI = i;new Thread(()->{System.out.println("世界即将崩坏,倒计时 :" + finalI);//事实上,这个println顺序应该是随机的clock.countDown();},String.valueOf(i)).start();}try {clock.await();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("天国降临!!!");}
}

CyclicBarrierDemo(收集器)

package com.qxy.practice.concurrent;import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;/*** @author : 戚欣扬* @Description :*/
public class CyclicBarrierDemo {public static void main(String[] args) {CyclicBarrier collect = new CyclicBarrier(7,()->{System.out.println("召唤神龙!超级赛亚人之神!!!");});for (int i = 1; i <8; i++) {int finalI = i;new Thread(()->{System.out.println("已经收集龙珠 :" + finalI);try {collect.await();} catch (InterruptedException e) {throw new RuntimeException(e);} catch (BrokenBarrierException e) {throw new RuntimeException(e);}},String.valueOf(i)).start();}}
}

SemaphoreDemo (信号量)可以代替锁

package com.qxy.practice.concurrent;import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;/*** @author : 戚欣扬* @Description :*/
public class SemaphoreDemo {Semaphore semaphore = new Semaphore(1);public void stopCar(){try {semaphore.acquire();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName()+" 抢到了车位 " );try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName()+" 离开了商场" );semaphore.release();}public static void main(String[] args) {SemaphoreDemo semaphoreDemo = new SemaphoreDemo();for (int i = 0; i < 5; i++) {new Thread(()->{semaphoreDemo.stopCar();},String.valueOf(i)).start();}}
}

以下是关于 JUC辅助类 CountDownLatch、CyclicBarrier、Semaphore 的常用方法、使用场景及常见面试题的整理:


CountDownLatch

常用方法

方法说明
countDown()将计数器减 1,当计数器为 0 时,所有等待的线程被唤醒。
await()阻塞当前线程,直到计数器为 0。
await(long timeout, TimeUnit unit)在指定时间内等待计数器为 0,超时返回 false
getCount()返回当前计数器的值。

使用场景

  1. 等待多个线程完成任务

    • 主线程等待所有子线程执行完毕后再继续(如启动服务时等待多个组件加载完成)。
    • 示例:多个线程并行下载文件,主线程等待所有下载完成后合并文件。
  2. 并行任务协调

    • 多个线程需要同时开始执行任务(如赛跑中的“发令枪”)。
    • 示例:初始化一个 CountDownLatch(1),多个线程在 await() 处等待,主线程调用 countDown() 后所有线程同时执行。
  3. 资源加载

    • 主线程依赖多个资源加载完成才能继续执行(如数据库连接、配置加载)。

常见面试题

  1. CountDownLatch 的计数器如何变化?是否可重置?

    • 计数器只能减少(通过 countDown()),初始值由构造函数指定。
    • 不可重置,一旦计数器为 0,后续调用 await() 会立即返回。
  2. CountDownLatch 和 CyclicBarrier 的区别?

    特性CountDownLatchCyclicBarrier
    计数器单次使用,不可重置可循环使用
    等待触发条件计数器为 0线程数达到阈值
    应用场景主线程等待多个线程完成多线程需要多次同步到达同一屏障
  3. CountDownLatch 如何实现并行任务的“同时开始”?

    • 初始化 CountDownLatch(1),所有线程在 await() 处等待。
    • 主线程调用 countDown() 后,所有线程同时解除阻塞,开始执行。
  4. CountDownLatch 的 await() 会被中断吗?

    • 会抛出 InterruptedException,需在 catch 块中处理。

CyclicBarrier

常用方法

方法说明
await()阻塞当前线程,直到到达屏障的线程数等于阈值(parties)。
await(long timeout, TimeUnit unit)在指定时间内等待,超时返回 BrokenBarrierException
getNumberWaiting()返回正在等待的线程数。
isBroken()判断屏障是否处于破损状态(有线程中断或异常)。

使用场景

  1. 多线程协作同步

    • 多个线程需要在某个点汇合后再继续执行(如分布式计算的阶段同步)。
    • 示例:多个线程分别处理数据后,到达屏障汇总结果。
  2. 循环使用场景

    • 需要多次重置的同步点(如模拟比赛的多轮竞赛)。
  3. 分阶段任务处理

    • 任务需要分阶段执行,每个阶段需所有线程完成后再进入下一阶段。

常见面试题

  1. CyclicBarrier 的 await() 如何触发?

    • 当调用 await() 的线程数等于构造时指定的 parties 值时触发,所有线程被释放。
  2. CyclicBarrier 的屏障如何复用?

    • 触发后自动重置,可多次使用。除非屏障被破坏(broken 标志为 true),否则可循环使用。
  3. CyclicBarrier 和 CountDownLatch 的核心区别?

    • CountDownLatch:单向计数,不可重置,主线程等待其他线程完成。
    • CyclicBarrier:双向同步,可循环使用,所有线程需到达屏障后共同继续。
  4. 如何通过 CyclicBarrier 执行屏障到达后的操作?

    • 使用带 Runnable 的构造函数 CyclicBarrier(int parties, Runnable barrierAction),当线程数达到阈值时,会执行 barrierAction

Semaphore

常用方法

方法说明
acquire()获取一个许可证,若无可用许可证则阻塞。
acquire(int permits)获取指定数量的许可证。
release()释放一个许可证。
release(int permits)释放指定数量的许可证。
tryAcquire()尝试获取一个许可证,失败立即返回 false
tryAcquire(long timeout, TimeUnit unit)在指定时间内尝试获取许可证。
availablePermits()返回当前可用的许可证数量。

使用场景

  1. 资源访问控制

    • 限制并发访问共享资源(如数据库连接池、线程池)。
    • 示例:控制同时访问某文件的线程数不超过 5 个。
  2. 流量控制

    • 限流(如 API 请求限流,每秒最多 100 个请求)。
  3. 多线程协作

    • 协调线程的执行顺序(如生产者-消费者模式中控制生产者速率)。

常见面试题

  1. Semaphore 的公平模式与非公平模式的区别?

    • 公平模式:按线程等待顺序分配许可证。
    • 非公平模式:允许插队,性能更高但可能引发饥饿。
  2. Semaphore 如何实现资源池?

    • 初始化 Semaphore(poolSize),每个线程需 acquire() 获取资源,使用后 release() 归还。
  3. Semaphore 和 ReentrantLock 的区别?

    • Semaphore:控制并发线程数量,支持多许可证。
    • ReentrantLock:互斥锁,控制单个资源的访问。
  4. 如何避免 Semaphore 的死锁?

    • 确保线程在获取许可证后最终释放(如在 finally 块中调用 release())。
  5. acquire()tryAcquire() 的区别?

    • acquire():阻塞等待直到获取许可证。
    • tryAcquire():非阻塞,立即返回是否成功。

总结

核心功能典型场景
CountDownLatch等待多个线程完成任务,单向计数。主线程等待所有子线程完成。
CyclicBarrier多线程在屏障处汇合,可循环使用。多阶段任务同步、分布式计算。
Semaphore控制并发线程数量,支持资源访问限流。资源池管理、流量控制、并发限制。

相关文章:

  • 面试篇:Java并发与多线程
  • 通信安全员考试重难点考哪些?
  • 服务器编译环境配置及数据接收脚本编写(11)
  • C++类与继承
  • 三、Python编程基础03
  • URP-UGUI相关知识
  • 高光谱相机在工业检测中的应用:LED屏检、PCB板缺陷检测
  • CONDA:用于 Co-Salient 目标检测的压缩深度关联学习(总结)
  • openharmony5.0.0中C++公共基础类测试-线程相关(一)
  • 【速写】hook与fx
  • 国际化不生效
  • 聊聊SpringAI流式输出的底层实现?
  • 安全复健|windows常见取证工具
  • 从零开始搭建Django博客③--前端界面实现
  • 超声三维测试水箱与超声功率计:精准医疗与工业检测的核心技术支撑
  • Java基础 4.23
  • GOC 课程制作
  • YOLO数据处理
  • 树莓派超全系列教程文档--(41)树莓派config.txt旧版内存控制选项
  • system verilog 语句 耗时规则
  • 宁德时代与广汽等五车企发布10款巧克力换电新车型:年内将将完成30城1000站计划
  • 2025航天文化艺术论坛在上海举办
  • 湃书单|澎湃新闻编辑们在读的19本书:在工作中迷失
  • A股三大股指集体高开,黄金股大幅回调
  • 金地集团:保交楼为经营的首要任务,将根据融资性现金流恢复程度等进行投资决策
  • 去年净流入人口达45万,居各省份第一:浙江带来哪些启示?