欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java并發(fā)編程之synchronized底層實(shí)現(xiàn)原理分析

 更新時(shí)間:2024年02月26日 10:32:31   作者:寒山道杳  
這篇文章主要介紹了Java并發(fā)編程之synchronized底層實(shí)現(xiàn)原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

一、為什么出現(xiàn)synchronized

對(duì)于程序員而言,不管是在平常的工作中還是面試中,都會(huì)經(jīng)常用到或者被問(wèn)到synchronized。在多線程并發(fā)編程中,synchronized早已是元老級(jí)的角色了,很多人都稱其為重量級(jí)鎖,但是隨著Java SE 1.6對(duì)其進(jìn)行各種優(yōu)化之后,便顯得不再是那么的重了。

也正是因?yàn)槎嗑€程并發(fā)的出現(xiàn),便產(chǎn)生了線程安全這樣的問(wèn)題,對(duì)于線程安全的主要原因如下:

  • 存在共享數(shù)據(jù)(也稱臨界資源)
  • 存在多條線程共同操作這些共享數(shù)據(jù)

而對(duì)于解決這樣的一個(gè)問(wèn)題的辦法是:同一時(shí)刻有且只有一條線程在操作共享數(shù)據(jù),其他線程必須等待該線程處理完數(shù)據(jù)后再對(duì)共享數(shù)據(jù)進(jìn)行操作

此時(shí)便產(chǎn)生了互斥鎖,互斥鎖的特性如下:

  • 互斥性:即在同一時(shí)刻只允許一個(gè)線程持有某個(gè)對(duì)象鎖,通過(guò)這種特性來(lái)實(shí)現(xiàn)多線程協(xié)調(diào)機(jī)制,這樣在同一時(shí)刻只有一個(gè)線程對(duì)所需要的同步的代碼塊(復(fù)合操作)進(jìn)行訪問(wèn)?;コ庑砸渤蔀榱瞬僮鞯脑有?。
  • 可見(jiàn)性:必須確保在鎖釋放之前,對(duì)共享變量所做的修改,對(duì)于隨后獲得該鎖的另一個(gè)線程可見(jiàn)(即在獲得鎖時(shí)應(yīng)獲得最新共享變量的值),否則另一個(gè)線程可能是在本地緩存的某個(gè)副本上繼續(xù)操作,從而引起數(shù)據(jù)不一致。

對(duì)于Java而言,synchronized關(guān)鍵字滿足了以上的要求。

二、實(shí)現(xiàn)原理

首先我們要知道synchronized鎖的不是代碼,鎖的是對(duì)象。

根據(jù)獲取的鎖的分類:獲取對(duì)象鎖和獲取類鎖:

獲取對(duì)像鎖的兩種方法

  • 1.同步代碼塊(synchronized(this),synchronized(類實(shí)例對(duì)象)),鎖是小括號(hào)()的實(shí)例對(duì)象
  • 2.同步非靜態(tài)方法(synchronized method),鎖是當(dāng)前對(duì)象是實(shí)例對(duì)象

獲取類鎖的兩種方法

  • 1.同步代碼塊(synchronized(類.class)),鎖是小括號(hào)()中的類對(duì)象(Class對(duì)象)
  • 2.同步靜態(tài)方法(synchronized static method),鎖是當(dāng)前對(duì)象的類對(duì)象(Class對(duì)象)

對(duì)象鎖和類鎖的總結(jié):有線程訪問(wèn)對(duì)象的同步代碼塊時(shí),另外的線程可以訪問(wèn)該兌對(duì)象的非同步代碼塊

  • 若鎖住的是同一個(gè)對(duì)象,一個(gè)線程在訪問(wèn)對(duì)象的 同步代碼塊時(shí),另一個(gè)訪問(wèn)對(duì)象的同步代碼塊的線程會(huì)被阻塞
  • 若鎖住的是同一個(gè)對(duì)象,一個(gè)線程在訪問(wèn)對(duì)象的 同步方法時(shí),另一個(gè)訪問(wèn)對(duì)象的同步方法的線程會(huì)被阻塞
  • 若鎖住的是同一個(gè)對(duì)象,一個(gè)線程在訪問(wèn)對(duì)象的 同步代碼塊時(shí),另一個(gè)訪問(wèn)對(duì)象的同步方法的線程會(huì)被阻塞
  • 同一個(gè)類的不同對(duì)象的對(duì)象鎖互不干擾
  • 類鎖由于是一種特殊的對(duì)象鎖,因此表現(xiàn)和上述1,2,3,4一致,由于一個(gè)只有一把對(duì)象鎖,所以同一個(gè)類的不同對(duì)象使用類鎖,將是同步的
  • 類鎖和對(duì)象鎖互不干擾

當(dāng)一個(gè)線程試圖訪問(wèn)同步代碼塊時(shí),它首先必須得到鎖,退出或者拋出異常時(shí)必須釋放鎖,那么鎖存在哪里呢?

我們先來(lái)看一段代碼:

public class SyncBlockTest {
    public void syncsTask() {
        synchronized (this) {
            System.out.println("Hello");
        }
    }
 
    public synchronized void syncTask() {
        System.out.println("Hello Baby");
    }
}

在使用javac工具把上面代碼變異成class,然后使用javap工具查看編譯好的class文件,如下:

  public com.interview.javabasic.thread.SyncBlockTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 8: 0
 
  public void syncsTask();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #3                  // String Hello
         9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: aload_1
        13: monitorexit
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit
        20: aload_2
        21: athrow
        22: return
      Exception table:
         from    to  target type
             4    14    17   any
            17    20    17   any
      LineNumberTable:
        line 10: 0
        line 11: 4
        line 12: 12
        line 13: 22
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 17
          locals = [ class com/interview/javabasic/thread/SyncBlockTest, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
 
  public synchronized void syncTask();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String Hello Baby
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 16: 0
        line 17: 8
}

同步代碼塊:從上面的字節(jié)碼中可以看出,同步語(yǔ)句塊的實(shí)現(xiàn)是使用monitorentermonitorexit指令的,monitorenter指向同步代碼塊的開(kāi)始位置,它首先去獲取PrintStream這個(gè)類,然后傳入“Hello”這個(gè)參數(shù),然后再調(diào)用PrintStream中的println()方法去打印,monitorexit指明同步代碼塊的結(jié)束位置,當(dāng)執(zhí)行到monitorenter時(shí),當(dāng)前線程將試圖獲取對(duì)象鎖所對(duì)應(yīng)的monitor的持有權(quán)。

同步方法:從上面代碼的syncTask()方法字節(jié)碼中看,這里面并沒(méi)monitorenter和monitorexit,且字節(jié)碼較短,其實(shí)這里方法的同步是隱式的,是無(wú)需通過(guò)字節(jié)碼指令控制,在上面可以看到一個(gè)“ACC_SYNCHRONIZED”這樣的一個(gè)訪問(wèn)標(biāo)志,用來(lái)區(qū)分一個(gè)方法是否是同步方法。當(dāng)方法調(diào)用時(shí),調(diào)用指令將會(huì)檢查ACC_SYNCHRONIZED是否被設(shè)置,如果被設(shè)置,當(dāng)前線程將會(huì)持有monitor,然后再執(zhí)行方法,最后不管方法是否正常完成都會(huì)釋放monitor。

三、實(shí)現(xiàn)synchronized的基礎(chǔ)

Java對(duì)象頭和monitor是實(shí)現(xiàn)synchronized的基礎(chǔ),下面將會(huì)說(shuō)說(shuō)關(guān)于Java對(duì)象頭和monitor。

Java對(duì)象頭:

hotspot虛擬機(jī)中,對(duì)象在內(nèi)存的布局分布分為3個(gè)部分:對(duì)象頭,實(shí)例數(shù)據(jù),和對(duì)齊填充。

對(duì)象頭的結(jié)構(gòu)如下:

虛擬機(jī)位數(shù)頭對(duì)象結(jié)構(gòu)說(shuō)明
32/64 bitMark Word默認(rèn)存儲(chǔ)對(duì)象的hashCode,分代年齡,鎖類型,鎖標(biāo)志位等信息
32/64 bitClass Metadata類型指針指向?qū)ο蟮念愒獢?shù)據(jù),JVM通過(guò)這個(gè)指針確定該對(duì)象是哪個(gè)類型的數(shù)據(jù)
32/64 bitArray length

數(shù)組的長(zhǎng)度(如果當(dāng)前的對(duì)象是數(shù)組

mark word 被設(shè)計(jì)為非固定的數(shù)據(jù)結(jié)構(gòu),以便在極小的空間內(nèi)存儲(chǔ)更多的信息,它會(huì)根據(jù)對(duì)象的狀態(tài)復(fù)用自己的存儲(chǔ)空間。

例如,在32位的Hotspot虛擬機(jī)中,如果對(duì)象處于未被鎖定的情況下。

那么Mark Word 的32bit空間中有25bit用于存儲(chǔ)對(duì)象的哈希碼、4bit用于存儲(chǔ)對(duì)象的分代年齡、2bi用于t存儲(chǔ)鎖的標(biāo)記位、1bit固定為0,而在其他的狀態(tài)下(輕量級(jí)鎖定、重量級(jí)鎖定、GC標(biāo)記、可偏向)下對(duì)象的存儲(chǔ)結(jié)構(gòu)如下:

存儲(chǔ)內(nèi)容標(biāo)志位狀態(tài)
對(duì)象哈希嗎、對(duì)象分代年齡01未鎖定
指向鎖記錄的指針00輕量級(jí)鎖定
指向重量級(jí)鎖的指針10膨脹(重量級(jí)鎖定)
空,不需要記錄信息11GC標(biāo)記
偏向線程ID、偏向時(shí)間戳、對(duì)象分代年齡01可偏向

monitor:

每個(gè)Java對(duì)象天生就自帶了一把看不見(jiàn)的鎖,它可以視為是一種同步工具或者是一種同步機(jī)制,monitor還是線程私有的數(shù)據(jù)結(jié)構(gòu),每一個(gè)線程都有一個(gè)可用monitor 列表,同時(shí)還有一個(gè)全局的可用列表,如上面所說(shuō)每一個(gè)被鎖住的對(duì)象都會(huì)持有一個(gè)monitor。

monitor結(jié)構(gòu)如下:

  • Owner:初始時(shí)為NULL表示當(dāng)前沒(méi)有任何線程擁有該monitor record,當(dāng)線程成功擁有該鎖后保存線程唯一標(biāo)識(shí),當(dāng)鎖被釋放時(shí)又設(shè)置為NULL;
  • EntryQ:關(guān)聯(lián)一個(gè)系統(tǒng)互斥鎖(semaphore),阻塞所有試圖鎖住monitor record失敗的線程。
  • RcThis:表示blocked或waiting在該monitor record上的所有線程的個(gè)數(shù)。
  • Nest:用來(lái)實(shí)現(xiàn)重入鎖的計(jì)數(shù)。
  • HashCode:保存從對(duì)象頭拷貝過(guò)來(lái)的HashCode值(可能還包含GC age)。
  • Candidate:用來(lái)避免不必要的阻塞或等待線程喚醒,因?yàn)槊恳淮沃挥幸粋€(gè)線程能夠成功擁有鎖,如果每次前一個(gè)釋放鎖的線程喚醒所有正在阻塞或等待的線程,會(huì)引起不必要的上下文切換(從阻塞到就緒然后因?yàn)楦?jìng)爭(zhēng)鎖失敗又被阻塞)從而導(dǎo)致性能嚴(yán)重下降。Candidate只有兩種可能的值0表示沒(méi)有需要喚醒的線程1表示要喚醒一個(gè)繼任線程來(lái)競(jìng)爭(zhēng)鎖。

四、鎖優(yōu)化

Java6以后,對(duì)鎖進(jìn)行了大量的優(yōu)化,例如:AdaptiveSpinning(自適應(yīng)自旋)、Lock Eliminate(鎖消除)、Lock Coarsening(鎖粗化)、Lightweight Locking(輕量級(jí)鎖)、Biased Locking(偏向鎖)等等

自旋鎖

  • 許多情況下,共享數(shù)據(jù)的鎖定狀態(tài)持續(xù)時(shí)間較短,切換線程不值得
  • 通過(guò)讓線程執(zhí)行忙循環(huán)等待鎖的釋放,不讓出CPU
  • 缺點(diǎn):若鎖被其他線程長(zhǎng)時(shí)間占用,會(huì)帶來(lái)許多性能上的開(kāi)銷

定義:所謂的自旋鎖就是讓沒(méi)有獲取到鎖的線程繼續(xù)等待一會(huì)兒,但不放棄CPU的執(zhí)行時(shí)間,這是的等一會(huì)和不放棄CPU的時(shí)間即是自旋鎖。

自適應(yīng)自旋鎖

  • 自旋的次數(shù)不再固定
  • 由前一次在同一鎖上的自旋時(shí)間及鎖擁有者的狀態(tài)來(lái)決定

鎖消除

鎖消除是對(duì)鎖更徹底的優(yōu)化,JIT編譯時(shí),對(duì)運(yùn)行上下文進(jìn)行掃描,去除不可能存在競(jìng)爭(zhēng)的鎖

public class StringBufferWithoutSync {
    public void add(String str1, String str2) {
        //StringBuffer是線程安全,由于sb只會(huì)在append方法中使用,不可能被其他線程引用
        //因此sb屬于不可能共享的資源,JVM會(huì)自動(dòng)消除內(nèi)部的鎖
        StringBuffer sb = new StringBuffer();
        sb.append(str1).append(str2);
    }
 
    public static void main(String[] args) {
        StringBufferWithoutSync withoutSync = new StringBufferWithoutSync();
        for (int i = 0; i < 1000; i++) {
            withoutSync.add("aaa", "bbb");
        }
    }
 
}

例如Java中的StringBuffer 它是線程安全的,由于其append()方法只會(huì)在內(nèi)部使用的,也就不可能被其他線程引用,因此對(duì)于這個(gè)變量sb而言,便不屬于共享資源了,JVM會(huì)自動(dòng)消除內(nèi)部的鎖。

鎖粗化

原則上我們都知道,在加同步鎖的時(shí)候,盡可能將同步塊的作用范圍先知道盡量小的范圍,及只在共享數(shù)據(jù)的實(shí)際工作范圍中進(jìn)行同步,這樣是為了需要同步的數(shù)量盡可能的變小,在存在鎖同步競(jìng)爭(zhēng)中,使得等待鎖的時(shí)間減小。

上述情況,大部分時(shí)候是正確的,但是如果存在一系列頻繁的操作,對(duì)同一個(gè)對(duì)象反復(fù)的加鎖、解鎖, 甚至加鎖時(shí)是在循環(huán)體中操作的,這樣即使沒(méi)有線程競(jìng)爭(zhēng),頻繁的進(jìn)行互斥鎖操作,也是導(dǎo)致不必要的性能開(kāi)銷。

對(duì)于解決這樣的問(wèn)題,我們只有盡可能的擴(kuò)大加鎖的范圍,例如下面的循環(huán)100次append,JVM會(huì)自己檢測(cè)到這樣的一個(gè)問(wèn)題,就會(huì)將加鎖的次數(shù)減至一次。

    public static String copyString(String target){
        int i = 0;
        StringBuffer sb = new StringBuffer();
        while (i<100){
            sb.append(target);
        }
        return sb.toString();
    }

五、synchronized鎖的狀態(tài)

synchronized鎖有四種狀態(tài)分別為:無(wú)鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖

鎖的膨脹方向:無(wú)鎖——>偏向鎖——>輕量級(jí)鎖——>重量級(jí)鎖

偏向鎖

作用:減少同一線程獲取鎖的代價(jià)

引入偏向鎖是因?yàn)榇蠖鄶?shù)情況下,鎖并不存在多線程競(jìng)爭(zhēng),總是由同一線程多次獲得

獲取鎖

  • 檢測(cè)Mark Word是否為可偏向狀態(tài),即是否為偏向鎖1,鎖標(biāo)識(shí)位為01;
  • 若為可偏向狀態(tài),則測(cè)試線程ID是否為當(dāng)前線程ID,如果是,則執(zhí)行步驟(5),否則執(zhí)行步驟(3);
  • 如果線程ID不為當(dāng)前線程ID,則通過(guò)CAS操作競(jìng)爭(zhēng)鎖,競(jìng)爭(zhēng)成功,則將Mark Word的線程ID替換為當(dāng)前線程ID,否則執(zhí)行線程(4);
  • 通過(guò)CAS競(jìng)爭(zhēng)鎖失敗,證明當(dāng)前存在多線程競(jìng)爭(zhēng)情況,當(dāng)?shù)竭_(dá)全局安全點(diǎn),獲得偏向鎖的線程被掛起,偏向鎖升級(jí)為輕量級(jí)鎖,然后被阻塞在安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼塊;
  • 執(zhí)行同步代碼塊

釋放鎖

偏向鎖的釋放采用了一種只有競(jìng)爭(zhēng)才會(huì)釋放鎖的機(jī)制,線程是不會(huì)主動(dòng)去釋放偏向鎖,需要等待其他線程來(lái)競(jìng)爭(zhēng)。偏向鎖的撤銷需要等待全局安全點(diǎn)(這個(gè)時(shí)間點(diǎn)是上沒(méi)有正在執(zhí)行的代碼)。

其步驟如下:

  • 暫停擁有偏向鎖的線程,判斷鎖對(duì)象石是否還處于被鎖定狀態(tài);
  • 撤銷偏向蘇,恢復(fù)到無(wú)鎖狀態(tài)(01)或者輕量級(jí)鎖的狀態(tài);

輕量級(jí)鎖

獲取鎖

  • 判斷當(dāng)前對(duì)象是否處于無(wú)鎖狀態(tài)(hashcode、0、01),若是,則JVM首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對(duì)象目前的Mark Word的拷貝(官方把這份拷貝加了一個(gè)
  • Displaced前綴,即Displaced Mark Word);否則執(zhí)行步驟(3);
  • JVM利用CAS操作嘗試將對(duì)象的Mark Word更新為指向Lock Record的指正,如果成功表示競(jìng)爭(zhēng)到鎖,則將鎖標(biāo)志位變成00(表示此對(duì)象處于輕量級(jí)鎖狀態(tài)),執(zhí)行同步操作;如果失敗則執(zhí)行步驟(3);
  • 判斷當(dāng)前對(duì)象的Mark Word是否指向當(dāng)前線程的棧幀,如果是則表示當(dāng)前線程已經(jīng)持有當(dāng)前對(duì)象的鎖,則直接執(zhí)行同步代碼塊;否則只能說(shuō)明該鎖對(duì)象已經(jīng)被其他線程搶占了,這時(shí)輕量級(jí)鎖需要膨脹為重量級(jí)鎖,鎖
  • 標(biāo)志位變成10,后面等待的線程將會(huì)進(jìn)入阻塞狀態(tài);

釋放鎖

輕量級(jí)鎖的釋放也是通過(guò)CAS操作來(lái)進(jìn)行的,主要步驟如下:

  • 取出在獲取輕量級(jí)鎖保存在Displaced Mark Word中的數(shù)據(jù);
  • 用CAS操作將取出的數(shù)據(jù)替換當(dāng)前對(duì)象的Mark Word中,如果成功,則說(shuō)明釋放鎖成功,否則執(zhí)行(3);
  • 如果CAS操作替換失敗,說(shuō)明有其他線程嘗試獲取該鎖,則需要在釋放鎖的同時(shí)需要喚醒被掛起的線程。

對(duì)于輕量級(jí)鎖,其性能提升的依據(jù)是“對(duì)于絕大部分的鎖,在整個(gè)生命周期內(nèi)都是不會(huì)存在競(jìng)爭(zhēng)的”,如果打破這個(gè)依據(jù)則除了互斥的開(kāi)銷外,還有額外的CAS操作,因此在有多線程競(jìng)爭(zhēng)的情況下,輕量級(jí)鎖比重量級(jí)鎖更慢;

重量級(jí)鎖

重量級(jí)鎖通過(guò)對(duì)象內(nèi)部的監(jiān)視器(monitor)實(shí)現(xiàn),其中monitor的本質(zhì)是依賴于底層操作系統(tǒng)的Mutex Lock實(shí)現(xiàn),操作系統(tǒng)實(shí)現(xiàn)線程之間的切換需要從用戶態(tài)到內(nèi)核態(tài)的切換,切換成本非常高。

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • SpringBoot如何優(yōu)雅地使用Swagger2

    SpringBoot如何優(yōu)雅地使用Swagger2

    這篇文章主要介紹了SpringBoot如何優(yōu)雅地使用Swagger2,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • java使用BeanUtils.copyProperties踩坑經(jīng)歷

    java使用BeanUtils.copyProperties踩坑經(jīng)歷

    最近在做個(gè)項(xiàng)目,踩了個(gè)坑特此記錄一下,本文主要介紹了使用BeanUtils.copyProperties踩坑經(jīng)歷,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • 解決springboot整合cxf-jaxrs中json轉(zhuǎn)換的問(wèn)題

    解決springboot整合cxf-jaxrs中json轉(zhuǎn)換的問(wèn)題

    這篇文章主要介紹了解決springboot整合cxf-jaxrs中json轉(zhuǎn)換的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Java深入淺出說(shuō)流的使用

    Java深入淺出說(shuō)流的使用

    這篇文章主要介紹了Java深入淺出說(shuō)流的使用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09
  • 淺析Java關(guān)鍵詞synchronized的使用

    淺析Java關(guān)鍵詞synchronized的使用

    Synchronized是java虛擬機(jī)為線程安全而引入的。這篇文章主要為大家介紹一下Java關(guān)鍵詞synchronized的使用與原理,需要的可以參考一下
    2022-12-12
  • Idea調(diào)用WebService的關(guān)鍵步驟和注意事項(xiàng)

    Idea調(diào)用WebService的關(guān)鍵步驟和注意事項(xiàng)

    這篇文章主要介紹了如何在Idea中調(diào)用WebService,包括理解WebService的基本概念、獲取WSDL文件、閱讀和理解WSDL文件、選擇對(duì)接測(cè)試工具或方式、發(fā)送請(qǐng)求和接收響應(yīng)、處理響應(yīng)結(jié)果以及錯(cuò)誤處理,需要的朋友可以參考下
    2025-01-01
  • SpringShell命令行之交互式Shell應(yīng)用開(kāi)發(fā)方式

    SpringShell命令行之交互式Shell應(yīng)用開(kāi)發(fā)方式

    本文將深入探討Spring Shell的核心特性、實(shí)現(xiàn)方式及應(yīng)用場(chǎng)景,幫助開(kāi)發(fā)者掌握這一強(qiáng)大工具,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Java之如何截取視頻第一幀

    Java之如何截取視頻第一幀

    這篇文章主要介紹了Java之如何截取視頻第一幀問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • Java獲取漢字拼音的全拼和首拼實(shí)現(xiàn)代碼分享

    Java獲取漢字拼音的全拼和首拼實(shí)現(xiàn)代碼分享

    這篇文章主要介紹了Java獲取漢字拼音的全拼和首拼實(shí)現(xiàn)代碼分享,本文直接給出實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2015-06-06
  • SpringBoot中maven項(xiàng)目打成war包部署在linux服務(wù)器上的方法

    SpringBoot中maven項(xiàng)目打成war包部署在linux服務(wù)器上的方法

    這篇文章主要介紹了SpringBoot中maven項(xiàng)目打成war包部署在linux服務(wù)器上的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05

最新評(píng)論