parent
f544897e49
commit
3d4ed4a8ac
|
@ -25,11 +25,11 @@
|
|||
|
||||
/** Utility class for working with {@link java.util.concurrent.locks.Lock locked objects}. Locked objects are an
|
||||
* alternative to synchronization.
|
||||
*
|
||||
*
|
||||
* Locking is preferable, if there is a distinction between read access (multiple threads may have read
|
||||
* access concurrently), and write access (only one thread may have write access at any given time.
|
||||
* In comparison, synchronization doesn't support read access, because synchronized access is exclusive.
|
||||
*
|
||||
*
|
||||
* Using this class is fairly straightforward:
|
||||
* <ol>
|
||||
* <li>While still in single thread mode, create an instance of {@link Locks.Lock} by calling
|
||||
|
@ -54,7 +54,7 @@
|
|||
* PrintStream ps = new Printstream(out);
|
||||
* lock = Locks.lock(ps);
|
||||
* }
|
||||
*
|
||||
*
|
||||
* public void log(String message) {
|
||||
* lock.runWriteLocked((ps) -> ps.println(message));
|
||||
* }
|
||||
|
@ -62,55 +62,55 @@
|
|||
* public void log(byte[] buffer) {
|
||||
* lock.runWriteLocked((ps) -> { ps.write(buffer); ps.println(); });
|
||||
* }
|
||||
* </pre>
|
||||
* </pre>
|
||||
*/
|
||||
public class Locks {
|
||||
public static class Lock<O extends Object> {
|
||||
private final O lockedObject;
|
||||
private final StampedLock lock = new StampedLock();
|
||||
public static class Lock<O extends Object> {
|
||||
private final O lockedObject;
|
||||
private final StampedLock lock = new StampedLock();
|
||||
|
||||
public Lock(O lockedObject) {
|
||||
this.lockedObject = Objects.requireNonNull(lockedObject, "Locked Object");
|
||||
}
|
||||
public Lock(O lockedObject) {
|
||||
this.lockedObject = Objects.requireNonNull(lockedObject, "Locked Object");
|
||||
}
|
||||
|
||||
public void runReadLocked(FailableConsumer<O,?> consumer) {
|
||||
runLocked(lock.readLock(), consumer);
|
||||
}
|
||||
public void runReadLocked(FailableConsumer<O, ?> consumer) {
|
||||
runLocked(lock.readLock(), consumer);
|
||||
}
|
||||
|
||||
public void runWriteLocked(FailableConsumer<O,?> consumer) {
|
||||
runLocked(lock.writeLock(), consumer);
|
||||
}
|
||||
public void runWriteLocked(FailableConsumer<O, ?> consumer) {
|
||||
runLocked(lock.writeLock(), consumer);
|
||||
}
|
||||
|
||||
public <T> T callReadLocked(FailableFunction<O,T,?> function) {
|
||||
return callLocked(lock.readLock(), function);
|
||||
}
|
||||
public <T> T callReadLocked(FailableFunction<O, T, ?> function) {
|
||||
return callLocked(lock.readLock(), function);
|
||||
}
|
||||
|
||||
public <T> T callWriteLocked(FailableFunction<O,T,?> function) {
|
||||
return callLocked(lock.writeLock(), function);
|
||||
}
|
||||
public <T> T callWriteLocked(FailableFunction<O, T, ?> function) {
|
||||
return callLocked(lock.writeLock(), function);
|
||||
}
|
||||
|
||||
protected void runLocked(long stamp, FailableConsumer<O,?> consumer) {
|
||||
try {
|
||||
consumer.accept(lockedObject);
|
||||
} catch (Throwable t) {
|
||||
throw Functions.rethrow(t);
|
||||
} finally {
|
||||
lock.unlock(stamp);
|
||||
}
|
||||
}
|
||||
protected void runLocked(long stamp, FailableConsumer<O, ?> consumer) {
|
||||
try {
|
||||
consumer.accept(lockedObject);
|
||||
} catch (Throwable t) {
|
||||
throw Functions.rethrow(t);
|
||||
} finally {
|
||||
lock.unlock(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> T callLocked(long stamp, FailableFunction<O,T,?> function) {
|
||||
try {
|
||||
return function.apply(lockedObject);
|
||||
} catch (Throwable t) {
|
||||
throw Functions.rethrow(t);
|
||||
} finally {
|
||||
lock.unlock(stamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
protected <T> T callLocked(long stamp, FailableFunction<O, T, ?> function) {
|
||||
try {
|
||||
return function.apply(lockedObject);
|
||||
} catch (Throwable t) {
|
||||
throw Functions.rethrow(t);
|
||||
} finally {
|
||||
lock.unlock(stamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static <O extends Object> Locks.Lock<O> lock(O object) {
|
||||
return new Locks.Lock<O>(object);
|
||||
}
|
||||
public static <O extends Object> Locks.Lock<O> lock(O object) {
|
||||
return new Locks.Lock<O>(object);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,58 +16,81 @@
|
|||
*/
|
||||
package org.apache.commons.lang3;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.function.LongConsumer;
|
||||
|
||||
import org.apache.commons.lang3.Functions.FailableConsumer;
|
||||
import org.apache.commons.lang3.Locks.Lock;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class LocksTest {
|
||||
@Test
|
||||
void testReadLock() throws Exception {
|
||||
final long DELAY=3000;
|
||||
final boolean[] booleanValues = new boolean[10];
|
||||
final Lock<boolean[]> lock = Locks.lock(booleanValues);
|
||||
final boolean[] runningValues = new boolean[10];
|
||||
private static final int NUMBER_OF_THREADS = 10;
|
||||
|
||||
final long startTime = System.currentTimeMillis();
|
||||
for (int i = 0; i < booleanValues.length; i++) {
|
||||
final int index = i;
|
||||
final FailableConsumer<boolean[],?> consumer = (b) -> {
|
||||
b[index] = false;
|
||||
Thread.sleep(DELAY);
|
||||
b[index] = true;
|
||||
modify(runningValues, index, false);
|
||||
};
|
||||
final Thread t = new Thread(() -> lock.runReadLocked(consumer));
|
||||
modify(runningValues, i, true);
|
||||
t.start();
|
||||
}
|
||||
while (someValueIsTrue(runningValues)) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
final long endTime = System.currentTimeMillis();
|
||||
for (int i = 0; i < booleanValues.length; i++) {
|
||||
assertTrue(booleanValues[i]);
|
||||
}
|
||||
// If our threads would be running in exclusive mode, then we'd need
|
||||
// at least DELAY milliseconds for each.
|
||||
assertTrue((endTime-startTime) < booleanValues.length*DELAY);
|
||||
}
|
||||
@Test
|
||||
void testReadLock() throws Exception {
|
||||
final long DELAY=3000;
|
||||
/** If our threads are running concurrently, then we expect to be faster
|
||||
* than running one after the other.
|
||||
*/
|
||||
runTest(DELAY, false, (l) -> assertTrue(l < NUMBER_OF_THREADS*DELAY));
|
||||
}
|
||||
|
||||
protected void modify(boolean[] booleanArray, int offset, boolean value) {
|
||||
synchronized(booleanArray) {
|
||||
booleanArray[offset] = value;
|
||||
}
|
||||
}
|
||||
protected boolean someValueIsTrue(boolean[] booleanArray) {
|
||||
synchronized(booleanArray) {
|
||||
for (int i = 0; i < booleanArray.length; i++) {
|
||||
if (booleanArray[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void testWriteLock() throws Exception {
|
||||
final long DELAY = 100;
|
||||
/** If our threads are running concurrently, then we expect to be no faster
|
||||
* than running one after the other.
|
||||
*/
|
||||
runTest(DELAY, true, (l) -> assertTrue(l >= NUMBER_OF_THREADS*DELAY));
|
||||
}
|
||||
|
||||
private void runTest(long delay, boolean exclusiveLock, LongConsumer runTimeCheck) throws InterruptedException {
|
||||
final boolean[] booleanValues = new boolean[10];
|
||||
final Lock<boolean[]> lock = Locks.lock(booleanValues);
|
||||
final boolean[] runningValues = new boolean[10];
|
||||
|
||||
final long startTime = System.currentTimeMillis();
|
||||
for (int i = 0; i < booleanValues.length; i++) {
|
||||
final int index = i;
|
||||
final FailableConsumer<boolean[], ?> consumer = (b) -> {
|
||||
b[index] = false;
|
||||
Thread.sleep(delay);
|
||||
b[index] = true;
|
||||
modify(runningValues, index, false);
|
||||
};
|
||||
final Thread t = new Thread(() -> {
|
||||
if (exclusiveLock) {
|
||||
lock.runWriteLocked(consumer);
|
||||
} else {
|
||||
lock.runReadLocked(consumer);
|
||||
}
|
||||
});
|
||||
modify(runningValues, i, true);
|
||||
t.start();
|
||||
}
|
||||
while (someValueIsTrue(runningValues)) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
final long endTime = System.currentTimeMillis();
|
||||
for (int i = 0; i < booleanValues.length; i++) {
|
||||
assertTrue(booleanValues[i]);
|
||||
}
|
||||
runTimeCheck.accept(endTime-startTime);
|
||||
}
|
||||
|
||||
protected void modify(boolean[] booleanArray, int offset, boolean value) {
|
||||
synchronized(booleanArray) {
|
||||
booleanArray[offset] = value;
|
||||
}
|
||||
}
|
||||
protected boolean someValueIsTrue(boolean[] booleanArray) {
|
||||
synchronized(booleanArray) {
|
||||
for (int i = 0; i < booleanArray.length; i++) {
|
||||
if (booleanArray[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue