volatile 是 Java 轻量级并发关键字,核心两大作用:保证共享变量的可见性,一个线程修改值后,其他线程能立即读到最新值;禁止指令重排序,防止 JVM 优化打乱代码执行顺序。它的底层依靠内存屏障实现,不会阻塞线程,开销极小。但要注意,volatile不能保证操作的原子性,无法处理 i++ 这类复合操作。适合用在状态标记量、单例双重检查锁定等场景,是并发编程中基础且常用的关键字。
synchronized 是 Java 内置的独占式可重入锁,保证线程安全。它可以修饰实例方法、静态方法、代码块,底层基于 JVM 的Monitor(管程) 实现。修饰代码块时用monitorenter和monitorexit字节码,修饰方法依靠标记位实现。JDK1.6 后做了大量优化,锁会逐级升级:无锁→偏向锁→轻量级锁→重量级锁。它能同时保证并发的三大特性:原子性、可见性、有序性,使用简单,异常自动释放锁,是入门级的线程同步方案。
CAS 即比较并交换,是一种无锁的原子操作,包含内存值、预期值、新值三个参数。原理是:先比较内存中的值是否等于预期值,相等就更新为新值,不相等则重试,全程不加锁。Java 中原子类(AtomicInteger)底层都基于 CAS 实现。它属于乐观锁,无线程阻塞、上下文切换开销,并发性能极高。通过自旋不断尝试更新,实现无锁编程,完美解决了多线程下的原子性问题,适合读多写少的高并发场景。
Lock 是 JDK 提供的并发锁接口,是手动控制的同步工具,核心实现是 ReentrantLock。它与 synchronized 的核心区别:Lock 是 API 层面的手动锁,需手动 lock/unlock;synchronized 是 JVM 层面的自动锁,自动释放。Lock 支持可中断、可超时、公平锁、多 Condition 队列,功能更灵活;synchronized 只有非公平锁,功能单一。性能上优化后两者接近,简单同步用 synchronized,复杂场景需要灵活特性时选择 Lock。
线程池是复用线程的容器,避免频繁创建销毁线程带来的开销。核心作用:控制并发线程数、复用线程、提升响应速度、管理线程生命周期。Java 通过ThreadPoolExecutor创建核心线程池,也可用 Executors 工具类快速生成。使用流程:定义任务(Runnable/Callable),提交给线程池,线程池自动分配线程执行。它自带任务队列、拒绝策略,能根据配置调节核心线程、最大线程数,是高并发编程中管理线程的标准方案。
happens-before 是 JMM 的核心规则,用来判断多线程操作的有序性和可见性,无需深入底层就能确定执行顺序。规则包含:volatile 写先于读、解锁先于加锁、线程 start 先于执行、join 先于结束、单线程内代码按序执行等。只要满足该原则,前一个操作的结果对后一个操作完全可见。它解决了指令重排和内存可见性问题,是编写线程安全代码的重要依据,也是理解 volatile、synchronized 的关键。
两者都是可重入独占锁,核心区别在功能和使用方式。synchronized 是 JVM 关键字,自动加锁解锁,异常自动释放,使用简单,仅支持非公平锁。ReentrantLock 是 JDK 实现类,手动加锁解锁,必须配合 finally 释放锁,功能更强大:支持公平 / 非公平锁、锁中断、超时获取锁、多个 Condition 等待队列。synchronized 无法获取锁状态,ReentrantLock 可以判断。JDK1.8 后性能接近,简单场景用 synchronized,复杂灵活需求用 ReentrantLock。
两者都是线程同步工具类,核心差异很大。CountDownLatch 是倒计时门闩,计数减到 0 后线程执行,不可重置、不可复用,用于一个线程等待多个线程完成。CyclicBarrier 是循环屏障,线程到达屏障后等待,集齐指定数量再一起执行,可重置、可循环使用,还支持回调函数。CountDownLatch 是等待事件完成,CyclicBarrier 是等待线程齐步走。前者适合同步启动 / 等待结束,后者适合多线程分批协作。
Java 中禁止使用 stop() 等废弃方法,会导致数据不一致。安全终止线程有两种方式:一是自定义volatile 布尔标志位,线程循环判断标志,为 false 时退出循环,优雅终止;二是使用interrupt()中断方法,给线程设置中断标志,线程在阻塞时会抛出 InterruptedException,捕获异常后清理资源并退出。两种方式都能让线程执行完逻辑、释放资源后正常结束,保证程序和数据的安全性。
ThreadLocal 是实现线程数据隔离的工具,为每个线程创建独立的变量副本,线程间互不干扰。原理是每个 Thread 对象内部有 ThreadLocalMap,以 ThreadLocal 为 key 存储数据。它不解决共享变量问题,仅做数据隔离。常用场景:存储用户登录会话、数据库连接、事务上下文、请求参数,避免层层传递参数。使用时必须调用remove()清理数据,否则线程池复用线程会引发内存泄漏和数据污染。
Semaphore 是信号量,用于控制同时访问资源的线程数量,实现限流、并发控制。它基于 AQS 实现,支持公平和非公平模式。核心方法:acquire()获取许可,release()释放许可。许可数量初始化时指定,线程获取不到许可会阻塞等待。主要用途:限制接口、数据库、连接池的并发访问量,防止高并发压垮资源;也可用于实现互斥锁,是高并发系统限流的常用工具。
ReadWriteLock 是读写锁,把锁分为读锁和写锁,核心规则:读锁共享、写锁独占,读写互斥、写写互斥。它的实现类是 ReentrantReadWriteLock。高并发读场景下,多个线程可以同时加读锁,无需阻塞;写操作时独占锁,保证数据安全。相比 synchronized 独占锁,读写锁大幅提升了读操作的并发性能,适合读多写少的业务场景,比如缓存查询、配置读取,是优化读密集型并发的利器。
StampedLock 是 JDK8 新增的读写锁,是 ReadWriteLock 的增强版,性能更高。它不支持可重入,通过版本戳(stamp)控制锁,支持乐观读,读操作完全无锁,只有写入时才校验版本戳是否变化。它没有 Condition 队列,功能比读写锁少,但并发性能远超 ReentrantReadWriteLock。适合读多写少、追求极致性能的场景,因为不支持重入且处理复杂,使用时要注意避免死锁和版本校验错误。
Thread 是类,Runnable 是函数式接口,是实现线程的两种方式。Thread 类实现了 Runnable 接口,直接继承 Thread 重写 run 方法,代码简单,但 Java 是单继承,无法再继承其他类,扩展性差。Runnable 接口需要传入 Thread 对象启动,避免了单继承局限,更符合面向接口编程,方便与线程池、Callable 结合。开发中优先使用 Runnable,解耦任务定义和线程执行,灵活性和扩展性更强。
Callable 是带返回值的任务接口,Runnable 无返回值;Future 用来获取异步任务的结果,但get() 方法会阻塞,功能单一。CompletableFuture 是 Java8 的增强版 Future,实现了 Future 和 CompletionStage 接口,支持异步回调、链式调用、多任务组合,无需阻塞等待结果。它内置线程池,支持异常处理、任务编排,解决了原生 Future 的阻塞痛点。高并发异步编程优先用 CompletableFuture,是接口异步化、分布式调用的首选。
Java 内存模型(JMM)是一套规范,定义了主内存和线程工作内存,解决多线程的可见性、原子性、有序性问题。所有共享变量存在主内存,线程操作时拷贝到工作内存。JMM 通过 volatile、synchronized、Lock、happens-before 规则保证并发安全。它屏蔽了底层硬件差异,统一了内存访问规则。对并发编程的影响:必须遵守 JMM 规则,否则会出现脏数据、线程不安全,是理解所有并发关键字和工具的基础。
Executor 是 Java 的线程池管理框架,简化线程的创建、调度、生命周期管理。核心包含 Executor、ExecutorService、ThreadPoolExecutor 等接口和类。它解决了手动创建线程的弊端:避免频繁创建销毁线程的开销、控制并发线程数、防止资源耗尽、解耦任务提交和执行。框架支持异步执行、定时任务、批量任务处理,提供了拒绝策略、线程监控等功能。是高并发开发的基础,让开发者专注业务,无需关心线程底层管理。
原子性是操作不可分割,要么全执行要么全不执行。保证原子性的三种方式:一是synchronized独占锁,阻塞线程保证原子性;二是CAS 无锁操作,基于自旋实现原子更新,无阻塞开销;三是原子类(AtomicInteger、AtomicLong),底层基于 CAS,封装了原子操作方法。i++、count++ 这类复合操作,必须用以上方式保证安全,volatile 无法保证原子性,这是并发编程最容易踩的坑。
Java 并发集合替代了低效的同步集合,常用的有:ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentSkipListSet、阻塞队列。它们的线程安全实现方式不同:ConcurrentHashMap 用CAS+ 分段锁 / 细粒度锁;CopyOnWriteArrayList 用写时复制,读无锁;阻塞队列用Lock+Condition。相比 Vector、HashTable 的 synchronized 独占锁,并发集合读操作无锁、锁粒度更小,高并发性能极高,是生产环境的首选。
死锁是多个线程互相持有对方需要的锁,无限阻塞。产生必须满足四个条件:互斥、持有并等待、不可抢占、循环等待。避免死锁只需破坏任一条件:统一固定锁的获取顺序,破坏循环等待;使用tryLock 超时机制,破坏持有并等待;减少锁嵌套,避免同时持有多把锁;用并发工具类替代独占锁。开发中规范加锁顺序,避免嵌套锁,就能有效防止死锁产生。
AQS 是 Java 并发锁的底层基础框架,ReentrantLock、CountDownLatch、Semaphore 都基于它实现。核心是volatile 修饰的 state 状态位和双向阻塞队列。线程获取锁失败时,会加入队列并阻塞;释放锁时,唤醒队列中的后继线程。AQS 支持独占和共享两种模式,封装了线程阻塞、唤醒、队列管理的底层逻辑。开发者只需重写 tryAcquire、tryRelease 等方法,就能快速实现同步组件。
Fork/Join 是 JDK7 的并行计算框架,基于分治思想,将大任务拆分成小任务并行执行,最后合并结果。核心是 ForkJoinPool 线程池,采用工作窃取算法,空闲线程会主动窃取其他线程的任务执行,CPU 利用率极高。适合 CPU 密集型计算,比如大数据量排序、数值计算。它简化了并行编程,无需手动管理线程,自动拆分合并任务,大幅提升多核 CPU 的计算效率。
阻塞队列是线程安全的队列,核心特性:队列满时写入阻塞,队列空时读取阻塞。常用实现:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue。它基于 Lock+Condition 实现,是生产者 - 消费者模式的核心组件。应用场景:线程池的任务存储、消息队列、数据缓冲。它自动处理线程等待唤醒,无需手动编写 wait/notify,简化了并发协作,是 Java 并发编程中解耦生产和消费的关键工具。
ThreadLocalRandom 是 JDK7 新增的线程安全随机数工具,替代了线程不安全的 Random。Random 底层用 CAS 竞争更新种子,高并发下竞争激烈,性能差。ThreadLocalRandom 为每个线程分配独立的种子,无竞争、无锁,生成随机数速度极快。使用时通过ThreadLocalRandom.current()获取实例,支持指定范围随机值。它解决了高并发下 Random 的性能瓶颈,是并发场景生成随机数的首选。
SynchronousQueue 是无容量的阻塞队列,不存储元素,插入操作必须等待对应的删除操作,反之亦然。相当于线程间的直接传输通道,生产者和消费者必须一对一匹配。它支持公平和非公平模式,底层基于 CAS 实现,无锁性能高。主要用在线程池(比如 Executors.newCachedThreadPool),适合任务数量不确定、需要快速传递的场景,不适合存储批量数据。
Phaser 是 JDK7 的阶段同步器,功能比 CountDownLatch 和 CyclicBarrier 更强大。CountDownLatch 不可复用,CyclicBarrier 可复用但固定线程数,Phaser支持动态调整线程数、多阶段同步、分层结构。它可以分多个阶段执行任务,每个阶段完成后自动进入下一个阶段,支持注册 / 注销线程。适用于复杂的多阶段并行任务,比如游戏加载、分布式任务调度,是灵活度最高的线程同步工具。
LockSupport 是线程阻塞唤醒工具类,是 AQS 的底层基础。核心方法:park()阻塞当前线程,unpark(Thread)唤醒指定线程。它比 wait/notify 更灵活:无需在同步代码块中使用、无顺序要求(unpark 先执行也有效)、精准唤醒指定线程,不会产生虚假唤醒。它底层依靠 UNSAFE 类的本地方法实现,是所有 Java 锁和同步工具实现阻塞唤醒的核心组件。
两者都是线程安全的 List,核心差异巨大。Vector 用 synchronized 修饰方法,独占锁,读写都阻塞,并发性能极差。CopyOnWriteArrayList 采用写时复制,写操作加锁复制新数组,读操作无锁、不阻塞。读性能极高,适合读多写少场景。Vector 支持迭代器快速失败,会抛异常;CopyOnWriteArrayList 是弱一致性迭代器,不抛异常。高并发读场景用 CopyOnWriteArrayList,Vector 基本被淘汰。
线程饥饿是线程长期获取不到锁,无法执行。原因是锁不公平、优先级过高线程抢占资源,避免用公平锁、合理设置线程优先级。活锁是线程不断重试操作,互相避让,始终无法执行,看似运行实则阻塞。避免活锁给重试加随机等待时间。死锁、饥饿、活锁是并发三大问题,核心解决思路:减少锁竞争、缩小锁粒度、使用公平锁、避免无限重试,保证线程公平执行。
join() 方法是 Thread 的线程协作方法,作用是让当前线程等待调用 join 的线程执行完毕后再继续。比如在主线程中调用 thread.join(),主线程会阻塞,直到子线程运行结束。底层基于 wait/notify 实现,子线程执行完后自动唤醒主线程。常用于需要等待子线程完成任务、获取结果的场景,保证线程执行顺序,是简单的线程同步方式。
yield() 是 Thread 的静态方法,作用是让当前线程让出 CPU 执行权,从运行态转为就绪态,重新参与 CPU 调度。它不会阻塞线程,也不会释放锁,让出 CPU 后可能立即再次获取执行权。该方法是启发式的,JVM 不一定遵守,主要用于线程调度调试,或让同优先级线程有机会执行。日常开发中极少使用,因为效果不可控,无法保证线程执行顺序。
原子类是 java.util.concurrent.atomic 包下的工具类,基于 CAS 实现无锁原子操作,保证线程安全。常用:AtomicInteger、AtomicLong、AtomicBoolean、AtomicReference。它们替代了 synchronized 锁,无阻塞、无上下文切换,并发性能极高。主要用于计数器、ID 生成、状态标记等场景,解决 i++ 这类复合操作的线程不安全问题,是读多写少高并发场景的轻量级解决方案。
wait()和 notify() 是 Object 的线程协作方法,必须在synchronized 同步代码块中使用。wait()会释放锁,让线程进入等待队列;notify() 随机唤醒一个等待线程,notifyAll()唤醒全部。正确用法:线程等待时用 while 循环判断条件,防止虚假唤醒;优先用 notifyAll() 避免线程遗漏。它们是最基础的等待 - 通知机制,用于线程间通信,现在更多被 Lock+Condition、并发工具类替代。
Exchanger 是线程间数据交换工具,用于两个线程交换数据。线程调用 exchange() 方法会阻塞,直到另一个线程也调用该方法,双方交换数据后继续执行。它支持超时设置,底层基于 CAS 实现。适用场景:两个线程传输数据、遗传算法、管道通信等,是最简单的线程间双向数据交换工具,仅支持两个线程配对使用。