Add new locks
This commit is contained in:
parent
97b7246ead
commit
ff44ed16ba
@ -0,0 +1,28 @@
|
|||||||
|
package org.warp.commonutils.locks;
|
||||||
|
|
||||||
|
import java.util.concurrent.Phaser;
|
||||||
|
|
||||||
|
public class FlexibleCountDownLatch {
|
||||||
|
|
||||||
|
private final Phaser phaser;
|
||||||
|
|
||||||
|
public FlexibleCountDownLatch(int initialSize) {
|
||||||
|
this.phaser = new Phaser(initialSize + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void await() {
|
||||||
|
phaser.arriveAndAwaitAdvance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void grow() {
|
||||||
|
phaser.register();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void grow(int n) {
|
||||||
|
phaser.bulkRegister(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void countDown() {
|
||||||
|
phaser.arriveAndDeregister();
|
||||||
|
}
|
||||||
|
}
|
41
src/main/java/org/warp/commonutils/locks/TransitLock.java
Normal file
41
src/main/java/org/warp/commonutils/locks/TransitLock.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package org.warp.commonutils.locks;
|
||||||
|
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
|
public class TransitLock {
|
||||||
|
|
||||||
|
private final FlexibleCountDownLatch unfinishedTransits = new FlexibleCountDownLatch(0);
|
||||||
|
private final Semaphore permits = new Semaphore(Integer.MAX_VALUE);
|
||||||
|
private final Object synchronization = new Object();
|
||||||
|
|
||||||
|
public TransitLock() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void allowTransit() {
|
||||||
|
var permitsToRelease = Math.max(0, Integer.MAX_VALUE - (permits.availablePermits() + 10000));
|
||||||
|
permits.release(permitsToRelease);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disallowTransit() {
|
||||||
|
synchronized (synchronization) {
|
||||||
|
unfinishedTransits.await();
|
||||||
|
permits.drainPermits();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void transit() {
|
||||||
|
startTransit();
|
||||||
|
endTransit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startTransit() {
|
||||||
|
synchronized (synchronization) {
|
||||||
|
unfinishedTransits.grow();
|
||||||
|
permits.acquireUninterruptibly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endTransit() {
|
||||||
|
unfinishedTransits.countDown();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package org.warp.commonutils.locks;
|
||||||
|
|
||||||
|
public class TransitReadWriteLock {
|
||||||
|
|
||||||
|
private final TransitLock writeLock;
|
||||||
|
private final TransitLock readLock;
|
||||||
|
private final Object synchronization = new Object();
|
||||||
|
|
||||||
|
public TransitReadWriteLock() {
|
||||||
|
this.writeLock = new TransitLock();
|
||||||
|
this.readLock = new TransitLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reAllowTransit() {
|
||||||
|
this.readLock.allowTransit();
|
||||||
|
this.writeLock.allowTransit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disallowTransit() {
|
||||||
|
synchronized (synchronization) {
|
||||||
|
this.writeLock.disallowTransit();
|
||||||
|
this.readLock.disallowTransit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reAllowTransitRead() {
|
||||||
|
this.readLock.allowTransit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disallowTransitRead() {
|
||||||
|
this.readLock.disallowTransit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reAllowTransitWrite() {
|
||||||
|
this.writeLock.allowTransit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disallowTransitWrite() {
|
||||||
|
this.writeLock.disallowTransit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void transitWrite() {
|
||||||
|
this.writeLock.transit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void transitRead() {
|
||||||
|
this.readLock.transit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startTransitWrite() {
|
||||||
|
synchronized (synchronization) {
|
||||||
|
this.writeLock.startTransit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startTransitRead() {
|
||||||
|
synchronized (synchronization) {
|
||||||
|
this.readLock.startTransit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endTransitWrite() {
|
||||||
|
writeLock.endTransit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endTransitRead() {
|
||||||
|
readLock.endTransit();
|
||||||
|
}
|
||||||
|
}
|
153
src/test/java/org/warp/commonutils/locks/TransitLockTest.java
Normal file
153
src/test/java/org/warp/commonutils/locks/TransitLockTest.java
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package org.warp.commonutils.locks;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class TransitLockTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransit() {
|
||||||
|
var lock = new TransitLock();
|
||||||
|
lock.transit();
|
||||||
|
lock.transit();
|
||||||
|
lock.transit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPartialTransit() {
|
||||||
|
var lock = new TransitLock();
|
||||||
|
lock.startTransit();
|
||||||
|
lock.endTransit();
|
||||||
|
lock.startTransit();
|
||||||
|
lock.endTransit();
|
||||||
|
lock.startTransit();
|
||||||
|
lock.endTransit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAllowTransit() {
|
||||||
|
var lock = new TransitLock();
|
||||||
|
lock.allowTransit();
|
||||||
|
lock.transit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultiAllowTransit() {
|
||||||
|
var lock = new TransitLock();
|
||||||
|
lock.allowTransit();
|
||||||
|
lock.allowTransit();
|
||||||
|
lock.allowTransit();
|
||||||
|
lock.transit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultiDisallowAllowTransit() {
|
||||||
|
var lock = new TransitLock();
|
||||||
|
lock.disallowTransit();
|
||||||
|
lock.allowTransit();
|
||||||
|
lock.disallowTransit();
|
||||||
|
lock.disallowTransit();
|
||||||
|
lock.allowTransit();
|
||||||
|
lock.transit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisallowTransit() {
|
||||||
|
var lock = new TransitLock();
|
||||||
|
lock.disallowTransit();
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
Assertions.assertThrows(TimeoutException.class, () -> {
|
||||||
|
CompletableFuture.runAsync(lock::transit).get(5, TimeUnit.MILLISECONDS);
|
||||||
|
}, "Disallowed transit didn't block a transit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultiDisallowTransit() {
|
||||||
|
var lock = new TransitLock();
|
||||||
|
lock.disallowTransit();
|
||||||
|
lock.disallowTransit();
|
||||||
|
lock.disallowTransit();
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
Assertions.assertThrows(TimeoutException.class, () -> {
|
||||||
|
CompletableFuture.runAsync(lock::transit).get(5, TimeUnit.MILLISECONDS);
|
||||||
|
}, "Disallowed transit didn't block a transit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeadlocks() {
|
||||||
|
var lock = new TransitLock();
|
||||||
|
AtomicInteger alreadyRunningTransits = new AtomicInteger();
|
||||||
|
|
||||||
|
var pool = Executors.newFixedThreadPool(100);
|
||||||
|
AtomicReference<AssertionError> failure = new AtomicReference<>();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
int iF = i;
|
||||||
|
pool.submit(() -> {
|
||||||
|
try {
|
||||||
|
for (int j = 0; j < 100; j++) {
|
||||||
|
if (iF % 2 == 0) {
|
||||||
|
lock.startTransit();
|
||||||
|
alreadyRunningTransits.getAndIncrement();
|
||||||
|
alreadyRunningTransits.decrementAndGet();
|
||||||
|
lock.endTransit();
|
||||||
|
} else {
|
||||||
|
lock.disallowTransit();
|
||||||
|
Assertions.assertEquals(0, alreadyRunningTransits.get());
|
||||||
|
lock.allowTransit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (AssertionError e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
failure.set(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
lock.allowTransit();
|
||||||
|
pool.shutdown();
|
||||||
|
if (failure.get() != null) throw failure.get();
|
||||||
|
Assertions.assertDoesNotThrow(() -> pool.awaitTermination(10, TimeUnit.SECONDS));
|
||||||
|
Assertions.assertTrue(pool.isTerminated());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParallelTransit() {
|
||||||
|
var lock = new TransitLock();
|
||||||
|
AtomicInteger alreadyRunningTransits = new AtomicInteger();
|
||||||
|
|
||||||
|
var pool = Executors.newFixedThreadPool(100);
|
||||||
|
AtomicReference<AssertionError> failure = new AtomicReference<>();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
pool.submit(() -> {
|
||||||
|
try {
|
||||||
|
lock.startTransit();
|
||||||
|
alreadyRunningTransits.getAndIncrement();
|
||||||
|
try {
|
||||||
|
Thread.sleep(20);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Assertions.fail(e);
|
||||||
|
}
|
||||||
|
alreadyRunningTransits.decrementAndGet();
|
||||||
|
lock.endTransit();
|
||||||
|
} catch (AssertionError e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
failure.set(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
lock.disallowTransit();
|
||||||
|
Assertions.assertEquals(0, alreadyRunningTransits.get());
|
||||||
|
lock.allowTransit();
|
||||||
|
pool.shutdown();
|
||||||
|
if (failure.get() != null) throw failure.get();
|
||||||
|
Assertions.assertDoesNotThrow(() -> pool.awaitTermination(10, TimeUnit.SECONDS));
|
||||||
|
Assertions.assertTrue(pool.isTerminated());
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user