最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java程序死鎖問題定位與解決方法

 更新時間:2024年11月27日 09:37:30   作者:徐俊生  
死鎖是一種特定的程序狀態(tài),主要是由于循環(huán)依賴導致彼此一直處于等待中,而使得程序陷入僵局,相當尷尬,死鎖不僅僅發(fā)生在線程之間,而對于資源獨占的進程之間同樣可能出現(xiàn)死鎖,本文給大家介紹了Java程序死鎖問題定位與解決方法,需要的朋友可以參考下

1. 死鎖概述

1.1 什么是死鎖

  1. 一定是發(fā)生在并發(fā)中;
  2. 互不相讓:當兩個(或更多)線程(或進程)相互持有對方所要的資源,又不主動釋放,導致程序陷入無盡的阻塞,這就是死鎖。

1.2 死鎖產(chǎn)生的必要條件

導致死鎖的條件有四個,這四個條件同時滿足就會產(chǎn)生死鎖。

  • 互斥條件:某些資源只能由一個線程獨占使用,其他線程在資源被占用時只能等待。
  • 請求和保持條件:一個線程因請求資源而阻塞時,對已獲得的資源保持不放。
  • 不可搶占條件:線程已獲得的資源,在未使用完之前,不能強行剝奪。
  • 循環(huán)等待條件:若干線程之間形成一種頭尾相接的循環(huán)等待資源關系。

2. 死鎖的案例分析

public class Resource {
    private String name;
    private int count;

    public Resource(String name) {
        this.name = name;
    }

    public void staticResource() {
        synchronized (this) {
            System.out.println("static resource");
            count++;
        }
    }

    public void saveResource(Resource resource) {
        synchronized (this) {
            System.out.println("save resource:" + Thread.currentThread().getName());
            resource.staticResource();
        }
    }
}
public class DeadLock {
    public static void main(String[] args) {
        Resource resource1 = new Resource("resource1");
        Resource resource2 = new Resource("resource2");
        Thread threadA = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                resource1.saveResource(resource2);
            }
        });

        Thread threadB = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                resource2.saveResource(resource1);
            }
        });

        threadA.start();
        threadB.start();
    }
}

打印結果:

save resource:Thread-0
save resource:Thread-1

死鎖原因分析:

  1. 線程 A 行為:
  • threadA 在調用 resource1.saveResource(resource2) 時:
    • 首先鎖住了 resource1 對象。
    • 然后試圖鎖住 resource2 對象,進入其 staticResource 方法。
  1. 線程B行為:
  • threadB 在調用 resource2.saveResource(resource1) 時:
    • 首先鎖住了 resource2 對象。
    • 然后試圖鎖住 resource1 對象,進入其 staticResource 方法。
  1. 死鎖發(fā)生的原因:
  • 如果 threadA 已經(jīng)鎖住 resource1,并等待鎖住 resource2,而此時 threadB 已經(jīng)鎖住 resource2 并等待鎖住 resource1,就會發(fā)生循環(huán)等待。
  • 兩個線程互相等待對方釋放鎖,從而陷入死鎖狀態(tài)。

3. 死鎖排查

  1. 首先,通過 jps 命令,查看 Java 進程的 pid。
C:\Users\shawn>jps
22568
24488 Launcher
10060 DeadLock
28076 Jps
  • 然后,通過 jstack <pid> 命令查看線程 dump 日志。當發(fā)現(xiàn)死鎖時,可以在打印的 dump 日志中找到Found one Java-level deadlock: 信息,根據(jù)信息的內容可以分析死鎖出現(xiàn)的原因。
C:\Users\shawn>jstack 23128
2024-11-23 15:38:34
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.321-b07 mixed mode):
=============================
Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x0000022b6c713f08 (object 0x000000076bdaa990, a com.atu.deadlock.Resource),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x0000022b6c7169a8 (object 0x000000076bdaa9e8, a com.atu.deadlock.Resource),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.atu.deadlock.Resource.staticResource(Resource.java:13)
        - waiting to lock <0x000000076bdaa990> (a com.atu.deadlock.Resource)
        at com.atu.deadlock.Resource.saveResource(Resource.java:21)
        - locked <0x000000076bdaa9e8> (a com.atu.deadlock.Resource)
        at com.atu.deadlock.DeadLock.lambda$main$1(DeadLock.java:18)
        at com.atu.deadlock.DeadLock$$Lambda$2/1096979270.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:750)
"Thread-0":
        at com.atu.deadlock.Resource.staticResource(Resource.java:13)
        - waiting to lock <0x000000076bdaa9e8> (a com.atu.deadlock.Resource)
        at com.atu.deadlock.Resource.saveResource(Resource.java:21)
        - locked <0x000000076bdaa990> (a com.atu.deadlock.Resource)
        at com.atu.deadlock.DeadLock.lambda$main$0(DeadLock.java:12)
        at com.atu.deadlock.DeadLock$$Lambda$1/1324119927.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:750)

Found 1 deadlock.

4. 線上發(fā)生死鎖應該怎么辦

  1. 首先保存案發(fā)現(xiàn)場,然后立刻重啟服務器(使用 java 相應的命令把整個堆棧信息保存下來),不能進一步影響用戶體驗;
  2. 暫時保證線上服務的安全,然后再利用剛才保存的信息,排查死鎖,修改代碼,重新發(fā)版。

5. 常見死鎖修復策略

前面我們說死鎖的四個必要條件,我們只需要破壞其中任意一個,就可以避免死鎖的產(chǎn)生。其中,互斥條件我們不可以破壞,因為這是互斥鎖的基本約束,其他三個條件都可以破壞。

  • 破壞請求和保持條件:線程在請求開始前,一次性申請所有的資源。
  • 破壞不可搶占條件:占用部分資源的線程進一步申請其他資源時,如果申請不到,可以主動釋放它占有的資源。
  • 破壞循環(huán)等待條件:靠按序申請資源來預防。按某一順序申請資源,釋放資源則反序釋放。破壞循環(huán)等待條件。

5.1 破壞請求和保持條件

要破壞占用資源所帶來的等待,可以一次性申請所有資源,保證同時申請這個操作是在一個臨界區(qū)中,然后通過一個單獨的角色來管理這個臨界區(qū)。

  • 這個角色有兩個很重要的功能,就是同時申請資源和同時釋放資源,并且這個角色一定是一個單例。

先定義一個 ApplyLock 類,用來實現(xiàn)統(tǒng)一鎖資源的申請,該類中有兩個方法:

  • 一個是 applyLock() 方法,用來申請鎖;
  • 另一個是free()方法,用來統(tǒng)一釋放鎖。
public class ApplyLock {
    private List<Object> list = new ArrayList<>();

    public synchronized boolean applyLock(Resource resource1, Resource resource2) {
        if (list.contains(resource1) || list.contains(resource2)) {
            return false;
        } else {
            list.add(resource1);
            list.add(resource2);
            return true;
        }
    }

    public synchronized void free(Resource resource1, Resource resource2) {
        list.remove(resource1);
        list.remove(resource2);
    }
}

修改 Resource 類,定義一個全局唯一的 ApplyLock 實例,然后在 saveResource 中調用 applyLock() 方法和 free() 方法進行統(tǒng)一鎖資源的獲取和釋放。

public class Resource {
    private String name;
    private int count;

    static ApplyLock applyLock = new ApplyLock();

    public Resource(String name) {
        this.name = name;
    }

    public void staticResource() {
        synchronized (this) {
            System.out.println("static resource");
            count++;
        }
    }

    public void saveResource(Resource resource) {
        applyLock.applyLock(this, resource);
        try {
            System.out.println("save resource:" + Thread.currentThread().getName());
            resource.staticResource();
        } finally {
            applyLock.free(this, resource);
        }
    }
}

由于當前涉及的相關資源都實現(xiàn)了一個統(tǒng)一的鎖資源獲取和釋放,從而打破了請求和保持條件。

5.2 破壞不可搶占條件

破壞不可搶占條件的核心是當前線程能夠主動釋放嘗試占有的資源,這一點 synchronized無法實現(xiàn)。

  • 原因是 synchronized 在申請不到資源時會直接進入阻塞狀態(tài),一旦線程被阻塞就無法再釋放已經(jīng)占有的資源。
  • 在 java.util.concurrent 包中的 Lock 鎖可以輕松地解決這個問題。Lock 接口中有一個 tryLock() 方法可以嘗試搶占資源,如果搶占成功則返回 true,否則返回 false,而且這個過程不會阻塞當前線程。
import java.util.concurrent.locks.ReentrantLock;

public class Resource {
    private String name;
    private int count;

    ReentrantLock lock = new ReentrantLock();

    public Resource(String name) {
        this.name = name;
    }

    public void staticResource() {
        if (lock.tryLock()) {
            try {
                System.out.println("static resource");
                count++;
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("搶鎖失敗");
        }
    }

    public void saveResource(Resource resource) {
        if (lock.tryLock()) {
            try {
                System.out.println("save resource:" + Thread.currentThread().getName());
                resource.staticResource();
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("搶鎖失敗");
        }
    }
}

5.3 破壞循環(huán)等待條件

破壞循環(huán)等待條件的基本思想是:把資源按照某種順序編號,所有鎖資源的申請都按照某種順序來獲取。 比如,可以根據(jù) hashCode 來確定加鎖順序,再根據(jù) hashCode 的大小確定加鎖的對象,實現(xiàn)代碼如下。

public class Resource {
    private String name;
    private int count;

    public Resource(String name) {
        this.name = name;
    }

    public void staticResource() {
        synchronized (this) {
            System.out.println("static resource");
            count++;
        }
    }

    public void saveResource(Resource resource) {
        Resource lock = this.hashCode() > resource.hashCode() ? this : resource;
        synchronized (lock) {
            System.out.println("save resource:" + Thread.currentThread().getName());
            resource.staticResource();
        }
    }
}

5.4 經(jīng)典的哲學家就餐問題

如圖所示:

  • 有 5 個哲學家圍坐在一張圓桌旁。
  • 每個哲學家都有一個吃飯和思考的狀態(tài)。
  • 圓桌上放著 5 根筷子(與哲學家數(shù)量相同)。
  • 哲學家必須同時拿起兩根筷子(左手和右手各一根)才能吃飯,吃完后放下筷子繼續(xù)思考。

問題描述:如果每個哲學家都拿起左邊的筷子并等待右邊的筷子,導致所有人相互等待,陷入死鎖。

  1. 編號為 0 的哲學家拿到編號為 0 的筷子,并等待編號為 1 的筷子。
  2. 編號為 1 的哲學家拿到編號為 1 的筷子,并等待編號為 2 的筷子。
  3. 編號為 2 的哲學家拿到編號為 2 的筷子,并等待編號為 3 的筷子。
  4. 編號為 3 的哲學家拿到編號為 3 的筷子,并等待編號為 4 的筷子。
  5. 編號為 4 的哲學家拿到編號為 4 的筷子,并等待編號為 0 的筷子。

哲學家就餐問題(死鎖):

public class DiningPhilosophers {
    public static class Philosopher implements Runnable {

        private Object leftChopstick;
        private Object rightChopstick;

        public Philosopher(Object leftChopstick, Object rightChopstick) {
            this.leftChopstick = leftChopstick;
            this.rightChopstick = rightChopstick;
        }

        @Override
        public void run() {
            while (true) {
                //思考
                try {
                    doAction("Thinking");
                    //吃飯
                    //拿起左邊筷子,拿起右邊筷子 放下右邊筷子 放下左邊筷子

                    synchronized (leftChopstick) {
                        doAction("Picked up left chopstick");
                        synchronized (rightChopstick) {
                            doAction("Picked up right chopstick -eating");

                            doAction("Put down right chopstick");
                        }
                        doAction("Put down left chopstick");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        private void doAction(String action) throws InterruptedException {
            System.out.println(Thread.currentThread().getName() + " " + action);
            Thread.sleep((long) Math.random() * 10);

        }
    }

    public static void main(String[] args) {
        Philosopher[] philosophers = new Philosopher[5];
        Object[] chopsticks = new Object[philosophers.length];
        for (int i = 0; i < chopsticks.length; i++) {
            chopsticks[i] = new Object();
        }
        for (int i = 0; i < philosophers.length; i++) {
            Object leftChopstick = chopsticks[i];
            Object rightChopstick = chopsticks[(i + 1) % chopsticks.length];

            philosophers[i] = new Philosopher(leftChopstick, rightChopstick);

            new Thread(philosophers[i], "哲學家" + (i + 1) + "號").start();
        }
    }
}

解決的方式有很多,這里我們通過改變一個哲學家拿筷子的順序,解決死鎖問題。

哲學家就餐的換手方案:

public class DiningPhilosophersFix {
    public static class Philosopher implements Runnable {

        private Object leftChopstick;
        private Object rightChopstick;

        public Philosopher(Object leftChopstick, Object rightChopstick) {
            this.leftChopstick = leftChopstick;
            this.rightChopstick = rightChopstick;
        }

        @Override
        public void run() {
            while (true) {
                //思考
                try {
                    doAction("Thinking");
                    //吃飯
                    //拿起左邊筷子,拿起右邊筷子 放下右邊筷子 放下左邊筷子

                    synchronized (leftChopstick) {
                        doAction("Picked up left chopstick");
                        synchronized (rightChopstick) {
                            doAction("Picked up right chopstick -eating");

                            doAction("Put down right chopstick");
                        }
                        doAction("Put down left chopstick");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        private void doAction(String action) throws InterruptedException {
            System.out.println(Thread.currentThread().getName() + " " + action);
            Thread.sleep((long) Math.random() * 10);

        }
    }

    public static void main(String[] args) {
        Philosopher[] philosophers = new Philosopher[5];
        Object[] chopsticks = new Object[philosophers.length];
        for (int i = 0; i < chopsticks.length; i++) {
            chopsticks[i] = new Object();
        }
        for (int i = 0; i < philosophers.length; i++) {
            Object leftChopstick = chopsticks[i];
            Object rightChopstick = chopsticks[(i + 1) % chopsticks.length];

            if (i == philosophers.length - 1) {
                philosophers[i] = new Philosopher(rightChopstick, leftChopstick);
            } else {
                philosophers[i] = new Philosopher(leftChopstick, rightChopstick);

            }
            new Thread(philosophers[i], "哲學家" + (i + 1) + "號").start();
        }
    }
}

6. 實際工程中如何有效避免死鎖

  1. 設置超時時間:
    • Lock 的 tryLock(long timeout, TimeUnit unit)
    • synchronized 不具備嘗試鎖的能力。
  2. 使用最小化鎖:減少鎖的數(shù)量和作用范圍,能顯著降低死鎖發(fā)生的概率。
  3. 避免嵌套鎖:盡量避免線程在持有一個鎖時嘗試獲取另一個鎖。
  4. 使用高級并發(fā)工具:Semaphore、CountDownLatch、ReadWriteLock。

7. 其他活性故障

死鎖是最常見的活躍性問題,除了死鎖之外,還有一些類似的問題,會導致程序無法順利執(zhí)行,統(tǒng)稱為活躍性問題。

7.1 活鎖

什么是活鎖:線程處于一種“忙碌但無效”的狀態(tài),始終無法完成任務。(俗稱內耗)

特點:

  • 程序一直在運行,但是一直在做沒有意義的工作。

活鎖代碼示例:

public class LiveLock {
    static class Spoon {
        private Diner owner; //就餐者

        public synchronized void use() {
            System.out.printf("%s has eaten!", owner.name);
        }

        public Spoon(Diner owner) {
            this.owner = owner;
        }

        public Diner getOwner() {
            return owner;
        }

        public void setOwner(Diner owner) {
            this.owner = owner;
        }

    }

    static class Diner {
        private String name;
        private boolean isHungry;

        public Diner(String name) {
            this.name = name;
            isHungry = true;
        }

        public void eatWith(Spoon spoon, Diner spouse) {
            while (isHungry) { //只有餓的情況下才能進來

                //問題在此處:一直再謙讓
                if (spouse.isHungry) {
                    System.out.println(name + ": 親愛的" + spouse.name + "你先吃吧");
                    spoon.setOwner(spouse);
                    continue;
                }

                spoon.use();
                isHungry = false;
                System.out.println(name + ": 我吃好了");
                spoon.setOwner(spouse);
            }
        }
    }

    public static void main(String[] args) {
        Diner husband = new Diner("牛郎");
        Diner wife = new Diner("織女");

        Spoon spoon = new Spoon(husband);

        new Thread(new Runnable() {
            @Override
            public void run() {
                husband.eatWith(spoon, wife);
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                wife.eatWith(spoon, husband);
            }
        }).start();
    }
}

打印結果:

牛郎: 親愛的織女你先吃吧
織女: 親愛的牛郎你先吃吧
牛郎: 親愛的織女你先吃吧
織女: 親愛的牛郎你先吃吧
牛郎: 親愛的織女你先吃吧
織女: 親愛的牛郎你先吃吧
牛郎: 親愛的織女你先吃吧
織女: 親愛的牛郎你先吃吧
牛郎: 親愛的織女你先吃吧
織女: 親愛的牛郎你先吃吧
...

解決:以太網(wǎng)的指數(shù)退避算法,加入隨機因素。

public class LiveLockFix {
    static class Spoon {
        private Diner owner; //就餐者

        public synchronized void use() {
            System.out.printf("%s has eaten!", owner.name);
        }

        public Spoon(Diner owner) {
            this.owner = owner;
        }

        public Diner getOwner() {
            return owner;
        }

        public void setOwner(Diner owner) {
            this.owner = owner;
        }

    }

    static class Diner {
        private String name;
        private boolean isHungry;

        public Diner(String name) {
            this.name = name;
            isHungry = true;
        }

        public void eatWith(Spoon spoon, Diner spouse) {
            while (isHungry) { //只有餓的情況下才能進來
                Random random = new Random();
                //問題在此處:一直再謙讓
                if (spouse.isHungry && random.nextInt(10) < 9) {
                    System.out.println(name + ": 親愛的" + spouse.name + "你先吃吧");
                    spoon.setOwner(spouse);
                    continue;
                }

                spoon.use();
                isHungry = false;
                System.out.println(name + ": 我吃好了");
                spoon.setOwner(spouse);
            }
        }
    }

    public static void main(String[] args) {
        Diner husband = new Diner("牛郎");
        Diner wife = new Diner("織女");

        Spoon spoon = new Spoon(husband);

        new Thread(new Runnable() {
            @Override
            public void run() {
                husband.eatWith(spoon, wife);
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                wife.eatWith(spoon, husband);
            }
        }).start();
    }
}

活鎖的解決方法:

  1. 增加隨機性:通過引入隨機的等待時間(如使用隨機退避算法),避免線程/進程按照相同的模式重復操作。
  2. 設置重試次數(shù)或超時:為線程的嘗試次數(shù)或時間限制設置一個閾值。如果超過限制,則采用其他策略,如強制退出或降級處理。

7.2 饑餓

線程饑餓問題其實指的公平性問題。是指某個線程因無法獲取所需資源而無法執(zhí)行,一直處于等待狀態(tài)的情況。

饑餓代碼示例:

public class StarvationExample {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        Thread highPriorityThread = new Thread(() -> {
            synchronized (lock) {
                while (true) {
                    System.out.println("High priority thread is running...");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        Thread lowPriorityThread = new Thread(() -> {
            synchronized (lock) {
                while (true) {
                    System.out.println("Low priority thread is running...");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        highPriorityThread.setPriority(Thread.MAX_PRIORITY);
        lowPriorityThread.setPriority(Thread.MIN_PRIORITY);

        highPriorityThread.start();
        lowPriorityThread.start();
    }
}

問題解析:

  • 上述代碼中,高優(yōu)先級線程(highPriorityThread)由于持有 lock 鎖資源,它可能會導致低優(yōu)先級線程(lowPriorityThread)一直無法執(zhí)行,從而出現(xiàn)線程饑餓的現(xiàn)象。

線程饑餓原因:

  1. 資源分配不均: 如果一個線程的優(yōu)先級一直較低,而系統(tǒng)的調度策略總是優(yōu)先執(zhí)行高優(yōu)先級的線程,那么低優(yōu)先級線程就可能一直得不到執(zhí)行的機會,從而發(fā)生饑餓。
  2. 線程被無限阻塞:當獲得鎖的線程需要執(zhí)行無限時間長的操作時(比如 IO 或者無限循環(huán)),那么后面的線程將會被無限阻塞,導致被餓死。

饑餓的解決方法:

  1. 設置合適的線程優(yōu)先級
  2. 使用公平性調度算法

8. 總結

  1. 死鎖
  • 特點:兩個或多個線程(進程)相互等待對方釋放資源,導致所有線程都無法繼續(xù)執(zhí)行。
  • 解決方法:避免一個線程持有多個資源的情況,或使用超時機制,如果一個線程在一定時間內沒能獲得鎖,就放棄等待。
  1. 活鎖
  • 特點:線程仍然在運行,但由于不斷地響應對方,始終沒有實際進展。
  • 解決方法:為避免活鎖,可以設置超時機制,或者使用協(xié)調機制來避免線程之間過度的反應。
  1. 饑餓
  • 特點:線程無法獲得執(zhí)行機會,但其他線程仍然在運行,造成某些線程得不到資源。
  • 解決方法:使用公平鎖或合理的優(yōu)先級策略,確保每個線程都有機會執(zhí)行,不會被長時間忽略。

以上就是Java程序死鎖問題定位與解決方法的詳細內容,更多關于Java程序死鎖的資料請關注腳本之家其它相關文章!

相關文章

  • java獲取鍵盤輸入的數(shù)字,并進行排序的方法

    java獲取鍵盤輸入的數(shù)字,并進行排序的方法

    今天小編就為大家分享一篇java獲取鍵盤輸入的數(shù)字,并進行排序的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-07-07
  • 關于Java利用反射實現(xiàn)動態(tài)運行一行或多行代碼

    關于Java利用反射實現(xiàn)動態(tài)運行一行或多行代碼

    這篇文章主要介紹了關于Java利用反射實現(xiàn)動態(tài)運行一行或多行代碼,借鑒了別人的方法和書上的內容,最后將題目完成了,和大家一起分享以下解決方法,需要的朋友可以參考下
    2023-04-04
  • Java操作壓縮包解壓過程詳解

    Java操作壓縮包解壓過程詳解

    這篇文章主要介紹了Java操作壓縮包解壓過程,項目開發(fā)中,總會遇到解壓縮文件的時候,比如用戶下載多個文件時,服務端可以將多個文件壓縮成一個文件,用戶上傳資料時,允許上傳壓縮文件,服務端進行解壓讀取每一個文件,使用場景是很多的,下面來詳細講解,需要的朋友可以參考下
    2024-10-10
  • 詳解Java使用sqlite 數(shù)據(jù)庫如何生成db文件

    詳解Java使用sqlite 數(shù)據(jù)庫如何生成db文件

    這篇文章主要介紹了詳解Java 操作sqllite 數(shù)據(jù)庫如何生成db文件的相關資料,需要的朋友可以參考下
    2017-07-07
  • SpringBoot使用索引來優(yōu)化SQL查詢的詳細步驟

    SpringBoot使用索引來優(yōu)化SQL查詢的詳細步驟

    在 Spring Boot 項目中使用索引來優(yōu)化 SQL 查詢是提升數(shù)據(jù)庫性能最常用的方法之一,下面是詳細的步驟和實踐指南,并通過代碼示例講解的非常詳細,需要的朋友可以參考下
    2025-05-05
  • Java的信號量semaphore講解

    Java的信號量semaphore講解

    這篇文章主要介紹了Java的信號量semaphore講解,Semaphore底層是基于AbstractQueuedSynchronizer來實現(xiàn)的,Semaphore稱為計數(shù)信號量,它允許n個任務同時訪問某個資源,需要的朋友可以參考下
    2023-12-12
  • 基于SpringBoot+Beetl實現(xiàn)動態(tài)數(shù)據(jù)庫DDL操作的實現(xiàn)指南

    基于SpringBoot+Beetl實現(xiàn)動態(tài)數(shù)據(jù)庫DDL操作的實現(xiàn)指南

    你是否曾經(jīng)為了應對頻繁的業(yè)務變化而疲于修改數(shù)據(jù)庫表結構?是否因為手動編寫SQL腳本而感到枯燥乏味?本文,我要分享一個強大的技術組——SpringBoot?+?Beetl,它可以幫助我們實現(xiàn)動態(tài)數(shù)據(jù)庫DDL操作,需要的朋友可以參考下
    2026-01-01
  • 沒有外網(wǎng)IDEA離線使用maven倉庫的方法

    沒有外網(wǎng)IDEA離線使用maven倉庫的方法

    這篇文章主要介紹了沒有外網(wǎng),IDEA如何離線使用maven倉庫,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-08-08
  • SpringBoot使用easy-captcha 實現(xiàn)驗證碼登錄功能(解決思路)

    SpringBoot使用easy-captcha 實現(xiàn)驗證碼登錄功能(解決思路)

    文章介紹了如何使用Spring Boot和Easy-Captcha實現(xiàn)驗證碼登錄功能,后端通過Easy-Captcha生成驗證碼并存儲在Redis中,前端獲取驗證碼并顯示給用戶,登錄時,前端將用戶輸入的驗證碼和標識符發(fā)送到后端進行驗證,感興趣的朋友跟隨小編一起看看吧
    2025-02-02
  • SpringBoot接口加密與解密的實現(xiàn)

    SpringBoot接口加密與解密的實現(xiàn)

    這篇文章主要介紹了SpringBoot接口加密與解密的實現(xiàn)
    2023-10-10

最新評論

张家川| 沂南县| 禹城市| 凤阳县| 揭西县| 曲周县| 屏东市| 察隅县| 古浪县| 明水县| 时尚| 华阴市| 乌鲁木齐市| 新巴尔虎右旗| 郸城县| 嵩明县| 茶陵县| 鞍山市| 翼城县| 玛多县| 喀喇| 吉木乃县| 广平县| 清水县| 冷水江市| 株洲市| 大英县| 岳池县| 霍州市| 万宁市| 佳木斯市| 馆陶县| 博爱县| 湾仔区| 邓州市| 手机| 五常市| 聂拉木县| 巴中市| 武胜县| 长治县|