Caffeine源碼分析

緩存過期算法

  1. LFU(Least Frequently Used祝钢,最近最不常用)
    根據(jù)最近使用的頻率來淘汰頻率低的數(shù)據(jù)。核心思想是:如果數(shù)據(jù)過去多次被訪問若厚,那么將來被訪問的概率也高拦英。該算法需要維護(hù)數(shù)據(jù)的訪問頻率,開銷很大测秸。此外不適合應(yīng)對(duì)突發(fā)性熱點(diǎn)訪問場(chǎng)景疤估。
  2. LRU(Least recently used,最近最少使用)
    根據(jù)最近訪問記錄淘汰最近很少被訪問的數(shù)據(jù)霎冯。核心思想是:如果數(shù)據(jù)最近被訪問過铃拇,那么將來被訪問的幾率也更高。缺點(diǎn)在于偶發(fā)性的批量操作會(huì)降低命中率沈撞。guavacache使用的就是此算法慷荔。
  3. TinyLFU
    輕量LFU算法,相比較LFU关串,使用更少的內(nèi)存來保存訪問頻率拧廊。TinyLFU保存了近期的訪問頻率,而不是整個(gè)生命周期的訪問頻率晋修,所以可以很好的應(yīng)對(duì)突發(fā)性熱點(diǎn)場(chǎng)景吧碾。這些訪問記錄會(huì)作為一個(gè)過濾頻率,當(dāng)新加入的數(shù)據(jù)訪問頻率比要淘汰的數(shù)據(jù)訪問頻率才加入緩存墓卦。


    image.png

    TinyLFU 通過 Count-Min Sketch 算法來記錄頻率信息倦春,它占用空間小且誤報(bào)率低。但在應(yīng)對(duì)稀疏的突發(fā)性訪問量大的場(chǎng)景落剪,將很難保存這類元素睁本,因?yàn)榭赡軣o法在短時(shí)間內(nèi)積累足夠的訪問頻率,從而被過濾器過濾掉忠怖。

  4. W-TinyLFU
    對(duì)TinyLFU在稀疏突發(fā)性訪問大的場(chǎng)景做了優(yōu)化呢堰,W-TinyLFU 將新記錄暫時(shí)放入 Window Cache 里面,只有通過 TinLFU 考察才能進(jìn)入 Main Cache凡泣。


    image.png

    其中過濾器使用了Count-Min Sketch算法(一種布隆過濾器的變種)實(shí)現(xiàn)枉疼,即根據(jù)不同的hash算法創(chuàng)建不同的數(shù)組皮假,針對(duì)每一個(gè)數(shù)據(jù)進(jìn)行多次hash,并在該hash算法的對(duì)應(yīng)數(shù)組hash索引位置上+1骂维,由于hash算法存在沖突惹资,那么在最后取計(jì)數(shù)的時(shí)候,取所有數(shù)組中最小的值即可航闺。

Caffeine簡(jiǎn)介

相對(duì)于guavacache褪测,caffeine采用更好的過期策略W-TinyLFU,并通過RingBuffer緩存訪問信息潦刃,再批量異步處理侮措;此外caffeine底層直接使用jdk1.8的ConcurrentHashMap(因此caffeine只能在1.8上使用),比1.7的ConcurrentHashMap要快不少(guavacache底層實(shí)現(xiàn)類似于1.7的ConcurrentHashMap)乖杠,官方號(hào)稱其性能接近jdk1.8的ConcurrentHashMap萝毛。下面是官方的測(cè)試對(duì)比圖:


image.png

caffeine使用和guava差不多,因此切換過來成本較低滑黔。

讀寫數(shù)據(jù)結(jié)構(gòu)

不同于guavacache采用accessQueue、recencyQueue环揽、writeQueue隊(duì)列來記錄讀寫操作略荡。caffeine采用的是readBuffer和writeBuffer。

readBuffer

采用多個(gè) RingBuffer(striped ring buffer 條帶環(huán)形緩沖歉胶,有損)汛兜,通過線程 id 哈希到對(duì)應(yīng)的RingBuffer。當(dāng)一個(gè)RingBuffer滿了后通今,后續(xù)的寫入會(huì)丟棄直到這個(gè)RingBuffer可用粥谬。
當(dāng)讀命中后,會(huì)將數(shù)據(jù)寫入RingBuffer辫塌,這個(gè)寫入操作性能很高漏策。然后由線程池(默認(rèn)forkjoin或自定義)異步消費(fèi)RingBuffer,將數(shù)據(jù)加入到數(shù)據(jù)淘汰隊(duì)列中臼氨。

writeBuffer

MpscGrowableArrayQueue實(shí)現(xiàn)掺喻,和JCTools中的原理差不多。寫和讀操作不一樣储矩,讀多寫少感耙,且不允許有損(丟失數(shù)據(jù)),mpsc可參考【netty學(xué)習(xí)筆記十七】Mpsc高性能無鎖隊(duì)列持隧。

自定義過期策略

caffeine除了支持expireAfterAccess和expireAfterWrite即硼,還支持expireAfter,即根據(jù)key定義不同的過期時(shí)間屡拨。這里的實(shí)現(xiàn)是用時(shí)間輪只酥,可參考【netty學(xué)習(xí)筆記十八】netty時(shí)間輪褥实。

源碼簡(jiǎn)析

直接看get方法,最終會(huì)調(diào)computeIfAbsent方法

public @Nullable V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction,
      boolean recordStats, boolean recordLoad) {
    long now = expirationTicker().read();

    // 這里的data就是ConcurrentHashMap
    Node<K, V> node = data.get(nodeFactory.newLookupKey(key));
    if (node != null) {
      V value = node.getValue();
      //如果key已存在且沒過期(這里過期都是exprire開頭的屬性)层皱,則返回value
      if ((value != null) && !hasExpired(node, now)) {
        if (!isComputingAsync(node)) {
          tryExpireAfterRead(node, key, value, expiry(), now);
          setAccessTime(node, now);
        }

        afterRead(node, now, /* recordHit */ recordStats);
        return value;
      }
    }
    if (recordStats) {
      mappingFunction = statsAware(mappingFunction, recordLoad);
    }
    Object keyRef = nodeFactory.newReferenceKey(key, keyReferenceQueue());
    return doComputeIfAbsent(key, keyRef, mappingFunction, new long[] { now }, recordStats);
  }

/** Returns the current value from a computeIfAbsent invocation. */
  @Nullable V doComputeIfAbsent(K key, Object keyRef,
      Function<? super K, ? extends V> mappingFunction, long[/* 1 */] now, boolean recordStats) {
    @SuppressWarnings("unchecked")
    V[] oldValue = (V[]) new Object[1];
    @SuppressWarnings("unchecked")
    V[] newValue = (V[]) new Object[1];
    @SuppressWarnings("unchecked")
    K[] nodeKey = (K[]) new Object[1];
    @SuppressWarnings({"unchecked", "rawtypes"})
    Node<K, V>[] removed = new Node[1];

    int[] weight = new int[2]; // old, new
    RemovalCause[] cause = new RemovalCause[1];
    Node<K, V> node = data.compute(keyRef, (k, n) -> {
      // n!=null表示數(shù)據(jù)是過期的
      if (n == null) {
        // 這里最終調(diào)用自定義加載數(shù)據(jù)方法那里
        newValue[0] = mappingFunction.apply(key);
        if (newValue[0] == null) {
          return null;
        }
        now[0] = expirationTicker().read();
        weight[1] = weigher.weigh(key, newValue[0]);
        n = nodeFactory.newNode(key, keyReferenceQueue(),
            newValue[0], valueReferenceQueue(), weight[1], now[0]);
        setVariableTime(n, expireAfterCreate(key, newValue[0], expiry(), now[0]));
        return n;
      }
      // 對(duì)節(jié)點(diǎn)加鎖性锭,若n過期了, 其他線程會(huì)等待n更新完畢
      synchronized (n) {
        nodeKey[0] = n.getKey();
        weight[0] = n.getWeight();
        oldValue[0] = n.getValue();
        if ((nodeKey[0] == null) || (oldValue[0] == null)) {
          cause[0] = RemovalCause.COLLECTED;
        } else if (hasExpired(n, now[0])) {
          cause[0] = RemovalCause.EXPIRED;
        } else {
          return n;
        }
        //刪除數(shù)據(jù)并重新加載
        writer.delete(nodeKey[0], oldValue[0], cause[0]);
        newValue[0] = mappingFunction.apply(key);
        if (newValue[0] == null) {
          removed[0] = n;
          n.retire();
          return null;
        }
        weight[1] = weigher.weigh(key, newValue[0]);
        n.setValue(newValue[0], valueReferenceQueue());
        n.setWeight(weight[1]);

        now[0] = expirationTicker().read();
        setVariableTime(n, expireAfterCreate(key, newValue[0], expiry(), now[0]));
        setAccessTime(n, now[0]);
        setWriteTime(n, now[0]);
        return n;
      }
    });

    if (node == null) {
      if (removed[0] != null) {
        afterWrite(new RemovalTask(removed[0]));
      }
      return null;
    }
    if (cause[0] != null) {
      // 舊值移除通知
      if (hasRemovalListener()) {
        notifyRemoval(nodeKey[0], oldValue[0], cause[0]);
      }
      statsCounter().recordEviction(weight[0]);
    }
    // 新值還未計(jì)算完叫胖?
    if (newValue[0] == null) {
      if (!isComputingAsync(node)) {
        tryExpireAfterRead(node, key, oldValue[0], expiry(), now[0]);
        setAccessTime(node, now[0]);
      }

      afterRead(node, now[0], /* recordHit */ recordStats);
      return oldValue[0];
    }
    //記錄寫操作
    if ((oldValue[0] == null) && (cause[0] == null)) {
      afterWrite(new AddTask(node, weight[1]));
    } else {
      int weightedDifference = (weight[1] - weight[0]);
      afterWrite(new UpdateTask(node, weightedDifference));
    }
    //返回新值
    return newValue[0];
  }

refreshAfterWrite和expireAfterWrite區(qū)別

和guava有點(diǎn)不一樣草冈,refreshAfterWrite是過期了直接返回舊值,然后通過異步線程池進(jìn)行刷新(默認(rèn)線程池為forkjoin)瓮增。而expireAfterWrite是一樣的怎棱,都是加載新值,其他線程需要等待绷跑。
refreshAfterWrite在BoundedLocalCache#afterRead -> refreshIfNeeded方法:

void refreshIfNeeded(Node<K, V> node, long now) {
    if (!refreshAfterWrite()) {
      return;
    }
    K key;
    V oldValue;
    long oldWriteTime = node.getWriteTime();
    long refreshWriteTime = (now + ASYNC_EXPIRY);
    if (((now - oldWriteTime) > refreshAfterWriteNanos())
        && ((key = node.getKey()) != null) && ((oldValue = node.getValue()) != null)
        && node.casWriteTime(oldWriteTime, refreshWriteTime)) {
      try {
        CompletableFuture<V> refreshFuture;
        long startTime = statsTicker().read();
        //是否異步
        if (isAsync) {
          @SuppressWarnings("unchecked")
          CompletableFuture<V> future = (CompletableFuture<V>) oldValue;
          if (Async.isReady(future)) {
            @SuppressWarnings("NullAway")
            CompletableFuture<V> refresh = future.thenCompose(value ->
              cacheLoader.asyncReload(key, value, executor));
            refreshFuture = refresh;
          } else {
            // no-op if load is pending
            node.casWriteTime(refreshWriteTime, oldWriteTime);
            return;
          }
        } else {
          @SuppressWarnings("NullAway")
          // 異常加載新值
          CompletableFuture<V> refresh = cacheLoader.asyncReload(key, oldValue, executor);
          refreshFuture = refresh;
        }
        refreshFuture.whenComplete((newValue, error) -> {
          long loadTime = statsTicker().read() - startTime;
          if (error != null) {
            logger.log(Level.WARNING, "Exception thrown during refresh", error);
            node.casWriteTime(refreshWriteTime, oldWriteTime);
            statsCounter().recordLoadFailure(loadTime);
            return;
          }

          @SuppressWarnings("unchecked")
          V value = (isAsync && (newValue != null)) ? (V) refreshFuture : newValue;

          boolean[] discard = new boolean[1];
          compute(key, (k, currentValue) -> {
            if (currentValue == null) {
              return value;
            } else if ((currentValue == oldValue) && (node.getWriteTime() == refreshWriteTime)) {
              return value;
            }
            discard[0] = true;
            return currentValue;
          }, /* recordMiss */ false, /* recordLoad */ false, /* recordLoadFailure */ true);

          if (discard[0] && hasRemovalListener()) {
            notifyRemoval(key, value, RemovalCause.REPLACED);
          }
          if (newValue == null) {
            statsCounter().recordLoadFailure(loadTime);
          } else {
            statsCounter().recordLoadSuccess(loadTime);
          }
        });
      } catch (Throwable t) {
        node.casWriteTime(refreshWriteTime, oldWriteTime);
        logger.log(Level.SEVERE, "Exception thrown when submitting refresh task", t);
      }
    }
  }

refreshAfterWrite異步加載過程如上拳恋,expireAfterWrite會(huì)在hasExpired就會(huì)判斷,如果過期就調(diào)用doComputeIfAbsent加載新值砸捏。

boolean hasExpired(Node<K, V> node, long now) {
    return (expiresAfterAccess() && (now - node.getAccessTime() >= expiresAfterAccessNanos()))
        | (expiresAfterWrite() && (now - node.getWriteTime() >= expiresAfterWriteNanos()))
        | (expiresVariable() && (now - node.getVariableTime() >= 0));
  }

Count–Min Sketch算法實(shí)現(xiàn)

上文寫到caffeine采用W-TinyLFU實(shí)現(xiàn)淘汰策略谬运,其中過濾器這塊使用了Count–Min Sketch算法。caffeine這塊的實(shí)現(xiàn)在FrequencySketch類中垦藏,在實(shí)現(xiàn)中做了一些優(yōu)化梆暖。
FrequencySketch使用一個(gè)long數(shù)組來記錄訪問頻率,數(shù)組大小為最接近緩存大小且比其大的2的冪數(shù)掂骏。在FrequencySketch中轰驳,認(rèn)為最大的訪問頻率是15,換成二進(jìn)制則是4位弟灼,那一個(gè)long理論上可以放16種算法级解。但caffeine將long分為16等份,每份4bit用來存儲(chǔ)對(duì)應(yīng)的頻率田绑。long結(jié)構(gòu)如下:


image.png

我們?cè)倏纯刺砑右淮卧L問頻率的代碼:

public void increment(@NonNull E e) {
    if (isNotInitialized()) {
      return;
    }
    // 和jdk的hashmap一樣勤哗,這里的spread操作會(huì)讓hash更均勻
    int hash = spread(e.hashCode());
    // 獲取一個(gè)小于16的數(shù),即判斷在long里那個(gè)等份
    int start = (hash & 3) << 2;

    // 設(shè)計(jì)了4種hash方法(對(duì)應(yīng)不同的種子)掩驱,分別計(jì)算4個(gè)不同的table(long數(shù)組)下標(biāo)
    int index0 = indexOf(hash, 0);
    int index1 = indexOf(hash, 1);
    int index2 = indexOf(hash, 2);
    int index3 = indexOf(hash, 3);
    // 將table[index]俺陋、table[index+1]、table[index+2]昙篙、table[index+3]對(duì)應(yīng)的等分追加1
    boolean added = incrementAt(index0, start);
    added |= incrementAt(index1, start + 1);
    added |= incrementAt(index2, start + 2);
    added |= incrementAt(index3, start + 3);
    //處理頻率很高但不經(jīng)常使用的數(shù)據(jù)
    if (added && (++size == sampleSize)) {
      reset();
    }
  }

  boolean incrementAt(int i, int j) {
    //j為16等份的下標(biāo)腊状,offset即為64等份的下標(biāo)
    int offset = j << 2;
    // mask用來判斷是否等于15
    long mask = (0xfL << offset);
    if ((table[i] & mask) != mask) {
      // 不等于15則追加1
      table[i] += (1L << offset);
      return true;
    }
    return false;
  }

數(shù)據(jù)保新

如果有些數(shù)據(jù)頻率很高,但不經(jīng)常使用怎么辦苔可,總不能一直放在long數(shù)組中吧缴挖。Caffeine有個(gè)機(jī)制,當(dāng)所有數(shù)據(jù)的統(tǒng)計(jì)頻率數(shù)達(dá)到某一個(gè)閾值(默認(rèn)為maximum的10倍)焚辅,則對(duì)所有數(shù)的頻率減半映屋。

if (added && (++size == sampleSize)) {
      reset();
    }

 /** Reduces every counter by half of its original value. */
  void reset() {
    int count = 0;
    for (int i = 0; i < table.length; i++) {
      count += Long.bitCount(table[i] & ONE_MASK);
      table[i] = (table[i] >>> 1) & RESET_MASK;
    }
    size = (size >>> 1) - (count >>> 2);
  }

Window

上面提到TinyLFU在面對(duì)突發(fā)性的稀疏流量時(shí)表現(xiàn)很差苟鸯,新數(shù)據(jù)很難積累到足夠多的頻率來通過過濾器。而caffeine在此基礎(chǔ)上做了優(yōu)化棚点,引入Window Tiny LFU(W-TinyLFU)早处。


image.png

image.png

當(dāng)window區(qū)數(shù)據(jù)滿了,就會(huì)根據(jù)LRU把數(shù)據(jù)candidate放入probation區(qū)瘫析,如果probation也滿了砌梆,則跟probation數(shù)據(jù)進(jìn)行pk,輸?shù)谋惶蕴?br> caffeine默認(rèn)配置為window容量占1%贬循,剩余的80%為Protected咸包,20%為probation(實(shí)驗(yàn)測(cè)試這樣配置效果最好),實(shí)際運(yùn)行時(shí)會(huì)動(dòng)態(tài)調(diào)整杖虾。
驅(qū)逐代碼如下:

/** Evicts entries if the cache exceeds the maximum. */
  @GuardedBy("evictionLock")
  void evictEntries() {
    if (!evicts()) {
      return;
    }
    //淘汰window區(qū)的記錄
    int candidates = evictFromWindow();
    //淘汰main區(qū)的記錄
    evictFromMain(candidates);
  }

/**
   * Evicts entries from the window space into the main space while the window size exceeds a
   * maximum.
   *
   * @return the number of candidate entries evicted from the window space
   */
  @GuardedBy("evictionLock")
  int evictFromWindow() {
    int candidates = 0;
    //獲取window queue的頭部節(jié)點(diǎn)
    Node<K, V> node = accessOrderWindowDeque().peek();
    //若超過window最大限制烂瘫,則處理
    while (windowWeightedSize() > windowMaximum()) {
      // The pending operations will adjust the size to reflect the correct weight
      if (node == null) {
        break;
      }
 
      Node<K, V> next = node.getNextInAccessOrder();
      if (node.getWeight() != 0) {
        node.makeMainProbation();
        //從window區(qū)移除并加入Probation
        accessOrderWindowDeque().remove(node);
        accessOrderProbationDeque().add(node);
        candidates++;
       //調(diào)整size
        setWindowWeightedSize(windowWeightedSize() - node.getPolicyWeight());
      }
      node = next;
    }

    return candidates;
  }

@GuardedBy("evictionLock")
  void evictFromMain(int candidates) {
    int victimQueue = PROBATION;
    //分別獲取第一個(gè)和最后個(gè)
    Node<K, V> victim = accessOrderProbationDeque().peekFirst();
    Node<K, V> candidate = accessOrderProbationDeque().peekLast();
    // 當(dāng)cache容量不夠時(shí)處理
    while (weightedSize() > maximum()) {
      // Stop trying to evict candidates and always prefer the victim
      if (candidates == 0) {
        candidate = null;
      }

      // Try evicting from the protected and window queues
     // 嘗試從protected和window區(qū)獲取victim數(shù)據(jù)
      if ((candidate == null) && (victim == null)) {
        if (victimQueue == PROBATION) {
          victim = accessOrderProtectedDeque().peekFirst();
          victimQueue = PROTECTED;
          continue;
        } else if (victimQueue == PROTECTED) {
          victim = accessOrderWindowDeque().peekFirst();
          victimQueue = WINDOW;
          continue;
        }

        // The pending operations will adjust the size to reflect the correct weight
        break;
      }

      // Skip over entries with zero weight
      if ((victim != null) && (victim.getPolicyWeight() == 0)) {
        victim = victim.getNextInAccessOrder();
        continue;
      } else if ((candidate != null) && (candidate.getPolicyWeight() == 0)) {
        candidate = candidate.getPreviousInAccessOrder();
        candidates--;
        continue;
      }

      // Evict immediately if only one of the entries is present
      if (victim == null) {
        @SuppressWarnings("NullAway")
        Node<K, V> previous = candidate.getPreviousInAccessOrder();
        Node<K, V> evict = candidate;
        candidate = previous;
        candidates--;
        evictEntry(evict, RemovalCause.SIZE, 0L);
        continue;
      } else if (candidate == null) {
        Node<K, V> evict = victim;
        victim = victim.getNextInAccessOrder();
        evictEntry(evict, RemovalCause.SIZE, 0L);
        continue;
      }

      // Evict immediately if an entry was collected
      K victimKey = victim.getKey();
      K candidateKey = candidate.getKey();
      if (victimKey == null) {
        @NonNull Node<K, V> evict = victim;
        victim = victim.getNextInAccessOrder();
        evictEntry(evict, RemovalCause.COLLECTED, 0L);
        continue;
      } else if (candidateKey == null) {
        candidates--;
        @NonNull Node<K, V> evict = candidate;
        candidate = candidate.getPreviousInAccessOrder();
        evictEntry(evict, RemovalCause.COLLECTED, 0L);
        continue;
      }

      // Evict immediately if the candidate's weight exceeds the maximum
      // weight太大的節(jié)點(diǎn)直接驅(qū)逐
      if (candidate.getPolicyWeight() > maximum()) {
        candidates--;
        Node<K, V> evict = candidate;
        candidate = candidate.getPreviousInAccessOrder();
        evictEntry(evict, RemovalCause.SIZE, 0L);
        continue;
      }

      // Evict the entry with the lowest frequency
      candidates--;
      // admit根據(jù)頻率記錄來比較
      if (admit(candidateKey, victimKey)) {
        Node<K, V> evict = victim;
        victim = victim.getNextInAccessOrder();
        evictEntry(evict, RemovalCause.SIZE, 0L);
        candidate = candidate.getPreviousInAccessOrder();
      } else {
        Node<K, V> evict = candidate;
        candidate = candidate.getPreviousInAccessOrder();
        evictEntry(evict, RemovalCause.SIZE, 0L);
      }
    }
  }
 //頻率比較
@GuardedBy("evictionLock")
  boolean admit(K candidateKey, K victimKey) {
    int victimFreq = frequencySketch().frequency(victimKey);
    int candidateFreq = frequencySketch().frequency(candidateKey);
    //誰大誰贏
    if (candidateFreq > victimFreq) {
      return true;
    //相等,則candidateFreq <= 5算輸奇适。這里有考慮衰減的情況坟比,怕candidate利用這個(gè)規(guī)律淘汰老數(shù)據(jù),主要是提高命中率嚷往。
    } else if (candidateFreq <= 5) {
      // The maximum frequency is 15 and halved to 7 after a reset to age the history. An attack
      // exploits that a hot candidate is rejected in favor of a hot victim. The threshold of a warm
      // candidate reduces the number of random acceptances to minimize the impact on the hit rate.
      return false;
    }
    // 相等且candidateFreq>5,則隨機(jī)淘汰一個(gè)
    int random = ThreadLocalRandom.current().nextInt();
    return ((random & 127) == 0);
  }

高性能讀寫操作

緩存對(duì)數(shù)據(jù)進(jìn)行處理(讀温算、寫)后,都會(huì)伴隨一些額外的操作间影,如:

  1. 判斷數(shù)據(jù)是否過期;
  2. 統(tǒng)計(jì)頻率茄茁;
  3. 記錄讀寫
  4. 統(tǒng)計(jì)命中率等
    在guava中魂贬,讀寫操作和這些額外的操作一起進(jìn)行。caffeine借鑒了WAL思想裙顽,執(zhí)行讀寫操作后付燥,將操作記錄記載緩沖區(qū),后面再異步處理愈犹,提高了性能键科。

ReadBuffer

每次讀命中后,會(huì)執(zhí)行afterRead:

void afterRead(Node<K, V> node, long now, boolean recordHit) {
    if (recordHit) {
      statsCounter().recordHits(1);
    }
    // 將數(shù)據(jù)加入ReadBuffer
    boolean delayable = skipReadBuffer() || (readBuffer.offer(node) != Buffer.FULL);
    if (shouldDrainBuffers(delayable)) {
      scheduleDrainBuffers();
    }
    refreshIfNeeded(node, now);
  }
// 首先會(huì)進(jìn)入BoundedBuffer的父類StripedBuffer
public int offer(E e) {
    int mask;
    int result = 0;
    Buffer<E> buffer;
    boolean uncontended = true;
    Buffer<E>[] buffers = table;
    if ((buffers == null)
        || (mask = buffers.length - 1) < 0
        //根據(jù)線程id判斷在哪個(gè)buffer
        || (buffer = buffers[getProbe() & mask]) == null
        || !(uncontended = ((result = buffer.offer(e)) != Buffer.FAILED))) {
      expandOrRetry(e, uncontended);
    }
    return result;
  }

public int offer(E e) {
      long head = readCounter;
      long tail = relaxedWriteCounter();
      //獲取buffer大小漩怎,超過則加入失敗勋颖,即允許有數(shù)據(jù)丟失
      long size = (tail - head);
      if (size >= SPACED_SIZE) {
        return Buffer.FULL;
      }
      // cas操作追加16
      if (casWriteCounter(tail, tail + OFFSET)) {
        //求余獲取下標(biāo)
        int index = (int) (tail & SPACED_MASK);
        buffer.lazySet(index, e);
        return Buffer.SUCCESS;
      }
      return Buffer.FAILED;
    }

這里要注意下,ringbuffer默認(rèn)為16勋锤,而其數(shù)組大小是256饭玲,這里是假設(shè)引用大小為4字節(jié)(ringbuffer存的是引用),緩存行大小為64叁执。所以這里每個(gè)緩存行只存一個(gè)數(shù)據(jù)茄厘,所以cas操作追加16矮冬,即數(shù)組中每16個(gè)元素只有一個(gè)有效存儲(chǔ),空間換時(shí)間次哈。

參考

https://albenw.github.io/posts/a4ae1aa2/
https://zhouxinghang.github.io/caffeine.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胎署,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子窑滞,更是在濱河造成了極大的恐慌琼牧,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件葛假,死亡現(xiàn)場(chǎng)離奇詭異障陶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)聊训,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門抱究,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人带斑,你說我怎么就攤上這事鼓寺。” “怎么了勋磕?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵妈候,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我挂滓,道長(zhǎng)苦银,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任赶站,我火速辦了婚禮幔虏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贝椿。我一直安慰自己想括,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布烙博。 她就那樣靜靜地躺著瑟蜈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渣窜。 梳的紋絲不亂的頭發(fā)上铺根,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音乔宿,去河邊找鬼夷都。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的囤官。 我是一名探鬼主播冬阳,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼党饮!你這毒婦竟也來了肝陪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤刑顺,失蹤者是張志新(化名)和其女友劉穎氯窍,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹲堂,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狼讨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了柒竞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片政供。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖朽基,靈堂內(nèi)的尸體忽然破棺而出布隔,到底是詐尸還是另有隱情,我是刑警寧澤稼虎,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布衅檀,位于F島的核電站,受9級(jí)特大地震影響霎俩,放射性物質(zhì)發(fā)生泄漏哀军。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一打却、第九天 我趴在偏房一處隱蔽的房頂上張望杉适。 院中可真熱鬧,春花似錦学密、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至毯侦,卻和暖如春哭靖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背侈离。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工试幽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卦碾。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓铺坞,卻偏偏與公主長(zhǎng)得像起宽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子济榨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348