亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

目錄
案例2-使用synchronized 實(shí)現(xiàn)單機(jī)鎖 " >案例2-使用synchronized 實(shí)現(xiàn)單機(jī)鎖
案例3-使用SETNX實(shí)現(xiàn)分布式鎖 " >案例3-使用SETNX實(shí)現(xiàn)分布式鎖
案例4-加入過期時(shí)間 " >案例4-加入過期時(shí)間
案例5-Redisson分散式鎖定 " >案例5-Redisson分散式鎖定
引入依賴" >引入依賴
初始化客戶端" >初始化客戶端
Redisson實(shí)現(xiàn)分布式鎖" >Redisson實(shí)現(xiàn)分布式鎖
Redisson 分布式鎖實(shí)現(xiàn)原理圖" >Redisson 分布式鎖實(shí)現(xiàn)原理圖
Redisson 底層源碼分析 " >Redisson 底層源碼分析
首頁 Java java教程 分散式鎖:5個(gè)案例,從入門到入土

分散式鎖:5個(gè)案例,從入門到入土

Aug 24, 2023 pm 02:48 PM
java 分散式鎖

今天要跟大家分享的是分散式鎖定#,本文使用五個(gè)案例、圖、原始碼分析等來分析。

常見的synchronized、Lock等這些鎖都是基於單一JVM的實(shí)作的,如果分散式場景下怎麼辦呢?這時(shí)候分散式鎖就出現(xiàn)了。

關(guān)於分散式的實(shí)作方案,在業(yè)界流行的有三種:

1、基於資料庫

2、基於Redis

3、基於Zookeeper

另外,還有使用etcd、consul來實(shí)現(xiàn)的。

在開發(fā)中使用最多的是RedisZookeeper兩個(gè)方案,而兩個(gè)方案中最複雜的,最容易出問題的就是Redis的實(shí)作方案,所以,我們今天就來把Redis實(shí)作方案都聊聊。

本文主要內(nèi)容

分散式鎖:5個(gè)案例,從入門到入土


分散式鎖定場景

估計(jì)部分朋友還不太清楚分散式的使用場景,下面我簡單羅列三種:分散式鎖:5個(gè)案例,從入門到入土


#「案例1

## #####如下程式碼模擬了下單減庫存的場景,我們分析下在高並發(fā)場景下會存在什麼問題###
@RestController
public class IndexController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 模擬下單減庫存的場景
     * @return
     */
    @RequestMapping(value = "/duduct_stock")
    public String deductStock(){
        // 從redis 中拿當(dāng)前庫存的值
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if(stock > 0){
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock",realStock + "");
            System.out.println("扣減成功,剩余庫存:" + realStock);
        }else{
            System.out.println("扣減失敗,庫存不足");
        }
        return "end";
    }
}
###假設(shè)在###Redis###中庫存(stock)初始值是100。 ######現(xiàn)在有5個(gè)客戶端同時(shí)請求該接口,可能就會存在同時(shí)執(zhí)行###
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));

這行代碼,獲取到的值都為100,緊跟著判斷大于0后都進(jìn)行-1操作,最后設(shè)置到redis 中的值都為99。但正常執(zhí)行完成后redis中的值應(yīng)為 95。

案例2-使用synchronized 實(shí)現(xiàn)單機(jī)鎖

在遇到案例1的問題后,大部分人的第一反應(yīng)都會想到加鎖來控制事務(wù)的原子性,如下代碼所示:

@RequestMapping(value = "/duduct_stock")
public String deductStock(){
    synchronized (this){
        // 從redis 中拿當(dāng)前庫存的值
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if(stock > 0){
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock",realStock + "");
            System.out.println("扣減成功,剩余庫存:" + realStock);
        }else{
            System.out.println("扣減失敗,庫存不足");
        }
    }
    return "end";
}

現(xiàn)在當(dāng)有多個(gè)請求訪問該接口時(shí),同一時(shí)刻只有一個(gè)請求可進(jìn)入方法體中進(jìn)行庫存的扣減,其余請求等候。

但我們都知道,synchronized 鎖是屬于JVM級別的,也就是我們俗稱的“單機(jī)鎖”。但現(xiàn)在基本大部分公司使用的都是集群部署,現(xiàn)在我們思考下以上代碼在集群部署的情況下還能保證庫存數(shù)據(jù)的一致性嗎?

分散式鎖:5個(gè)案例,從入門到入土

答案是不能,如上圖所示,請求經(jīng)Nginx分發(fā)后,可能存在多個(gè)服務(wù)同時(shí)從Redis中獲取庫存數(shù)據(jù),此時(shí)只加synchronized (單機(jī)鎖)是無效的,并發(fā)越高,出現(xiàn)問題的幾率就越大。

案例3-使用SETNX實(shí)現(xiàn)分布式鎖

setnx:將 key 的值設(shè)為 value,當(dāng)且僅當(dāng) key 不存在。

若給定 key 已經(jīng)存在,則 setnx 不做任何動作。

使用setnx實(shí)現(xiàn)簡單的分布式鎖:

/**
 * 模擬下單減庫存的場景
 * @return
 */
@RequestMapping(value = "/duduct_stock")
public String deductStock(){
    String lockKey = "product_001";
    // 使用 setnx 添加分布式鎖
    // 返回 true 代表之前redis中沒有key為 lockKey 的值,并已進(jìn)行成功設(shè)置
    // 返回 false 代表之前redis中已經(jīng)存在 lockKey 這個(gè)key了
    Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "wangcp");
    if(!result){
        // 代表已經(jīng)加鎖了
        return "error_code";
    }

    // 從redis 中拿當(dāng)前庫存的值
    int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
    if(stock > 0){
        int realStock = stock - 1;
        stringRedisTemplate.opsForValue().set("stock",realStock + "");
        System.out.println("扣減成功,剩余庫存:" + realStock);
    }else{
        System.out.println("扣減失敗,庫存不足");
    }

    // 釋放鎖
    stringRedisTemplate.delete(lockKey);
    return "end";
}

我們知道 Redis 是單線程執(zhí)行,現(xiàn)在再看案例2中的流程圖時(shí),哪怕高并發(fā)場景下多個(gè)請求都執(zhí)行到了setnx的代碼,redis會根據(jù)請求的先后順序進(jìn)行排列,只有排列在隊(duì)頭的請求才能設(shè)置成功。其它請求只能返回“error_code”。

當(dāng)setnx設(shè)置成功后,可執(zhí)行業(yè)務(wù)代碼對庫存扣減,執(zhí)行完成后對鎖進(jìn)行釋放。

我們再來思考下以上代碼已經(jīng)完美實(shí)現(xiàn)分布式鎖了嗎?能夠支撐高并發(fā)場景嗎?答案并不是,上面的代碼還是存在很多問題的,離真正的分布式鎖還差的很遠(yuǎn)。

我們分析一下,上面的代碼存在的問題:

死鎖:假如第一個(gè)請求在setnx加鎖完成后,執(zhí)行業(yè)務(wù)代碼時(shí)出現(xiàn)了異常,那釋放鎖的代碼就無法執(zhí)行,后面所有的請求也都無法進(jìn)行操作了。

針對死鎖的問題,我們對代碼再次進(jìn)行優(yōu)化,添加try-finally,在finally中添加釋放鎖代碼,這樣無論如何都會執(zhí)行釋放鎖代碼,如下所示:

/**
     * 模擬下單減庫存的場景
     * @return
     */
@RequestMapping(value = "/duduct_stock")
public String deductStock(){
    String lockKey = "product_001";

    try{
        // 使用 setnx 添加分布式鎖
        // 返回 true 代表之前redis中沒有key為 lockKey 的值,并已進(jìn)行成功設(shè)置
        // 返回 false 代表之前redis中已經(jīng)存在 lockKey 這個(gè)key了
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "wangcp");
        if(!result){
            // 代表已經(jīng)加鎖了
            return "error_code";
        }
        // 從redis 中拿當(dāng)前庫存的值
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if(stock > 0){
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock",realStock + "");
            System.out.println("扣減成功,剩余庫存:" + realStock);
        }else{
            System.out.println("扣減失敗,庫存不足");
        }
    }finally {
        // 釋放鎖
        stringRedisTemplate.delete(lockKey);
    }

    return "end";
}

經(jīng)過改進(jìn)后的代碼是否還存在問題呢?我們思考正常執(zhí)行的情況下應(yīng)該是沒有問題,但我們假設(shè)請求在執(zhí)行到業(yè)務(wù)代碼時(shí)服務(wù)突然宕機(jī)了,或者正巧你的運(yùn)維同事重新發(fā)版,粗暴的 kill -9 掉了呢,那代碼還能執(zhí)行 finally 嗎?

案例4-加入過期時(shí)間

針對想到的問題,對代碼再次進(jìn)行優(yōu)化,加入過期時(shí)間,這樣即便出現(xiàn)了上述的問題,在時(shí)間到期后鎖也會自動釋放掉,不會出現(xiàn)“死鎖”的情況。

@RequestMapping(value = "/duduct_stock")
public String deductStock(){
    String lockKey = "product_001";

    try{
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"wangcp",10,TimeUnit.SECONDS);
        if(!result){
            // 代表已經(jīng)加鎖了
            return "error_code";
        }
        // 從redis 中拿當(dāng)前庫存的值
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if(stock > 0){
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock",realStock + "");
            System.out.println("扣減成功,剩余庫存:" + realStock);
        }else{
            System.out.println("扣減失敗,庫存不足");
        }
    }finally {
        // 釋放鎖
        stringRedisTemplate.delete(lockKey);
    }

    return "end";
}

現(xiàn)在我們再思考一下,給鎖加入過期時(shí)間后就可以了嗎?就可以完美運(yùn)行不出問題了嗎?

超時(shí)時(shí)間設(shè)置的10s真的合適嗎?如果不合適設(shè)置多少秒合適呢?如下圖所示

分散式鎖:5個(gè)案例,從入門到入土
圖片

假設(shè)同一時(shí)間有三個(gè)請求。

  • 請求1先加鎖後需執(zhí)行15秒,但執(zhí)行到10秒時(shí)鎖定失效釋放。
  • 請求2進(jìn)入後加鎖執(zhí)行,在請求2執(zhí)行到5秒時(shí),請求1執(zhí)行完成進(jìn)行鎖定釋放,但此時(shí)釋放掉的是請求2的鎖定。
  • 請求3在請求2執(zhí)行5秒時(shí)開始執(zhí)行,但在執(zhí)行到3秒時(shí)請求2執(zhí)行完成將請求3的鎖定進(jìn)行釋放。

我們現(xiàn)在只是模擬3個(gè)請求便可看出問題,如果在真正高並發(fā)的場景下,可能鎖就會面臨「一直失效」或「永久失效」。

那麼具體問題出在哪裡呢?總結(jié)為以下幾點(diǎn):

  • 1.存在請求釋放鎖定時(shí)釋放掉的並不是自己的鎖定
  • 2.超時(shí)時(shí)間過短,存在程式碼未執(zhí)行完便自動釋放

針對問題我們思考對應(yīng)的解決方法:

  • 針對問題1,我們想到在請求進(jìn)入時(shí)產(chǎn)生一個(gè)唯一id,使用該唯一id作為鎖的value值,釋放時(shí)先進(jìn)行獲取比對,比對相同時(shí)再進(jìn)行釋放,這樣就可以解決釋放掉其它請求鎖的問題。
  • 針對問題2,我們思考不斷的延長過期時(shí)間真的適合嗎?設(shè)定短了有超時(shí)自動釋放的問題,設(shè)定長了又會出現(xiàn)宕機(jī)後一段時(shí)間鎖無法釋放的問題,雖然不會再出現(xiàn)「死鎖」。針對這個(gè)問題,如何解決呢?

案例5-Redisson分散式鎖定

Spring Boot整合Redisson步驟

#

引入依賴

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.6.5</version>
</dependency>

初始化客戶端

@Bean
public RedissonClient redisson(){
    // 單機(jī)模式
    Config config = new Config();
    config.useSingleServer().setAddress("redis://192.168.3.170:6379").setDatabase(0);
    return Redisson.create(config);
}

Redisson實(shí)現(xiàn)分布式鎖

@RestController
public class IndexController {

    @Autowired
    private RedissonClient redisson;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 模擬下單減庫存的場景
     * @return
     */
    @RequestMapping(value = "/duduct_stock")
    public String deductStock(){
        String lockKey = "product_001";
        // 1.獲取鎖對象
        RLock redissonLock = redisson.getLock(lockKey);
        try{
            // 2.加鎖
            redissonLock.lock();  // 等價(jià)于 setIfAbsent(lockKey,"wangcp",10,TimeUnit.SECONDS);
            // 從redis 中拿當(dāng)前庫存的值
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if(stock > 0){
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock",realStock + "");
                System.out.println("扣減成功,剩余庫存:" + realStock);
            }else{
                System.out.println("扣減失敗,庫存不足");
            }
        }finally {
            // 3.釋放鎖
            redissonLock.unlock();
        }
        return "end";
    }
}

Redisson 分布式鎖實(shí)現(xiàn)原理圖

分散式鎖:5個(gè)案例,從入門到入土
圖片

Redisson 底層源碼分析

我們點(diǎn)擊lock()方法,查看源碼,最終看到以下代碼

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);

        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                  "if (redis.call(&#39;exists&#39;, KEYS[1]) == 0) then " +
                      "redis.call(&#39;hset&#39;, KEYS[1], ARGV[2], 1); " +
                      "redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[1]); " +
                      "return nil; " +
                  "end; " +
                  "if (redis.call(&#39;hexists&#39;, KEYS[1], ARGV[2]) == 1) then " +
                      "redis.call(&#39;hincrby&#39;, KEYS[1], ARGV[2], 1); " +
                      "redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[1]); " +
                      "return nil; " +
                  "end; " +
                  "return redis.call(&#39;pttl&#39;, KEYS[1]);",
                    Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}

沒錯,加鎖最終執(zhí)行的就是這段lua 腳本語言。

if (redis.call(&#39;exists&#39;, KEYS[1]) == 0) then 
    redis.call(&#39;hset&#39;, KEYS[1], ARGV[2], 1); 
    redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[1]); 
    return nil; 
end;

腳本的主要邏輯為:

  • exists 判斷 key 是否存在
  • 當(dāng)判斷不存在則設(shè)置 key
  • 然后給設(shè)置的key追加過期時(shí)間

這樣來看其實(shí)和我們前面案例中的實(shí)現(xiàn)方法好像沒什么區(qū)別,但實(shí)際上并不是。

這段lua腳本命令在Redis中執(zhí)行時(shí),會被當(dāng)成一條命令來執(zhí)行,能夠保證原子性,故要不都成功,要不都失敗。

我們在源碼中看到Redssion的許多方法實(shí)現(xiàn)中很多都用到了lua腳本,這樣能夠極大的保證命令執(zhí)行的原子性。

下面是Redisson鎖自動“續(xù)命”源碼:

private void scheduleExpirationRenewal(final long threadId) {
    if (expirationRenewalMap.containsKey(getEntryName())) {
        return;
    }

    Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
        @Override
        public void run(Timeout timeout) throws Exception {

            RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                                                                     "if (redis.call(&#39;hexists&#39;, KEYS[1], ARGV[2]) == 1) then " +
                                                                     "redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[1]); " +
                                                                     "return 1; " +
                                                                     "end; " +
                                                                     "return 0;",
                                                                     Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));

            future.addListener(new FutureListener<Boolean>() {
                @Override
                public void operationComplete(Future<Boolean> future) throws Exception {
                    expirationRenewalMap.remove(getEntryName());
                    if (!future.isSuccess()) {
                        log.error("Can&#39;t update lock " + getName() + " expiration", future.cause());
                        return;
                    }

                    if (future.getNow()) {
                        // reschedule itself
                        scheduleExpirationRenewal(threadId);
                    }
                }
            });
        }
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

    if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) {
        task.cancel();
    }
}

這段代碼是在加鎖后開啟一個(gè)守護(hù)線程進(jìn)行監(jiān)聽。Redisson超時(shí)時(shí)間默認(rèn)設(shè)置30s,線程每10s調(diào)用一次判斷鎖還是否存在,如果存在則延長鎖的超時(shí)時(shí)間。

現(xiàn)在,我們再回過頭來看看案例5中的加鎖代碼與原理圖,其實(shí)完善到這種程度已經(jīng)可以滿足很多公司的使用了,并且很多公司也確實(shí)是這樣用的。但我們再思考下是否還存在問題呢?例如以下場景:

  • 眾所周知Redis在實(shí)際部署使用時(shí)都是叢集部署的,那在高並發(fā)場景下我們加鎖,當(dāng)把key寫入到master節(jié)點(diǎn)後,master還未同步到slave節(jié)點(diǎn)時(shí)master宕機(jī)了,原有的slave節(jié)點(diǎn)經(jīng)過選舉變?yōu)榱诵碌膍aster節(jié)點(diǎn),此時(shí)可能就會出現(xiàn)鎖定失效問題。
  • 透過分散式鎖定的實(shí)作機(jī)制我們知道,高並發(fā)場景下只有加鎖成功的請求可以繼續(xù)處理業(yè)務(wù)邏輯。那就出現(xiàn)了大夥都來加鎖,但有且僅有一個(gè)加鎖成功了,剩餘的都在等待。其實(shí)分散式鎖與高並發(fā)在語意上就是相違背的,我們的請求雖然都是並發(fā),但Redis幫我們把請求進(jìn)行了排隊(duì)執(zhí)行,也就是把我們的並行轉(zhuǎn)為了串行。串行執(zhí)行的程式碼肯定不存在並發(fā)問題了,但是程式的效能肯定也會因此受到影響。

針對這些問題,我們再次思考解決方案

  • 在思考解決方案時(shí)我們首先想到CAP原則(一致性、可用性、分區(qū)容錯性),那麼現(xiàn)在的Redis就是滿足AP(可用性、分區(qū)容錯性),如果想要解決該問題我們就需要尋找滿足CP(一致性、分區(qū)容錯性)的分散式系統(tǒng)。首先想到的就是Zookeeper,Zookeeper的群集間資料同步機(jī)制是當(dāng)主節(jié)點(diǎn)接收資料後不會立即回傳給客戶端成功的回饋,它會先與子節(jié)點(diǎn)進(jìn)行數(shù)據(jù)同步,半數(shù)以上的節(jié)點(diǎn)都完成同步後才會通知客戶端接收成功。且如果主節(jié)點(diǎn)宕機(jī)後,根據(jù)ZookeeperZab協(xié)議(Zookeeper原子廣播)重新選舉的主節(jié)點(diǎn)一定是已經(jīng)同步成功的。

    那麼問題來了,RedissonZookeeper分散式鎖定我們要如何選擇呢?答案是如果並發(fā)量沒有那麼高,可以用Zookeeper來做分散式鎖,但是它的並發(fā)能力遠(yuǎn)遠(yuǎn)不如Redis。如果你對並發(fā)要求比較高的話,那就用Redis,偶爾出現(xiàn)的主從架構(gòu)鎖失效的問題其實(shí)是可以容忍的。

  • 關(guān)於第二個(gè)提升效能的問題,我們可以參考ConcurrentHashMap的鎖定分段技術(shù)的思想,例如我們程式碼的庫存量目前為1000,那我們可以分為10段,每段100,然後對每段分別加鎖,這樣就可以同時(shí)執(zhí)行10個(gè)請求的加鎖與處理,當(dāng)然有要求的同學(xué)還可以繼續(xù)細(xì)分。但其實(shí)RedisQps已經(jīng)達(dá)到10W 了,沒有特別高並發(fā)量的場景下也是完全夠用的。

以上是分散式鎖:5個(gè)案例,從入門到入土的詳細(xì)內(nèi)容。更多資訊請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

本網(wǎng)站聲明
本文內(nèi)容由網(wǎng)友自願投稿,版權(quán)歸原作者所有。本站不承擔(dān)相應(yīng)的法律責(zé)任。如發(fā)現(xiàn)涉嫌抄襲或侵權(quán)的內(nèi)容,請聯(lián)絡(luò)admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費(fèi)脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅(qū)動的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費(fèi)的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費(fèi)的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強(qiáng)大的PHP整合開發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Laravel 教程
1597
29
PHP教程
1488
72
VSCODE設(shè)置。 JSON位置 VSCODE設(shè)置。 JSON位置 Aug 01, 2025 am 06:12 AM

settings.json文件位於用戶級或工作區(qū)級路徑,用於自定義VSCode設(shè)置。 1.用戶級路徑:Windows為C:\Users\\AppData\Roaming\Code\User\settings.json,macOS為/Users//Library/ApplicationSupport/Code/User/settings.json,Linux為/home//.config/Code/User/settings.json;2.工作區(qū)級路徑:項(xiàng)目根目錄下的.vscode/settings

如何使用JDBC處理Java的交易? 如何使用JDBC處理Java的交易? Aug 02, 2025 pm 12:29 PM

要正確處理JDBC事務(wù),必須先關(guān)閉自動提交模式,再執(zhí)行多個(gè)操作,最後根據(jù)結(jié)果提交或回滾;1.調(diào)用conn.setAutoCommit(false)以開始事務(wù);2.執(zhí)行多個(gè)SQL操作,如INSERT和UPDATE;3.若所有操作成功則調(diào)用conn.commit(),若發(fā)生異常則調(diào)用conn.rollback()確保數(shù)據(jù)一致性;同時(shí)應(yīng)使用try-with-resources管理資源,妥善處理異常並關(guān)閉連接,避免連接洩漏;此外建議使用連接池、設(shè)置保存點(diǎn)實(shí)現(xiàn)部分回滾,並保持事務(wù)盡可能短以提升性能。

Python Itertools組合示例 Python Itertools組合示例 Jul 31, 2025 am 09:53 AM

itertools.combinations用於生成從可迭代對像中選取指定數(shù)量元素的所有不重複組合(順序無關(guān)),其用法包括:1.從列表中選2個(gè)元素組合,如('A','B')、('A','C')等,避免重複順序;2.對字符串取3個(gè)字符組合,如"abc"、"abd",適用於子序列生成;3.求兩數(shù)之和等於目標(biāo)值的組合,如1 5=6,簡化雙重循環(huán)邏輯;組合與排列的區(qū)別在於順序是否重要,combinations視AB與BA為相同,而permutations視為不同;

在Java的掌握依賴注入春季和Guice 在Java的掌握依賴注入春季和Guice Aug 01, 2025 am 05:53 AM

依賴性(di)IsadesignpatternwhereObjectsReceivedenciesenciesExtern上,推廣looseSecouplingAndEaseerTestingThroughConstructor,setter,orfieldInjection.2.springfraMefringframeWorkSannotationsLikeLikeLike@component@component,@component,@service,@autowiredwithjava-service和@autowiredwithjava-ligatiredwithjava-lase-lightike

Python Pytest夾具示例 Python Pytest夾具示例 Jul 31, 2025 am 09:35 AM

fixture是用於為測試提供預(yù)設(shè)環(huán)境或數(shù)據(jù)的函數(shù),1.使用@pytest.fixture裝飾器定義fixture;2.在測試函數(shù)中以參數(shù)形式註入fixture;3.yield之前執(zhí)行setup,之後執(zhí)行teardown;4.通過scope參數(shù)控製作用域,如function、module等;5.將共用fixture放在conftest.py中實(shí)現(xiàn)跨文件共享,從而提升測試的可維護(hù)性和復(fù)用性。

如何使用Java的日曆? 如何使用Java的日曆? Aug 02, 2025 am 02:38 AM

使用java.time包中的類替代舊的Date和Calendar類;2.通過LocalDate、LocalDateTime和LocalTime獲取當(dāng)前日期時(shí)間;3.使用of()方法創(chuàng)建特定日期時(shí)間;4.利用plus/minus方法不可變地增減時(shí)間;5.使用ZonedDateTime和ZoneId處理時(shí)區(qū);6.通過DateTimeFormatter格式化和解析日期字符串;7.必要時(shí)通過Instant與舊日期類型兼容;現(xiàn)代Java中日期處理應(yīng)優(yōu)先使用java.timeAPI,它提供了清晰、不可變且線

了解Java虛擬機(jī)(JVM)內(nèi)部 了解Java虛擬機(jī)(JVM)內(nèi)部 Aug 01, 2025 am 06:31 AM

TheJVMenablesJava’s"writeonce,runanywhere"capabilitybyexecutingbytecodethroughfourmaincomponents:1.TheClassLoaderSubsystemloads,links,andinitializes.classfilesusingbootstrap,extension,andapplicationclassloaders,ensuringsecureandlazyclassloa

故障排除常見的java`ofmemoryError`場景'' 故障排除常見的java`ofmemoryError`場景'' Jul 31, 2025 am 09:07 AM

java.lang.OutOfMemoryError:Javaheapspace表示堆內(nèi)存不足,需檢查大對象處理、內(nèi)存洩漏及堆設(shè)置,通過堆轉(zhuǎn)儲分析工具定位並優(yōu)化代碼;2.Metaspace錯誤因類元數(shù)據(jù)過多,常見於動態(tài)類生成或熱部署,應(yīng)限制MaxMetaspaceSize並優(yōu)化類加載;3.Unabletocreatenewnativethread因係統(tǒng)線程資源耗盡,需檢查線程數(shù)限制、使用線程池、調(diào)整棧大小;4.GCoverheadlimitexceeded指GC頻繁但回收少,應(yīng)分析GC日誌,優(yōu)化

See all articles