Java死鎖和活鎖的聯(lián)系及說(shuō)明
本章還是重點(diǎn)介紹下java開(kāi)發(fā)過(guò)程中,會(huì)出現(xiàn)的鎖。
在多線(xiàn)程編程中,死鎖(Deadlock)和活鎖(Livelock)都是線(xiàn)程間協(xié)調(diào)不當(dāng)導(dǎo)致的執(zhí)行困境,但二者的表現(xiàn)和機(jī)制有顯著區(qū)別。
并發(fā)編程中的常見(jiàn)問(wèn)題
├── 活鎖 (Livelock)
│ ├── 定義:兩個(gè)或多個(gè)進(jìn)程或線(xiàn)程相互之間不斷嘗試執(zhí)行某個(gè)操作,但由于彼此的影響,這些操作都無(wú)法成功完成
│ ├── 特點(diǎn):系統(tǒng)不會(huì)崩潰,但也不會(huì)取得任何進(jìn)展
│ ├── 示例:線(xiàn)程A和線(xiàn)程B互相等待對(duì)方改變標(biāo)志
│
├── 饑餓 (Starvation)
│ ├── 定義:某個(gè)線(xiàn)程或進(jìn)程由于資源競(jìng)爭(zhēng)或調(diào)度策略的原因,長(zhǎng)時(shí)間無(wú)法獲得必要的資源,導(dǎo)致其無(wú)法執(zhí)行
│ ├── 特點(diǎn):某些線(xiàn)程或進(jìn)程始終得不到執(zhí)行的機(jī)會(huì)
│ ├── 示例:低優(yōu)先級(jí)任務(wù)始終無(wú)法執(zhí)行
│
├── 無(wú)鎖 (Lock-Free)
│ ├── 定義:不使用傳統(tǒng)的互斥鎖,而是使用原子操作和內(nèi)存模型來(lái)實(shí)現(xiàn)線(xiàn)程安全
│ ├── 特點(diǎn):提高并發(fā)性能,減少鎖的競(jìng)爭(zhēng)
│ ├── 示例:使用 AtomicInteger 實(shí)現(xiàn)線(xiàn)程安全的計(jì)數(shù)器
│
└── 死鎖 (Deadlock)
├── 定義:兩個(gè)或多個(gè)進(jìn)程或線(xiàn)程在執(zhí)行過(guò)程中,因爭(zhēng)奪資源而造成的一種相互等待的現(xiàn)象
├── 特點(diǎn):系統(tǒng)陷入停滯狀態(tài),無(wú)法繼續(xù)執(zhí)行
├── 示例:兩個(gè)線(xiàn)程互相等待對(duì)方持有的鎖
1、死鎖

1、產(chǎn)生原因
滿(mǎn)足下面四個(gè)必要條件,則會(huì)產(chǎn)生死鎖現(xiàn)象。
1、互斥條件:
線(xiàn)程對(duì)所分配到的資源進(jìn)行排他性使用,即在一段時(shí)間內(nèi)某資源只由一個(gè)線(xiàn)程占用。如果此時(shí)還有其它線(xiàn)程請(qǐng)求該資源,則請(qǐng)求者只能等待,直至占有資源的線(xiàn)程用畢釋放。
2、請(qǐng)求和保持條件:
線(xiàn)程已經(jīng)保持了至少一個(gè)資源,但又提出了新的資源請(qǐng)求,而該資源已被其它線(xiàn)程占有,此時(shí)請(qǐng)求線(xiàn)程阻塞,但又對(duì)自己已獲得的其它資源保持不放。
3、不剝奪條件:
線(xiàn)程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時(shí)由自己釋放。
4、循環(huán)等待條件:
在發(fā)生死鎖時(shí),必然存在一個(gè)線(xiàn)程 —— 資源的環(huán)形鏈,即線(xiàn)程集合 {T0,T1,T2,???,Tn} 中的 T0 正在等待一個(gè) T1 占用的資源;T1 正在等待 T2 占用的資源,……,Tn 正在等待已被 T0 占用的資源。
代碼示例:
class DeadlockExample {
private static final Object resource1 = new Object();
private static final Object resource2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1: Holding resource 1...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for resource 2...");
synchronized (resource2) {
System.out.println("Thread 1: Holding resource 1 and 2...");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println("Thread 2: Holding resource 2...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for resource 1...");
synchronized (resource1) {
System.out.println("Thread 2: Holding resource 1 and 2...");
}
}
});
thread1.start();
thread2.start();
}
}代碼解釋
resource1和resource2是兩個(gè)共享資源。thread1先獲取resource1,然后嘗試獲取resource2。thread2先獲取resource2,然后嘗試獲取resource1。- 由于
thread1和thread2都在等待對(duì)方釋放資源,從而導(dǎo)致死鎖。
由上面可知,死鎖的四個(gè)條件均滿(mǎn)足。
2、死鎖場(chǎng)景

3、解決方案
3.1. 預(yù)防
預(yù)防死鎖:
通過(guò)破壞產(chǎn)生死鎖的四個(gè)必要條件中的一個(gè)或幾個(gè)來(lái)預(yù)防死鎖的發(fā)生。例如,一次性獲取所有需要的資源,避免請(qǐng)求和保持條件;或者允許資源被剝奪。
避免死鎖:
在資源分配過(guò)程中,通過(guò)算法來(lái)判斷是否會(huì)發(fā)生死鎖,只有在不會(huì)發(fā)生死鎖的情況下才進(jìn)行資源分配。例如,銀行家算法。
1. 順序獲取鎖
方案:確保所有線(xiàn)程以相同的順序獲取鎖,避免不同的鎖獲取順序(最有效方案)。
public class DeadlockSolution1 {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread1 acquired lock1");
// 添加小延遲,增加死鎖概率
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread1 acquired lock2");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock1) { // 改為先獲取lock1,與thread1順序一致
System.out.println("Thread2 acquired lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread2 acquired lock2");
}
}
});
thread1.start();
thread2.start();
}
}2. 使用 tryLock (帶超時(shí))
方案:使用 ReentrantLock 的 tryLock() 方法,避免無(wú)限期等待,可以增加回退方案,并且超時(shí)情況下線(xiàn)程會(huì)釋放掉鎖。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DeadlockSolution2 {
private static final Lock lock1 = new ReentrantLock();
private static final Lock lock2 = new ReentrantLock();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
try {
if (lock1.tryLock(500, TimeUnit.MILLISECONDS)) {
System.out.println("Thread1 acquired lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
if (lock2.tryLock(500, TimeUnit.MILLISECONDS)) {
System.out.println("Thread1 acquired lock2");
lock2.unlock();
} else {
System.out.println("Thread1 failed to acquire lock2");
}
lock1.unlock();
} else {
System.out.println("Thread1 failed to acquire lock1");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
try {
if (lock2.tryLock(500, TimeUnit.MILLISECONDS)) {
System.out.println("Thread2 acquired lock2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
if (lock1.tryLock(500, TimeUnit.MILLISECONDS)) {
System.out.println("Thread2 acquired lock1");
lock1.unlock();
} else {
System.out.println("Thread2 failed to acquire lock1");
}
lock2.unlock();
} else {
System.out.println("Thread2 failed to acquire lock2");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
thread2.start();
}
}3. Phaser
方案:使用 java.util.concurrent 包中的高級(jí)工具類(lèi)替代顯式鎖。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Phaser;
public class DeadlockSolution4 {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
Phaser phaser = new Phaser(2); // 使用Phaser協(xié)調(diào)線(xiàn)程
executor.submit(() -> {
System.out.println("Task1 started");
phaser.arriveAndAwaitAdvance(); // 等待其他線(xiàn)程
System.out.println("Task1 completed");
});
executor.submit(() -> {
System.out.println("Task2 started");
phaser.arriveAndAwaitAdvance(); // 等待其他線(xiàn)程
System.out.println("Task2 completed");
});
executor.shutdown();
}
}4. 銀行家算法
- 安全狀態(tài):系統(tǒng)能按某種順序?yàn)樗羞M(jìn)程分配資源,使它們都能順利完成
- 不安全狀態(tài):系統(tǒng)可能進(jìn)入死鎖的狀態(tài)
四種數(shù)據(jù)結(jié)構(gòu):
- Available:可用資源向量
- Max:每個(gè)進(jìn)程最大需求矩陣
- Allocation:已分配資源矩陣
- Need:需求矩陣(Need = Max - Allocation)
實(shí)現(xiàn)步驟:
1. 安全狀態(tài)檢查算法
boolean isSafeState() {
// 1. 初始化
int[] work = Arrays.copyOf(Available, Available.length);
boolean[] finish = new boolean[processCount];
Arrays.fill(finish, false);
// 2. 尋找可滿(mǎn)足的進(jìn)程
int count = 0;
while (count < processCount) {
boolean found = false;
for (int i = 0; i < processCount; i++) {
if (!finish[i] && checkNeedLessThanWork(i, work)) {
// 3. 假設(shè)進(jìn)程i釋放資源
for (int j = 0; j < resourceTypes; j++) {
work[j] += Allocation[i][j];
}
finish[i] = true;
found = true;
count++;
}
}
if (!found) break; // 沒(méi)有找到可滿(mǎn)足的進(jìn)程
}
// 4. 檢查是否所有進(jìn)程都能完成
return count == processCount;
}
boolean checkNeedLessThanWork(int process, int[] work) {
for (int i = 0; i < resourceTypes; i++) {
if (Need[process][i] > work[i]) {
return false;
}
}
return true;
}2. 資源請(qǐng)求算法
boolean requestResources(int process, int[] request) {
// 1. 檢查請(qǐng)求是否超過(guò)聲明需求
for (int i = 0; i < resourceTypes; i++) {
if (request[i] > Need[process][i]) {
return false;
}
}
// 2. 檢查系統(tǒng)是否有足夠資源
for (int i = 0; i < resourceTypes; i++) {
if (request[i] > Available[i]) {
return false;
}
}
// 3. 嘗試分配
for (int i = 0; i < resourceTypes; i++) {
Available[i] -= request[i];
Allocation[process][i] += request[i];
Need[process][i] -= request[i];
}
// 4. 檢查安全性
if (isSafeState()) {
return true;
} else {
// 回滾分配
for (int i = 0; i < resourceTypes; i++) {
Available[i] += request[i];
Allocation[process][i] -= request[i];
Need[process][i] += request[i];
}
return false;
}
}銀行家算法通過(guò)以下機(jī)制預(yù)防死鎖:
事前預(yù)防:在資源分配前進(jìn)行安全性檢查
- 只有當(dāng)分配后系統(tǒng)仍處于安全狀態(tài)時(shí)才允許分配
- 避免了系統(tǒng)進(jìn)入可能導(dǎo)致死鎖的狀態(tài)
破壞死鎖必要條件:
- 破壞"循環(huán)等待"條件:通過(guò)確保至少有一個(gè)進(jìn)程總能獲得所需資源
- 破壞"占有并等待"條件:通過(guò)要求進(jìn)程一次性聲明最大需求
動(dòng)態(tài)決策:
- 每次資源請(qǐng)求都重新評(píng)估系統(tǒng)安全性
- 比靜態(tài)預(yù)防策略更靈活
3.2. 檢測(cè)
檢測(cè)死鎖:
- 通過(guò)一定的算法來(lái)檢測(cè)系統(tǒng)中是否存在死鎖。
- 例如,資源分配圖算法。
解除死鎖:
- 當(dāng)檢測(cè)到死鎖后,需要采取措施來(lái)解除死鎖。
- 例如,剝奪某個(gè)線(xiàn)程的資源,或者終止某個(gè)線(xiàn)程。
2、活鎖
1、介紹
活鎖是指線(xiàn)程不斷改變狀態(tài)來(lái)響應(yīng)對(duì)方,但整體任務(wù)無(wú)法推進(jìn)。
線(xiàn)程未阻塞,仍在不斷嘗試執(zhí)行操作,但因相互干擾陷入無(wú)效循環(huán),CPU 利用率不為 0(線(xiàn)程在執(zhí)行無(wú)意義的重試)。
2、場(chǎng)景
- 兩個(gè)線(xiàn)程(
Worker1和Worker2)需要同時(shí)獲取兩把鎖(lock1和lock2)才能工作。 - 如果某個(gè)線(xiàn)程發(fā)現(xiàn)鎖被占用,它會(huì)主動(dòng)釋放自己的鎖并重試(而不是阻塞等待)。
- 由于兩個(gè)線(xiàn)程同時(shí)讓步,導(dǎo)致它們無(wú)限循環(huán)地釋放和重試,但永遠(yuǎn)無(wú)法完成任務(wù)。
線(xiàn)程的調(diào)度由于不停切換,也會(huì)導(dǎo)致。
代碼示例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LivelockExample {
private final Lock lock1 = new ReentrantLock();
private final Lock lock2 = new ReentrantLock();
public void worker1() {
while (true) {
lock1.lock();
System.out.println("Worker1 獲取 lock1");
// 嘗試獲取 lock2,如果失敗就釋放 lock1 并重試
if (lock2.tryLock()) {
System.out.println("Worker1 獲取 lock2,完成任務(wù)");
lock2.unlock();
lock1.unlock();
break;
} else {
System.out.println("Worker1 無(wú)法獲取 lock2,釋放 lock1 并重試");
lock1.unlock();
}
// 短暫休眠,避免 CPU 100%
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public void worker2() {
while (true) {
lock2.lock();
System.out.println("Worker2 獲取 lock2");
// 嘗試獲取 lock1,如果失敗就釋放 lock2 并重試
if (lock1.tryLock()) {
System.out.println("Worker2 獲取 lock1,完成任務(wù)");
lock1.unlock();
lock2.unlock();
break;
} else {
System.out.println("Worker2 無(wú)法獲取 lock1,釋放 lock2 并重試");
lock2.unlock();
}
// 短暫休眠,避免 CPU 100%
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
LivelockExample example = new LivelockExample();
Thread t1 = new Thread(example::worker1);
Thread t2 = new Thread(example::worker2);
t1.start();
t2.start();
}
}以下是可能的執(zhí)行順序:
Worker1 獲取 lock1
Worker2 獲取 lock2
Worker1 無(wú)法獲取 lock2,釋放 lock1 并重試
Worker2 無(wú)法獲取 lock1,釋放 lock2 并重試
Worker1 獲取 lock1
Worker2 獲取 lock2
Worker1 無(wú)法獲取 lock2,釋放 lock1 并重試
Worker2 無(wú)法獲取 lock1,釋放 lock2 并重試
...(無(wú)限循環(huán))
現(xiàn)象:
- 兩個(gè)線(xiàn)程都在運(yùn)行(沒(méi)有阻塞,不像死鎖)。
- 它們不斷嘗試獲取對(duì)方的鎖,但發(fā)現(xiàn)鎖被占用后主動(dòng)釋放自己的鎖并重試。
- 由于同時(shí)讓步,導(dǎo)致無(wú)限循環(huán),任務(wù)無(wú)法完成。
3、處理方案
1. 引入隨機(jī)退避
示例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Random;
public class RandomBackoffSolution {
private final Lock lock1 = new ReentrantLock();
private final Lock lock2 = new ReentrantLock();
private final Random random = new Random();
public void worker1() {
while (true) {
lock1.lock();
System.out.println("Worker1 獲取 lock1");
try {
// 引入隨機(jī)退避
Thread.sleep(random.nextInt(100)); // 隨機(jī)等待0-99ms
if (lock2.tryLock()) {
try {
System.out.println("Worker1 獲取 lock2,完成任務(wù)");
return; // 成功完成任務(wù)
} finally {
lock2.unlock();
}
} else {
System.out.println("Worker1 無(wú)法獲取 lock2,釋放 lock1 并重試");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock1.unlock();
}
// 再次隨機(jī)退避
try {
Thread.sleep(random.nextInt(100));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public void worker2() {
while (true) {
lock2.lock();
System.out.println("Worker2 獲取 lock2");
try {
// 引入隨機(jī)退避
Thread.sleep(random.nextInt(100)); // 隨機(jī)等待0-99ms
if (lock1.tryLock()) {
try {
System.out.println("Worker2 獲取 lock1,完成任務(wù)");
return; // 成功完成任務(wù)
} finally {
lock1.unlock();
}
} else {
System.out.println("Worker2 無(wú)法獲取 lock1,釋放 lock2 并重試");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock2.unlock();
}
// 再次隨機(jī)退避
try {
Thread.sleep(random.nextInt(100));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
RandomBackoffSolution solution = new RandomBackoffSolution();
new Thread(solution::worker1).start();
new Thread(solution::worker2).start();
}
}2. 限制重試次數(shù)
示例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class RetryLimitSolution {
private final Lock lock1 = new ReentrantLock();
private final Lock lock2 = new ReentrantLock();
private static final int MAX_RETRIES = 5;
public void worker1() {
int retryCount = 0;
while (retryCount < MAX_RETRIES) {
retryCount++;
lock1.lock();
System.out.println("Worker1 獲取 lock1 (嘗試 " + retryCount + "/" + MAX_RETRIES + ")");
try {
if (lock2.tryLock()) {
try {
System.out.println("Worker1 獲取 lock2,完成任務(wù)");
return;
} finally {
lock2.unlock();
}
} else {
System.out.println("Worker1 無(wú)法獲取 lock2,釋放 lock1 并重試");
}
} finally {
lock1.unlock();
}
try {
Thread.sleep(100); // 固定等待時(shí)間
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("Worker1 達(dá)到最大重試次數(shù),放棄任務(wù)");
}
public void worker2() {
int retryCount = 0;
while (retryCount < MAX_RETRIES) {
retryCount++;
lock2.lock();
System.out.println("Worker2 獲取 lock2 (嘗試 " + retryCount + "/" + MAX_RETRIES + ")");
try {
if (lock1.tryLock()) {
try {
System.out.println("Worker2 獲取 lock1,完成任務(wù)");
return;
} finally {
lock1.unlock();
}
} else {
System.out.println("Worker2 無(wú)法獲取 lock1,釋放 lock2 并重試");
}
} finally {
lock2.unlock();
}
try {
Thread.sleep(100); // 固定等待時(shí)間
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("Worker2 達(dá)到最大重試次數(shù),放棄任務(wù)");
}
public static void main(String[] args) {
RetryLimitSolution solution = new RetryLimitSolution();
new Thread(solution::worker1).start();
new Thread(solution::worker2).start();
}
}3. 調(diào)整鎖獲取順序
這個(gè)方案比較推薦。示例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class OrderedLockSolution {
private final Lock lock1 = new ReentrantLock();
private final Lock lock2 = new ReentrantLock();
// 定義全局鎖獲取順序
private static final Object lockOrder = new Object();
public void worker1() {
while (true) {
// 按照固定順序獲取鎖
synchronized (lockOrder) {
lock1.lock();
System.out.println("Worker1 獲取 lock1");
try {
if (lock2.tryLock()) {
try {
System.out.println("Worker1 獲取 lock2,完成任務(wù)");
return;
} finally {
lock2.unlock();
}
} else {
System.out.println("Worker1 無(wú)法獲取 lock2,釋放 lock1 并重試");
}
} finally {
lock1.unlock();
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public void worker2() {
while (true) {
// 按照相同順序獲取鎖
synchronized (lockOrder) {
lock1.lock();
System.out.println("Worker2 獲取 lock1");
try {
if (lock2.tryLock()) {
try {
System.out.println("Worker2 獲取 lock2,完成任務(wù)");
return;
} finally {
lock2.unlock();
}
} else {
System.out.println("Worker2 無(wú)法獲取 lock2,釋放 lock1 并重試");
}
} finally {
lock1.unlock();
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
OrderedLockSolution solution = new OrderedLockSolution();
new Thread(solution::worker1).start();
new Thread(solution::worker2).start();
}
}3、聯(lián)系

總結(jié)
理解死鎖和活鎖的區(qū)別與聯(lián)系,有助于開(kāi)發(fā)出更健壯的并發(fā)程序。在實(shí)際開(kāi)發(fā)中,應(yīng)當(dāng)優(yōu)先考慮使用java.util.concurrent包提供的高級(jí)并發(fā)工具,而非直接使用低級(jí)的同步機(jī)制。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java自定義任務(wù)類(lèi)定時(shí)執(zhí)行任務(wù)示例 callable和future接口使用方法
Callable是類(lèi)似于Runnable的接口,實(shí)現(xiàn)Callable接口的類(lèi)和實(shí)現(xiàn)Runnable的類(lèi)都是可被其它線(xiàn)程執(zhí)行的任務(wù)2014-01-01
Spring MVC項(xiàng)目中l(wèi)og4J和AOP使用詳解
項(xiàng)目日志記錄是項(xiàng)目開(kāi)發(fā)、運(yùn)營(yíng)必不可少的內(nèi)容,有了它可以對(duì)系統(tǒng)有整體的把控,出現(xiàn)任何問(wèn)題都有蹤跡可尋。下面這篇文章主要給大家介紹了關(guān)于Spring MVC項(xiàng)目中l(wèi)og4J和AOP使用的相關(guān)資料,需要的朋友可以參考下。2017-12-12
java字符串的大寫(xiě)字母右移實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇java字符串的大寫(xiě)字母右移實(shí)現(xiàn)方法。小編覺(jué)得聽(tīng)不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04
SpringBoot 過(guò)濾器, 攔截器, 監(jiān)聽(tīng)器的具體使用
本文主要介紹了SpringBoot 過(guò)濾器, 攔截器, 監(jiān)聽(tīng)器的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05
mybatis-plus實(shí)體類(lèi)主鍵策略有3種(小結(jié))
這篇文章主要介紹了mybatis-plus實(shí)體類(lèi)主鍵策略有3種(小結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
SpringBoot設(shè)置首頁(yè)(默認(rèn)頁(yè))跳轉(zhuǎn)功能的實(shí)現(xiàn)方案
這篇文章主要介紹了SpringBoot設(shè)置首頁(yè)(默認(rèn)頁(yè))跳轉(zhuǎn)功能,本文通過(guò)兩種方案,給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07

