一、什么是JUC(java.util.concurrent)

1.定义

JUC = Java并发工具包,是JDK5引入的一整套高性能并发解决方案。在多线程环境下,安全、高效、可控地操作共享资源。

2.JUC解决了什么问题?

问题 传统方式 JUC方式
线程安全 synchronized Lock / 原子类
线程通信 wait / notify Condition
线程池 手写 Executor
并发容器 Vector ConcurrentHashMap
并发协作 CountDownLatch等

二、进程 VS 线程

1.进程

  • 资源分配的最小单位
  • 拥有独立的内存空间、文件描述符、堆、方法区

2.线程

  • CPU调度的最小单位
  • 线程之间共享进程资源
  • 共享:
    (1)堆
    (2)方法区
  • 独享:
    (1)栈
    (2)程序计数器
    进程负责资源,线程负责执行

三、Lock锁

1.synchronized 的问题

  • 只有一把锁
  • 无法中断
  • 无法公平
  • 锁粒度粗
  • 无法尝试加锁

2.Lock接口(核心:ReentrantLock)

2.1 基本使用

Lock lock = new ReentrantLock();

lock.lock();
try {
    // 临界区
} finally {
    lock.unlock();
}

2.2 相比 synchronized 的优势

能力 Lock synchronized
是否可中断
是否公平锁
是否可以尝试加锁 tryLock
是否多条件 Condition

2.3 公平锁 vs 非公平锁

new ReentrantLock(true);  // 公平锁
new ReentrantLock(false); // 非公平锁(默认)
  • 公平锁:按等待时间顺序
  • 非公平锁:吞吐量更高

2.4 Condition(精确唤醒)

Condition condition = lock.newCondition();

condition.await();
condition.signal();

替代wait / notify,支持多个条件队列
PS:synchronized 是 JVM 层锁,Lock 是 JDK 层锁,Lock 更灵活、功能更强,生产推荐。

四、生产者-消费者问题(并发通信核心)

1.经典问题

  • 生产者生产数据
  • 消费者消费数据
  • 中间容器有容量限制

2.使用Lock + Condition实现

class Resource {
    private int count = 0;
    private Lock lock = new ReentrantLock();
    private Condition notEmpty = lock.newCondition();
    private Condition notFull = lock.newCondition();

    public void produce() throws InterruptedException {
        lock.lock();
        try {
            while (count == 1) {
                notFull.await();
            }
            count++;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public void consume() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                notEmpty.await();
            }
            count--;
            notFull.signal();
        } finally {
            lock.unlock();
        }
    }
}

关键点:while防止虚假唤醒(if会导致虚假唤醒),精准唤醒

五、8锁现象

本质:多个synchronized方法,锁的是谁?

  • 普通方法 -> 锁的是对象
  • static方法 -> 锁的是Class
  • 两者互不影响
  • 不同对象互不影响

synchronized 锁的是对象监视器(Monitor)

六、集合类不安全问题

1.问题示例

List<String> list = new ArrayList<>();
list.add("a"); // 多线程会抛异常

常见异常:

  • ConcurrentModificationException
  • 数据覆盖
  • 数据丢失

2.解决方案对比

方案 是否推荐
Vector
Collections.synchronizedList
CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();

原理:

  • 写时复制
  • 读无锁
  • 适合:读多写少

3.Map解决方案

ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

七、Callable(有返回值的线程)

1.Runnable vs Callable

Runnable callable
是否有返回值
是否有异常
配合使用 Thread FutureTask

2.使用方式

Callable<Integer> task = () -> 100;
FutureTask<Integer> future = new FutureTask<>(task);

new Thread(future).start();
Integer result = future.get();

八、JUC常用辅助类

1.CountDownLatch(倒计时)

等所有线程执行完,再执行主线程

CountDownLatch latch = new CountDownLatch(3);
latch.await();

场景:服务启动校验、并行任务聚合

2.CyclicBarrier(循环屏障)

一组线程互相等待

CyclicBarrier barrier = new CyclicBarrier(3);
barrier.await();

场景:多线程分阶段计算

3.Semaphore(信号量)

Semaphore semaphore = new Semaphore(3);

场景:

  • 限流
  • 连接池
  • 线程并发控制

九、读写锁(ReentrantReadWriteLock)

1.核心思想

  • 读读并发
  • 读写互斥
  • 写写互斥
ReadWriteLock rwLock = new ReentrantReadWriteLock();

场景:缓存系统、配置读取

十、阻塞队列(重点)

1.什么是阻塞队列

  • 生产者放满 -> 阻塞
  • 消费者取空 -> 阻塞
    天然实现生产者 - 消费者

2.四组API

方法组 行为
add / remove 抛异常
offer / poll 返回值
put / task 阻塞
offee(timeout) 超时

3.常见实现

队列 场景
ArrayBlockingQueue 有界
LinkedBlockingQueue 高并发
SynchronousQueue 直接交付
DelayQueue 延迟任务

线程池底层就是BlockingQueue

十一、应用场景总结

技术实现 场景
ReentrantLock 复杂并发控制
Condition 精准线程通信
ConcurrentHashMap 高并发缓存
CountDownLatch 启动依赖
Semaphore 限流
ReadWriteLock 配置/缓存
BlockingQueue MQ、线程池

十二、总结

1.synchronized 和 Lock 区别?

Lock更灵活,支持公平锁、中断、条件队列,推荐Lock

2.为什么要用while而不是if

防止虚假唤醒,确保线程安全

3.ConcurrentHashMap 为什么线程安全?

JDK8 使用 CAS + synchronized + Node 锁,锁粒度更细

4.BlockingQueue 的作用?

解耦生产消费,避免手写 wait/notify

5.读写锁适合什么场景?

读多写少的共享资源,如缓存

6.虚假唤醒

线程在没有满足唤醒条件的情况下,被从 wait / await 状态中唤醒,这种现象就叫虚假唤醒。
注意:不是 notify/ signal 发错了,而是“线程醒了,但条件并不成立”
1.虚假唤醒发生在哪里?

  • Object.wait()
  • Condition.await()
  • LockSupport.park()
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐