叮叮叮!终于等到你~🌟
让我们一起学习 Java 中的多线程编程吧,这篇文章是我在学习过程中所记录的,内容涵盖:
多线程概述 → 线程该如何创建 → 线程的同步 → 线程之间如何通信 → 线程池的使用 → 常见的并发集合的使用。
每个阶段都有简单清晰的讲解,虽然内容有点多,可以慢慢一点点来逐步攻克每个知识点!!加油!gogogo!!👀
1. 概述
在这一章节可以大概了解多线程的相关内容~
这里有一个印象就好啦,对一些术语和知识过一遍
1.1为什么需要多线程呢?
想象一下~
有一家小面馆,老板一个人开店——他要煮面、点单、收钱、擦桌子,全靠自己。每次只能一件一件做,客人多了就要排队,急得团团转。后来老板雇了几个服务员一起干活,店里的效率蹭蹭上涨,客人也越来越多!
同样在计算机程序中,如果我们能让不同的任务同时进行,程序就能更快地完成工作啦!
1.2Java中的多线程是如何工作的?
Java提供了两种主要的方式来创建线程,这两种方式各有特点,适用于不同的应用场景:
继承Thread类
:就像你创建一个新食谱,基于一个已有的食谱进行修改。在Java中,你可以通过创建一个新的类,继承自Thread类,并重写它的run方法来定义你的任务。
实现Runnable接口
:这就像是按照食谱做菜,你不需要自己从头开始创造食谱,只需要按照已有的步骤来。在Java中,你可以创建一个实现了Runnable接口的类,并实现它的run方法,然后将其传递给Thread
对象。
1.3 线程的生命周期
线程也有对应的生命周期,就像人一样,有出生、成长、衰老和死亡的过程。
- 新建状态:线程被创建,但还没有开始工作。
- 就绪状态:线程准备好了,等待CPU的调度来执行。
- 运行状态:线程正在执行它的任务。
- 阻塞状态:线程因为某些原因(比如等待输入输出操作完成)暂时不能继续执行。
- 死亡状态:线程的任务执行完毕或者被强制停止。
1.4 线程同步
是指多个线程在访问共享资源时,通过一定的机制(如互斥锁、信号量等)来确保在同一时刻只有一个线程能够访问该资源,以避免数据不一致等问题。
当多个线程需要访问共享资源时,如果没有适当的同步机制
,就可能出现问题,比如数据不一致。Java提供了多种同步机制,比如synchronized关键字,来确保多个线程不会同时修改同一个资源。
1.5 线程池
👷♀️线程:就是打工人,一个线程干一个活。你创建新线程,就像临时招一个人,干完就走,效率低还费钱。
🏢线程池:是你公司提前招好的一批固定员工,没活的时候喝茶摸鱼(等着),有活就立刻上(执行任务)。干完再回来等下一个活,不用一直招人解雇,节省资源,响应快!
📦任务队列:就像老板把任务写在清单里排队发给员工做。
👮♀️线程池管理器:像个小组长,控制有多少员工上班、谁干活、干完了再干什么,有秩序地安排一切。
1.6 线程安全
线程安全 = 多个线程一起工作时,程序的数据不会乱,结果可预测、正确。
2. 线程的创建
2.1 Thread类
Thread
类是 Java 中用于创建和管理线程的核心类。它实现了启动新线程、停止线程、暂停线程等线程操作。
构造方法:
Thread():创建一个新的线程对象。
Thread(String name):创建一个带有指定名字的新的线程对象。
Thread(Runnable target):创建一个新的线程对象,并设置目标对象。
Thread(Runnable target, String name):创建一个新的线程对象,并设置目标对象和名字。
常用方法:
void start():启动线程。
void run():线程的执行逻辑,需要通过重写该方法来定义线程的执行代码。
void join():等待线程终止。
boolean isAlive():判断线程是否处于活动状态。
public class MyThread extends Thread {
@Override
public void run(){
System.out.println("MyThread is running...");
}
public static void main(String[] args){
MyThread myThread = new MyThread();
myThread.start();
}
}
2.2 Runnable接口
Runnable
接口主要用于定义线程要执行的任务,但它本身并非线程。若要让 Runnable 里定义的任务在新线程中执行,就需要借助 Thread类
。
方法:
- void run():线程的执行逻辑,需要通过实现该接口并实现run方法来定义线程的执行代码。
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("I am a runnable");
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
3. 线程同步和互斥
多线程就像一群贪吃的小仓鼠抢坚果。线程同步是让它们排好队轮流拿,别挤成一团;互斥呢,就是每次只允许一只小仓鼠进坚果房,其他在门外等着。
没有互斥,小仓鼠们会挤破头抢坚果,坚果撒一地乱套;而没有同步,小仓鼠们不知道什么时候该自己去拿,也不知道什么时候该等。所以,互斥和同步是一对形影不离的好搭档,一起守护坚果(共享资源)的秩序!
在多线程编程中,线程同步和互斥是为了解决多个线程并发访问共享资源时可能出现的问题。共享资源是多个线程共同使用和修改的数据,例如全局变量、静态变量等。当多个线程同时读写共享资源时,就可能导致数据不一致的问题。
在Java语言中,我们可以使用synchronized关键字、ReentrantLock类、Semaphore类等方式来实现线程同步和互斥。这些机制都提供了加锁和解锁的操作,保证了同一时间只有一个线程可以访问共享资源。不同的机制适用于不同的场景,你们可以根据具体的需求来选择合适的机制。
3.1 synchronized关键字
概念
synchronized是java中用于实现线程同步的关键字,它可以保证在同一时刻只有一个线程
可以执行某个方法或代码块。
使用场景
- 同步方法:对整个方法进行加锁
public class SynchronizedMethodExample {
private int count = 0;
// 同步方法:对整个方法进行加锁
public synchronized void increment(){
count++;
System.out.println("Incremented:"+count);
}
public static void main(String[] args) {
SynchronizedMethodExample example = new SynchronizedMethodExample();
// 创建两个线程来调用同步方法
Thread thread1 = new Thread(() -> example.increment());
Thread thread2 = new Thread(() -> example.increment());
thread1.start();
thread2.start();
}
}
- 同步代码块:对特定的代码段进行加锁
public class SynchronizedBlockExample {
private int count = 0;
private final Object lock = new Object();
public void increment(){
// 同步代码块
synchronized (lock){
count++;
System.out.println("Incremented:"+count);
}
}
public static void main(String[] args) {
SynchronizedBlockExample example = new SynchronizedBlockExample();
// 创建两个线程来调用包含同步代码块的方法
Thread thread1 = new Thread(() -> example.increment());
Thread thread2 = new Thread(() -> example.increment());
thread1.start();
thread2.start();
}
}
注意点:
- Lambda表达式的使用
// Lambda 表达式写法
new Thread(() -> example.increment());
// 等价的匿名内部类写法
new Thread(new Runnable() {
@Override
public void run() {
example.increment();
}
});
- 同步代码块格式
synchronized (对象引用) {
// 需要同步的代码
}
对象引用:指定一个对象作为锁(monitor),所有需要同步的代码块都必须使用同一个锁对象
工作原理:
- 获取锁:当线程进入 synchronized 代码块时,会尝试获取指定对象的锁。
- 执行代码:如果成功获取锁,则执行代码块中的代码。
- 释放锁:代码块执行完毕(或发生异常),自动释放锁,其他等待的线程可以继续竞争该锁。
3.2 ReentrantLock类
概念
ReentrantLock类
是Java提供的可重入锁
实现,可以用于替代synchronized关键字实现线程同步和互斥。
什么是可重入锁?
简单理解:一个线程可以多次获取同一把锁,而不会发生死锁。
具体行为:每次获取锁时,锁的持有计数器会增加;每次释放锁时,计数器会减少;当计数器为0时,锁才真正被释放。
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment(){
lock.lock(); // 获取锁
try {
count++;
System.out.println("Incremented:"+count);
} finally {
lock.unlock(); // 释放锁
}
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
// 创建两个线程来调用包含ReentrantLock的方法
Thread thread1 = new Thread(() -> example.increment());
Thread thread2 = new Thread(() -> example.increment());
thread1.start();
thread2.start();
}
}
使用场景:
1. 公平锁与非公平锁
公平锁
定义:按照请求锁的顺序来获取锁。即先请求锁的线程会先获得锁。
实现方式:通过构造函数指定 fair = true。
ReentrantLock fairLock = new ReentrantLock(true);
非公平锁(默认)
定义:不保证锁的获取顺序,允许插队。即后来的线程可能比先请求的线程更早获得锁。
实现方式:通过构造函数指定 fair = false(默认值)。
ReentrantLock nonFairLock = new ReentrantLock(false); // 默认
公平锁 VS 非公平锁
公平锁优点:避免饥饿现象,所有线程按顺序获取锁。
公平锁缺点:性能较低,因为需要维护等待队列。
非公平锁优点:性能较高,因为不需要维护等待队列。
非公平锁缺点:可能导致某些线程长期等待(饥饿)。
2. 可中断锁
定义:在等待获取锁时,可以通过interrupt() 方法中断等待状态。
作用:避免线程无限期地等待锁,提高程序的响应性。
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class InterruptibleLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void performTask() {
boolean acquired = false;
try {
// 尝试在5秒内获取锁
acquired = lock.tryLock(5, TimeUnit.SECONDS);
if (acquired) {
System.out.println("Lock acquired by " + Thread.currentThread().getName());
// 模拟耗时操作
Thread.sleep(2000);
} else {
System.out.println("Failed to acquire lock within 5 seconds");
}
} catch (InterruptedException e) {
System.out.println("Thread was interrupted while waiting for lock");
} finally {
if (acquired) {
lock.unlock();
System.out.println("Lock released by " + Thread.currentThread().getName());
}
}
}
public static void main(String[] args) throws InterruptedException {
InterruptibleLockExample example = new InterruptibleLockExample();
// 创建两个线程来调用包含 ReentrantLock 的方法
Thread thread1 = new Thread(() -> example.performTask(), "Thread-1");
Thread thread2 = new Thread(() -> example.performTask(), "Thread-2");
thread1.start();
thread2.start();
// 模拟中断线程2
Thread.sleep(1000); // 等待1秒
thread2.interrupt(); // 中断线程2
}
}
3. 超时获取锁
定义:在指定时间内尝试获取锁,如果超时则返回
方法:tryLock(long timeout, TimeUnit unit)
ReentrantLock lock = new ReentrantLock();
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) { // 尝试在5秒内获取锁
try {
// 操作代码
} finally {
lock.unlock();
}
} else {
System.out.println("Failed to acquire lock within 5 seconds");
}
} catch (InterruptedException e) {
System.out.println("Thread was interrupted while waiting for lock");
}
3.3 Semaphore类
概念
Semaphore(信号量)
是一个计数器,用于控制同时访问特定资源的线程数量。
它通过 acquire() 和 release()
方法来获取和释放许可证
使用场景
限制资源访问:例如,限制数据库连接数、文件读写等。
流量控制:用于控制并发线程的数量。
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private final Semaphore semaphore = new Semaphore(3); // 初始化信号量,允许3个线程同时访问
public void accessResource() {
try {
semaphore.acquire(); // 获取一个许可证
System.out.println(Thread.currentThread().getName() + " is accessing the resource");
Thread.sleep(1000); // 模拟耗时操作
System.out.println(Thread.currentThread().getName() + " has finished accessing the resource");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放一个许可证
}
}
public static void main(String[] args) {
SemaphoreExample example = new SemaphoreExample();
// 创建5个线程来访问资源
for (int i = 0; i < 5; i++) {
new Thread(example::accessResource, "Thread-" + i).start();
// 等价于:
// new Thread(() -> example.accessResource(), "Thread-" + i).start();
}
}
}
4. 线程通信
4.1 什么是线程通信嘞?
线程通信(Thread Communication)就是——多个线程之间共享数据,通过某种方式相互“打招呼”或“同步”进度。
你可以想象两个线程就像两个人在炒菜,一个切菜、一个炒菜,它们必须“配合”着来: 🥕切好了菜 → 通知炒菜的人:“我切完啦你炒吧!”
🍳炒好了菜 → 也得告诉切菜的:“我炒好了你快切下一个~”这种“打招呼”的过程,就是线程通信。
4.2 为什么要线程通信?
因为线程是并发执行的,天生是“各做各的”,但它们往往需要协作。
如果不通信,就会出现问题:
- 数据错乱(你还没切完我就炒了)
- 资源竞争(两个线程都想往锅里加菜)
- 死锁/卡顿(都等对方先动)
线程通信就是为了让多个线程协同工作,避免混乱,像跳双人舞一样合拍。
4.3 什么时候用线程通信?
只要你遇到下面这类场景,就要考虑线程通信:
- 🔄 生产者-消费者模型(一个线程生成数据,另一个处理)
- 🧮 某个线程要等另一个线程完成某件事之后再干活
- 💬 线程之间需要共享信息并同步进度
- 👀 有资源争用,要一个一个来访问资源
4.4 实现通信的方法
在 Java 中,实现线程通信常用的方法有 wait()
、notify()
和 notifyAll()
。
wait()
就像是让线程去 “睡一觉”,等待被唤醒;
notify()
是叫醒一个正在 “睡觉” 的线程;
notifyAll()
则是把所有 “睡觉” 的线程都叫醒。
4.5 线程通信的优缺点
优点 | 缺点 |
---|---|
✅ 线程能高效协作,提升性能 | ❌ 编写复杂,容易出错(忘记唤醒就GG) |
✅ 资源利用率高(线程不空转) | ❌ 死锁风险高(比如你等我,我等你) |
✅ 可以精细控制线程行为(精确唤醒) | ❌ 需要非常了解线程机制和同步原理 |
4.6 小案例
我们来写一个生产者-消费者模型的例子吧~
- 🍴 切菜工(Producer):负责放菜到桌子上
- 🔥 炒菜工(Consumer):负责炒掉桌上的菜 他们通过一个“共享盘子”通信~锅不能空转,桌子也不能堆太多菜。
- 定义盘子类,来实现共享变量通信
package ThreadCommunication;
// 盘子
public class Plate {
private String dish;
private boolean hasDish = false;
public synchronized void put(String dish){
while(hasDish) {
try {
wait(); // 桌上有菜,不能放
} catch(InterruptedException e){
Thread.currentThread().interrupt(); // 恢复中断状态
}
}
this.dish = dish;
hasDish = true;
System.out.println("🍴 切菜工放了菜:" + dish);
notify(); // 通知炒菜工来炒菜
}
public synchronized String take(){
while(!hasDish) {
try {
wait(); // 没菜炒不了,等着
} catch(InterruptedException e){
Thread.currentThread().interrupt(); // 恢复中断状态
}
}
System.out.println("🔥 炒菜工炒了菜:" + dish);
hasDish = false;
notify(); // 通知切菜工可以继续切了
return dish;
}
}
- 定义切菜工类,他要把切好的菜放进盘子里
package ThreadCommunication;
// 切菜工
public class Chef extends Thread {
private final Plate plate;
public Chef(Plate plate) {
this.plate = plate;
}
@Override
public void run(){
String[] menu = {"青椒肉丝", "土豆丝", "宫保鸡丁", "鱼香茄子"};
for(String dish : menu) {
plate.put(dish);
try {
Thread.sleep(1000); // 模拟切菜时间
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
- 定义炒菜工类,他要拿盘子的菜来炒
package ThreadCommunication;
// 炒菜工
public class Cook extends Thread {
private final Plate plate;
public Cook(Plate plate) {
this.plate = plate;
}
@Override
public void run(){
for(int i = 0; i < 4; i++) {
plate.take();
try {
Thread.sleep(1000); // 模拟炒菜时间
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
- 运行
package ThreadCommunication;
public class main {
public static void main(String[] args) {
Plate plate = new Plate();
Chef chef = new Chef(plate);
Cook cook = new Cook(plate);
chef.start();
cook.start();
}
}
- 输出
🍴 切菜工放了菜:青椒肉丝
🔥 炒菜工炒了菜:青椒肉丝
🍴 切菜工放了菜:土豆丝
🔥 炒菜工炒了菜:土豆丝
🍴 切菜工放了菜:宫保鸡丁
🔥 炒菜工炒了菜:宫保鸡丁
🍴 切菜工放了菜:鱼香茄子
🔥 炒菜工炒了菜:鱼香茄子
5. 线程池
5.1 什么是线程池?
线程池(Thread Pool)就是提前创建好一批线程,放在一个“池子”里,等有任务来了就从池子里拿线程去执行,执行完了再放回池子里,避免频繁创建和销毁线程的开销。
你可以把线程池想象成一个餐厅的服务员队伍:
- 顾客(任务)来了,经理(线程池)安排一个空闲的服务员(线程)去服务;
- 服务员服务完后回到休息区,等待下一个顾客;
- 如果顾客太多,经理会根据情况增加临时服务员,忙完后再让他们下班。
5.2 为什么用线程池?
使用线程池有以下几个好处:
- 降低资源消耗:重复利用已创建的线程,避免频繁创建和销毁线程带来的开销;
- 提高响应速度:任务到达时,可以不需要等到线程创建就能立即执行;
- 提高线程的可管理性:线程是稀缺资源,使用线程池可以进行统一的分配、调优和监控;
- 提供定时执行、定期执行、并发数控制等功能。
5.3 工作原理
线程池的工作原理可以简单描述为:
- 提交任务:将任务提交给线程池;
- 任务队列:线程池将任务放入任务队列中;
- 线程执行:线程池中的线程从任务队列中取出任务并执行;
- 线程复用:线程执行完任务后,不会被销毁,而是继续从任务队列中取出新的任务执行。
5.4 线程池类型
Java 提供了几种常用的线程池实现,分别适用于不同的场景:
- FixedThreadPool:
固定大小
的线程池,适用于任务量稳定的场景; - CachedThreadPool:
可缓存
的线程池,适用于执行很多短期异步任务的场景; - SingleThreadExecutor:
单线程
的线程池,适用于需要顺序执行任务的场景; - ScheduledThreadPool:支持
定时和周期性
任务执行的线程池,适用于需要定时执行任务的场景。
5.5 执行案例
举例固定大小的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小为3的线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交5个任务给线程池执行
for (int i = 1; i <= 5; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("任务 " + taskId + " 开始执行,线程:" + Thread.currentThread().getName());
try {
Thread.sleep(2000); // 模拟任务执行时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("任务 " + taskId + " 执行完毕,线程:" + Thread.currentThread().getName());
});
}
// 关闭线程池
executor.shutdown();
}
}
从输出可以看出,线程池中的3个线程被重复利用来执行5个任务。
任务 1 开始执行,线程:pool-1-thread-1
任务 3 开始执行,线程:pool-1-thread-3
任务 2 开始执行,线程:pool-1-thread-2
任务 1 执行完毕,线程:pool-1-thread-1
任务 2 执行完毕,线程:pool-1-thread-2
任务 4 开始执行,线程:pool-1-thread-1
任务 3 执行完毕,线程:pool-1-thread-3
任务 5 开始执行,线程:pool-1-thread-2
任务 4 执行完毕,线程:pool-1-thread-1
任务 5 执行完毕,线程:pool-1-thread-2
6. 并发集合
多线程编程是手段,并发编程是目的。
为了让多线程程序跑得安全又高效,我们需要并发集合这样的“线程安全的工具”。
6.1 为什么不能用普通集合?为什么要“并发集合”?
想象一下下面这个场景:
List<String> list = new ArrayList<>();
// 多线程同时往 list 添加元素
for (int i = 0; i < 10; i++) {
new Thread(() -> list.add("Hello")).start();
}
这段代码在多线程下可能会出错,因为 ArrayList
不是线程安全的,它可能:
- ❌ 添加过程中出错(数组越界、数据错乱)
- ❌ 抛出
ConcurrentModificationException
- ❌ 导致程序逻辑不一致(比如少加、重复、出错)
💥这就是并发问题!
6.2 并发集合能解决什么问题
并发集合的核心价值是:让多个线程安全地同时访问、操作同一个数据结构。它通过:
- ✨ 分段锁(比如 ConcurrentHashMap)
- ✨ 读写分离(比如 CopyOnWriteArrayList)
- ✨ 阻塞等待(比如 BlockingQueue)
这些设计,让线程之间不会互相“踩脚”,还能保证性能不崩溃、逻辑不乱套!
6.3 并发集合类简介
Java 提供了一些线程安全的集合类,主要在 java.util.concurrent
包下,常用的有:
集合类 | 类型 | 特点 |
---|---|---|
ConcurrentHashMap | Map | 支持高并发读写的哈希表 |
CopyOnWriteArrayList | List | 读多写少场景的线程安全列表 |
BlockingQueue | Queue | 支持阻塞式读写的队列,常用于生产者-消费者模型 |
6.4 并发集合使用
1. ConcurrentHashMap
—— 多线程下的 HashMap 升级版
✅ 特点:
- 支持并发读写,不会抛出
ConcurrentModificationException
; - 分段锁(JDK8以前)或 CAS+Synchronized(JDK8之后)优化性能;
- 读性能很高,适合高并发场景。
✅ 使用示例:
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 写入元素
map.put("apple", 3);
map.put("banana", 2);
// 并发读取或修改都不会抛异常
map.forEach((k, v) -> System.out.println(k + ": " + v));
}
}
✅ 适用场景:
- 多线程共享一个 Map,例如缓存、计数器、配置表等;
- 替代
Hashtable
和同步包装的HashMap
。
2. CopyOnWriteArrayList
—— 写时复制的 List
✅ 特点:
- 读操作不会加锁;
- 每次写操作都会复制整个数组,然后替换原有的;
- 读多写少时性能优秀,写多时慎用(开销大)。
✅ 使用示例:
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteDemo {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 添加元素
list.add("Java");
list.add("Python");
// 遍历不会报并发修改异常
for (String lang : list) {
System.out.println(lang);
}
// 可安全删除、添加
list.remove("Python");
list.add("Go");
}
}
✅ 适用场景:
- 读多写少的并发环境,例如:缓存的读取、订阅者列表等;
- 替代
Collections.synchronizedList()
。
3. BlockingQueue
—— 天生支持“阻塞”的队列
✅ 特点:
- 天生支持阻塞式 put/take 操作;
- 常见实现类:
ArrayBlockingQueue
(有界数组)LinkedBlockingQueue
(链表)PriorityBlockingQueue
(带优先级)DelayQueue
(延迟队列)
- 写入满了会等待,读取空了也会等待,非常适合线程间通信!
✅ 使用示例:经典的生产者-消费者模型
import java.util.concurrent.*;
public class BlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
// 生产者
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
queue.put("任务 " + i);
System.out.println("生产了:任务 " + i);
Thread.sleep(500);
}
} catch (InterruptedException e) {}
}).start();
// 消费者
new Thread(() -> {
try {
while (true) {
String task = queue.take();
System.out.println("消费了:" + task);
Thread.sleep(1000);
}
} catch (InterruptedException e) {}
}).start();
}
}
✅ 适用场景:
- 多线程之间的安全通信;
- 日志处理、任务调度、线程池的任务队列等。
4. 对比总结
集合类 | 读写性能 | 使用场景 | 缺点 |
---|---|---|---|
ConcurrentHashMap | 高并发读写 | 多线程共享Map | 键值对多时内存占用大 |
CopyOnWriteArrayList | 超强读性能 | 读多写少的列表 | 写开销大,效率低 |
BlockingQueue | 阻塞控制读写 | 线程通信、任务队列 | 适合特定模型,不通用 |
6.5 小结
在并发场景下,如果你碰到集合类并发问题,不要慌!看看:
- ❓是不是需要频繁读写 Map ➜ 用
ConcurrentHashMap
- ❓是不是读多写少的 List ➜ 用
CopyOnWriteArrayList
- ❓是不是生产者消费者模型 ➜ 用
BlockingQueue
而不是一上来就加 synchronized
,这样会大大影响性能!
太棒啦!你已成功解锁这篇 Java 多线程编程核心笔记的全部内容!🌟
能坚持到这里太厉害啦啦啦啦啦~
给你点赞👍🏻👍🏼👍🏽