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

Java 讀寫鎖實(shí)現(xiàn)原理淺析

 更新時間:2018年08月16日 09:17:07   作者:編輯部的故事  
這篇文章主要介紹了Java 讀寫鎖實(shí)現(xiàn)原理淺析,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下

最近做的一個小項(xiàng)目中有這樣的需求:整個項(xiàng)目有一份config.json保存著項(xiàng)目的一些配置,是存儲在本地文件的一個資源,并且應(yīng)用中存在讀寫(讀>>寫)更新問題。既然讀寫并發(fā)操作,那么就涉及到操作互斥,這里自然想到了讀寫鎖,本文對讀寫鎖方面的知識做個梳理。

為什么需要讀寫鎖?

與傳統(tǒng)鎖不同的是讀寫鎖的規(guī)則是可以共享讀,但只能一個寫,總結(jié)起來為:讀讀不互斥,讀寫互斥,寫寫互斥,而一般的獨(dú)占鎖是:讀讀互斥,讀寫互斥,寫寫互斥,而場景中往往讀遠(yuǎn)遠(yuǎn)大于寫,讀寫鎖就是為了這種優(yōu)化而創(chuàng)建出來的一種機(jī)制。

注意是讀遠(yuǎn)遠(yuǎn)大于寫,一般情況下獨(dú)占鎖的效率低來源于高并發(fā)下對臨界區(qū)的激烈競爭導(dǎo)致線程上下文切換。因此當(dāng)并發(fā)不是很高的情況下,讀寫鎖由于需要額外維護(hù)讀鎖的狀態(tài),可能還不如獨(dú)占鎖的效率高。因此需要根據(jù)實(shí)際情況選擇使用。

一個簡單的讀寫鎖實(shí)現(xiàn)

根據(jù)上面理論可以利用兩個int變量來簡單實(shí)現(xiàn)一個讀寫鎖,實(shí)現(xiàn)雖然爛,但是原理都是差不多的,值得閱讀下。

public class ReadWriteLock {
 /**
  * 讀鎖持有個數(shù)
  */
 private int readCount = 0;
 /**
  * 寫鎖持有個數(shù)
  */
 private int writeCount = 0;
 /**
  * 獲取讀鎖,讀鎖在寫鎖不存在的時候才能獲取
  */
 public synchronized void lockRead() throws InterruptedException {
  // 寫鎖存在,需要wait
  while (writeCount > 0) {
   wait();
  }
  readCount++;
 }
 /**
  * 釋放讀鎖
  */
 public synchronized void unlockRead() {
  readCount--;
  notifyAll();
 }
 /**
  * 獲取寫鎖,當(dāng)讀鎖存在時需要wait.
  */
 public synchronized void lockWrite() throws InterruptedException {
  // 先判斷是否有寫請求
  while (writeCount > 0) {
   wait();
  }
  // 此時已經(jīng)不存在獲取寫鎖的線程了,因此占坑,防止寫鎖饑餓
  writeCount++;
  // 讀鎖為0時獲取寫鎖
  while (readCount > 0) {
   wait();
  }
 }
 /**
  * 釋放讀鎖
  */
 public synchronized void unlockWrite() {
  writeCount--;
  notifyAll();
 }
}

ReadWriteLock的實(shí)現(xiàn)原理

在Java中ReadWriteLock的主要實(shí)現(xiàn)為ReentrantReadWriteLock,其提供了以下特性:

  • 公平性選擇:支持公平與非公平(默認(rèn))的鎖獲取方式,吞吐量非公平優(yōu)先于公平。
  • 可重入:讀線程獲取讀鎖之后可以再次獲取讀鎖,寫線程獲取寫鎖之后可以再次獲取寫鎖
  • 可降級:寫線程獲取寫鎖之后,其還可以再次獲取讀鎖,然后釋放掉寫鎖,那么此時該線程是讀鎖狀態(tài),也就是降級操作。

ReentrantReadWriteLock的結(jié)構(gòu)

ReentrantReadWriteLock的核心是由一個基于AQS的同步器Sync構(gòu)成,然后由其擴(kuò)展出ReadLock(共享鎖),WriteLock(排它鎖)所組成。

并且從ReentrantReadWriteLock的構(gòu)造函數(shù)中可以發(fā)現(xiàn)ReadLock與WriteLock使用的是同一個Sync,具體怎么實(shí)現(xiàn)同一個隊(duì)列既可以為共享鎖,又可以表示排他鎖下文會具體分析。

清單一:ReentrantReadWriteLock構(gòu)造函數(shù)

public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
  }

Sync的實(shí)現(xiàn)

sync是讀寫鎖實(shí)現(xiàn)的核心,sync是基于AQS實(shí)現(xiàn)的,在AQS中核心是state字段和雙端隊(duì)列,那么一個一個問題來分析。

Sync如何同時表示讀鎖與寫鎖?

清單2:讀寫鎖狀態(tài)獲取

static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

從代碼中獲取讀寫狀態(tài)可以看出其是把state(int32位)字段分成高16位與低16位,其中高16位表示讀鎖個數(shù),低16位表示寫鎖個數(shù),如下圖所示(圖來自Java并發(fā)編程藝術(shù))。

該圖表示當(dāng)前一個線程獲取到了寫鎖,并且重入了兩次,因此低16位是3,并且該線程又獲取了讀鎖,并且重入了一次,所以高16位是2,當(dāng)寫鎖被獲取時如果讀鎖不為0那么讀鎖一定是獲取寫鎖的這個線程。

讀鎖的獲取

讀鎖的獲取主要實(shí)現(xiàn)是AQS中的acquireShared方法,其調(diào)用過程如下代碼。

清單3:讀鎖獲取入口

// ReadLock
public void lock() {
  sync.acquireShared(1);
}
// AQS
public final void acquireShared(int arg) {
  if (tryAcquireShared(arg) < 0)
    doAcquireShared(arg);
}

其中doAcquireShared(arg)方法是獲取失敗之后AQS中入隊(duì)操作,等待被喚醒后重新獲取,那么關(guān)鍵點(diǎn)就是tryAcquireShared(arg)方法,方法有點(diǎn)長,因此先總結(jié)出獲取讀鎖所經(jīng)歷的步驟,獲取的第一部分步驟如下:

  • 操作1:讀寫需要互斥,因此當(dāng)存在寫鎖并且持有寫鎖的線程不是該線程時獲取失敗。
  • 操作2:是否存在等待寫鎖的線程,存在的話則獲取讀鎖需要等待,避免寫鎖饑餓。(寫鎖優(yōu)先級是比較高的)
  • 操作3:CAS獲取讀鎖,實(shí)際上是state字段的高16位自增。
  • 操作4:獲取成功后再ThreadLocal中記錄當(dāng)前線程獲取讀鎖的次數(shù)。

清單4:讀鎖獲取的第一部分

protected final int tryAcquireShared(int unused) {
     Thread current = Thread.currentThread();
     int c = getState();
     // 操作1:存在寫鎖,并且寫鎖不是當(dāng)前線程則直接去排隊(duì)
     if (exclusiveCount(c) != 0 &&
       getExclusiveOwnerThread() != current)
       return -1;

     int r = sharedCount(c);
     // 操作2:讀鎖是否該阻塞,對于非公平模式下寫鎖獲取優(yōu)先級會高,如果存在要獲取寫鎖的線程則讀鎖需要讓步,公平模式下則先來先到
     if (!readerShouldBlock() && 
       // 讀鎖使用高16位,因此存在獲取上限為2^16-1
       r < MAX_COUNT &&
       // 操作3:CAS修改讀鎖狀態(tài),實(shí)際上是讀鎖狀態(tài)+1
       compareAndSetState(c, c + SHARED_UNIT)) {
       // 操作4:執(zhí)行到這里說明讀鎖已經(jīng)獲取成功,因此需要記錄線程狀態(tài)。
       if (r == 0) {
         firstReader = current; // firstReader是把讀鎖狀態(tài)從0變成1的那個線程
         firstReaderHoldCount = 1;
       } else if (firstReader == current) { 
         firstReaderHoldCount++;
       } else {
         // 這些代碼實(shí)際上是從ThreadLocal中獲取當(dāng)前線程重入讀鎖的次數(shù),然后自增下。
         HoldCounter rh = cachedHoldCounter; // cachedHoldCounter是上一個獲取鎖成功的線程
         if (rh == null || rh.tid != getThreadId(current))
           cachedHoldCounter = rh = readHolds.get();
         else if (rh.count == 0)
           readHolds.set(rh);
         rh.count++;
       }
       return 1;
     }
     // 當(dāng)操作2,操作3失敗時執(zhí)行該邏輯
     return fullTryAcquireShared(current);
   }

當(dāng)操作2,操作3失敗時會執(zhí)行fullTryAcquireShared(current),為什么會這樣寫呢?個人認(rèn)為是一種補(bǔ)償操作,操作2與操作3失敗并不代表當(dāng)前線程沒有讀鎖的資格,并且這里的讀鎖是共享鎖,有資格就應(yīng)該被獲取成功,因此給予補(bǔ)償獲取讀鎖的操作。在fullTryAcquireShared(current)中是一個循環(huán)獲取讀鎖的過程,大致步驟如下:

  • 操作5:等同于操作2,存在寫鎖,且寫鎖線程并非當(dāng)前線程則直接返回失敗
  • 操作6:當(dāng)前線程是重入讀鎖,這里只會偏向第一個獲取讀鎖的線程以及最后一個獲取讀鎖的線程,其他都需要去AQS中排隊(duì)。
  • 操作7:CAS改變讀鎖狀態(tài)
  • 操作8:同操作4,獲取成功后再ThreadLocal中記錄當(dāng)前線程獲取讀鎖的次數(shù)。

清單5:讀鎖獲取的第二部分

final int fullTryAcquireShared(Thread current) {
      HoldCounter rh = null;
      // 最外層嵌套循環(huán)
      for (;;) {
        int c = getState();
        // 操作5:存在寫鎖,且寫鎖并非當(dāng)前線程則直接返回失敗
        if (exclusiveCount(c) != 0) {
          if (getExclusiveOwnerThread() != current)
            return -1;
          // else we hold the exclusive lock; blocking here
          // would cause deadlock.
        // 操作6:如果當(dāng)前線程是重入讀鎖則放行
        } else if (readerShouldBlock()) {
          // Make sure we're not acquiring read lock reentrantly
          // 當(dāng)前是firstReader,則直接放行,說明是已獲取的線程重入讀鎖
          if (firstReader == current) {
            // assert firstReaderHoldCount > 0;
          } else {
            // 執(zhí)行到這里說明是其他線程,如果是cachedHoldCounter(其count不為0)也就是上一個獲取鎖的線程則可以重入,否則進(jìn)入AQS中排隊(duì)
            // **這里也是對寫鎖的讓步**,如果隊(duì)列中頭結(jié)點(diǎn)為寫鎖,那么當(dāng)前獲取讀鎖的線程要進(jìn)入隊(duì)列中排隊(duì)
            if (rh == null) {
              rh = cachedHoldCounter;
              if (rh == null || rh.tid != getThreadId(current)) {
                rh = readHolds.get();
                if (rh.count == 0)
                  readHolds.remove();
              }
            }
            // 說明是上述剛初始化的rh,所以直接去AQS中排隊(duì)
            if (rh.count == 0)
              return -1;
          }
        }
        if (sharedCount(c) == MAX_COUNT)
          throw new Error("Maximum lock count exceeded");
        // 操作7:修改讀鎖狀態(tài),實(shí)際上讀鎖自增操作
        if (compareAndSetState(c, c + SHARED_UNIT)) {
          // 操作8:對ThreadLocal中維護(hù)的獲取鎖次數(shù)進(jìn)行更新。
          if (sharedCount(c) == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
          } else if (firstReader == current) {
            firstReaderHoldCount++;
          } else {
            if (rh == null)
              rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
              rh = readHolds.get();
            else if (rh.count == 0)
              readHolds.set(rh);
            rh.count++;
            cachedHoldCounter = rh; // cache for release
          }
          return 1;
        }
      }
    }

讀鎖的釋放

清單6:讀鎖釋放入口

// ReadLock
public void unlock() {
  sync.releaseShared(1);
}
// Sync
public final boolean releaseShared(int arg) {
  if (tryReleaseShared(arg)) {
    doReleaseShared(); // 這里實(shí)際上是釋放讀鎖后喚醒寫鎖的線程操作
    return true;
  }
  return false;
}

讀鎖的釋放主要是tryReleaseShared(arg)函數(shù),因此拆解其步驟如下:

  • 操作1:清理ThreadLocal中保存的獲取鎖數(shù)量信息
  • 操作2:CAS修改讀鎖個數(shù),實(shí)際上是自減一

清單7:讀鎖的釋放流程

protected final boolean tryReleaseShared(int unused) {
     Thread current = Thread.currentThread();
     // 操作1:清理ThreadLocal對應(yīng)的信息
     if (firstReader == current) {;
       if (firstReaderHoldCount == 1)
         firstReader = null;
       else
         firstReaderHoldCount--;
     } else {
       HoldCounter rh = cachedHoldCounter;
       if (rh == null || rh.tid != getThreadId(current))
         rh = readHolds.get();
       int count = rh.count;
       // 已釋放完的讀鎖的線程清空操作
       if (count <= 1) {
         readHolds.remove();
         // 如果沒有獲取鎖卻釋放則會報(bào)該錯誤
         if (count <= 0)
           throw unmatchedUnlockException();
       }
       --rh.count;
     }
     // 操作2:循環(huán)中利用CAS修改讀鎖狀態(tài)
     for (;;) {
       int c = getState();
       int nextc = c - SHARED_UNIT;
       if (compareAndSetState(c, nextc))
         // Releasing the read lock has no effect on readers,
         // but it may allow waiting writers to proceed if
         // both read and write locks are now free.
         return nextc == 0;
     }
   }

寫鎖的獲取

清單8:寫鎖的獲取入口

// WriteLock
 public void lock() {
    sync.acquire(1);
  }
// AQS
 public final void acquire(int arg) {
    // 嘗試獲取,獲取失敗后入隊(duì),入隊(duì)失敗則interrupt當(dāng)前線程
    if (!tryAcquire(arg) &&
      acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
      selfInterrupt();
  }

寫鎖的獲取也主要是tryAcquire(arg)方法,這里也拆解步驟:

  • 操作1:如果讀鎖數(shù)量不為0或者寫鎖數(shù)量不為0,并且不是重入操作,則獲取失敗。
  • 操作2:如果當(dāng)前鎖的數(shù)量為0,也就是不存在操作1的情況,那么該線程是有資格獲取到寫鎖,因此修改狀態(tài),設(shè)置獨(dú)占線程為當(dāng)前線程

清單9:寫鎖的獲取

protected final boolean tryAcquire(int acquires) {
  Thread current = Thread.currentThread();
  int c = getState();
  int w = exclusiveCount(c);
  // 操作1:c != 0,說明存在讀鎖或者寫鎖
  if (c != 0) {
    // (Note: if c != 0 and w == 0 then shared count != 0) 
    // 寫鎖為0,讀鎖不為0 或者獲取寫鎖的線程并不是當(dāng)前線程,直接失敗
    if (w == 0 || current != getExclusiveOwnerThread())
      return false;
    if (w + exclusiveCount(acquires) > MAX_COUNT)
      throw new Error("Maximum lock count exceeded");
    // Reentrant acquire
    // 執(zhí)行到這里說明是寫鎖線程的重入操作,直接修改狀態(tài),也不需要CAS因?yàn)闆]有競爭
    setState(c + acquires);
    return true;
  }
  // 操作2:獲取寫鎖,writerShouldBlock對于非公平模式直接返回fasle,對于公平模式則線程需要排隊(duì),因此需要阻塞。
  if (writerShouldBlock() ||
    !compareAndSetState(c, c + acquires))
    return false;
  setExclusiveOwnerThread(current);
  return true;
}

寫鎖的釋放

清單10:寫鎖的釋放入口

// WriteLock
public void unlock() {
    sync.release(1);
  }
// AQS
public final boolean release(int arg) {
  // 釋放鎖成功后喚醒隊(duì)列中第一個線程
  if (tryRelease(arg)) {
    Node h = head;
    if (h != null && h.waitStatus != 0)
      unparkSuccessor(h);
    return true;
  }
  return false;
}

寫鎖的釋放主要是tryRelease(arg)方法,其邏輯就比較簡單了,注釋很詳細(xì)。

清單11:寫鎖的釋放

protected final boolean tryRelease(int releases) {
   // 如果當(dāng)前線程沒有獲取寫鎖卻釋放,則直接拋異常
   if (!isHeldExclusively())
     throw new IllegalMonitorStateException();
   // 狀態(tài)變更至nextc
   int nextc = getState() - releases;
   // 因?yàn)閷戞i是可以重入,所以在都釋放完畢后要把獨(dú)占標(biāo)識清空
   boolean free = exclusiveCount(nextc) == 0;
   if (free)
     setExclusiveOwnerThread(null);
   // 修改狀態(tài)
   setState(nextc);
   return free;
 }

一些其他問題

鎖降級操作哪里體現(xiàn)?

鎖降級操作指的是一個線程獲取寫鎖之后再獲取讀鎖,然后讀鎖釋放掉寫鎖的過程。在tryAcquireShared(arg)獲取讀鎖的代碼中有如下代碼。

清單12:寫鎖降級策略

Thread current = Thread.currentThread();
      // 當(dāng)前狀態(tài)
      int c = getState();
      // 存在寫鎖,并且寫鎖不等于當(dāng)前線程時返回,換句話說等寫鎖為當(dāng)前線程時則可以繼續(xù)往下獲取讀鎖。
      if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;

。。。。。讀鎖獲取。。。。。

那么鎖降級有什么用?答案是為了可見性的保證。在ReentrantReadWriteLock的javadoc中有如下代碼,其是鎖降級的一個應(yīng)用示例。

class CachedData {
 Object data;
 volatile boolean cacheValid;
 final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
 
 void processCachedData() {
  // 獲取讀鎖
  rwl.readLock().lock();
  if (!cacheValid) {
   // Must release read lock before acquiring write lock,不釋放的話下面寫鎖會獲取不成功,造成死鎖
   rwl.readLock().unlock();
   // 獲取寫鎖
   rwl.writeLock().lock();
   try {
    // Recheck state because another thread might have
    // acquired write lock and changed state before we did.
    if (!cacheValid) {
     data = ...
     cacheValid = true;
    }
    // Downgrade by acquiring read lock before releasing write lock
    // 這里再次獲取讀鎖,如果不獲取那么當(dāng)寫鎖釋放后可能其他寫線程再次獲得寫鎖,導(dǎo)致下方`use(data)`時出現(xiàn)不一致的現(xiàn)象
    // 這個操作就是降級
    rwl.readLock().lock();
   } finally {
    rwl.writeLock().unlock(); // Unlock write, still hold read
   }
  }

  try {
  // 使用完后釋放讀鎖
   use(data);
  } finally {
   rwl.readLock().unlock();
  }
 }
 }}

公平與非公平的區(qū)別

清單13:公平下的Sync

static final class FairSync extends Sync {
   private static final long serialVersionUID = -2274990926593161451L;
   final boolean writerShouldBlock() {
     return hasQueuedPredecessors(); // 隊(duì)列中是否有元素,有責(zé)當(dāng)前操作需要block
   }
   final boolean readerShouldBlock() {
     return hasQueuedPredecessors();// 隊(duì)列中是否有元素,有責(zé)當(dāng)前操作需要block
   }
 }

公平下的Sync實(shí)現(xiàn)策略是所有獲取的讀鎖或者寫鎖的線程都需要入隊(duì)排隊(duì),按照順序依次去嘗試獲取鎖。

清單14:非公平下的Sync

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -8159625535654395037L;
    final boolean writerShouldBlock() {
      // 非公平下不考慮排隊(duì),因此寫鎖可以競爭獲取
      return false; // writers can always barge
    }
    final boolean readerShouldBlock() {
      /* As a heuristic to avoid indefinite writer starvation,
      * block if the thread that momentarily appears to be head
      * of queue, if one exists, is a waiting writer. This is
      * only a probabilistic effect since a new reader will not
      * block if there is a waiting writer behind other enabled
      * readers that have not yet drained from the queue.
      */
      // 這里實(shí)際上是一個優(yōu)先級,如果隊(duì)列中頭部元素時寫鎖,那么讀鎖需要等待,避免寫鎖饑餓。
      return apparentlyFirstQueuedIsExclusive();
    }
  }

非公平下由于搶占式獲取鎖,寫鎖是可能產(chǎn)生饑餓,因此解決辦法就是提高寫鎖的優(yōu)先級,換句話說獲取寫鎖之前先占坑。

總結(jié)

以上所述是小編給大家介紹的Java 讀寫鎖實(shí)現(xiàn)原理淺析,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!

相關(guān)文章

  • java遍歷Map的幾種方法分析

    java遍歷Map的幾種方法分析

    這篇文章主要介紹了java遍歷Map的幾種方法,結(jié)合實(shí)例形式分析了使用循環(huán)與迭代等方法操作Map遍歷的相關(guān)技巧,需要的朋友可以參考下
    2016-08-08
  • Java面試最容易被刷的重難點(diǎn)之鎖的使用策略

    Java面試最容易被刷的重難點(diǎn)之鎖的使用策略

    鎖像synchronized同步塊一樣,是一種線程同步機(jī)制,但比Java中的synchronized同步塊更復(fù)雜。因?yàn)殒i(以及其它更高級的線程同步機(jī)制)是由synchronized同步塊的方式實(shí)現(xiàn)的,所以我們還不能完全擺脫synchronized關(guān)鍵字
    2021-10-10
  • 詳解Java關(guān)鍵字final

    詳解Java關(guān)鍵字final

    今天帶大家學(xué)習(xí)Java基礎(chǔ)知識,文中對關(guān)鍵字final作了非常詳細(xì)的介紹,對正在學(xué)習(xí)Java的小伙伴們很有幫助,需要的朋友可以參考下
    2021-05-05
  • 使用Java實(shí)現(xiàn)獲取excel附件并解析

    使用Java實(shí)現(xiàn)獲取excel附件并解析

    在Java中,我們經(jīng)常需要讀取Excel文件中的數(shù)據(jù),進(jìn)行處理和分析,本文將介紹如何使用Java代碼來獲取與解析Excel文件中的附件,希望對大家有所幫助
    2025-04-04
  • 淺析final,finally,finalize 的區(qū)別

    淺析final,finally,finalize 的區(qū)別

    以下是對final,finally,finalize的區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過來參考下
    2013-09-09
  • 一文搞懂JMeter engine中HashTree的配置問題

    一文搞懂JMeter engine中HashTree的配置問題

    本文主要介紹了JMeter engine中HashTree的配置,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Java獲取IP地址以及MAC地址的示例代碼

    Java獲取IP地址以及MAC地址的示例代碼

    IP地址是用于在網(wǎng)絡(luò)上識別設(shè)備的唯一地址,而MAC地址是設(shè)備的物理地址,本文主要介紹了Java獲取IP地址以及MAC地址的示例代碼,具有一定的參考價值,感興趣的可以了解一下
    2024-04-04
  • Java8的Optional如何干掉空指針(示例詳解)

    Java8的Optional如何干掉空指針(示例詳解)

    這篇文章主要介紹了Java8的Optional如何干掉空指針,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-01-01
  • SpringCloud Gateway自定義filter獲取body中的數(shù)據(jù)為空的問題

    SpringCloud Gateway自定義filter獲取body中的數(shù)據(jù)為空的問題

    這篇文章主要介紹了SpringCloud Gateway自定義filter獲取body中的數(shù)據(jù)為空,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-10-10
  • IntelliJ IDEA 2020.1.2激活工具下載及破解方法免費(fèi)可用至2089年(強(qiáng)烈推薦)

    IntelliJ IDEA 2020.1.2激活工具下載及破解方法免費(fèi)可用至2089年(強(qiáng)烈推薦)

    這篇文章主要介紹了IntelliJ IDEA 2020.1.2激活工具下載及破解方法免費(fèi)可用至2089年(強(qiáng)烈推薦),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-09-09

最新評論

万荣县| 寿宁县| 静宁县| 达拉特旗| 大邑县| 高淳县| 通州区| 新干县| 墨脱县| 顺义区| 西乡县| 黑龙江省| 大悟县| 木兰县| 瑞丽市| 锡林浩特市| 平谷区| 屯昌县| 桃江县| 阳信县| 井冈山市| 哈密市| 西充县| 泽库县| 剑河县| 慈利县| 安庆市| 克什克腾旗| 大埔区| 罗山县| 法库县| 盐亭县| 神池县| 牡丹江市| 共和县| 平乡县| 城口县| 辽阳县| 荔浦县| 威宁| 鄢陵县|