1
0
mirror of https://github.com/bitwarden/help synced 2025-12-16 00:03:41 +00:00

Promote to Master (#748)

* initial commit

* adding quotes for the array error

* Create Gemfile

* Create Gemfile.lock

* add .nvmrc and .node-version

* removed /article from URL

* update links to work with netlify

* more fixed links

* link fixes

* update bad links

* Update netlify.toml

toml test for redirects

* article redirect

* link fixes

* Update index.html

* Update netlify.toml

* Update _config.yml

* Update netlify.toml

* Update netlify.toml

* Update netlify.toml

* Update netlify.toml

* Update netlify.toml

* add article back into URL for launch

* Update netlify.toml

* Update netlify.toml

* add order to categories front matter

* Update netlify.toml

* update

* sidemenu update

* Revert "sidemenu update"

This reverts commit 5441c3d35c.

* update order prop

* Navbar updates per Gary and compiler warnings

* font/style tweaks

* Update sidebar.html

* Stage Release Documentation (#739)

* initial drafts

* rewrite Custom Fields article to prioritize new context-menu option & better organize ancillary information

* edit

* edit

* Custom Field Context Menu & CAPTCHA item in release notes

* SSO relink event

* update rn

* small edits

* improve release notes titles

* fix side menu

* Edits courtest of mportune!

* update order

* link fixes

* link cleanup

* image updates and a link

* fix trailing slash

Co-authored-by: DanHillesheim <79476558+DanHillesheim@users.noreply.github.com>
This commit is contained in:
fred_the_tech_writer
2021-09-21 13:21:11 -04:00
committed by GitHub
parent 63f78e8979
commit 906e2ca0dd
3304 changed files with 386714 additions and 8864 deletions

View File

@@ -0,0 +1,17 @@
import org.jruby.Ruby;
import org.jruby.runtime.load.BasicLibraryService;
import java.io.IOException;
public class ConcurrentRubyService implements BasicLibraryService {
public boolean basicLoad(final Ruby runtime) throws IOException {
new com.concurrent_ruby.ext.AtomicReferenceLibrary().load(runtime, false);
new com.concurrent_ruby.ext.JavaAtomicBooleanLibrary().load(runtime, false);
new com.concurrent_ruby.ext.JavaAtomicFixnumLibrary().load(runtime, false);
new com.concurrent_ruby.ext.JavaSemaphoreLibrary().load(runtime, false);
new com.concurrent_ruby.ext.SynchronizationLibrary().load(runtime, false);
new com.concurrent_ruby.ext.JRubyMapBackendLibrary().load(runtime, false);
return true;
}
}

View File

@@ -0,0 +1,175 @@
package com.concurrent_ruby.ext;
import java.lang.reflect.Field;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyObject;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;
/**
* This library adds an atomic reference type to JRuby for use in the atomic
* library. We do a native version to avoid the implicit value coercion that
* normally happens through JI.
*
* @author headius
*/
public class AtomicReferenceLibrary implements Library {
public void load(Ruby runtime, boolean wrap) throws IOException {
RubyModule concurrentMod = runtime.defineModule("Concurrent");
RubyClass atomicCls = concurrentMod.defineClassUnder("JavaAtomicReference", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR);
try {
sun.misc.Unsafe.class.getMethod("getAndSetObject", Object.class);
atomicCls.setAllocator(JRUBYREFERENCE8_ALLOCATOR);
} catch (Exception e) {
// leave it as Java 6/7 version
}
atomicCls.defineAnnotatedMethods(JRubyReference.class);
}
private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new JRubyReference(runtime, klazz);
}
};
private static final ObjectAllocator JRUBYREFERENCE8_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new JRubyReference8(runtime, klazz);
}
};
@JRubyClass(name="JRubyReference", parent="Object")
public static class JRubyReference extends RubyObject {
volatile IRubyObject reference;
static final sun.misc.Unsafe UNSAFE;
static final long referenceOffset;
static {
try {
UNSAFE = UnsafeHolder.U;
Class k = JRubyReference.class;
referenceOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("reference"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public JRubyReference(Ruby runtime, RubyClass klass) {
super(runtime, klass);
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context) {
UNSAFE.putObject(this, referenceOffset, context.nil);
return context.nil;
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context, IRubyObject value) {
UNSAFE.putObject(this, referenceOffset, value);
return context.nil;
}
@JRubyMethod(name = {"get", "value"})
public IRubyObject get() {
return reference;
}
@JRubyMethod(name = {"set", "value="})
public IRubyObject set(IRubyObject newValue) {
UNSAFE.putObjectVolatile(this, referenceOffset, newValue);
return newValue;
}
@JRubyMethod(name = {"compare_and_set", "compare_and_swap"})
public IRubyObject compare_and_set(ThreadContext context, IRubyObject expectedValue, IRubyObject newValue) {
Ruby runtime = context.runtime;
if (expectedValue instanceof RubyNumeric) {
// numerics are not always idempotent in Ruby, so we need to do slower logic
return compareAndSetNumeric(context, expectedValue, newValue);
}
return runtime.newBoolean(UNSAFE.compareAndSwapObject(this, referenceOffset, expectedValue, newValue));
}
@JRubyMethod(name = {"get_and_set", "swap"})
public IRubyObject get_and_set(ThreadContext context, IRubyObject newValue) {
// less-efficient version for Java 6 and 7
while (true) {
IRubyObject oldValue = get();
if (UNSAFE.compareAndSwapObject(this, referenceOffset, oldValue, newValue)) {
return oldValue;
}
}
}
private IRubyObject compareAndSetNumeric(ThreadContext context, IRubyObject expectedValue, IRubyObject newValue) {
Ruby runtime = context.runtime;
// loop until:
// * reference CAS would succeed for same-valued objects
// * current and expected have different values as determined by #equals
while (true) {
IRubyObject current = reference;
if (!(current instanceof RubyNumeric)) {
// old value is not numeric, CAS fails
return runtime.getFalse();
}
RubyNumeric currentNumber = (RubyNumeric)current;
if (!currentNumber.equals(expectedValue)) {
// current number does not equal expected, fail CAS
return runtime.getFalse();
}
// check that current has not changed, or else allow loop to repeat
boolean success = UNSAFE.compareAndSwapObject(this, referenceOffset, current, newValue);
if (success) {
// value is same and did not change in interim...success
return runtime.getTrue();
}
}
}
}
private static final class UnsafeHolder {
private UnsafeHolder(){}
public static final sun.misc.Unsafe U = loadUnsafe();
private static sun.misc.Unsafe loadUnsafe() {
try {
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field f = unsafeClass.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (sun.misc.Unsafe) f.get(null);
} catch (Exception e) {
return null;
}
}
}
public static class JRubyReference8 extends JRubyReference {
public JRubyReference8(Ruby runtime, RubyClass klass) {
super(runtime, klass);
}
@Override
public IRubyObject get_and_set(ThreadContext context, IRubyObject newValue) {
// efficient version for Java 8
return (IRubyObject)UNSAFE.getAndSetObject(this, referenceOffset, newValue);
}
}
}

View File

@@ -0,0 +1,248 @@
package com.concurrent_ruby.ext;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import com.concurrent_ruby.ext.jsr166e.ConcurrentHashMap;
import com.concurrent_ruby.ext.jsr166e.ConcurrentHashMapV8;
import com.concurrent_ruby.ext.jsr166e.nounsafe.*;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;
import java.io.IOException;
import java.util.Map;
import static org.jruby.runtime.Visibility.PRIVATE;
/**
* Native Java implementation to avoid the JI overhead.
*
* @author thedarkone
*/
public class JRubyMapBackendLibrary implements Library {
public void load(Ruby runtime, boolean wrap) throws IOException {
RubyModule concurrentMod = runtime.defineModule("Concurrent");
RubyModule thread_safeMod = concurrentMod.defineModuleUnder("Collection");
RubyClass jrubyRefClass = thread_safeMod.defineClassUnder("JRubyMapBackend", runtime.getObject(), BACKEND_ALLOCATOR);
jrubyRefClass.setAllocator(BACKEND_ALLOCATOR);
jrubyRefClass.defineAnnotatedMethods(JRubyMapBackend.class);
}
private static final ObjectAllocator BACKEND_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new JRubyMapBackend(runtime, klazz);
}
};
@JRubyClass(name="JRubyMapBackend", parent="Object")
public static class JRubyMapBackend extends RubyObject {
// Defaults used by the CHM
static final int DEFAULT_INITIAL_CAPACITY = 16;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public static final boolean CAN_USE_UNSAFE_CHM = canUseUnsafeCHM();
private ConcurrentHashMap<IRubyObject, IRubyObject> map;
private static ConcurrentHashMap<IRubyObject, IRubyObject> newCHM(int initialCapacity, float loadFactor) {
if (CAN_USE_UNSAFE_CHM) {
return new ConcurrentHashMapV8<IRubyObject, IRubyObject>(initialCapacity, loadFactor);
} else {
return new com.concurrent_ruby.ext.jsr166e.nounsafe.ConcurrentHashMapV8<IRubyObject, IRubyObject>(initialCapacity, loadFactor);
}
}
private static ConcurrentHashMap<IRubyObject, IRubyObject> newCHM() {
return newCHM(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
private static boolean canUseUnsafeCHM() {
try {
new com.concurrent_ruby.ext.jsr166e.ConcurrentHashMapV8(); // force class load and initialization
return true;
} catch (Throwable t) { // ensuring we really do catch everything
// Doug's Unsafe setup errors always have this "Could not ini.." message
if (isCausedBySecurityException(t)) {
return false;
}
throw (t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t));
}
}
private static boolean isCausedBySecurityException(Throwable t) {
while (t != null) {
if ((t.getMessage() != null && t.getMessage().contains("Could not initialize intrinsics")) || t instanceof SecurityException) {
return true;
}
t = t.getCause();
}
return false;
}
public JRubyMapBackend(Ruby runtime, RubyClass klass) {
super(runtime, klass);
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context) {
map = newCHM();
return context.getRuntime().getNil();
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context, IRubyObject options) {
map = toCHM(context, options);
return context.getRuntime().getNil();
}
private ConcurrentHashMap<IRubyObject, IRubyObject> toCHM(ThreadContext context, IRubyObject options) {
Ruby runtime = context.getRuntime();
if (!options.isNil() && options.respondsTo("[]")) {
IRubyObject rInitialCapacity = options.callMethod(context, "[]", runtime.newSymbol("initial_capacity"));
IRubyObject rLoadFactor = options.callMethod(context, "[]", runtime.newSymbol("load_factor"));
int initialCapacity = !rInitialCapacity.isNil() ? RubyNumeric.num2int(rInitialCapacity.convertToInteger()) : DEFAULT_INITIAL_CAPACITY;
float loadFactor = !rLoadFactor.isNil() ? (float)RubyNumeric.num2dbl(rLoadFactor.convertToFloat()) : DEFAULT_LOAD_FACTOR;
return newCHM(initialCapacity, loadFactor);
} else {
return newCHM();
}
}
@JRubyMethod(name = "[]", required = 1)
public IRubyObject op_aref(ThreadContext context, IRubyObject key) {
IRubyObject value;
return ((value = map.get(key)) == null) ? context.getRuntime().getNil() : value;
}
@JRubyMethod(name = {"[]="}, required = 2)
public IRubyObject op_aset(IRubyObject key, IRubyObject value) {
map.put(key, value);
return value;
}
@JRubyMethod
public IRubyObject put_if_absent(IRubyObject key, IRubyObject value) {
IRubyObject result = map.putIfAbsent(key, value);
return result == null ? getRuntime().getNil() : result;
}
@JRubyMethod
public IRubyObject compute_if_absent(final ThreadContext context, final IRubyObject key, final Block block) {
return map.computeIfAbsent(key, new ConcurrentHashMap.Fun<IRubyObject, IRubyObject>() {
@Override
public IRubyObject apply(IRubyObject key) {
return block.yieldSpecific(context);
}
});
}
@JRubyMethod
public IRubyObject compute_if_present(final ThreadContext context, final IRubyObject key, final Block block) {
IRubyObject result = map.computeIfPresent(key, new ConcurrentHashMap.BiFun<IRubyObject, IRubyObject, IRubyObject>() {
@Override
public IRubyObject apply(IRubyObject key, IRubyObject oldValue) {
IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue);
return result.isNil() ? null : result;
}
});
return result == null ? context.getRuntime().getNil() : result;
}
@JRubyMethod
public IRubyObject compute(final ThreadContext context, final IRubyObject key, final Block block) {
IRubyObject result = map.compute(key, new ConcurrentHashMap.BiFun<IRubyObject, IRubyObject, IRubyObject>() {
@Override
public IRubyObject apply(IRubyObject key, IRubyObject oldValue) {
IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue);
return result.isNil() ? null : result;
}
});
return result == null ? context.getRuntime().getNil() : result;
}
@JRubyMethod
public IRubyObject merge_pair(final ThreadContext context, final IRubyObject key, final IRubyObject value, final Block block) {
IRubyObject result = map.merge(key, value, new ConcurrentHashMap.BiFun<IRubyObject, IRubyObject, IRubyObject>() {
@Override
public IRubyObject apply(IRubyObject oldValue, IRubyObject newValue) {
IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue);
return result.isNil() ? null : result;
}
});
return result == null ? context.getRuntime().getNil() : result;
}
@JRubyMethod
public RubyBoolean replace_pair(IRubyObject key, IRubyObject oldValue, IRubyObject newValue) {
return getRuntime().newBoolean(map.replace(key, oldValue, newValue));
}
@JRubyMethod(name = "key?", required = 1)
public RubyBoolean has_key_p(IRubyObject key) {
return map.containsKey(key) ? getRuntime().getTrue() : getRuntime().getFalse();
}
@JRubyMethod
public IRubyObject key(IRubyObject value) {
final IRubyObject key = map.findKey(value);
return key == null ? getRuntime().getNil() : key;
}
@JRubyMethod
public IRubyObject replace_if_exists(IRubyObject key, IRubyObject value) {
IRubyObject result = map.replace(key, value);
return result == null ? getRuntime().getNil() : result;
}
@JRubyMethod
public IRubyObject get_and_set(IRubyObject key, IRubyObject value) {
IRubyObject result = map.put(key, value);
return result == null ? getRuntime().getNil() : result;
}
@JRubyMethod
public IRubyObject delete(IRubyObject key) {
IRubyObject result = map.remove(key);
return result == null ? getRuntime().getNil() : result;
}
@JRubyMethod
public RubyBoolean delete_pair(IRubyObject key, IRubyObject value) {
return getRuntime().newBoolean(map.remove(key, value));
}
@JRubyMethod
public IRubyObject clear() {
map.clear();
return this;
}
@JRubyMethod
public IRubyObject each_pair(ThreadContext context, Block block) {
for (Map.Entry<IRubyObject,IRubyObject> entry : map.entrySet()) {
block.yieldSpecific(context, entry.getKey(), entry.getValue());
}
return this;
}
@JRubyMethod
public RubyFixnum size(ThreadContext context) {
return context.getRuntime().newFixnum(map.size());
}
@JRubyMethod
public IRubyObject get_or_default(IRubyObject key, IRubyObject defaultValue) {
return map.getValueOrDefault(key, defaultValue);
}
@JRubyMethod(visibility = PRIVATE)
public JRubyMapBackend initialize_copy(ThreadContext context, IRubyObject other) {
map = newCHM();
return this;
}
}
}

View File

@@ -0,0 +1,93 @@
package com.concurrent_ruby.ext;
import org.jruby.Ruby;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.RubyNil;
import org.jruby.RubyObject;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
public class JavaAtomicBooleanLibrary implements Library {
public void load(Ruby runtime, boolean wrap) throws IOException {
RubyModule concurrentMod = runtime.defineModule("Concurrent");
RubyClass atomicCls = concurrentMod.defineClassUnder("JavaAtomicBoolean", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR);
atomicCls.defineAnnotatedMethods(JavaAtomicBoolean.class);
}
private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new JavaAtomicBoolean(runtime, klazz);
}
};
@JRubyClass(name = "JavaAtomicBoolean", parent = "Object")
public static class JavaAtomicBoolean extends RubyObject {
private AtomicBoolean atomicBoolean;
public JavaAtomicBoolean(Ruby runtime, RubyClass metaClass) {
super(runtime, metaClass);
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context, IRubyObject value) {
atomicBoolean = new AtomicBoolean(convertRubyBooleanToJavaBoolean(value));
return context.nil;
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context) {
atomicBoolean = new AtomicBoolean();
return context.nil;
}
@JRubyMethod(name = "value")
public IRubyObject value() {
return getRuntime().newBoolean(atomicBoolean.get());
}
@JRubyMethod(name = "true?")
public IRubyObject isAtomicTrue() {
return getRuntime().newBoolean(atomicBoolean.get());
}
@JRubyMethod(name = "false?")
public IRubyObject isAtomicFalse() {
return getRuntime().newBoolean((atomicBoolean.get() == false));
}
@JRubyMethod(name = "value=")
public IRubyObject setAtomic(ThreadContext context, IRubyObject newValue) {
atomicBoolean.set(convertRubyBooleanToJavaBoolean(newValue));
return context.nil;
}
@JRubyMethod(name = "make_true")
public IRubyObject makeTrue() {
return getRuntime().newBoolean(atomicBoolean.compareAndSet(false, true));
}
@JRubyMethod(name = "make_false")
public IRubyObject makeFalse() {
return getRuntime().newBoolean(atomicBoolean.compareAndSet(true, false));
}
private boolean convertRubyBooleanToJavaBoolean(IRubyObject newValue) {
if (newValue instanceof RubyBoolean.False || newValue instanceof RubyNil) {
return false;
} else {
return true;
}
}
}
}

View File

@@ -0,0 +1,113 @@
package com.concurrent_ruby.ext;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyFixnum;
import org.jruby.RubyModule;
import org.jruby.RubyObject;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;
import org.jruby.runtime.Block;
public class JavaAtomicFixnumLibrary implements Library {
public void load(Ruby runtime, boolean wrap) throws IOException {
RubyModule concurrentMod = runtime.defineModule("Concurrent");
RubyClass atomicCls = concurrentMod.defineClassUnder("JavaAtomicFixnum", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR);
atomicCls.defineAnnotatedMethods(JavaAtomicFixnum.class);
}
private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new JavaAtomicFixnum(runtime, klazz);
}
};
@JRubyClass(name = "JavaAtomicFixnum", parent = "Object")
public static class JavaAtomicFixnum extends RubyObject {
private AtomicLong atomicLong;
public JavaAtomicFixnum(Ruby runtime, RubyClass metaClass) {
super(runtime, metaClass);
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context) {
this.atomicLong = new AtomicLong(0);
return context.nil;
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context, IRubyObject value) {
this.atomicLong = new AtomicLong(rubyFixnumToLong(value));
return context.nil;
}
@JRubyMethod(name = "value")
public IRubyObject getValue() {
return getRuntime().newFixnum(atomicLong.get());
}
@JRubyMethod(name = "value=")
public IRubyObject setValue(ThreadContext context, IRubyObject newValue) {
atomicLong.set(rubyFixnumToLong(newValue));
return context.nil;
}
@JRubyMethod(name = {"increment", "up"})
public IRubyObject increment() {
return getRuntime().newFixnum(atomicLong.incrementAndGet());
}
@JRubyMethod(name = {"increment", "up"})
public IRubyObject increment(IRubyObject value) {
long delta = rubyFixnumToLong(value);
return getRuntime().newFixnum(atomicLong.addAndGet(delta));
}
@JRubyMethod(name = {"decrement", "down"})
public IRubyObject decrement() {
return getRuntime().newFixnum(atomicLong.decrementAndGet());
}
@JRubyMethod(name = {"decrement", "down"})
public IRubyObject decrement(IRubyObject value) {
long delta = rubyFixnumToLong(value);
return getRuntime().newFixnum(atomicLong.addAndGet(-delta));
}
@JRubyMethod(name = "compare_and_set")
public IRubyObject compareAndSet(ThreadContext context, IRubyObject expect, IRubyObject update) {
return getRuntime().newBoolean(atomicLong.compareAndSet(rubyFixnumToLong(expect), rubyFixnumToLong(update)));
}
@JRubyMethod
public IRubyObject update(ThreadContext context, Block block) {
for (;;) {
long _oldValue = atomicLong.get();
IRubyObject oldValue = getRuntime().newFixnum(_oldValue);
IRubyObject newValue = block.yield(context, oldValue);
if (atomicLong.compareAndSet(_oldValue, rubyFixnumToLong(newValue))) {
return newValue;
}
}
}
private long rubyFixnumToLong(IRubyObject value) {
if (value instanceof RubyFixnum) {
RubyFixnum fixNum = (RubyFixnum) value;
return fixNum.getLongValue();
} else {
throw getRuntime().newArgumentError("value must be a Fixnum");
}
}
}
}

View File

@@ -0,0 +1,159 @@
package com.concurrent_ruby.ext;
import java.io.IOException;
import java.util.concurrent.Semaphore;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyFixnum;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyObject;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
public class JavaSemaphoreLibrary {
public void load(Ruby runtime, boolean wrap) throws IOException {
RubyModule concurrentMod = runtime.defineModule("Concurrent");
RubyClass atomicCls = concurrentMod.defineClassUnder("JavaSemaphore", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR);
atomicCls.defineAnnotatedMethods(JavaSemaphore.class);
}
private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new JavaSemaphore(runtime, klazz);
}
};
@JRubyClass(name = "JavaSemaphore", parent = "Object")
public static class JavaSemaphore extends RubyObject {
private JRubySemaphore semaphore;
public JavaSemaphore(Ruby runtime, RubyClass metaClass) {
super(runtime, metaClass);
}
@JRubyMethod
public IRubyObject initialize(ThreadContext context, IRubyObject value) {
this.semaphore = new JRubySemaphore(rubyFixnumInt(value, "count"));
return context.nil;
}
@JRubyMethod
public IRubyObject acquire(ThreadContext context, IRubyObject value) throws InterruptedException {
this.semaphore.acquire(rubyFixnumToPositiveInt(value, "permits"));
return context.nil;
}
@JRubyMethod(name = "available_permits")
public IRubyObject availablePermits(ThreadContext context) {
return getRuntime().newFixnum(this.semaphore.availablePermits());
}
@JRubyMethod(name = "drain_permits")
public IRubyObject drainPermits(ThreadContext context) {
return getRuntime().newFixnum(this.semaphore.drainPermits());
}
@JRubyMethod
public IRubyObject acquire(ThreadContext context) throws InterruptedException {
this.semaphore.acquire(1);
return context.nil;
}
@JRubyMethod(name = "try_acquire")
public IRubyObject tryAcquire(ThreadContext context) throws InterruptedException {
return getRuntime().newBoolean(semaphore.tryAcquire(1));
}
@JRubyMethod(name = "try_acquire")
public IRubyObject tryAcquire(ThreadContext context, IRubyObject permits) throws InterruptedException {
return getRuntime().newBoolean(semaphore.tryAcquire(rubyFixnumToPositiveInt(permits, "permits")));
}
@JRubyMethod(name = "try_acquire")
public IRubyObject tryAcquire(ThreadContext context, IRubyObject permits, IRubyObject timeout) throws InterruptedException {
return getRuntime().newBoolean(
semaphore.tryAcquire(
rubyFixnumToPositiveInt(permits, "permits"),
rubyNumericToLong(timeout, "timeout"),
java.util.concurrent.TimeUnit.SECONDS)
);
}
@JRubyMethod
public IRubyObject release(ThreadContext context) {
this.semaphore.release(1);
return getRuntime().newBoolean(true);
}
@JRubyMethod
public IRubyObject release(ThreadContext context, IRubyObject value) {
this.semaphore.release(rubyFixnumToPositiveInt(value, "permits"));
return getRuntime().newBoolean(true);
}
@JRubyMethod(name = "reduce_permits")
public IRubyObject reducePermits(ThreadContext context, IRubyObject reduction) throws InterruptedException {
this.semaphore.publicReducePermits(rubyFixnumToNonNegativeInt(reduction, "reduction"));
return context.nil;
}
private int rubyFixnumInt(IRubyObject value, String paramName) {
if (value instanceof RubyFixnum) {
RubyFixnum fixNum = (RubyFixnum) value;
return (int) fixNum.getLongValue();
} else {
throw getRuntime().newArgumentError(paramName + " must be integer");
}
}
private int rubyFixnumToNonNegativeInt(IRubyObject value, String paramName) {
if (value instanceof RubyFixnum && ((RubyFixnum) value).getLongValue() >= 0) {
RubyFixnum fixNum = (RubyFixnum) value;
return (int) fixNum.getLongValue();
} else {
throw getRuntime().newArgumentError(paramName + " must be a non-negative integer");
}
}
private int rubyFixnumToPositiveInt(IRubyObject value, String paramName) {
if (value instanceof RubyFixnum && ((RubyFixnum) value).getLongValue() > 0) {
RubyFixnum fixNum = (RubyFixnum) value;
return (int) fixNum.getLongValue();
} else {
throw getRuntime().newArgumentError(paramName + " must be an integer greater than zero");
}
}
private long rubyNumericToLong(IRubyObject value, String paramName) {
if (value instanceof RubyNumeric && ((RubyNumeric) value).getDoubleValue() > 0) {
RubyNumeric fixNum = (RubyNumeric) value;
return fixNum.getLongValue();
} else {
throw getRuntime().newArgumentError(paramName + " must be a float greater than zero");
}
}
class JRubySemaphore extends Semaphore {
public JRubySemaphore(int permits) {
super(permits);
}
public JRubySemaphore(int permits, boolean value) {
super(permits, value);
}
public void publicReducePermits(int i) {
reducePermits(i);
}
}
}
}

View File

@@ -0,0 +1,307 @@
package com.concurrent_ruby.ext;
import org.jruby.Ruby;
import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.RubyObject;
import org.jruby.RubyThread;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;
import sun.misc.Unsafe;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class SynchronizationLibrary implements Library {
private static final Unsafe UNSAFE = loadUnsafe();
private static final boolean FULL_FENCE = supportsFences();
private static Unsafe loadUnsafe() {
try {
Class ncdfe = Class.forName("sun.misc.Unsafe");
Field f = ncdfe.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get((java.lang.Object) null);
} catch (Exception var2) {
return null;
} catch (NoClassDefFoundError var3) {
return null;
}
}
private static boolean supportsFences() {
if (UNSAFE == null) {
return false;
} else {
try {
Method m = UNSAFE.getClass().getDeclaredMethod("fullFence", new Class[0]);
if (m != null) {
return true;
}
} catch (Exception var1) {
// nothing
}
return false;
}
}
private static final ObjectAllocator JRUBY_OBJECT_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new JRubyObject(runtime, klazz);
}
};
private static final ObjectAllocator OBJECT_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new Object(runtime, klazz);
}
};
private static final ObjectAllocator ABSTRACT_LOCKABLE_OBJECT_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new AbstractLockableObject(runtime, klazz);
}
};
private static final ObjectAllocator JRUBY_LOCKABLE_OBJECT_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new JRubyLockableObject(runtime, klazz);
}
};
public void load(Ruby runtime, boolean wrap) throws IOException {
RubyModule synchronizationModule = runtime.
defineModule("Concurrent").
defineModuleUnder("Synchronization");
RubyModule jrubyAttrVolatileModule = synchronizationModule.defineModuleUnder("JRubyAttrVolatile");
jrubyAttrVolatileModule.defineAnnotatedMethods(JRubyAttrVolatile.class);
defineClass(runtime, synchronizationModule, "AbstractObject", "JRubyObject",
JRubyObject.class, JRUBY_OBJECT_ALLOCATOR);
defineClass(runtime, synchronizationModule, "JRubyObject", "Object",
Object.class, OBJECT_ALLOCATOR);
defineClass(runtime, synchronizationModule, "Object", "AbstractLockableObject",
AbstractLockableObject.class, ABSTRACT_LOCKABLE_OBJECT_ALLOCATOR);
defineClass(runtime, synchronizationModule, "AbstractLockableObject", "JRubyLockableObject",
JRubyLockableObject.class, JRUBY_LOCKABLE_OBJECT_ALLOCATOR);
defineClass(runtime, synchronizationModule, "Object", "JRuby",
JRuby.class, new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new JRuby(runtime, klazz);
}
});
}
private RubyClass defineClass(
Ruby runtime,
RubyModule namespace,
String parentName,
String name,
Class javaImplementation,
ObjectAllocator allocator) {
final RubyClass parentClass = namespace.getClass(parentName);
if (parentClass == null) {
System.out.println("not found " + parentName);
throw runtime.newRuntimeError(namespace.toString() + "::" + parentName + " is missing");
}
final RubyClass newClass = namespace.defineClassUnder(name, parentClass, allocator);
newClass.defineAnnotatedMethods(javaImplementation);
return newClass;
}
// Facts:
// - all ivar reads are without any synchronisation of fences see
// https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/runtime/ivars/VariableAccessor.java#L110-110
// - writes depend on UnsafeHolder.U, null -> SynchronizedVariableAccessor, !null -> StampedVariableAccessor
// SynchronizedVariableAccessor wraps with synchronized block, StampedVariableAccessor uses fullFence or
// volatilePut
// TODO (pitr 16-Sep-2015): what do we do in Java 9 ?
// module JRubyAttrVolatile
public static class JRubyAttrVolatile {
// volatile threadContext is used as a memory barrier per the JVM memory model happens-before semantic
// on volatile fields. any volatile field could have been used but using the thread context is an
// attempt to avoid code elimination.
private static volatile int volatileField;
@JRubyMethod(name = "full_memory_barrier", visibility = Visibility.PUBLIC)
public static IRubyObject fullMemoryBarrier(ThreadContext context, IRubyObject self) {
// Prevent reordering of ivar writes with publication of this instance
if (!FULL_FENCE) {
// Assuming that following volatile read and write is not eliminated it simulates fullFence.
// If it's eliminated it'll cause problems only on non-x86 platforms.
// http://shipilev.net/blog/2014/jmm-pragmatics/#_happens_before_test_your_understanding
final int volatileRead = volatileField;
volatileField = context.getLine();
} else {
UNSAFE.fullFence();
}
return context.nil;
}
@JRubyMethod(name = "instance_variable_get_volatile", visibility = Visibility.PUBLIC)
public static IRubyObject instanceVariableGetVolatile(
ThreadContext context,
IRubyObject self,
IRubyObject name) {
// Ensure we ses latest value with loadFence
if (!FULL_FENCE) {
// piggybacking on volatile read, simulating loadFence
final int volatileRead = volatileField;
return ((RubyBasicObject) self).instance_variable_get(context, name);
} else {
UNSAFE.loadFence();
return ((RubyBasicObject) self).instance_variable_get(context, name);
}
}
@JRubyMethod(name = "instance_variable_set_volatile", visibility = Visibility.PUBLIC)
public static IRubyObject InstanceVariableSetVolatile(
ThreadContext context,
IRubyObject self,
IRubyObject name,
IRubyObject value) {
// Ensure we make last update visible
if (!FULL_FENCE) {
// piggybacking on volatile write, simulating storeFence
final IRubyObject result = ((RubyBasicObject) self).instance_variable_set(name, value);
volatileField = context.getLine();
return result;
} else {
// JRuby uses StampedVariableAccessor which calls fullFence
// so no additional steps needed.
// See https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/runtime/ivars/StampedVariableAccessor.java#L151-L159
return ((RubyBasicObject) self).instance_variable_set(name, value);
}
}
}
@JRubyClass(name = "JRubyObject", parent = "AbstractObject")
public static class JRubyObject extends RubyObject {
public JRubyObject(Ruby runtime, RubyClass metaClass) {
super(runtime, metaClass);
}
}
@JRubyClass(name = "Object", parent = "JRubyObject")
public static class Object extends JRubyObject {
public Object(Ruby runtime, RubyClass metaClass) {
super(runtime, metaClass);
}
}
@JRubyClass(name = "AbstractLockableObject", parent = "Object")
public static class AbstractLockableObject extends Object {
public AbstractLockableObject(Ruby runtime, RubyClass metaClass) {
super(runtime, metaClass);
}
}
@JRubyClass(name = "JRubyLockableObject", parent = "AbstractLockableObject")
public static class JRubyLockableObject extends JRubyObject {
public JRubyLockableObject(Ruby runtime, RubyClass metaClass) {
super(runtime, metaClass);
}
@JRubyMethod(name = "synchronize", visibility = Visibility.PROTECTED)
public IRubyObject rubySynchronize(ThreadContext context, Block block) {
synchronized (this) {
return block.yield(context, null);
}
}
@JRubyMethod(name = "ns_wait", optional = 1, visibility = Visibility.PROTECTED)
public IRubyObject nsWait(ThreadContext context, IRubyObject[] args) {
Ruby runtime = context.runtime;
if (args.length > 1) {
throw runtime.newArgumentError(args.length, 1);
}
Double timeout = null;
if (args.length > 0 && !args[0].isNil()) {
timeout = args[0].convertToFloat().getDoubleValue();
if (timeout < 0) {
throw runtime.newArgumentError("time interval must be positive");
}
}
if (Thread.interrupted()) {
throw runtime.newConcurrencyError("thread interrupted");
}
boolean success = false;
try {
success = context.getThread().wait_timeout(this, timeout);
} catch (InterruptedException ie) {
throw runtime.newConcurrencyError(ie.getLocalizedMessage());
} finally {
// An interrupt or timeout may have caused us to miss
// a notify that we consumed, so do another notify in
// case someone else is available to pick it up.
if (!success) {
this.notify();
}
}
return this;
}
@JRubyMethod(name = "ns_signal", visibility = Visibility.PROTECTED)
public IRubyObject nsSignal(ThreadContext context) {
notify();
return this;
}
@JRubyMethod(name = "ns_broadcast", visibility = Visibility.PROTECTED)
public IRubyObject nsBroadcast(ThreadContext context) {
notifyAll();
return this;
}
}
@JRubyClass(name = "JRuby")
public static class JRuby extends RubyObject {
public JRuby(Ruby runtime, RubyClass metaClass) {
super(runtime, metaClass);
}
@JRubyMethod(name = "sleep_interruptibly", visibility = Visibility.PUBLIC, module = true)
public static IRubyObject sleepInterruptibly(final ThreadContext context, IRubyObject receiver, final Block block) {
try {
context.getThread().executeBlockingTask(new RubyThread.BlockingTask() {
@Override
public void run() throws InterruptedException {
block.call(context);
}
@Override
public void wakeup() {
context.getThread().getNativeThread().interrupt();
}
});
} catch (InterruptedException e) {
throw context.runtime.newThreadError("interrupted in Concurrent::Synchronization::JRuby.sleep_interruptibly");
}
return context.nil;
}
}
}

View File

@@ -0,0 +1,31 @@
package com.concurrent_ruby.ext.jsr166e;
import java.util.Map;
import java.util.Set;
public interface ConcurrentHashMap<K, V> {
/** Interface describing a function of one argument */
public interface Fun<A,T> { T apply(A a); }
/** Interface describing a function of two arguments */
public interface BiFun<A,B,T> { T apply(A a, B b); }
public V get(K key);
public V put(K key, V value);
public V putIfAbsent(K key, V value);
public V computeIfAbsent(K key, Fun<? super K, ? extends V> mf);
public V computeIfPresent(K key, BiFun<? super K, ? super V, ? extends V> mf);
public V compute(K key, BiFun<? super K, ? super V, ? extends V> mf);
public V merge(K key, V value, BiFun<? super V, ? super V, ? extends V> mf);
public boolean replace(K key, V oldVal, V newVal);
public V replace(K key, V value);
public boolean containsKey(K key);
public boolean remove(Object key, Object value);
public V remove(K key);
public void clear();
public Set<Map.Entry<K,V>> entrySet();
public int size();
public V getValueOrDefault(Object key, V defaultValue);
public boolean containsValue(V value);
public K findKey(V value);
}

View File

@@ -0,0 +1,203 @@
/*
* 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/publicdomain/zero/1.0/
*/
// This is based on 1.9 version.
package com.concurrent_ruby.ext.jsr166e;
import java.util.concurrent.atomic.AtomicLong;
import java.io.IOException;
import java.io.Serializable;
import java.io.ObjectInputStream;
/**
* One or more variables that together maintain an initially zero
* {@code long} sum. When updates (method {@link #add}) are contended
* across threads, the set of variables may grow dynamically to reduce
* contention. Method {@link #sum} (or, equivalently, {@link
* #longValue}) returns the current total combined across the
* variables maintaining the sum.
*
* <p>This class is usually preferable to {@link AtomicLong} when
* multiple threads update a common sum that is used for purposes such
* as collecting statistics, not for fine-grained synchronization
* control. Under low update contention, the two classes have similar
* characteristics. But under high contention, expected throughput of
* this class is significantly higher, at the expense of higher space
* consumption.
*
* <p>This class extends {@link Number}, but does <em>not</em> define
* methods such as {@code hashCode} and {@code compareTo} because
* instances are expected to be mutated, and so are not useful as
* collection keys.
*
* <p><em>jsr166e note: This class is targeted to be placed in
* java.util.concurrent.atomic.</em>
*
* @since 1.8
* @author Doug Lea
*/
public class LongAdder extends Striped64 implements Serializable {
private static final long serialVersionUID = 7249069246863182397L;
/**
* Version of plus for use in retryUpdate
*/
final long fn(long v, long x) { return v + x; }
/**
* Creates a new adder with initial sum of zero.
*/
public LongAdder() {
}
/**
* Adds the given value.
*
* @param x the value to add
*/
public void add(long x) {
Cell[] as; long b, v; HashCode hc; Cell a; int n;
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
int h = (hc = threadHashCode.get()).code;
if (as == null || (n = as.length) < 1 ||
(a = as[(n - 1) & h]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
retryUpdate(x, hc, uncontended);
}
}
/**
* Equivalent to {@code add(1)}.
*/
public void increment() {
add(1L);
}
/**
* Equivalent to {@code add(-1)}.
*/
public void decrement() {
add(-1L);
}
/**
* Returns the current sum. The returned value is <em>NOT</em> an
* atomic snapshot: Invocation in the absence of concurrent
* updates returns an accurate result, but concurrent updates that
* occur while the sum is being calculated might not be
* incorporated.
*
* @return the sum
*/
public long sum() {
long sum = base;
Cell[] as = cells;
if (as != null) {
int n = as.length;
for (int i = 0; i < n; ++i) {
Cell a = as[i];
if (a != null)
sum += a.value;
}
}
return sum;
}
/**
* Resets variables maintaining the sum to zero. This method may
* be a useful alternative to creating a new adder, but is only
* effective if there are no concurrent updates. Because this
* method is intrinsically racy, it should only be used when it is
* known that no threads are concurrently updating.
*/
public void reset() {
internalReset(0L);
}
/**
* Equivalent in effect to {@link #sum} followed by {@link
* #reset}. This method may apply for example during quiescent
* points between multithreaded computations. If there are
* updates concurrent with this method, the returned value is
* <em>not</em> guaranteed to be the final value occurring before
* the reset.
*
* @return the sum
*/
public long sumThenReset() {
long sum = base;
Cell[] as = cells;
base = 0L;
if (as != null) {
int n = as.length;
for (int i = 0; i < n; ++i) {
Cell a = as[i];
if (a != null) {
sum += a.value;
a.value = 0L;
}
}
}
return sum;
}
/**
* Returns the String representation of the {@link #sum}.
* @return the String representation of the {@link #sum}
*/
public String toString() {
return Long.toString(sum());
}
/**
* Equivalent to {@link #sum}.
*
* @return the sum
*/
public long longValue() {
return sum();
}
/**
* Returns the {@link #sum} as an {@code int} after a narrowing
* primitive conversion.
*/
public int intValue() {
return (int)sum();
}
/**
* Returns the {@link #sum} as a {@code float}
* after a widening primitive conversion.
*/
public float floatValue() {
return (float)sum();
}
/**
* Returns the {@link #sum} as a {@code double} after a widening
* primitive conversion.
*/
public double doubleValue() {
return (double)sum();
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();
s.writeLong(sum());
}
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
busy = 0;
cells = null;
base = s.readLong();
}
}

View File

@@ -0,0 +1,342 @@
/*
* 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/publicdomain/zero/1.0/
*/
// This is based on 1.5 version.
package com.concurrent_ruby.ext.jsr166e;
import java.util.Random;
/**
* A package-local class holding common representation and mechanics
* for classes supporting dynamic striping on 64bit values. The class
* extends Number so that concrete subclasses must publicly do so.
*/
abstract class Striped64 extends Number {
/*
* This class maintains a lazily-initialized table of atomically
* updated variables, plus an extra "base" field. The table size
* is a power of two. Indexing uses masked per-thread hash codes.
* Nearly all declarations in this class are package-private,
* accessed directly by subclasses.
*
* Table entries are of class Cell; a variant of AtomicLong padded
* to reduce cache contention on most processors. Padding is
* overkill for most Atomics because they are usually irregularly
* scattered in memory and thus don't interfere much with each
* other. But Atomic objects residing in arrays will tend to be
* placed adjacent to each other, and so will most often share
* cache lines (with a huge negative performance impact) without
* this precaution.
*
* In part because Cells are relatively large, we avoid creating
* them until they are needed. When there is no contention, all
* updates are made to the base field. Upon first contention (a
* failed CAS on base update), the table is initialized to size 2.
* The table size is doubled upon further contention until
* reaching the nearest power of two greater than or equal to the
* number of CPUS. Table slots remain empty (null) until they are
* needed.
*
* A single spinlock ("busy") is used for initializing and
* resizing the table, as well as populating slots with new Cells.
* There is no need for a blocking lock: When the lock is not
* available, threads try other slots (or the base). During these
* retries, there is increased contention and reduced locality,
* which is still better than alternatives.
*
* Per-thread hash codes are initialized to random values.
* Contention and/or table collisions are indicated by failed
* CASes when performing an update operation (see method
* retryUpdate). Upon a collision, if the table size is less than
* the capacity, it is doubled in size unless some other thread
* holds the lock. If a hashed slot is empty, and lock is
* available, a new Cell is created. Otherwise, if the slot
* exists, a CAS is tried. Retries proceed by "double hashing",
* using a secondary hash (Marsaglia XorShift) to try to find a
* free slot.
*
* The table size is capped because, when there are more threads
* than CPUs, supposing that each thread were bound to a CPU,
* there would exist a perfect hash function mapping threads to
* slots that eliminates collisions. When we reach capacity, we
* search for this mapping by randomly varying the hash codes of
* colliding threads. Because search is random, and collisions
* only become known via CAS failures, convergence can be slow,
* and because threads are typically not bound to CPUS forever,
* may not occur at all. However, despite these limitations,
* observed contention rates are typically low in these cases.
*
* It is possible for a Cell to become unused when threads that
* once hashed to it terminate, as well as in the case where
* doubling the table causes no thread to hash to it under
* expanded mask. We do not try to detect or remove such cells,
* under the assumption that for long-running instances, observed
* contention levels will recur, so the cells will eventually be
* needed again; and for short-lived ones, it does not matter.
*/
/**
* Padded variant of AtomicLong supporting only raw accesses plus CAS.
* The value field is placed between pads, hoping that the JVM doesn't
* reorder them.
*
* JVM intrinsics note: It would be possible to use a release-only
* form of CAS here, if it were provided.
*/
static final class Cell {
volatile long p0, p1, p2, p3, p4, p5, p6;
volatile long value;
volatile long q0, q1, q2, q3, q4, q5, q6;
Cell(long x) { value = x; }
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long valueOffset;
static {
try {
UNSAFE = getUnsafe();
Class<?> ak = Cell.class;
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}
/**
* Holder for the thread-local hash code. The code is initially
* random, but may be set to a different value upon collisions.
*/
static final class HashCode {
static final Random rng = new Random();
int code;
HashCode() {
int h = rng.nextInt(); // Avoid zero to allow xorShift rehash
code = (h == 0) ? 1 : h;
}
}
/**
* The corresponding ThreadLocal class
*/
static final class ThreadHashCode extends ThreadLocal<HashCode> {
public HashCode initialValue() { return new HashCode(); }
}
/**
* Static per-thread hash codes. Shared across all instances to
* reduce ThreadLocal pollution and because adjustments due to
* collisions in one table are likely to be appropriate for
* others.
*/
static final ThreadHashCode threadHashCode = new ThreadHashCode();
/** Number of CPUS, to place bound on table size */
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
* Table of cells. When non-null, size is a power of 2.
*/
transient volatile Cell[] cells;
/**
* Base value, used mainly when there is no contention, but also as
* a fallback during table initialization races. Updated via CAS.
*/
transient volatile long base;
/**
* Spinlock (locked via CAS) used when resizing and/or creating Cells.
*/
transient volatile int busy;
/**
* Package-private default constructor
*/
Striped64() {
}
/**
* CASes the base field.
*/
final boolean casBase(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val);
}
/**
* CASes the busy field from 0 to 1 to acquire lock.
*/
final boolean casBusy() {
return UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1);
}
/**
* Computes the function of current and new value. Subclasses
* should open-code this update function for most uses, but the
* virtualized form is needed within retryUpdate.
*
* @param currentValue the current value (of either base or a cell)
* @param newValue the argument from a user update call
* @return result of the update function
*/
abstract long fn(long currentValue, long newValue);
/**
* Handles cases of updates involving initialization, resizing,
* creating new Cells, and/or contention. See above for
* explanation. This method suffers the usual non-modularity
* problems of optimistic retry code, relying on rechecked sets of
* reads.
*
* @param x the value
* @param hc the hash code holder
* @param wasUncontended false if CAS failed before call
*/
final void retryUpdate(long x, HashCode hc, boolean wasUncontended) {
int h = hc.code;
boolean collide = false; // True if last slot nonempty
for (;;) {
Cell[] as; Cell a; int n; long v;
if ((as = cells) != null && (n = as.length) > 0) {
if ((a = as[(n - 1) & h]) == null) {
if (busy == 0) { // Try to attach new Cell
Cell r = new Cell(x); // Optimistically create
if (busy == 0 && casBusy()) {
boolean created = false;
try { // Recheck under lock
Cell[] rs; int m, j;
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
busy = 0;
}
if (created)
break;
continue; // Slot is now non-empty
}
}
collide = false;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
else if (a.cas(v = a.value, fn(v, x)))
break;
else if (n >= NCPU || cells != as)
collide = false; // At max size or stale
else if (!collide)
collide = true;
else if (busy == 0 && casBusy()) {
try {
if (cells == as) { // Expand table unless stale
Cell[] rs = new Cell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
cells = rs;
}
} finally {
busy = 0;
}
collide = false;
continue; // Retry with expanded table
}
h ^= h << 13; // Rehash
h ^= h >>> 17;
h ^= h << 5;
}
else if (busy == 0 && cells == as && casBusy()) {
boolean init = false;
try { // Initialize table
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
busy = 0;
}
if (init)
break;
}
else if (casBase(v = base, fn(v, x)))
break; // Fall back on using base
}
hc.code = h; // Record index for next time
}
/**
* Sets base and all cells to the given value.
*/
final void internalReset(long initialValue) {
Cell[] as = cells;
base = initialValue;
if (as != null) {
int n = as.length;
for (int i = 0; i < n; ++i) {
Cell a = as[i];
if (a != null)
a.value = initialValue;
}
}
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long baseOffset;
private static final long busyOffset;
static {
try {
UNSAFE = getUnsafe();
Class<?> sk = Striped64.class;
baseOffset = UNSAFE.objectFieldOffset
(sk.getDeclaredField("base"));
busyOffset = UNSAFE.objectFieldOffset
(sk.getDeclaredField("busy"));
} catch (Exception e) {
throw new Error(e);
}
}
/**
* Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package.
* Replace with a simple call to Unsafe.getUnsafe when integrating
* into a jdk.
*
* @return a sun.misc.Unsafe
*/
private static sun.misc.Unsafe getUnsafe() {
try {
return sun.misc.Unsafe.getUnsafe();
} catch (SecurityException se) {
try {
return java.security.AccessController.doPrivileged
(new java.security
.PrivilegedExceptionAction<sun.misc.Unsafe>() {
public sun.misc.Unsafe run() throws Exception {
java.lang.reflect.Field f = sun.misc
.Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (sun.misc.Unsafe) f.get(null);
}});
} catch (java.security.PrivilegedActionException e) {
throw new RuntimeException("Could not initialize intrinsics",
e.getCause());
}
}
}
}

View File

@@ -0,0 +1,204 @@
/*
* 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/publicdomain/zero/1.0/
*/
// This is based on 1.9 version.
package com.concurrent_ruby.ext.jsr166e.nounsafe;
import java.util.concurrent.atomic.AtomicLong;
import java.io.IOException;
import java.io.Serializable;
import java.io.ObjectInputStream;
/**
* One or more variables that together maintain an initially zero
* {@code long} sum. When updates (method {@link #add}) are contended
* across threads, the set of variables may grow dynamically to reduce
* contention. Method {@link #sum} (or, equivalently, {@link
* #longValue}) returns the current total combined across the
* variables maintaining the sum.
*
* <p>This class is usually preferable to {@link AtomicLong} when
* multiple threads update a common sum that is used for purposes such
* as collecting statistics, not for fine-grained synchronization
* control. Under low update contention, the two classes have similar
* characteristics. But under high contention, expected throughput of
* this class is significantly higher, at the expense of higher space
* consumption.
*
* <p>This class extends {@link Number}, but does <em>not</em> define
* methods such as {@code hashCode} and {@code compareTo} because
* instances are expected to be mutated, and so are not useful as
* collection keys.
*
* <p><em>jsr166e note: This class is targeted to be placed in
* java.util.concurrent.atomic.</em>
*
* @since 1.8
* @author Doug Lea
*/
public class LongAdder extends Striped64 implements Serializable {
private static final long serialVersionUID = 7249069246863182397L;
/**
* Version of plus for use in retryUpdate
*/
final long fn(long v, long x) { return v + x; }
/**
* Creates a new adder with initial sum of zero.
*/
public LongAdder() {
}
/**
* Adds the given value.
*
* @param x the value to add
*/
public void add(long x) {
Cell[] as; long b, v; HashCode hc; Cell a; int n;
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
int h = (hc = threadHashCode.get()).code;
if (as == null || (n = as.length) < 1 ||
(a = as[(n - 1) & h]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
retryUpdate(x, hc, uncontended);
}
}
/**
* Equivalent to {@code add(1)}.
*/
public void increment() {
add(1L);
}
/**
* Equivalent to {@code add(-1)}.
*/
public void decrement() {
add(-1L);
}
/**
* Returns the current sum. The returned value is <em>NOT</em> an
* atomic snapshot: Invocation in the absence of concurrent
* updates returns an accurate result, but concurrent updates that
* occur while the sum is being calculated might not be
* incorporated.
*
* @return the sum
*/
public long sum() {
long sum = base;
Cell[] as = cells;
if (as != null) {
int n = as.length;
for (int i = 0; i < n; ++i) {
Cell a = as[i];
if (a != null)
sum += a.value;
}
}
return sum;
}
/**
* Resets variables maintaining the sum to zero. This method may
* be a useful alternative to creating a new adder, but is only
* effective if there are no concurrent updates. Because this
* method is intrinsically racy, it should only be used when it is
* known that no threads are concurrently updating.
*/
public void reset() {
internalReset(0L);
}
/**
* Equivalent in effect to {@link #sum} followed by {@link
* #reset}. This method may apply for example during quiescent
* points between multithreaded computations. If there are
* updates concurrent with this method, the returned value is
* <em>not</em> guaranteed to be the final value occurring before
* the reset.
*
* @return the sum
*/
public long sumThenReset() {
long sum = base;
Cell[] as = cells;
base = 0L;
if (as != null) {
int n = as.length;
for (int i = 0; i < n; ++i) {
Cell a = as[i];
if (a != null) {
sum += a.value;
a.value = 0L;
}
}
}
return sum;
}
/**
* Returns the String representation of the {@link #sum}.
* @return the String representation of the {@link #sum}
*/
public String toString() {
return Long.toString(sum());
}
/**
* Equivalent to {@link #sum}.
*
* @return the sum
*/
public long longValue() {
return sum();
}
/**
* Returns the {@link #sum} as an {@code int} after a narrowing
* primitive conversion.
*/
public int intValue() {
return (int)sum();
}
/**
* Returns the {@link #sum} as a {@code float}
* after a widening primitive conversion.
*/
public float floatValue() {
return (float)sum();
}
/**
* Returns the {@link #sum} as a {@code double} after a widening
* primitive conversion.
*/
public double doubleValue() {
return (double)sum();
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();
s.writeLong(sum());
}
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
busy = 0;
cells = null;
base = s.readLong();
}
}

View File

@@ -0,0 +1,291 @@
/*
* 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/publicdomain/zero/1.0/
*/
// This is based on 1.5 version.
package com.concurrent_ruby.ext.jsr166e.nounsafe;
import java.util.Random;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
/**
* A package-local class holding common representation and mechanics
* for classes supporting dynamic striping on 64bit values. The class
* extends Number so that concrete subclasses must publicly do so.
*/
abstract class Striped64 extends Number {
/*
* This class maintains a lazily-initialized table of atomically
* updated variables, plus an extra "base" field. The table size
* is a power of two. Indexing uses masked per-thread hash codes.
* Nearly all declarations in this class are package-private,
* accessed directly by subclasses.
*
* Table entries are of class Cell; a variant of AtomicLong padded
* to reduce cache contention on most processors. Padding is
* overkill for most Atomics because they are usually irregularly
* scattered in memory and thus don't interfere much with each
* other. But Atomic objects residing in arrays will tend to be
* placed adjacent to each other, and so will most often share
* cache lines (with a huge negative performance impact) without
* this precaution.
*
* In part because Cells are relatively large, we avoid creating
* them until they are needed. When there is no contention, all
* updates are made to the base field. Upon first contention (a
* failed CAS on base update), the table is initialized to size 2.
* The table size is doubled upon further contention until
* reaching the nearest power of two greater than or equal to the
* number of CPUS. Table slots remain empty (null) until they are
* needed.
*
* A single spinlock ("busy") is used for initializing and
* resizing the table, as well as populating slots with new Cells.
* There is no need for a blocking lock: When the lock is not
* available, threads try other slots (or the base). During these
* retries, there is increased contention and reduced locality,
* which is still better than alternatives.
*
* Per-thread hash codes are initialized to random values.
* Contention and/or table collisions are indicated by failed
* CASes when performing an update operation (see method
* retryUpdate). Upon a collision, if the table size is less than
* the capacity, it is doubled in size unless some other thread
* holds the lock. If a hashed slot is empty, and lock is
* available, a new Cell is created. Otherwise, if the slot
* exists, a CAS is tried. Retries proceed by "double hashing",
* using a secondary hash (Marsaglia XorShift) to try to find a
* free slot.
*
* The table size is capped because, when there are more threads
* than CPUs, supposing that each thread were bound to a CPU,
* there would exist a perfect hash function mapping threads to
* slots that eliminates collisions. When we reach capacity, we
* search for this mapping by randomly varying the hash codes of
* colliding threads. Because search is random, and collisions
* only become known via CAS failures, convergence can be slow,
* and because threads are typically not bound to CPUS forever,
* may not occur at all. However, despite these limitations,
* observed contention rates are typically low in these cases.
*
* It is possible for a Cell to become unused when threads that
* once hashed to it terminate, as well as in the case where
* doubling the table causes no thread to hash to it under
* expanded mask. We do not try to detect or remove such cells,
* under the assumption that for long-running instances, observed
* contention levels will recur, so the cells will eventually be
* needed again; and for short-lived ones, it does not matter.
*/
/**
* Padded variant of AtomicLong supporting only raw accesses plus CAS.
* The value field is placed between pads, hoping that the JVM doesn't
* reorder them.
*
* JVM intrinsics note: It would be possible to use a release-only
* form of CAS here, if it were provided.
*/
static final class Cell {
volatile long p0, p1, p2, p3, p4, p5, p6;
volatile long value;
volatile long q0, q1, q2, q3, q4, q5, q6;
static AtomicLongFieldUpdater<Cell> VALUE_UPDATER = AtomicLongFieldUpdater.newUpdater(Cell.class, "value");
Cell(long x) { value = x; }
final boolean cas(long cmp, long val) {
return VALUE_UPDATER.compareAndSet(this, cmp, val);
}
}
/**
* Holder for the thread-local hash code. The code is initially
* random, but may be set to a different value upon collisions.
*/
static final class HashCode {
static final Random rng = new Random();
int code;
HashCode() {
int h = rng.nextInt(); // Avoid zero to allow xorShift rehash
code = (h == 0) ? 1 : h;
}
}
/**
* The corresponding ThreadLocal class
*/
static final class ThreadHashCode extends ThreadLocal<HashCode> {
public HashCode initialValue() { return new HashCode(); }
}
/**
* Static per-thread hash codes. Shared across all instances to
* reduce ThreadLocal pollution and because adjustments due to
* collisions in one table are likely to be appropriate for
* others.
*/
static final ThreadHashCode threadHashCode = new ThreadHashCode();
/** Number of CPUS, to place bound on table size */
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
* Table of cells. When non-null, size is a power of 2.
*/
transient volatile Cell[] cells;
/**
* Base value, used mainly when there is no contention, but also as
* a fallback during table initialization races. Updated via CAS.
*/
transient volatile long base;
/**
* Spinlock (locked via CAS) used when resizing and/or creating Cells.
*/
transient volatile int busy;
AtomicLongFieldUpdater<Striped64> BASE_UPDATER = AtomicLongFieldUpdater.newUpdater(Striped64.class, "base");
AtomicIntegerFieldUpdater<Striped64> BUSY_UPDATER = AtomicIntegerFieldUpdater.newUpdater(Striped64.class, "busy");
/**
* Package-private default constructor
*/
Striped64() {
}
/**
* CASes the base field.
*/
final boolean casBase(long cmp, long val) {
return BASE_UPDATER.compareAndSet(this, cmp, val);
}
/**
* CASes the busy field from 0 to 1 to acquire lock.
*/
final boolean casBusy() {
return BUSY_UPDATER.compareAndSet(this, 0, 1);
}
/**
* Computes the function of current and new value. Subclasses
* should open-code this update function for most uses, but the
* virtualized form is needed within retryUpdate.
*
* @param currentValue the current value (of either base or a cell)
* @param newValue the argument from a user update call
* @return result of the update function
*/
abstract long fn(long currentValue, long newValue);
/**
* Handles cases of updates involving initialization, resizing,
* creating new Cells, and/or contention. See above for
* explanation. This method suffers the usual non-modularity
* problems of optimistic retry code, relying on rechecked sets of
* reads.
*
* @param x the value
* @param hc the hash code holder
* @param wasUncontended false if CAS failed before call
*/
final void retryUpdate(long x, HashCode hc, boolean wasUncontended) {
int h = hc.code;
boolean collide = false; // True if last slot nonempty
for (;;) {
Cell[] as; Cell a; int n; long v;
if ((as = cells) != null && (n = as.length) > 0) {
if ((a = as[(n - 1) & h]) == null) {
if (busy == 0) { // Try to attach new Cell
Cell r = new Cell(x); // Optimistically create
if (busy == 0 && casBusy()) {
boolean created = false;
try { // Recheck under lock
Cell[] rs; int m, j;
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
busy = 0;
}
if (created)
break;
continue; // Slot is now non-empty
}
}
collide = false;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
else if (a.cas(v = a.value, fn(v, x)))
break;
else if (n >= NCPU || cells != as)
collide = false; // At max size or stale
else if (!collide)
collide = true;
else if (busy == 0 && casBusy()) {
try {
if (cells == as) { // Expand table unless stale
Cell[] rs = new Cell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
cells = rs;
}
} finally {
busy = 0;
}
collide = false;
continue; // Retry with expanded table
}
h ^= h << 13; // Rehash
h ^= h >>> 17;
h ^= h << 5;
}
else if (busy == 0 && cells == as && casBusy()) {
boolean init = false;
try { // Initialize table
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
busy = 0;
}
if (init)
break;
}
else if (casBase(v = base, fn(v, x)))
break; // Fall back on using base
}
hc.code = h; // Record index for next time
}
/**
* Sets base and all cells to the given value.
*/
final void internalReset(long initialValue) {
Cell[] as = cells;
base = initialValue;
if (as != null) {
int n = as.length;
for (int i = 0; i < n; ++i) {
Cell a = as[i];
if (a != null)
a.value = initialValue;
}
}
}
}

View File

@@ -0,0 +1,199 @@
/*
* 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/publicdomain/zero/1.0/
*/
// This is based on 1.16 version
package com.concurrent_ruby.ext.jsr166y;
import java.util.Random;
/**
* A random number generator isolated to the current thread. Like the
* global {@link java.util.Random} generator used by the {@link
* java.lang.Math} class, a {@code ThreadLocalRandom} is initialized
* with an internally generated seed that may not otherwise be
* modified. When applicable, use of {@code ThreadLocalRandom} rather
* than shared {@code Random} objects in concurrent programs will
* typically encounter much less overhead and contention. Use of
* {@code ThreadLocalRandom} is particularly appropriate when multiple
* tasks (for example, each a {@link ForkJoinTask}) use random numbers
* in parallel in thread pools.
*
* <p>Usages of this class should typically be of the form:
* {@code ThreadLocalRandom.current().nextX(...)} (where
* {@code X} is {@code Int}, {@code Long}, etc).
* When all usages are of this form, it is never possible to
* accidently share a {@code ThreadLocalRandom} across multiple threads.
*
* <p>This class also provides additional commonly used bounded random
* generation methods.
*
* @since 1.7
* @author Doug Lea
*/
public class ThreadLocalRandom extends Random {
// same constants as Random, but must be redeclared because private
private static final long multiplier = 0x5DEECE66DL;
private static final long addend = 0xBL;
private static final long mask = (1L << 48) - 1;
/**
* The random seed. We can't use super.seed.
*/
private long rnd;
/**
* Initialization flag to permit calls to setSeed to succeed only
* while executing the Random constructor. We can't allow others
* since it would cause setting seed in one part of a program to
* unintentionally impact other usages by the thread.
*/
boolean initialized;
// Padding to help avoid memory contention among seed updates in
// different TLRs in the common case that they are located near
// each other.
private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
/**
* The actual ThreadLocal
*/
private static final ThreadLocal<ThreadLocalRandom> localRandom =
new ThreadLocal<ThreadLocalRandom>() {
protected ThreadLocalRandom initialValue() {
return new ThreadLocalRandom();
}
};
/**
* Constructor called only by localRandom.initialValue.
*/
ThreadLocalRandom() {
super();
initialized = true;
}
/**
* Returns the current thread's {@code ThreadLocalRandom}.
*
* @return the current thread's {@code ThreadLocalRandom}
*/
public static ThreadLocalRandom current() {
return localRandom.get();
}
/**
* Throws {@code UnsupportedOperationException}. Setting seeds in
* this generator is not supported.
*
* @throws UnsupportedOperationException always
*/
public void setSeed(long seed) {
if (initialized)
throw new UnsupportedOperationException();
rnd = (seed ^ multiplier) & mask;
}
protected int next(int bits) {
rnd = (rnd * multiplier + addend) & mask;
return (int) (rnd >>> (48-bits));
}
/**
* Returns a pseudorandom, uniformly distributed value between the
* given least value (inclusive) and bound (exclusive).
*
* @param least the least value returned
* @param bound the upper bound (exclusive)
* @throws IllegalArgumentException if least greater than or equal
* to bound
* @return the next value
*/
public int nextInt(int least, int bound) {
if (least >= bound)
throw new IllegalArgumentException();
return nextInt(bound - least) + least;
}
/**
* Returns a pseudorandom, uniformly distributed value
* between 0 (inclusive) and the specified value (exclusive).
*
* @param n the bound on the random number to be returned. Must be
* positive.
* @return the next value
* @throws IllegalArgumentException if n is not positive
*/
public long nextLong(long n) {
if (n <= 0)
throw new IllegalArgumentException("n must be positive");
// Divide n by two until small enough for nextInt. On each
// iteration (at most 31 of them but usually much less),
// randomly choose both whether to include high bit in result
// (offset) and whether to continue with the lower vs upper
// half (which makes a difference only if odd).
long offset = 0;
while (n >= Integer.MAX_VALUE) {
int bits = next(2);
long half = n >>> 1;
long nextn = ((bits & 2) == 0) ? half : n - half;
if ((bits & 1) == 0)
offset += n - nextn;
n = nextn;
}
return offset + nextInt((int) n);
}
/**
* Returns a pseudorandom, uniformly distributed value between the
* given least value (inclusive) and bound (exclusive).
*
* @param least the least value returned
* @param bound the upper bound (exclusive)
* @return the next value
* @throws IllegalArgumentException if least greater than or equal
* to bound
*/
public long nextLong(long least, long bound) {
if (least >= bound)
throw new IllegalArgumentException();
return nextLong(bound - least) + least;
}
/**
* Returns a pseudorandom, uniformly distributed {@code double} value
* between 0 (inclusive) and the specified value (exclusive).
*
* @param n the bound on the random number to be returned. Must be
* positive.
* @return the next value
* @throws IllegalArgumentException if n is not positive
*/
public double nextDouble(double n) {
if (n <= 0)
throw new IllegalArgumentException("n must be positive");
return nextDouble() * n;
}
/**
* Returns a pseudorandom, uniformly distributed value between the
* given least value (inclusive) and bound (exclusive).
*
* @param least the least value returned
* @param bound the upper bound (exclusive)
* @return the next value
* @throws IllegalArgumentException if least greater than or equal
* to bound
*/
public double nextDouble(double least, double bound) {
if (least >= bound)
throw new IllegalArgumentException();
return nextDouble() * (bound - least) + least;
}
private static final long serialVersionUID = -5851777807851030925L;
}