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

Java中的自旋鎖與適應(yīng)性自旋鎖的區(qū)別

 更新時(shí)間:2023年10月13日 10:30:36   作者:我用漂亮的押韻形容被掠奪一空的愛(ài)情  
這篇文章主要介紹了Java中的自旋鎖與適應(yīng)性自旋鎖的區(qū)別,當(dāng)一個(gè)線程嘗試去獲取某一把鎖的時(shí)候,如果這個(gè)鎖此時(shí)已經(jīng)被別人獲取(占用),那么此線程就無(wú)法獲取到這把鎖,該線程將會(huì)等待,間隔一段時(shí)間后會(huì)再次嘗試獲取,需要的朋友可以參考下

自旋鎖

1、概念:

當(dāng)一個(gè)線程嘗試去獲取某一把鎖的時(shí)候,如果這個(gè)鎖此時(shí)已經(jīng)被別人獲取(占用),那么此線程就無(wú)法獲取到這把鎖,該線程將會(huì)等待,間隔一段時(shí)間后會(huì)再次嘗試獲取。這種采用循環(huán)加鎖 -> 等待的機(jī)制被稱為自旋鎖(spinlock)

2、提出背景

由于在多處理器環(huán)境中某些資源的有限性,有時(shí)需要互斥訪問(wèn)(mutual exclusion),這時(shí)候就需要引入鎖的概念,只有獲取了鎖的線程才能夠?qū)Y源進(jìn)行訪問(wèn),由于多線程的核心是CPU的時(shí)間分片,所以同一時(shí)刻只能有一個(gè)線程獲取到鎖。那么就面臨一個(gè)問(wèn)題,那么沒(méi)有獲取到鎖的線程應(yīng)該怎么辦?

通常有兩種處理方式:一種是沒(méi)有獲取到鎖的線程就一直循環(huán)等待判斷該資源是否已經(jīng)釋放鎖,這種鎖叫做自旋鎖,它不用將線程阻塞起來(lái)(NON-BLOCKING);還有一種處理方式就是把自己阻塞起來(lái),等待重新調(diào)度請(qǐng)求,這種叫做互斥鎖。

3、自旋鎖的原理

自旋鎖的原理比較簡(jiǎn)單,如果持有鎖的線程能在短時(shí)間內(nèi)釋放鎖資源,那么那些等待競(jìng)爭(zhēng)鎖的線程就不需要做內(nèi)核態(tài)和用戶態(tài)之間的切換進(jìn)入阻塞狀態(tài),它們只需要等一等(自旋),等到持有鎖的線程釋放鎖之后即可獲取,這樣就避免了用戶進(jìn)程和內(nèi)核切換的消耗。

因?yàn)樽孕i避免了操作系統(tǒng)進(jìn)程調(diào)度和線程切換,所以自旋鎖通常適用在時(shí)間比較短的情況下。由于這個(gè)原因,操作系統(tǒng)的內(nèi)核經(jīng)常使用自旋鎖。但是,如果長(zhǎng)時(shí)間上鎖的話,自旋鎖會(huì)非常耗費(fèi)性能,它阻止了其他線程的運(yùn)行和調(diào)度。線程持有鎖的時(shí)間越長(zhǎng),則持有該鎖的線程將被 OS(Operating System) 調(diào)度程序中斷的風(fēng)險(xiǎn)越大。如果發(fā)生中斷情況,那么其他線程將保持旋轉(zhuǎn)狀態(tài)(反復(fù)嘗試獲取鎖),而持有該鎖的線程并不打算釋放鎖,這樣導(dǎo)致的是結(jié)果是無(wú)限期推遲,直到持有鎖的線程可以完成并釋放它為止。

解決上面這種情況一個(gè)很好的方式是給自旋鎖設(shè)定一個(gè)自旋時(shí)間,等時(shí)間一到立即釋放自旋鎖。自旋鎖的目的是占著CPU資源不進(jìn)行釋放,等到獲取鎖立即進(jìn)行處理。但是如何去選擇自旋時(shí)間呢?如果自旋執(zhí)行時(shí)間太長(zhǎng),會(huì)有大量的線程處于自旋狀態(tài)占用 CPU 資源,進(jìn)而會(huì)影響整體系統(tǒng)的性能。因此自旋的周期選的額外重要!JDK在1.6 引入了適應(yīng)性自旋鎖,適應(yīng)性自旋鎖意味著自旋時(shí)間不是固定的了,而是由前一次在同一個(gè)鎖上的自旋時(shí)間以及鎖擁有的狀態(tài)來(lái)決定,基本認(rèn)為一個(gè)線程上下文切換的時(shí)間是最佳的一個(gè)時(shí)間。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2F4aWFvYm9nZQ==,size_16,color_FFFFFF,t_70

4、 自旋鎖的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):自旋鎖盡可能的減少線程的阻塞,這對(duì)于鎖的競(jìng)爭(zhēng)不激烈,且占用鎖時(shí)間非常短的代碼塊來(lái)說(shuō)性能能大幅度的提升,因?yàn)樽孕南臅?huì)小于線程阻塞掛起再喚醒的操作的消耗,這些操作會(huì)導(dǎo)致線程發(fā)生兩次上下文切換!

缺點(diǎn):但是如果鎖的競(jìng)爭(zhēng)激烈,或者持有鎖的線程需要長(zhǎng)時(shí)間占用鎖執(zhí)行同步塊,這時(shí)候就不適合使用自旋鎖了,因?yàn)樽孕i在獲取鎖前一直都是占用 cpu 做無(wú)用功,占著 XX 不 XX,同時(shí)有大量線程在競(jìng)爭(zhēng)一個(gè)鎖,會(huì)導(dǎo)致獲取鎖的時(shí)間很長(zhǎng),線程自旋的消耗大于線程阻塞掛起操作的消耗,其它需要 cpu 的線程又不能獲取到 cpu,造成 cpu 的浪費(fèi)。所以這種情況下我們要關(guān)閉自旋鎖。

5、自旋鎖開(kāi)啟

雖然在JDK1.4.2的時(shí)候就引入了自旋鎖,但是需要使用“-XX:+UseSpinning”參數(shù)來(lái)開(kāi)啟。在到了JDK1.6以后,就已經(jīng)是默認(rèn)開(kāi)啟了。

舉個(gè)栗子:

public class SpinLockTest {
    /**
     * 持有鎖的線程,null表示鎖未被線程持有
     */
    private AtomicReference<Thread> ref = new AtomicReference<>();
    public void lock(){
        Thread currentThread = Thread.currentThread();
        while(!ref.compareAndSet(null, currentThread)){
            //當(dāng)ref為null的時(shí)候compareAndSet返回true,反之為false
            //通過(guò)循環(huán)不斷的自旋判斷鎖是否被其他線程持有
        }
    }
    public void unLock() {
        Thread cur = Thread.currentThread();
        if(ref.get() != cur){
            //exception ...
        }
        ref.set(null);
    }
}
//自旋鎖測(cè)試
public class SpinLockTestTest {
    static int count = 0;
    @Test
    public void spinLockTest() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        CountDownLatch countDownLatch = new CountDownLatch(100);
        SpinLockTest2 spinLockTest2 = new SpinLockTest2();
        for (int i = 0; i < 100; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    spinLockTest2.lock();
                    ++count;
                    spinLockTest2.unLock();
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        System.out.println (count);
   }
}

通過(guò)上面的代碼可以看出,自旋就是在循環(huán)判斷條件是否滿足,那么會(huì)有什么問(wèn)題嗎?如果鎖被占用很長(zhǎng)時(shí)間的話,自旋的線程等待的時(shí)間也會(huì)變長(zhǎng),白白浪費(fèi)掉處理器資源。因此在JDK中,自旋操作默認(rèn)10次,我們可以通過(guò)參數(shù)“-XX:PreBlockSpin”來(lái)設(shè)置,當(dāng)超過(guò)來(lái)此參數(shù)的值,則會(huì)使用傳統(tǒng)的線程掛起方式來(lái)等待鎖釋放。

自適應(yīng)自旋鎖

隨著JDK的更新,在1.6的時(shí)候,又出現(xiàn)了一個(gè)叫做“自適應(yīng)自旋鎖”的玩意。它的出現(xiàn)使得自旋操作變得聰明起來(lái),不再跟之前一樣死板。所謂的“自適應(yīng)”意味著對(duì)于同一個(gè)鎖對(duì)象,線程的自旋時(shí)間是根據(jù)上一個(gè)持有該鎖的線程的自旋時(shí)間以及狀態(tài)來(lái)確定的。例如對(duì)于A鎖對(duì)象來(lái)說(shuō),如果一個(gè)線程剛剛通過(guò)自旋獲得到了鎖,并且該線程也在運(yùn)行中,那么JVM會(huì)認(rèn)為此次自旋操作也是有很大的機(jī)會(huì)可以拿到鎖,因此它會(huì)讓自旋的時(shí)間相對(duì)延長(zhǎng)。但是如果對(duì)于B鎖對(duì)象自旋操作很少成功的話,JVM甚至可能直接忽略自旋操作。因此,自適應(yīng)自旋鎖是一個(gè)更加智能,對(duì)我們的業(yè)務(wù)性能更加友好的一個(gè)鎖。

總結(jié)

自旋鎖是為了提高資源的使用頻率而出現(xiàn)的一種鎖,自旋鎖說(shuō)的是線程獲取鎖的時(shí)候,如果鎖被其他線程持有,則當(dāng)前線程將循環(huán)等待,直到獲取到鎖。

自旋鎖在等待期間不會(huì)睡眠或者釋放自己的線程。自旋鎖不適用于長(zhǎng)時(shí)間持有CPU的情況,這會(huì)加劇系統(tǒng)的負(fù)擔(dān),為了解決這種情況,需要設(shè)定自旋周期,那么自旋周期的設(shè)定也是一門(mén)學(xué)問(wèn)。

在自旋鎖中 另有三種常見(jiàn)的鎖形式:TicketLock、CLHlock和MCSlock

這里直接舉栗子:

  • TicketLock 是一種同步機(jī)制或鎖定算法,它是一種自旋鎖,它使用ticket 來(lái)控制線程執(zhí)行順序。
/**
 * @Description TicketLock 是一種同步機(jī)制或鎖定算法,它是一種自旋鎖,它使用ticket 來(lái)控制線程執(zhí)行順序。
 *
 * TicketLock 是基于先進(jìn)先出(FIFO) 隊(duì)列的機(jī)制。
 * 它增加了鎖的公平性,其設(shè)計(jì)原則如下:TicketLock 中有兩個(gè) int 類型的數(shù)值,
 * 開(kāi)始都是0,第一個(gè)值是隊(duì)列ticket(隊(duì)列票據(jù)), 第二個(gè)值是 出隊(duì)(票據(jù))。
 * 隊(duì)列票據(jù)是線程在隊(duì)列中的位置,而出隊(duì)票據(jù)是現(xiàn)在持有鎖的票證的隊(duì)列位置。
 * 可能有點(diǎn)模糊不清,簡(jiǎn)單來(lái)說(shuō),就是隊(duì)列票據(jù)是你取票號(hào)的位置,出隊(duì)票據(jù)是你距離叫號(hào)的位置。
 *
 *
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020-05-29
 */
//這個(gè)設(shè)計(jì)是有問(wèn)題的,因?yàn)楂@得自己的號(hào)碼之后,是可以對(duì)號(hào)碼進(jìn)行更改的,這就造成系統(tǒng)紊亂,鎖不能及時(shí)釋放。
//需要有一個(gè)能確保每個(gè)人按會(huì)著自己號(hào)碼排隊(duì)辦業(yè)務(wù)的角色
public class TicketLock {
    // 隊(duì)列票據(jù)(當(dāng)前排隊(duì)號(hào)碼)
    private AtomicInteger queueNum = new AtomicInteger();
    // 出隊(duì)票據(jù)(當(dāng)前需等待號(hào)碼)
    private AtomicInteger dueueNum = new AtomicInteger();
    // 獲取鎖:如果獲取成功,返回當(dāng)前線程的排隊(duì)號(hào)
    public int lock(){
        int currentTicketNum = dueueNum.incrementAndGet();
        while (currentTicketNum != queueNum.get()){
            // doSomething...
        }
        return currentTicketNum;
    }
    // 釋放鎖:傳入當(dāng)前排隊(duì)的號(hào)碼
    public void unLock(int ticketNum){
        queueNum.compareAndSet(ticketNum,ticketNum + 1);
    }
    //改進(jìn)后,這就不再需要返回值,辦業(yè)務(wù)的時(shí)候,要將當(dāng)前的這一個(gè)號(hào)碼緩存起來(lái),在辦完業(yè)務(wù)后,需要釋放緩存的這條票據(jù)。
    //缺點(diǎn):雖然解決了公平性的問(wèn)題,但是多處理器系統(tǒng)上,每個(gè)進(jìn)程/線程占用的處理器都在讀寫(xiě)同一個(gè)變量queueNum ,
    // 每次讀寫(xiě)操作都必須在多個(gè)處理器緩存之間進(jìn)行緩存同步,這會(huì)導(dǎo)致繁重的系統(tǒng)總線和內(nèi)存的流量,大大降低系統(tǒng)整體的性能。
    //為了解決這個(gè)問(wèn)題,MCSLock 和 CLHLock 應(yīng)運(yùn)而生
    public class TicketLock2 {
        // 隊(duì)列票據(jù)(當(dāng)前排隊(duì)號(hào)碼)
        private AtomicInteger queueNum = new AtomicInteger();
        // 出隊(duì)票據(jù)(當(dāng)前需等待號(hào)碼)
        private AtomicInteger dueueNum = new AtomicInteger();
        //線程內(nèi)部的存儲(chǔ)類,可以在指定線程內(nèi)存儲(chǔ)數(shù)據(jù),數(shù)據(jù)存儲(chǔ)以后,只有指定線程可以得到存儲(chǔ)數(shù)據(jù)
        private ThreadLocal<Integer> ticketLocal = new ThreadLocal<>();
        public void lock(){
            int currentTicketNum = dueueNum.incrementAndGet();
            // 獲取鎖的時(shí)候,將當(dāng)前線程的排隊(duì)號(hào)保存起來(lái)
            ticketLocal.set(currentTicketNum);
            while (currentTicketNum != queueNum.get()){
                // doSomething...
            }
        }
        // 釋放鎖:從排隊(duì)緩沖池中取
        public void unLock(){
            Integer currentTicket = ticketLocal.get();
            queueNum.compareAndSet(currentTicket,currentTicket + 1);
        }
    }
}
  • TicketLock 是基于隊(duì)列的,那么 CLHLock 就是基于鏈表設(shè)計(jì)的
/**
 * @Description
 * TicketLock 是基于隊(duì)列的,那么 CLHLock 就是基于鏈表設(shè)計(jì)的
 *
 * CLH 是一種基于鏈表的可擴(kuò)展,高性能,公平的自旋鎖,申請(qǐng)線程只能在本地變量上自旋,
 * 它會(huì)不斷輪詢前驅(qū)的狀態(tài),如果發(fā)現(xiàn)前驅(qū)釋放了鎖就結(jié)束自旋。
 *
 *
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020-05-29
 */
public class CLHLock {
    public static class CLHNode{
        private volatile boolean isLocked = true;
    }
    // 尾部節(jié)點(diǎn)
    private volatile CLHNode tail;
    private static final ThreadLocal<CLHNode> LOCAL = new ThreadLocal<>();
    private static final AtomicReferenceFieldUpdater<CLHLock,CLHNode> UPDATER =
            AtomicReferenceFieldUpdater.newUpdater(CLHLock.class,CLHNode.class,"tail");
    public void lock(){
        // 新建節(jié)點(diǎn)并將節(jié)點(diǎn)與當(dāng)前線程保存起來(lái)
        CLHNode node = new CLHNode();
        LOCAL.set(node);
        // 將新建的節(jié)點(diǎn)設(shè)置為尾部節(jié)點(diǎn),并返回舊的節(jié)點(diǎn)(原子操作),這里舊的節(jié)點(diǎn)實(shí)際上就是當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)
        CLHNode preNode = UPDATER.getAndSet(this,node);
        if(preNode != null){
            // 前驅(qū)節(jié)點(diǎn)不為null表示當(dāng)鎖被其他線程占用,通過(guò)不斷輪詢判斷前驅(qū)節(jié)點(diǎn)的鎖標(biāo)志位等待前驅(qū)節(jié)點(diǎn)釋放鎖
            while (preNode.isLocked){
            }
            preNode = null;
            LOCAL.set(node);
        }
        // 如果不存在前驅(qū)節(jié)點(diǎn),表示該鎖沒(méi)有被其他線程占用,則當(dāng)前線程獲得鎖
    }
    public void unlock() {
        // 獲取當(dāng)前線程對(duì)應(yīng)的節(jié)點(diǎn)
        CLHNode node = LOCAL.get();
        // 如果tail節(jié)點(diǎn)等于node,則將tail節(jié)點(diǎn)更新為null,同時(shí)將node的lock狀態(tài)職位false,表示當(dāng)前線程釋放了鎖
        if (!UPDATER.compareAndSet(this, node, null)) {
            node.isLocked = false;
        }
        node = null;
    }
}
  • MCS Spinlock 是一種基于鏈表的可擴(kuò)展、高性能、公平的自旋鎖,申請(qǐng)線程只在本地變量上自旋,直接前驅(qū)負(fù)責(zé)通知其結(jié)束自旋,從而極大地減少了不必要的處理器緩存同步的次數(shù),降低了總線和內(nèi)存的開(kāi)銷
/**
 * @Description
 * MCS Spinlock 是一種基于鏈表的可擴(kuò)展、高性能、公平的自旋鎖,申請(qǐng)線程只在本地變量上自旋,
 * 直接前驅(qū)負(fù)責(zé)通知其結(jié)束自旋,從而極大地減少了不必要的處理器緩存同步的次數(shù),降低了總線和內(nèi)存的開(kāi)銷。
 *
 * 總結(jié):
 * 1.都是基于鏈表,不同的是CLHLock是基于隱式鏈表,沒(méi)有真正的后續(xù)節(jié)點(diǎn)屬性,MCSLock是顯示鏈表,有一個(gè)指向后續(xù)節(jié)點(diǎn)的屬性。
 * 2.將獲取鎖的線程狀態(tài)借助節(jié)點(diǎn)(node)保存,每個(gè)線程都有一份獨(dú)立的節(jié)點(diǎn),這樣就解決了TicketLock多處理器緩存同步的問(wèn)題。
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020-05-29
 */
public class MCSLock {
    public static class MCSNode {
        volatile MCSNode next;
        volatile boolean isLocked = true;
    }
    private static final ThreadLocal<MCSNode> NODE = new ThreadLocal<>();
    // 隊(duì)列
    @SuppressWarnings("unused")
    private volatile MCSNode queue;
    private static final AtomicReferenceFieldUpdater<MCSLock,MCSNode> UPDATE =
            AtomicReferenceFieldUpdater.newUpdater(MCSLock.class,MCSNode.class,"queue");
    public void lock(){
        // 創(chuàng)建節(jié)點(diǎn)并保存到ThreadLocal中
        MCSNode currentNode = new MCSNode();
        NODE.set(currentNode);
        // 將queue設(shè)置為當(dāng)前節(jié)點(diǎn),并且返回之前的節(jié)點(diǎn)
        MCSNode preNode = UPDATE.getAndSet(this, currentNode);
        if (preNode != null) {
            // 如果之前節(jié)點(diǎn)不為null,表示鎖已經(jīng)被其他線程持有
            preNode.next = currentNode;
            // 循環(huán)判斷,直到當(dāng)前節(jié)點(diǎn)的鎖標(biāo)志位為false
            while (currentNode.isLocked) {
            }
        }
    }
    public void unlock() {
        MCSNode currentNode = NODE.get();
        // next為null表示沒(méi)有正在等待獲取鎖的線程
        if (currentNode.next == null) {
            // 更新?tīng)顟B(tài)并設(shè)置queue為null
            if (UPDATE.compareAndSet(this, currentNode, null)) {
                // 如果成功了,表示queue==currentNode,即當(dāng)前節(jié)點(diǎn)后面沒(méi)有節(jié)點(diǎn)了
                return;
            } else {
                // 如果不成功,表示queue!=currentNode,即當(dāng)前節(jié)點(diǎn)后面多了一個(gè)節(jié)點(diǎn),表示有線程在等待
                // 如果當(dāng)前節(jié)點(diǎn)的后續(xù)節(jié)點(diǎn)為null,則需要等待其不為null(參考加鎖方法)
                while (currentNode.next == null) {
                }
            }
        } else {
            // 如果不為null,表示有線程在等待獲取鎖,此時(shí)將等待線程對(duì)應(yīng)的節(jié)點(diǎn)鎖狀態(tài)更新為false,同時(shí)將當(dāng)前線程的后繼節(jié)點(diǎn)設(shè)為null
            currentNode.next.isLocked = false;
            currentNode.next = null;
        }
    }
}

到此這篇關(guān)于Java中的自旋鎖與適應(yīng)性自旋鎖的區(qū)別的文章就介紹到這了,更多相關(guān)自旋鎖與適應(yīng)性自旋鎖區(qū)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot一個(gè)接口多個(gè)實(shí)現(xiàn)類的調(diào)用方式總結(jié)

    SpringBoot一個(gè)接口多個(gè)實(shí)現(xiàn)類的調(diào)用方式總結(jié)

    這篇文章主要介紹了SpringBoot一個(gè)接口多個(gè)實(shí)現(xiàn)類的調(diào)用方式,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2024-01-01
  • Java貪吃蛇游戲完善版

    Java貪吃蛇游戲完善版

    這篇文章主要為大家詳細(xì)介紹了Java貪吃蛇游戲完善版,支持菜單操作,鍵盤(pán)監(jiān)聽(tīng),可加速,減速,統(tǒng)計(jì)得分等功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-04-04
  • 深入理解Spring中RabbitMQ的Channel

    深入理解Spring中RabbitMQ的Channel

    這篇文章主要介紹了深入理解Spring中RabbitMQ的Channel,在RabbitMq中,channel表示邏輯連接或者叫虛擬連接,是棣屬于TCP連接的,一個(gè)TCP連接里可以創(chuàng)建多個(gè)channel,在Rabbit MQ里,消息的發(fā)送和接收都是基于channel的,需要的朋友可以參考下
    2023-08-08
  • Java中讀取文件轉(zhuǎn)換為字符串的方法

    Java中讀取文件轉(zhuǎn)換為字符串的方法

    今天小編就為大家分享一篇Java中讀取文件轉(zhuǎn)換為字符串的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-07-07
  • Java重寫(xiě)equals及hashcode方法流程解析

    Java重寫(xiě)equals及hashcode方法流程解析

    這篇文章主要介紹了Java重寫(xiě)equals及hashcode方法流程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • 使用JAVA實(shí)現(xiàn)http通信詳解

    使用JAVA實(shí)現(xiàn)http通信詳解

    本文給大家匯總介紹了幾種java實(shí)現(xiàn)http通訊的方法,非常的簡(jiǎn)單實(shí)用,有需要的小伙伴可以參考下。
    2015-08-08
  • Java 讀取圖片的mimeType的方法

    Java 讀取圖片的mimeType的方法

    本篇文章主要介紹了Java 讀取圖片的mimeType的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-01-01
  • Java DecimalFormat 保留小數(shù)位及四舍五入的陷阱介紹

    Java DecimalFormat 保留小數(shù)位及四舍五入的陷阱介紹

    這篇文章主要介紹了Java DecimalFormat 保留小數(shù)位及四舍五入的陷阱,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • 編譯期動(dòng)態(tài)替換三方包中的Class文件過(guò)程詳解

    編譯期動(dòng)態(tài)替換三方包中的Class文件過(guò)程詳解

    這篇文章主要為大家介紹了編譯期動(dòng)態(tài)替換三方包中的Class文件過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • SSM框架搭建圖文教程(推薦)

    SSM框架搭建圖文教程(推薦)

    下面小編就為大家?guī)?lái)一篇SSM框架搭建圖文教程(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09

最新評(píng)論