Java重入鎖(ReentrantLock)從入門到源碼深度解析
引言
在多線程編程的世界里,鎖是最核心的同步工具之一。Java從語言層面提供了synchronized關(guān)鍵字來實(shí)現(xiàn)線程同步,簡單而有效。然而,隨著并發(fā)需求的復(fù)雜化,synchronized的局限性逐漸顯現(xiàn)——它無法響應(yīng)中斷、無法設(shè)置超時、默認(rèn)非公平且靈活性不足。為了解決這些問題,Java在java.util.concurrent.locks包中提供了ReentrantLock(重入鎖),一個功能更強(qiáng)大、使用更靈活的鎖工具。
本文將帶你全方位地認(rèn)識ReentrantLock,從基本概念到高級特性,從使用方式到源碼剖析,從底層原理到實(shí)際應(yīng)用。無論你是初學(xué)者還是希望深入理解并發(fā)編程的開發(fā)者,相信都能從中獲得啟發(fā)。全文約8500字,建議結(jié)合實(shí)踐閱讀。
第一部分:重入鎖基礎(chǔ)概念
1.1 什么是重入鎖?
重入鎖(Reentrant Lock),顧名思義,就是支持重入特性的鎖。重入是指:同一個線程在持有鎖的情況下,可以多次獲取同一把鎖而不會被阻塞。
舉個例子:如果一個線程已經(jīng)獲得了某個對象的鎖,當(dāng)它再次請求該對象的鎖時,會直接成功,而不是死鎖等待。這種機(jī)制在遞歸方法調(diào)用或嵌套同步塊中至關(guān)重要。
public class ReentrantExample {
private final Object lock = new Object();
public void methodA() {
synchronized (lock) {
// 已經(jīng)持有鎖
methodB(); // 再次請求同一把鎖
}
}
public void methodB() {
synchronized (lock) {
// 這里不會死鎖,因為synchronized是可重入的
System.out.println("methodB執(zhí)行");
}
}
}ReentrantLock同樣支持這種重入特性,但它提供了比synchronized更豐富的功能。
1.2 為什么需要重入鎖?
synchronized作為Java內(nèi)置的關(guān)鍵字,使用簡單,由JVM自動加鎖和解鎖,且經(jīng)過多年的優(yōu)化(偏向鎖、輕量級鎖、重量級鎖升級),性能已經(jīng)不遜色于ReentrantLock。既然如此,為什么還需要ReentrantLock?
這是因為ReentrantLock彌補(bǔ)了synchronized的幾個功能性缺陷:
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 使用方式 | 關(guān)鍵字,自動釋放 | API調(diào)用,需手動釋放 |
| 鎖獲取響應(yīng)中斷 | 不支持 | 支持(lockInterruptibly()) |
| 嘗試獲取鎖 | 不支持 | 支持(tryLock()) |
| 超時獲取鎖 | 不支持 | 支持(tryLock(long, TimeUnit)) |
| 公平鎖 | 非公平 | 可設(shè)置公平/非公平 |
| 條件變量 | 每個對象一個等待集 | 一個鎖可綁定多個Condition |
| 獲取鎖狀態(tài) | 無法得知 | 可查詢持有線程、等待隊列等 |
簡單來說,當(dāng)需要更精細(xì)的控制同步行為時,ReentrantLock是更好的選擇。
1.3 ReentrantLock的基本用法
在深入原理之前,我們先來看看ReentrantLock的標(biāo)準(zhǔn)使用模式:
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 獲取鎖
try {
count++;
} finally {
lock.unlock(); // 必須在finally中釋放鎖!
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}核心要點(diǎn):
lock()和unlock()必須成對出現(xiàn)- 解鎖操作必須放在
finally塊中,確保無論是否發(fā)生異常都能釋放鎖 - 不能在
try塊中調(diào)用lock(),因為lock()本身可能拋出異常
第二部分:ReentrantLock的核心特性
2.1 可重入性
可重入性是ReentrantLock命名中的核心特性。它通過計數(shù)機(jī)制實(shí)現(xiàn):
public class ReentrantDemo {
private final ReentrantLock lock = new ReentrantLock();
public void outer() {
lock.lock();
try {
System.out.println("外層方法獲取鎖");
inner();
} finally {
lock.unlock();
}
}
public void inner() {
lock.lock(); // 同一線程再次獲取鎖
try {
System.out.println("內(nèi)層方法再次獲取鎖");
} finally {
lock.unlock();
}
}
}內(nèi)部原理:每個鎖關(guān)聯(lián)一個持有線程和一個計數(shù)器。當(dāng)線程第一次獲取鎖時,計數(shù)器置為1;同一個線程再次獲取鎖時,計數(shù)器遞增;每釋放一次,計數(shù)器遞減;當(dāng)計數(shù)器歸零時,鎖完全釋放,其他線程才能獲取。
2.2 公平鎖與非公平鎖
2.2.1 概念解析
- 公平鎖(FairSync):線程按照請求鎖的先后順序(FIFO)獲取鎖,不會產(chǎn)生饑餓現(xiàn)象。
- 非公平鎖(NonfairSync):線程在獲取鎖時,允許"插隊",即直接嘗試搶占鎖,如果搶占成功就直接獲得鎖,搶占失敗才進(jìn)入隊列等待。
ReentrantLock默認(rèn)使用非公平鎖,但可以通過構(gòu)造器參數(shù)設(shè)置為公平鎖:
ReentrantLock fairLock = new ReentrantLock(true); // 公平鎖 ReentrantLock unfairLock = new ReentrantLock(false); // 非公平鎖 ReentrantLock defaultLock = new ReentrantLock(); // 默認(rèn)非公平鎖
2.2.2 為什么默認(rèn)非公平鎖?
非公平鎖雖然可能導(dǎo)致線程饑餓,但性能更高。原因在于:
- 公平鎖需要維護(hù)嚴(yán)格的排隊機(jī)制,線程喚醒有開銷
- 非公平鎖減少了線程的掛起和喚醒次數(shù)
- 在高并發(fā)場景下,非公平鎖的吞吐量通常優(yōu)于公平鎖
2.2.3 源碼層面的差異
我們來看看非公平鎖的lock()方法:
// NonfairSync的lock方法
final void lock() {
// 直接嘗試搶占鎖(插隊)
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
而公平鎖的lock()方法:
// FairSync的lock方法
final void lock() {
acquire(1); // 直接進(jìn)入隊列,沒有搶占機(jī)會
}
公平鎖的tryAcquire方法中多了一個關(guān)鍵判斷:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 公平鎖的額外判斷:隊列中是否有前驅(qū)節(jié)點(diǎn)
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// ... 重入邏輯
return false;
}
hasQueuedPredecessors()檢查隊列中是否有等待時間更長的線程,確保嚴(yán)格FIFO。
2.3 可中斷鎖
synchronized在等待鎖的過程中無法響應(yīng)中斷,而ReentrantLock提供了可中斷的獲取鎖方式:
public class InterruptibleDemo {
private final ReentrantLock lock = new ReentrantLock();
public void performTask() throws InterruptedException {
// 可響應(yīng)中斷的鎖獲取
lock.lockInterruptibly();
try {
// 執(zhí)行需要同步的操作
System.out.println(Thread.currentThread().getName() + " 獲得鎖");
Thread.sleep(5000);
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws Exception {
InterruptibleDemo demo = new InterruptibleDemo();
Thread t1 = new Thread(() -> {
try {
demo.performTask();
} catch (InterruptedException e) {
System.out.println("線程1被中斷");
}
});
Thread t2 = new Thread(() -> {
try {
demo.performTask();
} catch (InterruptedException e) {
System.out.println("線程2被中斷");
}
});
t1.start();
Thread.sleep(100); // 確保t1先獲得鎖
t2.start();
// 中斷正在等待鎖的t2
t2.interrupt();
}
}當(dāng)t2在等待鎖時被中斷,會立即拋出InterruptedException,從而有機(jī)會響應(yīng)中斷,而不是無限阻塞。
2.4 限時等待鎖
在實(shí)際開發(fā)中,無限等待鎖可能導(dǎo)致系統(tǒng)死鎖或響應(yīng)延遲。ReentrantLock提供了帶超時的鎖獲取方法:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TimeoutDemo {
private final ReentrantLock lock = new ReentrantLock();
public boolean tryExecute() {
try {
// 嘗試在3秒內(nèi)獲取鎖
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
System.out.println(Thread.currentThread().getName() + " 獲得鎖");
Thread.sleep(2000); // 模擬業(yè)務(wù)操作
return true;
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " 獲取鎖超時");
return false;
}
} catch (InterruptedException e) {
System.out.println("線程被中斷");
return false;
}
}
}tryLock()還有無參版本:如果鎖可用則立即獲取,否則立即返回false,不會阻塞。
2.5 條件變量(Condition)
Condition將Object的wait()、notify()、notifyAll()方法分解為不同的條件對象,使得一個鎖可以支持多個等待集,實(shí)現(xiàn)更精細(xì)的線程協(xié)作。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class BoundedBuffer {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Object[] items = new Object[10];
private int putIndex, takeIndex, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
// 當(dāng)隊列滿時,等待notFull條件
while (count == items.length) {
notFull.await(); // 釋放鎖,進(jìn)入等待
}
items[putIndex] = x;
if (++putIndex == items.length) putIndex = 0;
count++;
// 通知等待notEmpty條件的線程
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
// 當(dāng)隊列空時,等待notEmpty條件
while (count == 0) {
notEmpty.await();
}
Object x = items[takeIndex];
if (++takeIndex == items.length) takeIndex = 0;
count--;
// 通知等待notFull條件的線程
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}優(yōu)勢:與synchronized相比,Condition可以創(chuàng)建多個等待集,更靈活地控制線程協(xié)作。
第三部分:ReentrantLock與synchronized的全面對比
3.1 異同點(diǎn)總結(jié)
| 比較維度 | synchronized | ReentrantLock |
|---|---|---|
| 實(shí)現(xiàn)方式 | JVM內(nèi)置關(guān)鍵字 | Java API實(shí)現(xiàn),基于AQS |
| 鎖釋放 | 自動釋放(退出同步塊) | 手動釋放(需finally中unlock) |
| 可重入性 | 支持 | 支持 |
| 公平性 | 非公平 | 可公平可非公平 |
| 響應(yīng)中斷 | 不支持 | 支持(lockInterruptibly) |
| 超時獲取 | 不支持 | 支持(tryLock帶超時) |
| 嘗試獲取 | 不支持 | 支持(tryLock無參) |
| 條件變量 | 每個對象一個等待集 | 一個鎖可多個Condition |
| 鎖狀態(tài)查詢 | 無法查詢 | 可查詢持有線程、等待隊列長度等 |
| 性能 | JDK6后優(yōu)化良好 | 高競爭場景表現(xiàn)更優(yōu) |
3.2 如何選擇?
根據(jù)實(shí)際場景選擇:
- 優(yōu)先使用synchronized:當(dāng)同步邏輯簡單、不需要高級特性時。它簡潔、不易出錯,且JVM持續(xù)優(yōu)化。
- 需要公平鎖:必須保證線程獲取鎖的順序時。
- 需要可中斷鎖:希望避免線程無限期阻塞時。
- 需要超時獲取鎖:防止死鎖或保證響應(yīng)時間時。
- 需要多個條件變量:生產(chǎn)者-消費(fèi)者模式等復(fù)雜協(xié)作時。
- 高競爭場景:
ReentrantLock在高并發(fā)下表現(xiàn)更好。
第四部分:ReentrantLock源碼深度剖析
4.1 AQS基礎(chǔ):重入鎖的基石
要理解ReentrantLock,必須先理解AQS(AbstractQueuedSynchronizer)。AQS是Java并發(fā)包的基石,ReentrantLock、Semaphore、CountDownLatch等工具都基于它實(shí)現(xiàn)。
4.1.1 AQS的核心思想
AQS維護(hù)了兩個核心元素:
- volatile int state:同步狀態(tài),對于
ReentrantLock,state表示鎖的持有次數(shù)(0表示未持有,≥1表示持有次數(shù))。 - FIFO等待隊列(CLH隊列變體):用于存放獲取鎖失敗的線程。
核心操作:通過CAS(Compare And Swap)原子性地修改state值,成功則獲得鎖,失敗則進(jìn)入等待隊列。
4.1.2 AQS的關(guān)鍵方法
| 方法 | 描述 |
|---|---|
tryAcquire(int arg) | 嘗試獲取鎖,由子類實(shí)現(xiàn) |
tryRelease(int arg) | 嘗試釋放鎖,由子類實(shí)現(xiàn) |
acquire(int arg) | 獲取鎖的模板方法 |
release(int arg) | 釋放鎖的模板方法 |
ReentrantLock內(nèi)部類Sync繼承AQS,并實(shí)現(xiàn)了tryAcquire和tryRelease。
4.2 非公平鎖源碼解析
4.2.1 加鎖過程
// ReentrantLock.NonfairSync
final void lock() {
// 第一步:直接嘗試搶占鎖(插隊)
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 搶占失敗,進(jìn)入AQS流程
}
compareAndSetState(0, 1):通過CAS嘗試將state從0改為1。如果成功,表示當(dāng)前線程直接搶到了鎖,設(shè)置獨(dú)占線程為當(dāng)前線程。- 如果CAS失敗(鎖已被其他線程持有),調(diào)用
acquire(1)進(jìn)入AQS的獲取流程。
// AbstractQueuedSynchronizer.acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 再次嘗試獲?。ǚ枪桨妫?
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入等待隊列
selfInterrupt();
}
這里的tryAcquire調(diào)用的是NonfairSync實(shí)現(xiàn)的nonfairTryAcquire:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// state為0,說明鎖空閑,再次嘗試CAS搶占
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 重入:同一線程再次獲取鎖
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
非公平的體現(xiàn):即使線程已經(jīng)進(jìn)入等待隊列,在tryAcquire階段仍然會嘗試CAS搶占,而不是嚴(yán)格排隊。
4.2.2 入隊等待
如果tryAcquire失敗,則執(zhí)行addWaiter將當(dāng)前線程封裝成Node加入等待隊列尾部:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node); // 入隊失敗或有并發(fā)時,通過自旋CAS入隊
return node;
}然后執(zhí)行acquireQueued,在隊列中自旋等待:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 如果前驅(qū)是頭節(jié)點(diǎn),再次嘗試獲取鎖
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 檢查是否需要掛起線程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
當(dāng)線程獲取鎖失敗時,會被park(掛起),等待前驅(qū)線程釋放鎖時unpark喚醒。
4.2.3 釋放鎖
// ReentrantLock.Sync
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
釋放鎖時遞減state,直到state歸零才真正釋放鎖。然后AQS會喚醒隊列中的下一個節(jié)點(diǎn)。
4.3 公平鎖源碼解析
公平鎖的lock()方法直接調(diào)用acquire(1),沒有搶占嘗試:
// FairSync.lock
final void lock() {
acquire(1);
}
公平鎖的tryAcquire與非公平鎖的核心區(qū)別在于多了hasQueuedPredecessors()判斷:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 公平鎖:檢查隊列中是否有前驅(qū)節(jié)點(diǎn)
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// ... 重入邏輯
return false;
}
hasQueuedPredecessors()判斷當(dāng)前線程之前是否有等待的線程,確保FIFO順序。
4.4 限時獲取鎖的實(shí)現(xiàn)
tryLock(long timeout, TimeUnit unit)的底層通過doAcquireNanos實(shí)現(xiàn):
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
long lastTime = System.nanoTime();
final Node node = addWaiter(Node.EXCLUSIVE);
// ...
for (;;) {
// 嘗試獲取鎖
// 計算剩余時間,超時則返回false
nanosTimeout -= System.nanoTime() - lastTime;
if (nanosTimeout <= 0) {
cancelAcquire(node);
return false;
}
// 如果超時時間短,自旋;否則掛起
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
// ...
}
}
通過LockSupport.parkNanos實(shí)現(xiàn)限時阻塞,超時后自動喚醒并返回失敗。
第五部分:CAS與AQS——重入鎖的底層基石
5.1 CAS操作
CAS(Compare And Swap)是并發(fā)編程中實(shí)現(xiàn)無鎖算法的核心技術(shù)。它是一條CPU原子指令,包含三個操作數(shù):
- 內(nèi)存地址V
- 期望值A(chǔ)
- 新值B
僅當(dāng)V的值等于A時,才將V更新為B,整個過程原子完成。
在Java中,Unsafe類提供了CAS操作,ReentrantLock通過CAS修改AQS的state字段。
5.2 CAS的ABA問題
ABA問題:線程1讀取變量值為A,此時線程2將A改為B再改回A,線程1CAS時發(fā)現(xiàn)仍是A,于是更新成功。但實(shí)際上變量已經(jīng)被修改過。
解決方案:使用版本號或時間戳。Java提供了AtomicStampedReference來解決ABA問題。
5.3 AQS的設(shè)計精髓
AQS的核心設(shè)計理念包括:
- 模板方法模式:定義獲取/釋放鎖的骨架,具體實(shí)現(xiàn)由子類完成。
- CLH隊列變體:高效的雙向隊列管理等待線程。
- 狀態(tài)依賴:通過
state表示同步狀態(tài)。 - 自旋與阻塞結(jié)合:短時間內(nèi)自旋,長時間阻塞,平衡性能。
- LockSupport:提供線程掛起和喚醒的底層支持。
第六部分:實(shí)戰(zhàn)應(yīng)用與最佳實(shí)踐
6.1 標(biāo)準(zhǔn)使用模板
public class SafeCounter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
// 帶超時的獲取
public boolean tryIncrement(long timeout, TimeUnit unit) {
try {
if (lock.tryLock(timeout, unit)) {
try {
count++;
return true;
} finally {
lock.unlock();
}
}
return false;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}6.2 監(jiān)控與調(diào)試
ReentrantLock提供了監(jiān)控鎖狀態(tài)的方法:
ReentrantLock lock = new ReentrantLock();
// 查詢鎖狀態(tài)
System.out.println("鎖持有線程: " + lock.getOwner());
System.out.println("等待線程數(shù): " + lock.getQueueLength());
System.out.println("是否被當(dāng)前線程持有: " + lock.isHeldByCurrentThread());
System.out.println("是否公平鎖: " + lock.isFair());這些方法對調(diào)試死鎖、監(jiān)控系統(tǒng)狀態(tài)非常有幫助。
6.3 常見陷阱與注意事項
- 忘記釋放鎖:必須在finally中unlock。
- 在try塊內(nèi)lock:lock()可能拋出異常,應(yīng)該先lock再try。
- 鎖的可見性問題:ReentrantLock保證內(nèi)存可見性,無需額外volatile。
- 重入計數(shù)溢出:重入次數(shù)受int范圍限制,理論上可達(dá)21億次。
- 與synchronized混用:不同鎖機(jī)制之間不互斥,需注意設(shè)計。
6.4 性能考量
- 低競爭場景:synchronized性能略優(yōu)或持平
- 高競爭場景:ReentrantLock性能更好
- 公平鎖性能低于非公平鎖
- 避免在鎖內(nèi)執(zhí)行耗時操作
結(jié)語
ReentrantLock作為Java并發(fā)包中的核心工具,以其強(qiáng)大的功能和靈活的機(jī)制,成為高并發(fā)編程中不可或缺的利器。通過本文的學(xué)習(xí),我們深入理解了:
- 可重入性的實(shí)現(xiàn)原理
- 公平鎖與非公平鎖的源碼差異
- 可中斷、限時等待等高級特性
- AQS作為底層的核心架構(gòu)
- 最佳實(shí)踐與性能考量
掌握ReentrantLock不僅僅是學(xué)會使用一個類,更是理解Java并發(fā)編程思想的重要一步。在實(shí)際開發(fā)中,根據(jù)場景選擇合適的同步工具,平衡功能與性能,才能寫出高質(zhì)量的多線程程序。
到此這篇關(guān)于Java重入鎖(ReentrantLock)全面解析:從入門到源碼深度剖析的文章就介紹到這了,更多相關(guān)Java重入鎖ReentrantLock內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
搭建MyBatis開發(fā)環(huán)境及基本的CURD介紹
這篇文章主要介紹了搭建MyBatis開發(fā)環(huán)境及基本的CURD,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08
springboot 2.3之后消失的hibernate-validator解決方法
這篇文章主要介紹了springboot 2.3之后消失的hibernate-validator解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
Mybatis Plus Wrapper查詢某幾列的方法實(shí)現(xiàn)
MybatisPlus中,使用Wrapper的select和notSelect方法可以精確控制查詢的字段,本文就來介紹一下Mybatis Plus Wrapper查詢某幾列的方法實(shí)現(xiàn),感興趣的可以了解一下2024-10-10
使用IntelliJ IDEA調(diào)式Stream流的方法步驟
本文主要介紹了使用IntelliJ IDEA調(diào)式Stream流的方法步驟,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05
spring?cloud?eureka注冊原理-注冊失敗填坑筆記
這篇文章主要介紹了spring?cloud?eureka注冊原理-注冊失敗填坑筆記,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-05-05
將一個數(shù)組按照固定大小進(jìn)行拆分成數(shù)組的方法
下面小編就為大家?guī)硪黄獙⒁粋€數(shù)組按照固定大小進(jìn)行拆分成數(shù)組的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-11-11
SpringBoot內(nèi)嵌tomcat處理有特殊字符轉(zhuǎn)義的問題
這篇文章主要介紹了SpringBoot內(nèi)嵌tomcat處理有特殊字符轉(zhuǎn)義的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06

