Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 47 additions & 47 deletions src/main/java/org/apache/commons/lang3/Locks.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@
*/
package org.apache.commons.lang3;

import java.util.Objects;
import java.util.concurrent.locks.StampedLock;

import org.apache.commons.lang3.Functions.FailableConsumer;
import org.apache.commons.lang3.Functions.FailableFunction;

import java.util.Objects;
import java.util.concurrent.locks.StampedLock;


/** 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
Expand All @@ -54,63 +54,63 @@
* PrintStream ps = new Printstream(out);
* lock = Locks.lock(ps);
* }
*
*
* public void log(String message) {
* lock.runWriteLocked((ps) -&gt; ps.println(message));
* }
*
* public void log(byte[] buffer) {
* lock.runWriteLocked((ps) -&gt; { 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);
}
}
95 changes: 48 additions & 47 deletions src/test/java/org/apache/commons/lang3/LocksTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,58 +16,59 @@
*/
package org.apache.commons.lang3;

import static org.junit.jupiter.api.Assertions.*;

import org.apache.commons.lang3.Functions.FailableConsumer;
import org.apache.commons.lang3.Locks.Lock;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertTrue;

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];
@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];

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);
}

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);
}
protected void modify(boolean[] booleanArray, int offset, boolean value) {
synchronized (booleanArray) {
booleanArray[offset] = value;
}
}

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;
}
}
protected boolean someValueIsTrue(boolean[] booleanArray) {
synchronized (booleanArray) {
for (int i = 0; i < booleanArray.length; i++) {
if (booleanArray[i]) {
return true;
}
}
return false;
}
}
}