Skip to content

Instantly share code, notes, and snippets.

@budhash
Created March 7, 2016 19:49
Show Gist options
  • Save budhash/5b471a7cea1f1ee629b9 to your computer and use it in GitHub Desktop.
Save budhash/5b471a7cea1f1ee629b9 to your computer and use it in GitHub Desktop.
A small toolkit of classes that support lock-free thread-safe programming on single records
/*
* Copyright (c) 2012 Jan Kotek
*
* 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.
*/
/*
* Adopted from Apache Harmony with following copyright:
*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/licenses/publicdomain
*/
package org.mapdb;
import java.io.DataInput;
import java.io.IOException;
/**
* <p>
* A small toolkit of classes that support lock-free thread-safe
* programming on single records. In essence, the classes here
* provide provide an atomic conditional update operation of the form:
* </p>
*
* <pre>
* boolean compareAndSet(expectedValue, updateValue);
* </pre>
*
* <p>
* This method (which varies in argument types across different
* classes) atomically sets a record to the {@code updateValue} if it
* currently holds the {@code expectedValue}, reporting {@code true} on
* success. Classes jere also contain methods to get and
* unconditionally set values.
* </p><p>
*
* The specifications of these methods enable to
* employ more efficient internal DB locking. CompareAndSwap
* operation is typically faster than using transactions, global lock or other
* concurrent protection.
*
* </p><p>
* Instances of classes
* {@link Atomic.Boolean},
* {@link Atomic.Integer},
* {@link Atomic.Long},
* {@link Atomic.String} and
* {@link Atomic.Var}
* each provide access and updates to a single record of the
* corresponding type. Each class also provides appropriate utility
* methods for that type. For example, classes {@code Atomic.Long} and
* {@code Atomic.Integer} provide atomic increment methods. One
* application is to generate unique keys for Maps:
* </p>
* <pre>
* Atomic.Long id = Atomic.getLong("mapId");
* map.put(id.getAndIncrement(), "something");
* </pre>
*
* <p>
* Atomic classes are designed primarily as building blocks for
* implementing non-blocking data structures and related infrastructure
* classes. The {@code compareAndSet} method is not a general
* replacement for locking. It applies only when critical updates for an
* object are confined to a <em>single</em> record.
*</p><p>
*
* Atomic classes are not general purpose replacements for
* {@code java.lang.Integer} and related classes. They do <em>not</em>
* define methods such as {@code hashCode} and
* {@code compareTo}. (Because atomic records are expected to be
* mutated, they are poor choices for hash table keys.) Additionally,
* classes are provided only for those types that are commonly useful in
* intended applications. Other types has to be wrapped into general {@link Atomic.Var}
* </p><p>
*
* You can also hold floats using
* {@link java.lang.Float#floatToIntBits} and
* {@link java.lang.Float#intBitsToFloat} conversions, and doubles using
* {@link java.lang.Double#doubleToLongBits} and
* {@link java.lang.Double#longBitsToDouble} conversions.
* </p>
*
*/
final public class Atomic {
private Atomic(){}
/**
* An {@code int} record that may be updated atomically. An
* {@code Atomic@Integer} is used in applications such as atomically
* incremented counters, and cannot be used as a replacement for an
* {@link java.lang.Integer}. However, this class does extend
* {@code Number} to allow uniform access by tools and utilities that
* deal with numerically-based classes.
*/
public final static class Integer extends Number {
private static final long serialVersionUID = 4615119399830853054L;
protected final Store store;
protected final long recid;
public Integer(Store store, long recid) {
this.store = store;
this.recid = recid;
}
/**
* @return recid under which value is saved
*/
public long getRecid(){
return recid;
}
/**
* Gets the current value.
*
* @return the current value
*/
public final int get() {
return store.get(recid, Serializer.INTEGER);
}
/**
* Sets to the given value.
*
* @param newValue the new value
*/
public final void set(int newValue) {
store.update(recid, newValue, Serializer.INTEGER);
}
/**
* Atomically sets to the given value and returns the old value.
*
* @param newValue the new value
* @return the previous value
*/
public final int getAndSet(int newValue) {
//$DELAY$
for (;;) {
int current = get();
//$DELAY$
if (compareAndSet(current, newValue)) {
//$DELAY$
return current;
}
}
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return store.compareAndSwap(recid, expect, update, Serializer.INTEGER);
}
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
//$DELAY$
for (;;) {
//$DELAY$
int current = get();
int next = current + 1;
//$DELAY$
if (compareAndSet(current, next)) {
//$DELAY$
return current;
}
}
}
/**
* Atomically decrements by one the current value.
*
* @return the previous value
*/
public final int getAndDecrement() {
//$DELAY$
for (;;) {
//$DELAY$
int current = get();
int next = current - 1;
//$DELAY$
if (compareAndSet(current, next)) {
//$DELAY$
return current;
}
}
}
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the previous value
*/
public final int getAndAdd(int delta) {
//$DELAY$
for (;;) {
//$DELAY$
int current = get();
int next = current + delta;
//$DELAY$
if (compareAndSet(current, next)) {
//$DELAY$
return current;
}
}
}
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
//$DELAY$
for (;;) {
//$DELAY$
int current = get();
int next = current + 1;
//$DELAY$
if (compareAndSet(current, next)) {
//$DELAY$
return next;
}
}
}
/**
* Atomically decrements by one the current value.
*
* @return the updated value
*/
public final int decrementAndGet() {
//$DELAY$
for (;;) {
//$DELAY$
int current = get();
int next = current - 1;
//$DELAY$
if (compareAndSet(current, next)) {
//$DELAY$
return next;
}
}
}
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the updated value
*/
public final int addAndGet(int delta) {
//$DELAY$
for (;;) {
//$DELAY$
int current = get();
int next = current + delta;
//$DELAY$
if (compareAndSet(current, next)) {
//$DELAY$
return next;
}
}
}
/**
* Returns the String representation of the current value.
* @return the String representation of the current value.
*/
public java.lang.String toString() {
return java.lang.Integer.toString(get());
}
public int intValue() {
return get();
}
public long longValue() {
return (long)get();
}
public float floatValue() {
return (float)get();
}
public double doubleValue() {
return (double)get();
}
}
/**
* A {@code long} record that may be updated atomically. An
* {@code Atomic#Long} is used in applications such as atomically
* incremented sequence numbers, and cannot be used as a replacement
* for a {@link java.lang.Long}. However, this class does extend
* {@code Number} to allow uniform access by tools and utilities that
* deal with numerically-based classes.
*/
public final static class Long extends Number{
private static final long serialVersionUID = 2882620413591274781L;
protected final Store store;
protected final long recid;
public Long(Store store, long recid) {
this.store = store;
this.recid = recid;
}
/**
* @return recid under which value is saved
*/
public long getRecid(){
return recid;
}
/**
* Gets the current value.
*
* @return the current value
*/
public final long get() {
return store.get(recid, Serializer.LONG);
}
/**
* Sets to the given value.
*
* @param newValue the new value
*/
public final void set(long newValue) {
store.update(recid, newValue, Serializer.LONG);
}
/**
* Atomically sets to the given value and returns the old value.
*
* @param newValue the new value
* @return the previous value
*/
public final long getAndSet(long newValue) {
//$DELAY$
while (true) {
//$DELAY$
long current = get();
//$DELAY$
if (compareAndSet(current, newValue)) {
//$DELAY$
return current;
}
}
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(long expect, long update) {
return store.compareAndSwap(recid, expect, update, Serializer.LONG);
}
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final long getAndIncrement() {
//$DELAY$
while (true) {
//$DELAY$
long current = get();
long next = current + 1;
//$DELAY$
if (compareAndSet(current, next)) {
//$DELAY$
return current;
}
}
}
/**
* Atomically decrements by one the current value.
*
* @return the previous value
*/
public final long getAndDecrement() {
//$DELAY$
while (true) {
//$DELAY$
long current = get();
long next = current - 1;
//$DELAY$
if (compareAndSet(current, next)) {
//$DELAY$
return current;
}
}
}
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the previous value
*/
public final long getAndAdd(long delta) {
//$DELAY$
while (true) {
//$DELAY$
long current = get();
long next = current + delta;
//$DELAY$
if (compareAndSet(current, next)) {
//$DELAY$
return current;
}
}
}
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final long incrementAndGet() {
//$DELAY$
for (;;) {
//$DELAY$
long current = get();
long next = current + 1;
//$DELAY$
if (compareAndSet(current, next)) {
//$DELAY$
return next;
}
}
}
/**
* Atomically decrements by one the current value.
*
* @return the updated value
*/
public final long decrementAndGet() {
//$DELAY$
for (;;) {
//$DELAY$
long current = get();
long next = current - 1;
//$DELAY$
if (compareAndSet(current, next)) {
//$DELAY$
return next;
}
}
}
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the updated value
*/
public final long addAndGet(long delta) {
//$DELAY$
for (;;) {
//$DELAY$
long current = get();
long next = current + delta;
//$DELAY$
if (compareAndSet(current, next)) {
//$DELAY$
return next;
}
}
}
/**
* Returns the String representation of the current value.
* @return the String representation of the current value.
*/
public java.lang.String toString() {
return java.lang.Long.toString(get());
}
public int intValue() {
return (int)get();
}
public long longValue() {
return get();
}
public float floatValue() {
return (float)get();
}
public double doubleValue() {
return (double)get();
}
}
/**
* A {@code boolean} record that may be updated atomically.
*/
public final static class Boolean {
protected final Store store;
protected final long recid;
public Boolean(Store store, long recid) {
this.store = store;
this.recid = recid;
}
/**
* @return recid under which value is saved
*/
public long getRecid(){
return recid;
}
/**
* Returns the current value.
*
* @return the current value
*/
public final boolean get() {
return store.get(recid, Serializer.BOOLEAN);
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(boolean expect, boolean update) {
return store.compareAndSwap(recid, expect, update, Serializer.BOOLEAN);
}
/**
* Unconditionally sets to the given value.
*
* @param newValue the new value
*/
public final void set(boolean newValue) {
store.update(recid, newValue, Serializer.BOOLEAN);
}
/**
* Atomically sets to the given value and returns the previous value.
*
* @param newValue the new value
* @return the previous value
*/
public final boolean getAndSet(boolean newValue) {
//$DELAY$
for (;;) {
//$DELAY$
boolean current = get();
//$DELAY$
if (compareAndSet(current, newValue)) {
//$DELAY$
return current;
}
}
}
/**
* Returns the String representation of the current value.
* @return the String representation of the current value.
*/
public java.lang.String toString() {
return java.lang.Boolean.toString(get());
}
}
/**
* A {@code String} record that may be updated atomically.
*/
public final static class String{
protected final Store store;
protected final long recid;
public String(Store store, long recid) {
this.store = store;
this.recid = recid;
}
/**
* @return recid under which value is saved
*/
public long getRecid(){
return recid;
}
public java.lang.String toString() {
return get();
}
/**
* Returns the current value.
*
* @return the current value
*/
public final java.lang.String get() {
return store.get(recid, Serializer.STRING_NOSIZE);
}
/**
* Atomically sets the value to the given updated value
* if the current value equals the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(java.lang.String expect, java.lang.String update) {
return store.compareAndSwap(recid, expect, update, Serializer.STRING_NOSIZE);
}
/**
* Unconditionally sets to the given value.
*
* @param newValue the new value
*/
public final void set(java.lang.String newValue) {
store.update(recid, newValue, Serializer.STRING_NOSIZE);
}
/**
* Atomically sets to the given value and returns the previous value.
*
* @param newValue the new value
* @return the previous value
*/
public final java.lang.String getAndSet(java.lang.String newValue) {
//$DELAY$
for (;;) {
//$DELAY$
java.lang.String current = get();
//$DELAY$
if (compareAndSet(current, newValue)) {
//$DELAY$
return current;
}
}
}
}
/**
* Atomically updated variable which may contain any type of record.
*/
public static final class Var<E> {
protected final Store store;
protected final long recid;
protected final Serializer<E> serializer;
public Var(Store store, long recid, Serializer<E> serializer) {
this.store = store;
this.recid = recid;
this.serializer = serializer;
}
//
// /* used for deserialization */
// protected Var(Store store, SerializerBase serializerBase, DataInput is, SerializerBase.FastArrayList<Object> objectStack) throws IOException {
// objectStack.add(this);
// this.store = store;
// this.recid = DBUtil.unpackLong(is);
// this.serializer = (Serializer<E>) serializerBase.deserialize(is,objectStack);
// }
/**
* @return recid under which value is saved
*/
public long getRecid(){
return recid;
}
public java.lang.String toString() {
E v = get();
return v==null? null : v.toString();
}
/**
* Returns the current value.
*
* @return the current value
*/
public final E get() {
return store.get(recid, serializer);
}
/**
* Atomically sets the value to the given updated value
* if the current value equals the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(E expect, E update) {
return store.compareAndSwap(recid, expect, update, serializer);
}
/**
* Unconditionally sets to the given value.
*
* @param newValue the new value
*/
public final void set(E newValue) {
store.update(recid, newValue, serializer);
}
/**
* Atomically sets to the given value and returns the previous value.
*
* @param newValue the new value
* @return the previous value
*/
public final E getAndSet(E newValue) {
//$DELAY$
for (;;) {
//$DELAY$
E current = get();
//$DELAY$
if (compareAndSet(current, newValue)) {
//$DELAY$
return current;
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment