Java并發(fā)編程之鎖、并發(fā)容器、阻塞隊列與異步編程實戰(zhàn)代碼
前言
在多核 CPU 成為主流的今天,并發(fā)編程已成為后端開發(fā)者必備的核心技能。本文系統(tǒng)梳理 Java 并發(fā)編程中的鎖機(jī)制、并發(fā)容器、阻塞隊列與異步編程四大核心模塊,深入解析底層實現(xiàn)原理,結(jié)合實戰(zhàn)代碼幫助讀者掌握高并發(fā)場景下的技術(shù)選型與避坑策略。
一、顯式鎖 ReentrantLock 深度解析
1.1 ReentrantLock 與 synchronized 核心區(qū)別
synchronized是 Java 原生的隱式鎖,基于 JVM 實現(xiàn),自動完成鎖的獲取與釋放;而ReentrantLock是 JDK 提供的顯式鎖,基于 AQS(AbstractQueuedSynchronizer)框架實現(xiàn),需要手動控制加鎖與釋放。
對比維度 | synchronized | ReentrantLock |
實現(xiàn)層面 | JVM 層面 | JDK API 層面 |
鎖釋放 | 自動釋放 | 必須手動 unlock () |
可中斷 | 不支持 | 支持 lockInterruptibly () |
可超時 | 不支持 | 支持 tryLock (timeout) |
公平鎖 | 僅非公平 | 支持公平 / 非公平 |
條件變量 | 僅 1 個等待隊列 | 支持多個 Condition |
synchronized在 JDK6 后進(jìn)行了大量優(yōu)化,包括偏向鎖、輕量級鎖、自旋鎖等,性能已大幅提升。但在需要靈活控制鎖行為的場景下,ReentrantLock 仍是首選。
1.2 可重入性實現(xiàn)原理
可重入鎖指同一個線程可以多次獲取同一把鎖而不會產(chǎn)生死鎖。ReentrantLock 通過 AQS 的 state 狀態(tài)變量和 exclusiveOwnerThread 實現(xiàn)可重入。
當(dāng)線程首次獲取鎖時,state 從 0 變?yōu)?1,并記錄當(dāng)前持有線程;當(dāng)同一線程再次獲取鎖時,state 進(jìn)行累加;釋放鎖時 state 遞減,直到 state 歸 0 才真正釋放鎖。
// ReentrantLock.NonfairSync.tryAcquire()核心邏輯
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 鎖空閑,CAS嘗試獲取
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 同一線程重入,state累加
int nextc = c + acquires;
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}可重入性避免了同一線程反復(fù)獲取鎖導(dǎo)致的死鎖,是遞歸調(diào)用場景的必備特性。
1.3 可中斷鎖實現(xiàn)原理
synchronized獲取鎖時不可中斷,線程會一直阻塞直到獲取鎖;ReentrantLock 通過lockInterruptibly()方法支持中斷響應(yīng)。
當(dāng)調(diào)用lockInterruptibly()時,如果線程在等待隊列中被中斷,會直接拋出 InterruptedException,不再繼續(xù)等待鎖。這為取消阻塞操作提供了可能,是實現(xiàn)超時獲取鎖的基礎(chǔ)。
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
// AQS.acquireInterruptibly()
public final void acquireInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg); // 可中斷的等待邏輯
}1.4 可超時獲取鎖實現(xiàn)原理
tryLock(long timeout, TimeUnit unit)方法支持在指定時間內(nèi)嘗試獲取鎖,超時則返回 false。其實現(xiàn)基于 LockSupport.parkNanos () 進(jìn)行限時等待,在等待過程中同時檢測中斷和超時。
// 超時獲取鎖使用示例
public boolean tryLockWithTimeout(ReentrantLock lock, long timeoutMs) {
try {
return lock.tryLock(timeoutMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}可超時特性在分布式系統(tǒng)中尤為重要,可以有效避免因網(wǎng)絡(luò)波動導(dǎo)致的線程永久阻塞。
1.5 公平鎖與非公平鎖實現(xiàn)原理
ReentrantLock 默認(rèn)采用非公平鎖,可通過構(gòu)造函數(shù)參數(shù)指定為公平鎖:
ReentrantLock fairLock = new ReentrantLock(true); // 公平鎖 ReentrantLock unfairLock = new ReentrantLock(false); // 非公平鎖(默認(rèn))
公平鎖嚴(yán)格按照線程請求順序分配鎖,新線程必須加入等待隊列尾部;非公平鎖允許新線程在鎖釋放時直接嘗試搶占,不考慮隊列中等待的線程。
公平鎖的tryAcquire()會額外檢查hasQueuedPredecessors(),確保只有等待隊列中沒有前驅(qū)節(jié)點時才嘗試獲取鎖。
非公平鎖性能通常優(yōu)于公平鎖(吞吐量高約 30%),但可能產(chǎn)生線程饑餓。公平鎖保證了順序性,但增加了上下文切換開銷。
1.6 實戰(zhàn)代碼示例
/**
* ReentrantLock完整使用示例
*/
public class ReentrantLockDemo {
private final ReentrantLock lock = new ReentrantLock(true);
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Queue<Integer> queue = new LinkedList<>();
private static final int CAPACITY = 10;
public void produce(int data) throws InterruptedException {
lock.lock();
try {
while (queue.size() == CAPACITY) {
notFull.await(); // 隊列滿,生產(chǎn)者等待
}
queue.offer(data);
notEmpty.signal(); // 喚醒消費(fèi)者
} finally {
lock.unlock(); // 必須在finally中釋放鎖
}
}
public int consume() throws InterruptedException {
lock.lockInterruptibly(); // 可中斷方式獲取鎖
try {
while (queue.isEmpty()) {
notEmpty.await();
}
int data = queue.poll();
notFull.signal();
return data;
} finally {
lock.unlock();
}
}
}注意事項:
unlock()必須放在 finally 塊中,防止異常導(dǎo)致鎖泄漏使用多個 Condition 可以精確控制喚醒條件,這是 synchronized 不具備的能力
二、ConcurrentHashMap 底層原理與演進(jìn)
2.1 JDK7 分段鎖設(shè)計原理
JDK7 的 ConcurrentHashMap 采用 \\ 分段鎖(Segment)\\ 設(shè)計,整個哈希表被拆分為 16 個 Segment 數(shù)組,每個 Segment 獨(dú)立加鎖。
核心數(shù)據(jù)結(jié)構(gòu):
ConcurrentHashMap
└── Segment[] (默認(rèn)16個)
└── HashEntry[] (每個Segment獨(dú)立的哈希表)
└── HashEntry鏈表Segment 繼承自 ReentrantLock,每次 put 操作只鎖定對應(yīng)的 Segment,其他 Segment 的讀寫不受影響。理論上最大并發(fā)度等于 Segment 數(shù)量(默認(rèn) 16)。
但分段鎖存在明顯缺陷:
并發(fā)度固定,無法隨數(shù)組擴(kuò)容動態(tài)提升
空間浪費(fèi),每個 Segment 都需要獨(dú)立的鎖和數(shù)據(jù)結(jié)構(gòu)
跨段操作(如 size ())需要加鎖所有 Segment,性能較差
2.2 JDK8 架構(gòu)演進(jìn):CAS + synchronized
JDK8 徹底放棄分段鎖,采用數(shù)組 + 鏈表 + 紅黑樹結(jié)構(gòu),與 HashMap 保持一致。鎖的粒度從 Segment 級別細(xì)化到每個哈希桶(Node)級別。
核心改進(jìn):
使用
synchronized替代 ReentrantLock,JVM 對 synchronized 的優(yōu)化更成熟無鎖競爭時使用 CAS 進(jìn)行無鎖化更新
僅在哈希桶發(fā)生哈希沖突時才對首節(jié)點加鎖
鏈表長度超過 8 時自動轉(zhuǎn)為紅黑樹,解決哈希碰撞攻擊
// JDK8 ConcurrentHashMap.putVal()核心邏輯
final V putVal(K key, V value, boolean onlyIfAbsent) {
int hash = spread(key.hashCode());
for (Node<K,V>[] tab = table;;) {
int i = (n - 1) & hash;
Node<K,V> f = tabAt(tab, i);
if (f == null) {
// 桶為空,CAS直接插入
if (casTabAt(tab, i, null, new Node(hash, key, value)))
break;
} else {
synchronized (f) { // 僅鎖定桶的首節(jié)點
if (tabAt(tab, i) == f) { // 雙重檢查
// 鏈表或紅黑樹插入邏輯
}
}
}
}
}2.3 放棄分段鎖的深層原因
JDK8 放棄分段鎖主要基于以下考量:
1. 鎖粒度更細(xì)
分段鎖最小粒度是 Segment,JDK8 最小粒度是哈希桶
并發(fā)度隨數(shù)組容量動態(tài)提升,理論最大并發(fā)度等于數(shù)組長度
2. synchronized 性能優(yōu)化
JDK6 后 synchronized 引入偏向鎖、輕量級鎖、自適應(yīng)自旋
無鎖競爭時偏向鎖性能優(yōu)于 ReentrantLock
JVM 可以對 synchronized 進(jìn)行逃逸分析等深度優(yōu)化
3. 減少內(nèi)存開銷
消除 Segment 對象的內(nèi)存占用
數(shù)據(jù)結(jié)構(gòu)與 HashMap 統(tǒng)一,代碼復(fù)用性更高
4. 紅黑樹引入
解決哈希碰撞導(dǎo)致的鏈表過長問題
極端情況下時間復(fù)雜度從 O (n) 降至 O (logn)
2.4 擴(kuò)容機(jī)制深度解析
ConcurrentHashMap 的擴(kuò)容是并發(fā)協(xié)作式的,支持多線程共同參與擴(kuò)容,這是其核心亮點。
擴(kuò)容觸發(fā)條件:
元素數(shù)量達(dá)到閾值(容量 × 加載因子)
單桶鏈表長度超過 8 但數(shù)組容量小于 64
擴(kuò)容核心流程:
擴(kuò)容準(zhǔn)備:創(chuàng)建 nextTable,容量為原數(shù)組 2 倍
擴(kuò)容標(biāo)記:sizeCtl 設(shè)為負(fù)數(shù),標(biāo)記正在擴(kuò)容
任務(wù)分配:每個線程負(fù)責(zé)連續(xù)的 16 個桶的遷移
并發(fā)遷移:
處理完的桶設(shè)置為 ForwardingNode
遇到 ForwardingNode 自動跳過或協(xié)助擴(kuò)容
擴(kuò)容完成:table 指向 nextTable,重置 sizeCtl
// 擴(kuò)容時的ForwardingNode標(biāo)記
static final class ForwardingNode<K,V> extends Node<K,V> {
final Node<K,V>[] nextTable;
ForwardingNode(Node<K,V>[] tab) {
super(MOVED, null, null, null);
this.nextTable = tab;
}
}并發(fā)擴(kuò)容的精妙之處:
讀操作遇到 ForwardingNode 會轉(zhuǎn)發(fā)到新數(shù)組,不阻塞
寫操作遇到 ForwardingNode 會主動協(xié)助擴(kuò)容,實現(xiàn) "多線程幫忙"
遷移過程采用 "復(fù)制 + 清除" 方式,保證數(shù)據(jù)一致性
2.5 完整工作流程梳理
以 put 操作為例,完整執(zhí)行流程:
計算 key 的哈希值,定位哈希桶位置
如果數(shù)組未初始化,CAS 觸發(fā)初始化
如果目標(biāo)桶為空,CAS 直接插入新節(jié)點
如果遇到 ForwardingNode,協(xié)助擴(kuò)容后重試
否則,對桶首節(jié)點加 synchronized 鎖
遍歷鏈表或紅黑樹,key 存在則更新,不存在則追加
鏈表長度超過 8,觸發(fā)樹化或擴(kuò)容
釋放鎖,CAS 更新元素計數(shù),檢查是否需要擴(kuò)容
整個過程中,鎖的持有時間極短,大部分操作都是無鎖的 CAS 操作,這是 ConcurrentHashMap 高性能的根本原因。
三、BlockingQueue 阻塞隊列實戰(zhàn)
3.1 阻塞隊列核心作用
BlockingQueue是 Java 并發(fā)包中最重要的數(shù)據(jù)結(jié)構(gòu)之一,專門解決生產(chǎn)者 - 消費(fèi)者模式的線程協(xié)作問題。
核心特性:
隊列滿時,生產(chǎn)者線程自動阻塞,直到有消費(fèi)者消費(fèi)
隊列空時,消費(fèi)者線程自動阻塞,直到有生產(chǎn)者生產(chǎn)
所有操作都是線程安全的,內(nèi)部通過鎖和條件變量實現(xiàn)
四種核心操作模式:
操作方式 | 拋出異常 | 返回特殊值 | 阻塞等待 | 超時等待 |
插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移除 | remove() | poll() | take() | poll(time, unit) |
檢查 | element() | peek() | - | - |
阻塞隊列是線程池的核心組件,也是解耦生產(chǎn)消費(fèi)速率不匹配的標(biāo)準(zhǔn)解決方案。
3.2 ArrayBlockingQueue 詳解
ArrayBlockingQueue是基于數(shù)組實現(xiàn)的有界阻塞隊列,創(chuàng)建時必須指定容量。
核心特點:
有界隊列,容量固定不可擴(kuò)容
單鎖雙 Condition 機(jī)制(notEmpty + notFull)
支持公平 / 非公平模式
讀寫共用同一把鎖,并發(fā)度較低
// ArrayBlockingQueue核心結(jié)構(gòu)
public class ArrayBlockingQueue<E> {
final Object[] items; // 存儲數(shù)組
int takeIndex; // 取元素位置
int putIndex; // 放元素位置
int count; // 元素數(shù)量
final ReentrantLock lock; // 單鎖
private final Condition notEmpty;
private final Condition notFull;
}適用場景:隊列大小可預(yù)估、對內(nèi)存占用敏感的場景。
3.3 LinkedBlockingQueue 詳解
LinkedBlockingQueue是基于鏈表實現(xiàn)的阻塞隊列,默認(rèn)容量為 Integer.MAX_VALUE。
核心特點:
雙鎖分離設(shè)計(takeLock + putLock),讀寫互不阻塞
默認(rèn)無界(實際最大 2^31-1),也可指定容量
吞吐量高于 ArrayBlockingQueue
內(nèi)存占用相對較高
// LinkedBlockingQueue雙鎖設(shè)計 private final ReentrantLock takeLock = new ReentrantLock(); private final Condition notEmpty = takeLock.newCondition(); private final ReentrantLock putLock = new ReentrantLock(); private final Condition notFull = putLock.newCondition();
注意:無界模式下如果生產(chǎn)速度遠(yuǎn)大于消費(fèi)速度,可能導(dǎo)致 OOM。
適用場景:生產(chǎn)消費(fèi)速率差異較大、追求高吞吐量的場景。
3.4 SynchronousQueue 詳解
SynchronousQueue是一個不存儲元素的阻塞隊列,每個插入操作必須等待另一個線程的移除操作。
核心特點:
內(nèi)部沒有緩沖區(qū),隊列容量始終為 0
支持公平 / 非公平模式
直接傳遞,不存儲元素
是 Executors.newCachedThreadPool () 的默認(rèn)隊列
// SynchronousQueue典型用法
SynchronousQueue<Integer> queue = new SynchronousQueue<>();
// 生產(chǎn)者線程
new Thread(() -> {
queue.put(1); // 會阻塞直到有消費(fèi)者take
}).start();
// 消費(fèi)者線程
new Thread(() -> {
queue.take(); // 會阻塞直到有生產(chǎn)者put
}).start();適用場景:任務(wù)必須立即處理、不允許排隊的場景,如 CachedThreadPool。
3.5 DelayQueue 原理與延時任務(wù)應(yīng)用
DelayQueue是支持延時獲取元素的無界阻塞隊列,元素必須實現(xiàn) Delayed 接口。
核心原理:
內(nèi)部基于 PriorityQueue 實現(xiàn),按過期時間排序
只有當(dāng)元素的延遲時間到期后才能被取出
隊首元素永遠(yuǎn)是最早過期的元素
// 延時任務(wù)元素定義
public class DelayedTask implements Delayed {
private final long executeTime;
private final Runnable task;
public DelayedTask(long delayMs, Runnable task) {
this.executeTime = System.currentTimeMillis() + delayMs;
this.task = task;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(executeTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed other) {
return Long.compare(this.executeTime, ((DelayedTask)other).executeTime);
}
}典型應(yīng)用場景:
訂單超時自動取消
會話超時清理
定時任務(wù)調(diào)度
緩存過期失效
// 延時隊列使用示例
DelayQueue<DelayedTask> delayQueue = new DelayQueue<>();
delayQueue.put(new DelayedTask(30000, () -> System.out.println("30秒后執(zhí)行")));
delayQueue.put(new DelayedTask(60000, () -> System.out.println("60秒后執(zhí)行")));
// 消費(fèi)者線程
while (true) {
DelayedTask task = delayQueue.take(); // 阻塞直到任務(wù)到期
task.run();
}3.6 四者對比與選型建議
特性 | ArrayBlockingQueue | LinkedBlockingQueue | SynchronousQueue | DelayQueue |
容量 | 有界 | 可選有界 / 無界 | 0 | 無界 |
數(shù)據(jù)結(jié)構(gòu) | 數(shù)組 | 鏈表 | 直接傳遞 | 優(yōu)先級隊列 |
鎖機(jī)制 | 單鎖 | 雙鎖分離 | CAS | 單鎖 |
公平性 | 支持 | 不支持 | 支持 | 不支持 |
吞吐量 | 中等 | 高 | 極高 | 低 |
典型應(yīng)用 | 固定大小線程池 | 固定大小線程池 | 緩存線程池 | 延時任務(wù) |
選型建議:
需要控制隊列大小 → ArrayBlockingQueue
追求高吞吐量 → LinkedBlockingQueue
任務(wù)必須立即執(zhí)行 → SynchronousQueue
需要延時執(zhí)行 → DelayQueue
四、CompletableFuture 異步編程
4.1 異步編程背景
傳統(tǒng) Future 接口的局限性:
無法手動完成
不支持鏈?zhǔn)秸{(diào)用
不支持異常處理
無法組合多個 Future
CompletableFuture在 Java8 中引入,實現(xiàn)了 CompletionStage 接口,提供了豐富的異步編程能力,支持函數(shù)式編程風(fēng)格。
4.2 核心 API 分類詳解
1. 創(chuàng)建類 API
// 使用默認(rèn)線程池
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> System.out.println("異步任務(wù)"));
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "返回結(jié)果");
// 使用自定義線程池(推薦)
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "自定義線程池", executor);
// 手動完成
CompletableFuture<String> future4 = new CompletableFuture<>();
future4.complete("手動設(shè)置結(jié)果");
future4.completeExceptionally(new RuntimeException("手動異常"));注意:默認(rèn)使用 ForkJoinPool.commonPool (),所有 CompletableFuture 共享,CPU 密集型任務(wù)建議使用自定義線程池。
2. 鏈?zhǔn)睫D(zhuǎn)換類 API
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World") // 同步轉(zhuǎn)換
.thenApplyAsync(s -> s.toUpperCase()) // 異步轉(zhuǎn)換
.thenAccept(s -> System.out.println(s)) // 消費(fèi)結(jié)果
.thenRun(() -> System.out.println("執(zhí)行完成")); // 僅執(zhí)行,不消費(fèi)結(jié)果thenApply:輸入 T,輸出 U,類似 mapthenAccept:輸入 T,無輸出,消費(fèi)型thenRun:無輸入無輸出,僅執(zhí)行動作
3. 組合類 API
// AND組合:兩個都完成才執(zhí)行 CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Hello"); CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "World"); f1.thenCombine(f2, (s1, s2) -> s1 + " " + s2) .thenAccept(System.out::println); // 輸出 Hello World // OR組合:任意一個完成就執(zhí)行 CompletableFuture<String> fast = f1.applyToEither(f2, s -> s + " faster");
4. 異常處理 API
CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("出錯了");
return "正常";
})
.exceptionally(ex -> {
System.out.println("捕獲異常: " + ex.getMessage());
return "默認(rèn)值"; // 異常時返回默認(rèn)值
})
.handle((result, ex) -> {
if (ex != null) {
return "處理異常";
}
return result;
})
.whenComplete((result, ex) -> {
// 無論成功失敗都會執(zhí)行,不改變結(jié)果
System.out.println("執(zhí)行完成");
});5. 多任務(wù)組合
// 所有任務(wù)都完成 CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2, f3); // 任意一個任務(wù)完成 CompletableFuture<Object> any = CompletableFuture.anyOf(f1, f2, f3);
4.3 實戰(zhàn)示例:并行調(diào)用優(yōu)化
/**
* 并行調(diào)用多個服務(wù),聚合結(jié)果
*/
public class CompletableFutureDemo {
public UserInfo getUserInfo(Long userId) {
// 并行調(diào)用三個接口
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> userService.getUser(userId));
CompletableFuture<List<Order>> orderFuture = CompletableFuture.supplyAsync(() -> orderService.getOrders(userId));
CompletableFuture<List<Address>> addrFuture = CompletableFuture.supplyAsync(() -> addressService.getAddresses(userId));
// 等待所有完成,聚合結(jié)果
return CompletableFuture.allOf(userFuture, orderFuture, addrFuture)
.thenApply(v -> {
UserInfo info = new UserInfo();
info.setUser(userFuture.join());
info.setOrders(orderFuture.join());
info.setAddresses(addrFuture.join());
return info;
})
.exceptionally(ex -> {
log.error("獲取用戶信息失敗", ex);
return null;
})
.join();
}
}通過 CompletableFuture,原本串行 3 秒的調(diào)用可以優(yōu)化到 1 秒完成,這是微服務(wù)架構(gòu)下的常用優(yōu)化手段。
結(jié)語
本文系統(tǒng)解析了 Java 并發(fā)編程的四大核心模塊:ReentrantLock 的靈活鎖機(jī)制、ConcurrentHashMap 的高性能并發(fā)設(shè)計、BlockingQueue 的生產(chǎn)者 - 消費(fèi)者模式、CompletableFuture 的異步編程能力。
這些技術(shù)是構(gòu)建高并發(fā)系統(tǒng)的基石。建議結(jié)合實際項目深入實踐,重點關(guān)注各組件的適用場景與性能特性,避免在生產(chǎn)環(huán)境中出現(xiàn)并發(fā)安全問題。進(jìn)階可深入研究 AQS 框架、JMM 內(nèi)存模型與無鎖算法。
到此這篇關(guān)于Java并發(fā)編程之鎖、并發(fā)容器、阻塞隊列與異步編程實戰(zhàn)代碼的文章就介紹到這了,更多相關(guān)Java并發(fā)編程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot中@RestControllerAdvice @ExceptionHandler異常統(tǒng)一處
這篇文章主要介紹了SpringBoot中@RestControllerAdvice @ExceptionHandler異常統(tǒng)一處理類失效原因,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01
SpringBoot實現(xiàn)解析.mdb文件的實戰(zhàn)指南
這篇文章主要為大家詳細(xì)介紹了如何在SpringBoot項目中解析舊版Microsoft Access的.mdb文件,文中提供完整的Maven依賴配置、核心工具類封裝及使用示例,幫助開發(fā)者快速完成遺留系統(tǒng)數(shù)據(jù)遷移,希望對大家有所幫助2026-02-02
APT?注解處理器實現(xiàn)?Lombok?常用注解功能詳解
這篇文章主要為大家介紹了使用APT?注解處理器實現(xiàn)?Lombok?常用注解功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
Springboot升級到2.7.2結(jié)合nacos遇到的坑及解決
這篇文章主要介紹了Springboot升級到2.7.2結(jié)合nacos遇到的坑及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06
tomcat在Linux環(huán)境下的安裝與配置詳細(xì)教程
這篇文章主要介紹了tomcat在Linux環(huán)境下安裝與配置的相關(guān)資料,涵蓋Java環(huán)境準(zhǔn)備、下載解壓、啟動服務(wù)、部署項目及管理界面設(shè)置,適合開發(fā)測試環(huán)境搭建,需要的朋友可以參考下2025-05-05
Java ArrayList與LinkedList及HashMap容器的用法區(qū)別
這篇文章主要介紹了Java ArrayList與LinkedList及HashMap容器的用法區(qū)別,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-07-07

