All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.orientechnologies.common.concur.lock.OThreadCountersHashTable Maven / Gradle / Ivy

The newest version!
package com.orientechnologies.common.concur.lock;

import com.orientechnologies.common.hash.OMurmurHash3;
import com.orientechnologies.common.serialization.types.OLongSerializer;

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;

/**
 * @author Andrey Lomakin Andrey Lomakin
 * @since 8/20/14
 */
public final class OThreadCountersHashTable {
  private static final int                                        SEED             = 362498820;

  private static final int                                        NCPU             = Runtime.getRuntime().availableProcessors();
  private static final int                                        DEFAULT_SIZE     = 1 << (32 - Integer
                                                                                       .numberOfLeadingZeros((NCPU << 2) - 1));

  public static final int                                         THRESHOLD        = 10;
  private final boolean                                           deadThreadsAreAllowed;

  private final ThreadLocal                            hashEntry        = new ThreadLocal();

  private volatile int                                            activeTableIndex = 0;

  private final AtomicReference[]>[] tables;
  private final AtomicInteger[]                                   busyCounters;

  private final AtomicBoolean                                     tablesAreBusy    = new AtomicBoolean(false);

  public OThreadCountersHashTable() {
    this(DEFAULT_SIZE, false);
  }

  public OThreadCountersHashTable(int initialSize, boolean deadThreadsAreAllowed) {
    this.deadThreadsAreAllowed = deadThreadsAreAllowed;

    AtomicReference[] activeTable = new AtomicReference[initialSize << 1];
    AtomicReference[]>[] tables = new AtomicReference[32];

    for (int i = 0; i < activeTable.length; i++)
      activeTable[i] = new AtomicReference(new EntryHolder(0, null, false));

    tables[0] = new AtomicReference[]>(activeTable);
    for (int i = 1; i < tables.length; i++)
      tables[i] = new AtomicReference[]>(null);

    AtomicInteger[] counters = new AtomicInteger[32];
    for (int i = 0; i < counters.length; i++)
      counters[i] = new AtomicInteger();

    busyCounters = counters;

    this.tables = tables;
  }

  public void increment() {
    HashEntry entry = hashEntry.get();

    if (entry == null) {
      final Thread thread = Thread.currentThread();
      entry = new HashEntry(thread, hashCodesByThreadId(thread.getId()));

      assert search(entry.thread) == null;
      insert(entry);
      assert search(entry.thread).thread == thread;

      hashEntry.set(entry);
    }

    entry.threadCounter++;
  }

  public void decrement() {
    final HashEntry entry = hashEntry.get();

    assert entry != null;

    entry.threadCounter--;
  }

  public boolean isEmpty() {
    int activeTableIndex;
    do {
      activeTableIndex = this.activeTableIndex;

      for (int i = 0; i <= activeTableIndex; i++) {
        if (i != activeTableIndex) {
          while (busyCounters[i].get() != 0)
            ;
        }

        final AtomicReference[] table = tables[i].get();
        if (tableCountersNotEmpty(table))
          return false;
      }

    } while (this.activeTableIndex != activeTableIndex);

    return true;
  }

  private boolean tableCountersNotEmpty(AtomicReference[] table) {
    for (AtomicReference entryHolderRef : table) {
      final EntryHolder entryHolder = entryHolderRef.get();

      if (!entryIsEmpty(entryHolder) && entryHolder.entry.threadCounter > 0)
        return true;
    }
    return false;
  }

  private boolean entryIsEmpty(EntryHolder entryHolder) {
    if (deadThreadsAreAllowed)
      return entryHolder.entry == null;

    return entryHolder.entry == null || !entryHolder.entry.thread.isAlive();
  }

  HashEntry search(Thread thread) {
    int[] hashCodes = hashCodesByThreadId(thread.getId());
    int activeTableIndex;
    do {
      activeTableIndex = this.activeTableIndex;
      for (int i = 0; i <= activeTableIndex; i++) {
        final AtomicReference[] table = tables[i].get();

        if (i != activeTableIndex) {
          while (busyCounters[i].get() != 0)
            ;
        }

        final HashEntry entry = searchInTables(thread, hashCodes, table);
        if (entry != null)
          return entry;
      }
    } while (activeTableIndex != this.activeTableIndex);

    return null;
  }

  private static HashEntry searchInTables(Thread thread, int[] hashCodes, AtomicReference[] tables) {
    while (true) {
      final int firstTableIndex = firstSubTableIndex(hashCodes, tables.length);
      final EntryHolder firstEntryHolderRnd1 = tables[firstTableIndex].get();

      if (firstEntryHolderRnd1.entry != null && firstEntryHolderRnd1.entry.thread == thread)
        return firstEntryHolderRnd1.entry;

      final int secondTableIndex = secondSubTableIndex(hashCodes, tables.length);
      final EntryHolder secondEntryHolderRnd1 = tables[secondTableIndex].get();

      if (secondEntryHolderRnd1.entry != null && secondEntryHolderRnd1.entry.thread == thread)
        return secondEntryHolderRnd1.entry;

      final EntryHolder firstEntryHolderRnd2 = tables[firstTableIndex].get();
      if (firstEntryHolderRnd2.entry != null && firstEntryHolderRnd2.entry.thread == thread)
        return firstEntryHolderRnd2.entry;

      final EntryHolder secondEntryHolderRnd2 = tables[secondTableIndex].get();

      if (secondEntryHolderRnd2.entry != null && secondEntryHolderRnd2.entry.thread == thread)
        return secondEntryHolderRnd2.entry;

      if (!checkCounter(firstEntryHolderRnd1.counter, secondEntryHolderRnd1.counter, firstEntryHolderRnd2.counter,
          secondEntryHolderRnd2.counter)) {
        return null;
      }
    }
  }

  private FindResult find(Thread thread, int[] hashCodes, AtomicReference[] tables) {
    while (true) {
      FindResult result = null;

      final int firstTableIndex = firstSubTableIndex(hashCodes, tables.length);
      final EntryHolder firstEntryHolderRnd1 = tables[firstTableIndex].get();

      final int secondTableIndex = secondSubTableIndex(hashCodes, tables.length);
      final EntryHolder secondEntryHolderRnd1 = tables[secondTableIndex].get();

      if (firstEntryHolderRnd1.markedForRelocation) {
        helpRelocate(firstTableIndex, false, tables);
        continue;
      }

      if (firstEntryHolderRnd1.entry != null && firstEntryHolderRnd1.entry.thread == thread)
        result = new FindResult(true, true, firstEntryHolderRnd1, secondEntryHolderRnd1);

      if (secondEntryHolderRnd1.markedForRelocation) {
        helpRelocate(secondTableIndex, false, tables);
        continue;
      }

      if (secondEntryHolderRnd1.entry != null && secondEntryHolderRnd1.entry.thread == thread) {
        assert result == null;
        result = new FindResult(true, false, firstEntryHolderRnd1, secondEntryHolderRnd1);
      }

      if (result != null)
        return result;

      final EntryHolder firstEntryHolderRnd2 = tables[firstTableIndex].get();
      final EntryHolder secondEntryHolderRnd2 = tables[secondTableIndex].get();

      if (firstEntryHolderRnd2.markedForRelocation) {
        helpRelocate(firstTableIndex, false, tables);
        continue;
      }

      if (firstEntryHolderRnd2.entry != null && firstEntryHolderRnd2.entry.thread == thread)
        result = new FindResult(true, true, firstEntryHolderRnd2, secondEntryHolderRnd2);

      if (secondEntryHolderRnd2.markedForRelocation) {
        helpRelocate(secondTableIndex, false, tables);
        continue;
      }

      if (secondEntryHolderRnd2.entry != null && secondEntryHolderRnd2.entry.thread == thread) {
        assert result == null;
        result = new FindResult(true, false, firstEntryHolderRnd1, secondEntryHolderRnd1);
      }

      if (result != null)
        return result;

      if (!checkCounter(firstEntryHolderRnd1.counter, secondEntryHolderRnd1.counter, firstEntryHolderRnd2.counter,
          secondEntryHolderRnd2.counter))
        return new FindResult(false, false, firstEntryHolderRnd2, secondEntryHolderRnd2);
    }
  }

  private static boolean checkCounter(long firstEntryRnd1, long secondEntryRnd1, long firstEntryRnd2, long secondEntryRnd2) {
    return firstEntryRnd2 - firstEntryRnd1 >= 2 && secondEntryRnd2 - secondEntryRnd1 >= 2 && secondEntryRnd2 - firstEntryRnd1 >= 3;
  }

  void insert(Thread thread) {
    HashEntry entry = new HashEntry(thread, hashCodesByThreadId(thread.getId()));
    insert(entry);
  }

  private void insert(final HashEntry newEntry) {
    while (true) {
      final int activeTableIndex = this.activeTableIndex;
      final AtomicReference[] table = tables[activeTableIndex].get();

      final AtomicInteger counter = busyCounters[activeTableIndex];

      counter.getAndIncrement();
      boolean result = insertInTables(newEntry, table);
      counter.getAndDecrement();

      if (!result) {
        if (!rehash())
          LockSupport.parkNanos(10);
      } else {

        return;
      }

    }
  }

  private boolean insertInTables(final HashEntry newEntry, AtomicReference[] tables) {
    while (true) {
      final FindResult result = find(newEntry.thread, newEntry.hashCodes, tables);

      assert !result.found;

      if (entryIsEmpty(result.firstEntryHolder)) {
        final int firstTableIndex = firstSubTableIndex(newEntry.hashCodes, tables.length);
        final EntryHolder holder = result.firstEntryHolder;

        if (tables[firstTableIndex].compareAndSet(holder, new EntryHolder(holder.counter, newEntry, false)))
          return true;
      }

      if (entryIsEmpty(result.secondEntryHolder)) {
        final int secondTableIndex = secondSubTableIndex(newEntry.hashCodes, tables.length);
        final EntryHolder holder = result.secondEntryHolder;

        if (tables[secondTableIndex].compareAndSet(holder, new EntryHolder(holder.counter, newEntry, false)))
          return true;
      }

      if (!relocate(firstSubTableIndex(newEntry.hashCodes, tables.length), tables))
        return false;
    }
  }

  private boolean rehash() {
    if (!tablesAreBusy.compareAndSet(false, true))
      return false;

    AtomicReference[] activeTable = tables[activeTableIndex].get();
    AtomicReference[] newActiveTable = new AtomicReference[activeTable.length << 1];

    for (int i = 0; i < newActiveTable.length; i++)
      newActiveTable[i] = new AtomicReference(new EntryHolder(0, null, false));

    tables[activeTableIndex + 1].set(newActiveTable);
    activeTableIndex++;

    tablesAreBusy.set(false);
    return true;
  }

  private boolean relocate(int entryIndex, AtomicReference[] tables) {
    int startLevel = 0;
    final int tableSize = tables.length >> 1;

    path_discovery: while (true) {
      if (startLevel >= THRESHOLD)
        startLevel = 0;

      boolean found = false;

      final int[] route = new int[10];
      int depth = startLevel;
      do {
        EntryHolder entryHolder = tables[entryIndex].get();

        while (entryHolder.markedForRelocation) {
          helpRelocate(entryIndex, false, tables);
          entryHolder = tables[entryIndex].get();
        }

        if (!entryIsEmpty(entryHolder)) {
          route[depth] = entryIndex;

          if (entryIndex < tableSize)
            entryIndex = secondSubTableIndex(entryHolder.entry.hashCodes, tables.length);
          else
            entryIndex = firstSubTableIndex(entryHolder.entry.hashCodes, tables.length);

          depth++;
        } else
          found = true;
      } while (!found && depth < THRESHOLD);

      if (found) {
        for (int i = depth - 1; i >= 0; i--) {
          final int index = route[i];

          EntryHolder entryHolder = tables[index].get();

          if (entryHolder.markedForRelocation) {
            helpRelocate(index, false, tables);
            entryHolder = tables[index].get();
          }

          if (entryIsEmpty(entryHolder))
            continue;

          final int destinationIndex = index < tableSize ? secondSubTableIndex(entryHolder.entry.hashCodes, tables.length)
              : firstSubTableIndex(entryHolder.entry.hashCodes, tables.length);

          EntryHolder destinationEntry = tables[destinationIndex].get();

          if (!entryIsEmpty(destinationEntry)) {
            startLevel = i + 1;
            entryIndex = destinationIndex;
            continue path_discovery;
          }

          if (!helpRelocate(index, true, tables)) {
            startLevel = i + 1;
            entryIndex = destinationIndex;
            continue path_discovery;
          }
        }
      }

      return found;
    }
  }

  private boolean helpRelocate(int entryIndex, boolean initiator, AtomicReference[] tables) {
    final int tableSize = tables.length >> 1;

    while (true) {
      EntryHolder src = tables[entryIndex].get();

      while (initiator && !src.markedForRelocation) {
        if (entryIsEmpty(src))
          return true;

        tables[entryIndex].compareAndSet(src, new EntryHolder(src.counter, src.entry, true));
        src = tables[entryIndex].get();
      }

      if (!src.markedForRelocation)
        return true;

      final int destinationIndex = entryIndex < tableSize ? secondSubTableIndex(src.entry.hashCodes, tables.length)
          : firstSubTableIndex(src.entry.hashCodes, tables.length);

      final EntryHolder destinationHolder = tables[destinationIndex].get();

      if (entryIsEmpty(destinationHolder)) {
        final long newCounter = destinationHolder.counter > src.counter ? destinationHolder.counter + 1 : src.counter + 1;

        if (src != tables[entryIndex].get())
          continue;

        if (tables[destinationIndex].compareAndSet(destinationHolder, new EntryHolder(newCounter, src.entry, false))) {
          tables[entryIndex].compareAndSet(src, new EntryHolder(src.counter + 1, null, false));
          return true;
        } else
          continue;
      }

      if (destinationHolder.entry == src.entry) {
        tables[entryIndex].compareAndSet(src, new EntryHolder(src.counter + 1, null, false));
        return true;
      }

      tables[entryIndex].compareAndSet(src, new EntryHolder(src.counter, src.entry, false));
      return false;
    }
  }

  private static int secondSubTableIndex(int[] hashCodes, int size) {
    final int subTableSize = size >> 1;
    return (hashCodes[1] & (subTableSize - 1)) + subTableSize;
  }

  private static int firstSubTableIndex(int[] hashCodes, int size) {
    return hashCodes[0] & ((size >> 1) - 1);
  }

  private static int[] hashCodesByThreadId(final long threadId) {
    final byte[] serializedId = new byte[8];
    OLongSerializer.INSTANCE.serializeNative(threadId, serializedId, 0);

    final long hashCode = OMurmurHash3.murmurHash3_x64_64(serializedId, SEED);
    return new int[] { (int) (hashCode & 0xFFFFFFFFL), (int) (hashCode >>> 32) };
  }

  static final class HashEntry {
    private final Thread  thread;
    private final int[]   hashCodes;

    private volatile long p0            = 0, p1 = 1, p2 = 2, p3 = 3, p4 = 4, p5 = 5, p6 = 6, p7 = 7;
    private volatile long threadCounter = 0;
    private volatile long p8            = 0, p9 = 1, p10 = 2, p11 = 3, p12 = 4, p13 = 5, p14 = 6;

    private HashEntry(Thread thread, int[] hashCodes) {
      this.thread = thread;
      this.hashCodes = hashCodes;
    }

    public Thread getThread() {
      return thread;
    }

    @Override
    public String toString() {
      modCounters();

      return "HashEntry{" + "thread=" + thread + ", hashCodes=" + Arrays.toString(hashCodes) + ", p0=" + p0 + ", p1=" + p1
          + ", p2=" + p2 + ", p3=" + p3 + ", p4=" + p4 + ", p5=" + p5 + ", p6=" + p6 + ", p7=" + p7 + ", threadCounter="
          + threadCounter + ", p8=" + p8 + ", p9=" + p9 + ", p10=" + p10 + ", p11=" + p11 + ", p12=" + p12 + ", p13=" + p13
          + ", p14=" + p14 + '}';
    }

    @Override
    public boolean equals(Object o) {
      if (this == o)
        return true;
      if (o == null || getClass() != o.getClass())
        return false;

      HashEntry hashEntry = (HashEntry) o;

      if (p1 != hashEntry.p1)
        return false;
      if (p10 != hashEntry.p10)
        return false;
      if (p11 != hashEntry.p11)
        return false;
      if (p12 != hashEntry.p12)
        return false;
      if (p13 != hashEntry.p13)
        return false;
      if (p14 != hashEntry.p14)
        return false;
      if (p2 != hashEntry.p2)
        return false;
      if (p3 != hashEntry.p3)
        return false;
      if (p4 != hashEntry.p4)
        return false;
      if (p5 != hashEntry.p5)
        return false;
      if (p6 != hashEntry.p6)
        return false;
      if (p7 != hashEntry.p7)
        return false;
      if (p8 != hashEntry.p8)
        return false;
      if (p9 != hashEntry.p9)
        return false;
      if (p0 != hashEntry.p0)
        return false;
      if (threadCounter != hashEntry.threadCounter)
        return false;
      if (!Arrays.equals(hashCodes, hashEntry.hashCodes))
        return false;
      if (thread != null ? !thread.equals(hashEntry.thread) : hashEntry.thread != null)
        return false;

      return true;
    }

    private void modCounters() {
      final Random random = new Random();
      p0 = random.nextLong();
      p1 = random.nextLong();
      p2 = random.nextLong();
      p3 = random.nextLong();
      p4 = random.nextLong();
      p5 = random.nextLong();
      p6 = random.nextLong();
      p7 = random.nextLong();
      p8 = random.nextLong();
      p9 = random.nextLong();
      p10 = random.nextLong();
      p11 = random.nextLong();
      p12 = random.nextLong();
      p13 = random.nextLong();
      p14 = random.nextLong();
    }

    @Override
    public int hashCode() {
      int result = thread != null ? thread.hashCode() : 0;
      result = 31 * result + (hashCodes != null ? Arrays.hashCode(hashCodes) : 0);
      result = 31 * result + (int) (p0 ^ (p0 >>> 32));
      result = 31 * result + (int) (p1 ^ (p1 >>> 32));
      result = 31 * result + (int) (p2 ^ (p2 >>> 32));
      result = 31 * result + (int) (p3 ^ (p3 >>> 32));
      result = 31 * result + (int) (p4 ^ (p4 >>> 32));
      result = 31 * result + (int) (p5 ^ (p5 >>> 32));
      result = 31 * result + (int) (p6 ^ (p6 >>> 32));
      result = 31 * result + (int) (p7 ^ (p7 >>> 32));
      result = 31 * result + (int) (threadCounter ^ (threadCounter >>> 32));
      result = 31 * result + (int) (p8 ^ (p8 >>> 32));
      result = 31 * result + (int) (p9 ^ (p9 >>> 32));
      result = 31 * result + (int) (p10 ^ (p10 >>> 32));
      result = 31 * result + (int) (p11 ^ (p11 >>> 32));
      result = 31 * result + (int) (p12 ^ (p12 >>> 32));
      result = 31 * result + (int) (p13 ^ (p13 >>> 32));
      result = 31 * result + (int) (p14 ^ (p14 >>> 32));
      return result;
    }
  }

  private static final class EntryHolder {
    private final long      counter;
    private final HashEntry entry;
    private final boolean   markedForRelocation;

    private EntryHolder(long counter, HashEntry entry, boolean markedForRelocation) {
      this.counter = counter;
      this.entry = entry;
      this.markedForRelocation = markedForRelocation;
    }
  }

  private static final class FindResult {
    private final boolean     found;
    private final boolean     firstTable;
    private final EntryHolder firstEntryHolder;
    private final EntryHolder secondEntryHolder;

    private FindResult(boolean found, boolean firstTable, EntryHolder firstEntryHolder, EntryHolder secondEntryHolder) {
      this.found = found;
      this.firstTable = firstTable;
      this.firstEntryHolder = firstEntryHolder;
      this.secondEntryHolder = secondEntryHolder;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy