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

org.ttzero.excel.hash.LockFreeBitArray Maven / Gradle / Ivy

package org.ttzero.excel.hash;

import java.math.RoundingMode;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLongArray;
import java.util.concurrent.atomic.LongAdder;

import static java.lang.Math.abs;
import static java.math.RoundingMode.HALF_EVEN;
import static java.math.RoundingMode.HALF_UP;

/**
 * Models a lock-free array of bits.
 *
 * 

We use this instead of java.util.BitSet because we need access to the array of longs and we * need compare-and-swap. */ public final class LockFreeBitArray { private static final int LONG_ADDRESSABLE_BITS = 6; final AtomicLongArray data; private final LongAdder bitCount; LockFreeBitArray(long bits) { this(new long[(int) (divide(bits, 64, RoundingMode.CEILING))]); } // Used by serialization LockFreeBitArray(long[] data) { this.data = new AtomicLongArray(data); this.bitCount = new LongAdder(); long bitCount = 0; for (long value : data) { bitCount += Long.bitCount(value); } this.bitCount.add(bitCount); } /** Returns true if the bit changed value. */ boolean set(long bitIndex) { if (get(bitIndex)) { return false; } int longIndex = (int) (bitIndex >>> LONG_ADDRESSABLE_BITS); long mask = 1L << bitIndex; // only cares about low 6 bits of bitIndex long oldValue; long newValue; do { oldValue = data.get(longIndex); newValue = oldValue | mask; if (oldValue == newValue) { return false; } } while (!data.compareAndSet(longIndex, oldValue, newValue)); // We turned the bit on, so increment bitCount. bitCount.increment(); return true; } boolean get(long bitIndex) { return (data.get((int) (bitIndex >>> LONG_ADDRESSABLE_BITS)) & (1L << bitIndex)) != 0; } /** * Careful here: if threads are mutating the atomicLongArray while this method is executing, the * final long[] will be a "rolling snapshot" of the state of the bit array. This is usually good * enough, but should be kept in mind. */ public static long[] toPlainArray(AtomicLongArray atomicLongArray) { long[] array = new long[atomicLongArray.length()]; for (int i = 0; i < array.length; ++i) { array[i] = atomicLongArray.get(i); } return array; } /** Number of bits */ long bitSize() { return (long) data.length() * Long.SIZE; } LockFreeBitArray copy() { return new LockFreeBitArray(toPlainArray(data)); } /** * Combines the two BitArrays using bitwise OR. * *

NOTE: Because of the use of atomics, if the other LockFreeBitArray is being mutated while * this operation is executing, not all of those new 1's may be set in the final state of this * LockFreeBitArray. The ONLY guarantee provided is that all the bits that were set in the other * LockFreeBitArray at the start of this method will be set in this LockFreeBitArray at the end * of this method. */ void putAll(LockFreeBitArray other) { for (int i = 0; i < data.length(); i++) { long otherLong = other.data.get(i); long ourLongOld; long ourLongNew; boolean changedAnyBits = true; do { ourLongOld = data.get(i); ourLongNew = ourLongOld | otherLong; if (ourLongOld == ourLongNew) { changedAnyBits = false; break; } } while (!data.compareAndSet(i, ourLongOld, ourLongNew)); if (changedAnyBits) { int bitsAdded = Long.bitCount(ourLongNew) - Long.bitCount(ourLongOld); bitCount.add(bitsAdded); } } } @Override public boolean equals(Object o) { if (o instanceof LockFreeBitArray) { LockFreeBitArray lockFreeBitArray = (LockFreeBitArray) o; return Arrays.equals(toPlainArray(data), toPlainArray(lockFreeBitArray.data)); } return false; } @Override public int hashCode() { return Arrays.hashCode(toPlainArray(data)); } /** * Returns the result of dividing {@code p} by {@code q}, rounding using the specified {@code * RoundingMode}. * * @throws ArithmeticException if {@code q == 0}, or if {@code mode == UNNECESSARY} and {@code a} * is not an integer multiple of {@code b} */ @SuppressWarnings("fallthrough") public static long divide(long p, long q, RoundingMode mode) { long div = p / q; // throws if q == 0 long rem = p - q * div; // equals p % q if (rem == 0) { return div; } /* * Normal Java division rounds towards 0, consistently with RoundingMode.DOWN. We just have to * deal with the cases where rounding towards 0 is wrong, which typically depends on the sign of * p / q. * * signum is 1 if p and q are both nonnegative or both negative, and -1 otherwise. */ int signum = 1 | (int) ((p ^ q) >> (Long.SIZE - 1)); boolean increment; switch (mode) { case UNNECESSARY: // fall through case DOWN: increment = false; break; case UP: increment = true; break; case CEILING: increment = signum > 0; break; case FLOOR: increment = signum < 0; break; case HALF_EVEN: case HALF_DOWN: case HALF_UP: long absRem = abs(rem); long cmpRemToHalfDivisor = absRem - (abs(q) - absRem); // subtracting two nonnegative longs can't overflow // cmpRemToHalfDivisor has the same sign as compare(abs(rem), abs(q) / 2). if (cmpRemToHalfDivisor == 0) { // exactly on the half mark increment = (mode == HALF_UP | (mode == HALF_EVEN & (div & 1) != 0)); } else { increment = cmpRemToHalfDivisor > 0; // closer to the UP value } break; default: throw new AssertionError(); } return increment ? div + signum : div; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy