Java Util Concurrent

本文最后更新于 2 分钟前,文中所描述的信息可能已发生改变。

并发编程允许程序同时执行多个任务,提高程序效率。Java 通过多线程实现并发。主要类和接口包括:

  • Thread 类:表示线程的类,通过继承 Thread 类来创建线程。
  • Runnable 接口:表示任务的接口,通过实现 Runnable 接口来创建线程。
  • Callable 接口:表示有返回值的任务的接口,通过实现 Callable 接口来创建线程。
  • Executor 框架:用于管理线程池和任务调度,如 ExecutorService、ThreadPoolExecutor。

详情

volatile 是 Java 提供的一种轻量级的同步机制,它具有两个特性:可见性和有序性。

  • 可见性:volatile 保证了线程间变量是可见的,即当一个线程修改了共享变量后,该变量会立马同步到主内存,其余线程监听到数据变化后会使得自己缓存的原数据失效,并触发 read 操作读取新修改的变量的值。
  • 有序性:volatile 保证了线程对变量的修改是有序的,即禁止指令重排序
    • 当程序执行到 volatile 变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行。
    • 在进行指令优化时,不能将在对 volatile 变量访问的语句放在其后面执行,也不能把 volatile 变量后面的语句放到其前面执行。
  • 应用场景
    • volatile 不能保证原子性,即不能保证多个线程同时修改一个变量时的线程安全性
    • volatile 不能代替锁,它只能保证可见性和有序性,不能保证原子性。
    • volatile 适用于一个线程写,多个线程读的场景。
详情
  • 继承 Thread 类
java
public class MyThread extends Thread {
    @Override
    public void run() {
        // ...
    }
}
  • 实现 Runnable 接口
java
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // ...
    }
}
  • 实现 Callable 接口
java
public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        // ...
        return 0;
    }
}
  • 使用线程池
java
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.execute(new MyRunnable());
Future<Integer> future = executor.submit(new MyCallable());
详情
方式是否支持返回值是否可复用应用场景
继承 Thread 类不支持不可复用简单场景
实现 Runnable 接口不支持可复用多线程共享资源
实现 Callable 接口支持可复用需要返回值的任务
使用线程池支持可复用高并发场景
详情
类别类名说明
基础类String, StringBuffer, AtomicIntegerStringBuffer 使用了同步方法,线程安全
集合类Vector, ConcurrentHashMap, CopyOnWriteArrayList使用同步或锁机制保证线程安全
工具类Collections.synchronizedList通过包装实现线程安全的集合
Blocking 队列LinkedBlockingQueue, ArrayBlockingQueue支持生产者-消费者模型,线程安全
原子类AtomicInteger, AtomicLong, AtomicReference使用 CAS(Compare And Swap)实现原子操作

锁机制

详情
  • 悲观锁基于悲观的假设,认为共享资源在每次访问时都会发生冲突,因此在每次操作时都会加锁。 这种锁机制会导致其他线程阻塞,直到锁被释放。Java 中的 synchronized 和 ReentrantLock 是悲观锁的典型实现方式。 虽然悲观锁能有效避免数据竞争,但在高并发场景下会导致线程阻塞、上下文切换频繁,从而影响系统性能,并且还可能引发死锁问题。
  • 乐观锁基于乐观的假设,认为共享资源在每次访问时不会发生冲突,因此无须加锁,只需在提交修改时验证数据是否被其他线程修改。 Java 中的 AtomicInteger 和 LongAdder 等类通过 CAS(Compare-And-Swap)算法实现了乐观锁。 乐观锁避免了线程阻塞和死锁问题,在读多写少的场景中性能优越。但在写操作频繁的情况下,可能会导致大量重试和失败,从而影响性能。

详情
  • 公平锁:按照线程请求的顺序来获取锁,即先到先得。
  • 非公平锁:不考虑线程请求的顺序,有可能后请求的线程先获取到锁。

CAS (Compare And Swap)

  • 定义:CAS 是一种乐观锁,通过比较当前值和期望值是否一样来决定是否更新。
  • 相关类:AtomicInteger、AtomicLong、AtomicReference 等。
  • 相关问题
问题解决方案
ABA 问题使用版本号或时间戳解决,如 AtomicStampedReference
循环时间长开销大限制重试次数,避免无限循环,如自旋锁
只能保证一个共享变量的原子操作使用 AtomicReference 或加锁

ABA 问题:线程 1 读取数据 A,线程 2 将 A 改为 B,再改回 A,线程 1 再次读取 A,认为 A 未变化。

synchronized

  • 定义:synchronized 是一种悲观锁,通过获取锁来保证同一时刻只有一个线程执行。
  • 特点:可重入、非公平锁、不可中断、性能高。
  • 使用场景:适用于代码块同步、单例模式、简单场景。

ReentrantLock

  • 定义:ReentrantLock 是一种可重入锁,通过 AQS 实现。
  • 特点:支持公平/非公平锁、可中断、超时获取。
  • 使用场景:适用于复杂场景,如高并发、需要公平锁、可中断、超时获取或多个条件变量等场景。

详情
  • 区别
对比项synchronizedReentrantLock
底层实现JVM 内置,基于 MonitorJDK 提供的 API,基于 AQS(AbstractQueuedSynchronizer)
锁类型悲观锁,只能独占(排他锁)支持公平/非公平锁、可中断、超时获取
可重入性✅ 支持✅ 支持
公平性❌ 非公平✅ 支持公平锁和非公平锁
锁的范围作用在方法或代码块灵活,作用于任意代码块
是否可中断❌ 不可中断✅ 可以中断 lockInterruptibly()
是否可超时❌ 不支持超时获取锁✅ tryLock(timeout) 支持超时等待
是否需要手动释放❌ 自动释放(异常时也会释放)✅ 需要 lock() 和 unlock() 手动释放
性能JVM 内部优化,性能高适用于复杂场景,但开销稍大
  • 使用场景
    • synchronized:适用于代码块同步,单例模式,简单场景下使用,性能较高。
    • ReentrantLock:适用于复杂场景,如高并发,需要公平锁、可中断、超时获取或多个条件变量等场景。
  • 注意事项
    • synchronized: 无需手动释放锁,异常时会自动释放锁,代码块优先级高于方法
    • ReentrantLock: 需要手动释放锁,finally 块中释放锁,条件变量不易过多使用,影响性能

线程池

ThreadLocal

参考

Common Resources for Materials Calculation