private Object putIfMatch( final long key, final Object putval, final Object expVal ) {
      final int hash = hash(key);
      assert putval != null;
      assert !(putval instanceof Prime);
      assert !(expVal instanceof Prime);
      final int len      = _keys.length;
      int idx = (hash & (len-1)); // The first key

      // ---
      // Key-Claim stanza: spin till we can claim a Key (or force a resizing).
      int reprobe_cnt=0;
      long   K;
      Object V;
      while( true ) {           // Spin till we get a Key slot
        V = _vals[idx];         // Get old value
        K = _keys[idx];         // Get current key
        if( K == NO_KEY ) {     // Slot is free?
          // Found an empty Key slot - which means this Key has never been in
          // this table.  No need to put a Tombstone - the Key is not here!
          if( putval == TOMBSTONE ) return TOMBSTONE; // Not-now & never-been in this table
          if( expVal == MATCH_ANY ) return TOMBSTONE; // Will not match, even after K inserts
          // Claim the zero key-slot
          if( CAS_key(idx, NO_KEY, key) ) { // Claim slot for Key
            _slots.add(1);      // Raise key-slots-used count
            break;              // Got it!
          }
          // CAS to claim the key-slot failed.
          //
          // This re-read of the Key points out an annoying short-coming of Java
          // CAS.  Most hardware CAS's report back the existing value - so that
          // if you fail you have a *witness* - the value which caused the CAS
          // to fail.  The Java API turns this into a boolean destroying the
          // witness.  Re-reading does not recover the witness because another
          // thread can write over the memory after the CAS.  Hence we can be in
          // the unfortunate situation of having a CAS fail *for cause* but
          // having that cause removed by a later store.  This turns a
          // non-spurious-failure CAS (such as Azul has) into one that can
          // apparently spuriously fail - and we avoid apparent spurious failure
          // by not allowing Keys to ever change.
          K = _keys[idx];       // CAS failed, get updated value
          assert K != NO_KEY ;  // If keys[idx] is NO_KEY, CAS shoulda worked
        }
        // Key slot was not null, there exists a Key here
        if( K == key )
          break;                // Got it!

        // get and put must have the same key lookup logic!  Lest 'get' give
        // up looking too soon.
        //topmap._reprobes.add(1);
        if( ++reprobe_cnt >= reprobe_limit(len) ) {
          // We simply must have a new table to do a 'put'.  At this point a
          // 'get' will also go to the new table (if any).  We do not need
          // to claim a key slot (indeed, we cannot find a free one to claim!).
          final CHM newchm = resize();
          if( expVal != null ) _nbhml.help_copy(); // help along an existing copy
          return newchm.putIfMatch(key,putval,expVal);
        }

        idx = (idx+1)&(len-1); // Reprobe!
      } // End of spinning till we get a Key slot

      while ( true ) {              // Spin till we insert a value
        // ---
        // Found the proper Key slot, now update the matching Value slot.  We
        // never put a null, so Value slots monotonically move from null to
        // not-null (deleted Values use Tombstone).  Thus if 'V' is null we
        // fail this fast cutout and fall into the check for table-full.
        if( putval == V ) return V; // Fast cutout for no-change

        // See if we want to move to a new table (to avoid high average re-probe
        // counts).  We only check on the initial set of a Value from null to
        // not-null (i.e., once per key-insert).
        if( (V == null && tableFull(reprobe_cnt,len)) ||
            // Or we found a Prime: resize is already in progress.  The resize
            // call below will do a CAS on _newchm forcing the read.
            V instanceof Prime) {
          resize();               // Force the new table copy to start
          return copy_slot_and_check(idx,expVal).putIfMatch(key,putval,expVal);
        }

        // ---
        // We are finally prepared to update the existing table
        //assert !(V instanceof Prime); // always true, so IDE warnings if uncommented

        // Must match old, and we do not?  Then bail out now.  Note that either V
        // or expVal might be TOMBSTONE.  Also V can be null, if we've never
        // inserted a value before.  expVal can be null if we are called from
        // copy_slot.

        if( expVal != NO_MATCH_OLD && // Do we care about expected-Value at all?
            V != expVal &&            // No instant match already?
            (expVal != MATCH_ANY || V == TOMBSTONE || V == null) &&
            !(V==null && expVal == TOMBSTONE) &&    // Match on null/TOMBSTONE combo
            (expVal == null || !expVal.equals(V)) ) // Expensive equals check at the last
          return (V==null) ? TOMBSTONE : V;         // Do not update!

        // Actually change the Value in the Key,Value pair
        if( CAS_val(idx, V, putval ) ) break;

        // CAS failed
        // Because we have no witness, we do not know why it failed.
        // Indeed, by the time we look again the value under test might have flipped
        // a thousand times and now be the expected value (despite the CAS failing).
        // Check for the never-succeed condition of a Prime value and jump to any
        // nested table, or else just re-run.

        // We would not need this load at all if CAS returned the value on which
        // the CAS failed (AKA witness). The new CAS semantics are supported via
        // VarHandle in JDK9.
        V = _vals[idx];         // Get new value

        // If a Prime'd value got installed, we need to re-run the put on the
        // new table.  Otherwise we lost the CAS to another racing put.
        // Simply retry from the start.
        if( V instanceof Prime )
          return copy_slot_and_check(idx,expVal).putIfMatch(key,putval,expVal);

        // Simply retry from the start.
        // NOTE: need the fence, since otherwise '_vals[idx]' load could be hoisted
        // out of loop.
        int dummy = DUMMY_VOLATILE;
      }

      // CAS succeeded - we did the update!
      // Both normal put's and table-copy calls putIfMatch, but table-copy
      // does not (effectively) increase the number of live k/v pairs.
      if( expVal != null ) {
        // Adjust sizes - a striped counter
        if(  (V == null || V == TOMBSTONE) && putval != TOMBSTONE ) _size.add( 1);
        if( !(V == null || V == TOMBSTONE) && putval == TOMBSTONE ) _size.add(-1);
      }

      // We won; we know the update happened as expected.
      return (V==null && expVal!=null) ? TOMBSTONE : V;
    }
Auto-boxing version of {@link #replace}.
jctools-core/src/main/java/org/jctools/maps/NonBlockingHashMapLong.java