gt:     final long writeCount = counter.get();
context: //@formatter:off
  /*
   * Copyright 2010 Google Inc. All Rights Reserved.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package jane.test.map;
  
  import java.util.AbstractCollection;
  import java.util.AbstractMap;
  import java.util.AbstractSet;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.Queue;
  import java.util.Set;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.ConcurrentLinkedQueue;
  import java.util.concurrent.ConcurrentMap;
  import java.util.concurrent.atomic.AtomicLong;
  import java.util.concurrent.atomic.AtomicReference;
  import java.util.concurrent.locks.Lock;
  import java.util.concurrent.locks.ReentrantLock;
  
  /**
   * A hash table supporting full concurrency of retrievals, adjustable expected
   * concurrency for updates, and a maximum capacity to bound the map by. This
   * implementation differs from {@link ConcurrentHashMap} in that it maintains a
   * page replacement algorithm that is used to evict an entry when the map has
   * exceeded its capacity. Unlike the <tt>Java Collections Framework</tt>, this
   * map does not have a publicly visible constructor and instances are created
   * through a {@link Builder}.
   * <p>
   * An entry is evicted from the map when the <tt>weighted capacity</tt> exceeds
   * its <tt>maximum weighted capacity</tt> threshold. A {@link EntryWeigher}
   * determines how many units of capacity that an entry consumes. The default
   * weigher assigns each value a weight of <tt>1</tt> to bound the map by the
   * total number of key-value pairs. A map that holds collections may choose to
   * weigh values by the number of elements in the collection and bound the map
   * by the total number of elements that it contains. A change to a value that
   * modifies its weight requires that an update operation is performed on the
   * map.
   * <p>
   * The <tt>concurrency level</tt> determines the number of threads that can
   * concurrently modify the table. Using a significantly higher or lower value
   * than needed can waste space or lead to thread contention, but an estimate
   * within an order of magnitude of the ideal value does not usually have a
   * noticeable impact. Because placement in hash tables is essentially random,
   * the actual concurrency will vary.
   * <p>
   * This class and its views and iterators implement all of the
   * <em>optional</em> methods of the {@link Map} and {@link Iterator}
   * interfaces.
   * <p>
   * Like {@link java.util.Hashtable} but unlike {@link HashMap}, this class
   * does <em>not</em> allow <tt>null</tt> to be used as a key or value. Unlike
   * {@link java.util.LinkedHashMap}, this class does <em>not</em> provide
   * predictable iteration order. A snapshot of the keys and entries may be
   * obtained in ascending and descending order of retention.
   *
   * @author ben.manes@gmail.com (Ben Manes)
   * @param <K> the type of keys maintained by this map
   * @param <V> the type of mapped values
   * @see <a href="http://code.google.com/p/concurrentlinkedhashmap/">
   *      http://code.google.com/p/concurrentlinkedhashmap/</a>
   */
  // @ThreadSafe
  public final class ConcurrentLinkedHashMap<K, V> extends AbstractMap<K, V>
      implements ConcurrentMap<K, V> {
    /*
     * This class performs a best-effort bounding of a ConcurrentHashMap using a
     * page-replacement algorithm to determine which entries to evict when the
     * capacity is exceeded.
     *
     * The page replacement algorithm's data structures are kept eventually
     * consistent with the map. An update to the map and recording of reads may
     * not be immediately reflected on the algorithm's data structures. These
     * structures are guarded by a lock and operations are applied in batches to
     * avoid lock contention. The penalty of applying the batches is spread across
     * threads so that the amortized cost is slightly higher than performing just
     * the ConcurrentHashMap operation.
     *
     * A memento of the reads and writes that were performed on the map are
     * recorded in buffers. These buffers are drained at the first opportunity
     * after a write or when the read buffer exceeds a threshold size. The reads
     * are recorded in a lossy buffer, allowing the reordering operations to be
     * discarded if the draining process cannot keep up. Due to the concurrent
     * nature of the read and write operations a strict policy ordering is not
     * possible, but is observably strict when single threaded.
     *
     * Due to a lack of a strict ordering guarantee, a task can be executed
     * out-of-order, such as a removal followed by its addition. The state of the
     * entry is encoded within the value's weight.
     *
     * Alive: The entry is in both the hash-table and the page replacement policy.
     * This is represented by a positive weight.
     *
     * Retired: The entry is not in the hash-table and is pending removal from the
     * page replacement policy. This is represented by a negative weight.
     *
     * Dead: The entry is not in the hash-table and is not in the page replacement
     * policy. This is represented by a weight of zero.
     *
     * The Least Recently Used page replacement algorithm was chosen due to its
     * simplicity, high hit rate, and ability to be implemented with O(1) time
     * complexity.
     */
  
    /** The number of CPUs */
    static final int NCPU = Runtime.getRuntime().availableProcessors();
  
    /** The maximum weighted capacity of the map. */
    static final long MAXIMUM_CAPACITY = Long.MAX_VALUE - Integer.MAX_VALUE;
  
    /** The number of read buffers to use. */
    static final int NUMBER_OF_READ_BUFFERS = ceilingNextPowerOfTwo(NCPU);
  
    /** Mask value for indexing into the read buffers. */
    static final int READ_BUFFERS_MASK = NUMBER_OF_READ_BUFFERS - 1;
  
    /** The number of pending read operations before attempting to drain. */
    static final int READ_BUFFER_THRESHOLD = 32;
  
    /** The maximum number of read operations to perform per amortized drain. */
    static final int READ_BUFFER_DRAIN_THRESHOLD = 2 * READ_BUFFER_THRESHOLD;
  
    /** The maximum number of pending reads per buffer. */
    static final int READ_BUFFER_SIZE = 2 * READ_BUFFER_DRAIN_THRESHOLD;
  
    /** Mask value for indexing into the read buffer. */
    static final int READ_BUFFER_INDEX_MASK = READ_BUFFER_SIZE - 1;
  
    /** The maximum number of write operations to perform per amortized drain. */
    static final int WRITE_BUFFER_DRAIN_THRESHOLD = 16;
  
    private static int ceilingNextPowerOfTwo(@SuppressWarnings("SameParameterValue") int x) {
      // From Hacker's Delight, Chapter 3, Harry S. Warren Jr.
      return 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(x - 1));
    }
  
    // The backing data store holding the key-value associations
    private final ConcurrentMap<K, Node<K, V>> data;
  
    // These fields provide support to bound the map by a maximum capacity
    // @GuardedBy("evictionLock")
    private final long[] readBufferReadCount;
    // @GuardedBy("evictionLock")
    private final LinkedDeque<Node<K, V>> evictionDeque;
  
    // @GuardedBy("evictionLock") // must write under lock
    private final AtomicLong weightedSize;
    // @GuardedBy("evictionLock") // must write under lock
    private final AtomicLong capacity;
  
    private final Lock evictionLock;
    private final Queue<Runnable> writeBuffer;
    private final AtomicLong[] readBufferWriteCount;
    private final AtomicLong[] readBufferDrainAtWriteCount;
    private final AtomicReference<Node<K, V>>[][] readBuffers;
  
    private final AtomicReference<DrainStatus> drainStatus;
  
    private transient Set<K> keySet;
    private transient Collection<V> values;
    private transient Set<Entry<K, V>> entrySet;
  
    /** Creates an instance based on the builder's configuration. */
    @SuppressWarnings({"unchecked"})
    private ConcurrentLinkedHashMap(Builder builder) {
      // The data store and its maximum capacity
      int concurrencyLevel = builder.concurrencyLevel;
      capacity = new AtomicLong(Math.min(builder.capacity, MAXIMUM_CAPACITY));
      data = new ConcurrentHashMap<>(builder.initialCapacity, 0.75f, concurrencyLevel);
  
      // The eviction support
      evictionLock = new ReentrantLock();
      weightedSize = new AtomicLong();
      evictionDeque = new LinkedDeque<>();
      writeBuffer = new ConcurrentLinkedQueue<>();
      drainStatus = new AtomicReference<>(DrainStatus.IDLE);
  
      readBufferReadCount = new long[NUMBER_OF_READ_BUFFERS];
      readBufferWriteCount = new AtomicLong[NUMBER_OF_READ_BUFFERS];
      readBufferDrainAtWriteCount = new AtomicLong[NUMBER_OF_READ_BUFFERS];
      readBuffers = new AtomicReference[NUMBER_OF_READ_BUFFERS][READ_BUFFER_SIZE];
      for (int i = 0; i < NUMBER_OF_READ_BUFFERS; i++) {
        readBufferWriteCount[i] = new AtomicLong();
        readBufferDrainAtWriteCount[i] = new AtomicLong();
        readBuffers[i] = new AtomicReference[READ_BUFFER_SIZE];
        for (int j = 0; j < READ_BUFFER_SIZE; j++) {
          readBuffers[i][j] = new AtomicReference<>();
        }
      }
    }
  
    /** Ensures that the object is not null. */
    static void checkNotNull(Object o) {
      if (o == null) {
        throw new NullPointerException();
      }
    }
  
    /** Ensures that the argument expression is true. */
    static void checkArgument(boolean expression) {
      if (!expression) {
        throw new IllegalArgumentException();
      }
    }
  
    /** Ensures that the state expression is true. */
    static void checkState(boolean expression) {
      if (!expression) {
        throw new IllegalStateException();
      }
    }
  
    /* ---------------- Eviction Support -------------- */
  
    /**
     * Retrieves the maximum weighted capacity of the map.
     *
     * @return the maximum weighted capacity
     */
    public long capacity() {
      return capacity.get();
    }
  
    /**
     * Sets the maximum weighted capacity of the map and eagerly evicts entries
     * until it shrinks to the appropriate size.
     *
     * @param capacity the maximum weighted capacity of the map
     * @throws IllegalArgumentException if the capacity is negative
     */
    public void setCapacity(long capacity) {
      checkArgument(capacity >= 0);
      evictionLock.lock();
      try {
        this.capacity.lazySet(Math.min(capacity, MAXIMUM_CAPACITY));
        drainBuffers();
        evict();
      } finally {
        evictionLock.unlock();
      }
    }
  
    /** Determines whether the map has exceeded its capacity. */
    // @GuardedBy("evictionLock")
    private boolean hasOverflowed() {
      return weightedSize.get() > capacity.get();
    }
  
    /**
     * Evicts entries from the map while it exceeds the capacity and appends
     * evicted entries to the notification queue for processing.
     */
    // @GuardedBy("evictionLock")
    private void evict() {
      // Attempts to evict entries from the map if it exceeds the maximum
      // capacity. If the eviction fails due to a concurrent removal of the
      // victim, that removal may cancel out the addition that triggered this
      // eviction. The victim is eagerly unlinked before the removal task so
      // that if an eviction is still required then a new victim will be chosen
      // for removal.
      while (hasOverflowed()) {
        final Node<K, V> node = evictionDeque.poll();
  
        // If weighted values are used, then the pending operations will adjust
        // the size to reflect the correct weight
        if (node == null) {
          return;
        }
  
        // Notify the listener only if the entry was evicted
        //noinspection StatementWithEmptyBody
        if (data.remove(node.key, node)) {
        }
  
        makeDead(node);
      }
    }
  
    /**
     * Performs the post-processing work required after a read.
     *
     * @param node the entry in the page replacement policy
     */
    private void afterRead(Node<K, V> node) {
      final int bufferIndex = readBufferIndex();
      final long writeCount = recordRead(bufferIndex, node);
      drainOnReadIfNeeded(bufferIndex, writeCount);
    }
  
    /** Returns the index to the read buffer to record into. */
    static int readBufferIndex() {
      // A buffer is chosen by the thread's id so that tasks are distributed in a
      // pseudo evenly manner. This helps avoid hot entries causing contention
      // due to other threads trying to append to the same buffer.
      return ((int) Thread.currentThread().getId()) & READ_BUFFERS_MASK;
    }
  
    /**
     * Records a read in the buffer and return its write count.
     *
     * @param bufferIndex the index to the chosen read buffer
     * @param node the entry in the page replacement policy
     * @return the number of writes on the chosen read buffer
     */
    private long recordRead(int bufferIndex, Node<K, V> node) {
      // The location in the buffer is chosen in a racy fashion as the increment
      // is not atomic with the insertion. This means that concurrent reads can
      // overlap and overwrite one another, resulting in a lossy buffer.
      final AtomicLong counter = readBufferWriteCount[bufferIndex];
  