Neil Bartlett
Neil Bartlett

Reputation: 23958

Java Concurrency: Paired locks with shared access

I am looking for a Java implementation of the following concurrency semantics. I want something similar to ReadWriteLock except symmetrical, i.e. both the read and write sides can be shared amongst many threads, but read excludes write and vice versa.

  1. There are two locks, let's call them A and B.
  2. Lock A is shared, i.e. there may be multiple threads holding it concurrently. Lock B is also shared, there may be multiple threads holding it concurrently.
  3. If any thread holds lock A then no thread may take B – threads attempting to take B shall block until all threads holding A have released A.
  4. If any thread holds lock B then no thread may take A – threads attempting to take A shall block until all threads holding B have released B.

Is there an existing library class that achieves this? At the moment I have approximated the desired functionality with a ReadWriteLock because fortunately the tasks done in the context of lock B are somewhat rarer. It feels like a hack though, and it could affect the performance of my program under heavy load.

Upvotes: 9

Views: 2241

Answers (4)

starikoff
starikoff

Reputation: 1660

How about

class ABSync {

    private int aHolders;
    private int bHolders;

    public synchronized void lockA() throws InterruptedException {
        while (bHolders > 0) {
            wait();
        }
        aHolders++;
    }

    public synchronized void lockB() throws InterruptedException {
        while (aHolders > 0) {
            wait();
        }
        bHolders++;
    }

    public synchronized void unlockA() {
        aHolders = Math.max(0, aHolders - 1);
        if (aHolders == 0) {
            notifyAll();
        }
    }

    public synchronized void unlockB() {
        bHolders = Math.max(0, bHolders - 1);
        if (bHolders == 0) {
            notifyAll();
        }
    }
}

Update: As for "fairness" (or, rather, non-starvation), OPs requirements don't mention it. In order to implement OPs requirements + some form of fairness/non-starvation, it should be specified explicitly (what do you consider fair, how should it behave when flows of requests for currently dominant and non-dominant locks come in etc). One of the ways to implement it would be:

class ABMoreFairSync {

    private Lock lock = new ReentrantLock(true);
    public final Part A, B;

    public ABMoreFairSync() {
        A = new Part();
        B = new Part();
        A.other = B;
        B.other = A;
    }

    private class Part {
        private Condition canGo = lock.newCondition();
        private int currentGeneration, lastGeneration;
        private int holders;
        private Part other;

        public void lock() throws InterruptedException {
            lock.lockInterruptibly();
            try {
                int myGeneration = lastGeneration;
                if (other.holders > 0 || currentGeneration < myGeneration) {
                    if (other.currentGeneration == other.lastGeneration) {
                        other.lastGeneration++;
                    }
                    while (other.holders > 0 || currentGeneration < myGeneration) {
                        canGo.await();
                    }
                }
                holders++;
            } finally {
                lock.unlock();
            }
        }

        public void unlock() throws InterruptedException {
            lock.lockInterruptibly();
            try {
                holders = Math.max(0, holders - 1);
                if (holders == 0) {
                    currentGeneration++;
                    other.canGo.signalAll();
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

To be used as in:

 sync.A.lock();
 try {
     ...
 } finally {
     sync.A.unlock();
 }

The idea of generations here is taken from "Java Concurrency in Practice", Listing 14.9.

Upvotes: 0

snovelli
snovelli

Reputation: 6058

Short answer:

In the standard library, there is nothing like what you need.

Long answer:

To easily implement a custom Lock you should subclass or delegate to an AbstractQueuedSynchronizer.

The following code is an example of a non-fair lock that implements what you need, including some (non exhausting) test. I called it LeftRightLock because of the binary nature of your requirements.

The concept is pretty straightforward:

AbstractQueuedSynchronizer exposes a method to atomically set the state of an int using the Compare and swap idiom ( compareAndSetState(int expect, int update) ), we can use the exposed state keep the count of the threads holding the lock, setting it to a positive value in case the Right lock is being held or a negative value in case the Left lock is being held.

Than we just make sure of the following conditions: - you can lock Left only if the state of the internal AbstractQueuedSynchronizer is zero or negative - you can lock Right only if the state of the internal AbstractQueuedSynchronizer is zero or positive

LeftRightLock.java


import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Lock;

/**
 * A binary mutex with the following properties:
 *
 * Exposes two different {@link Lock}s: LEFT, RIGHT.
 *
 * When LEFT is held other threads can acquire LEFT but thread trying to acquire RIGHT will be
 * blocked. When RIGHT is held other threads can acquire RIGHT but thread trying to acquire LEFT
 * will be blocked.
 */
public class LeftRightLock {

    public static final int ACQUISITION_FAILED = -1;
    public static final int ACQUISITION_SUCCEEDED = 1;

    private final LeftRightSync sync = new LeftRightSync();

    public void lockLeft() {
        sync.acquireShared(LockSide.LEFT.getV());
    }

    public void lockRight() {
        sync.acquireShared(LockSide.RIGHT.getV());
    }

    public void releaseLeft() {
        sync.releaseShared(LockSide.LEFT.getV());
    }

    public void releaseRight() {
        sync.releaseShared(LockSide.RIGHT.getV());
    }

    public boolean tryLockLeft() {
        return sync.tryAcquireShared(LockSide.LEFT) == ACQUISITION_SUCCEEDED;
    }

    public boolean tryLockRight() {
        return sync.tryAcquireShared(LockSide.RIGHT) == ACQUISITION_SUCCEEDED;
    }

    private enum LockSide {
        LEFT(-1), NONE(0), RIGHT(1);

        private final int v;

        LockSide(int v) {
            this.v = v;
        }

        public int getV() {
            return v;
        }
    }

    /**
     * <p>
     * Keep count the count of threads holding either the LEFT or the RIGHT lock.
     * </p>
     *
     * <li>A state ({@link AbstractQueuedSynchronizer#getState()}) greater than 0 means one or more threads are holding RIGHT lock. </li>
     * <li>A state ({@link AbstractQueuedSynchronizer#getState()}) lower than 0 means one or more threads are holding LEFT lock.</li>
     * <li>A state ({@link AbstractQueuedSynchronizer#getState()}) equal to zero means no thread is holding any lock.</li>
     */
    private static final class LeftRightSync extends AbstractQueuedSynchronizer {


        @Override
        protected int tryAcquireShared(int requiredSide) {
            return (tryChangeThreadCountHoldingCurrentLock(requiredSide, ChangeType.ADD) ? ACQUISITION_SUCCEEDED : ACQUISITION_FAILED);
        }    

        @Override
        protected boolean tryReleaseShared(int requiredSide) {
            return tryChangeThreadCountHoldingCurrentLock(requiredSide, ChangeType.REMOVE);
        }

        public boolean tryChangeThreadCountHoldingCurrentLock(int requiredSide, ChangeType changeType) {
            if (requiredSide != 1 && requiredSide != -1)
                throw new AssertionError("You can either lock LEFT or RIGHT (-1 or +1)");

            int curState;
            int newState;
            do {
                curState = this.getState();
                if (!sameSide(curState, requiredSide)) {
                    return false;
                }

                if (changeType == ChangeType.ADD) {
                    newState = curState + requiredSide;
                } else {
                    newState = curState - requiredSide;
                }
                //TODO: protect against int overflow (hopefully you won't have so many threads)
            } while (!this.compareAndSetState(curState, newState));
            return true;
        }    

        final int tryAcquireShared(LockSide lockSide) {
            return this.tryAcquireShared(lockSide.getV());
        }

        final boolean tryReleaseShared(LockSide lockSide) {
            return this.tryReleaseShared(lockSide.getV());
        }

        private boolean sameSide(int curState, int requiredSide) {
            return curState == 0 || sameSign(curState, requiredSide);
        }

        private boolean sameSign(int a, int b) {
            return (a >= 0) ^ (b < 0);
        }

        public enum ChangeType {
            ADD, REMOVE
        }
    }
}

LeftRightLockTest.java


import org.junit.Test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class LeftRightLockTest {


    int logLineSequenceNumber = 0;
    private LeftRightLock sut = new LeftRightLock();

    @Test(timeout = 2000)
    public void acquiringLeftLockExcludeAcquiringRightLock() throws Exception {
        sut.lockLeft();


        Future<Boolean> task = Executors.newSingleThreadExecutor().submit(() -> sut.tryLockRight());
        assertFalse("I shouldn't be able to acquire the RIGHT lock!", task.get());
    }

    @Test(timeout = 2000)
    public void acquiringRightLockExcludeAcquiringLeftLock() throws Exception {
        sut.lockRight();
        Future<Boolean> task = Executors.newSingleThreadExecutor().submit(() -> sut.tryLockLeft());
        assertFalse("I shouldn't be able to acquire the LEFT lock!", task.get());
    }

    @Test(timeout = 2000)
    public void theLockShouldBeReentrant() throws Exception {
        sut.lockLeft();
        assertTrue(sut.tryLockLeft());
    }

    @Test(timeout = 2000)
    public void multipleThreadShouldBeAbleToAcquireTheSameLock_Right() throws Exception {
        sut.lockRight();
        Future<Boolean> task = Executors.newSingleThreadExecutor().submit(() -> sut.tryLockRight());
        assertTrue(task.get());
    }

    @Test(timeout = 2000)
    public void multipleThreadShouldBeAbleToAcquireTheSameLock_left() throws Exception {
        sut.lockLeft();
        Future<Boolean> task = Executors.newSingleThreadExecutor().submit(() -> sut.tryLockLeft());
        assertTrue(task.get());
    }

    @Test(timeout = 2000)
    public void shouldKeepCountOfAllTheThreadsHoldingTheSide() throws Exception {

        CountDownLatch latchA = new CountDownLatch(1);
        CountDownLatch latchB = new CountDownLatch(1);


        Thread threadA = spawnThreadToAcquireLeftLock(latchA, sut);
        Thread threadB = spawnThreadToAcquireLeftLock(latchB, sut);

        System.out.println("Both threads have acquired the left lock.");

        try {
            latchA.countDown();
            threadA.join();
            boolean acqStatus = sut.tryLockRight();
            System.out.println("The right lock was " + (acqStatus ? "" : "not") + " acquired");
            assertFalse("There is still a thread holding the left lock. This shouldn't succeed.", acqStatus);
        } finally {
            latchB.countDown();
            threadB.join();
        }

    }

    @Test(timeout = 5000)
    public void shouldBlockThreadsTryingToAcquireLeftIfRightIsHeld() throws Exception {
        sut.lockLeft();

        CountDownLatch taskStartedLatch = new CountDownLatch(1);

        final Future<Boolean> task = Executors.newSingleThreadExecutor().submit(() -> {
            taskStartedLatch.countDown();
            sut.lockRight();
            return false;
        });

        taskStartedLatch.await();
        Thread.sleep(100);

        assertFalse(task.isDone());
    }

    @Test
    public void shouldBeFreeAfterRelease() throws Exception {
        sut.lockLeft();
        sut.releaseLeft();
        assertTrue(sut.tryLockRight());
    }

    @Test
    public void shouldBeFreeAfterMultipleThreadsReleaseIt() throws Exception {
        CountDownLatch latch = new CountDownLatch(1);

        final Thread thread1 = spawnThreadToAcquireLeftLock(latch, sut);
        final Thread thread2 = spawnThreadToAcquireLeftLock(latch, sut);

        latch.countDown();

        thread1.join();
        thread2.join();

        assertTrue(sut.tryLockRight());

    }

    @Test(timeout = 2000)
    public void lockShouldBeReleasedIfNoThreadIsHoldingIt() throws Exception {
        CountDownLatch releaseLeftLatch = new CountDownLatch(1);
        CountDownLatch rightLockTaskIsRunning = new CountDownLatch(1);

        Thread leftLockThread1 = spawnThreadToAcquireLeftLock(releaseLeftLatch, sut);
        Thread leftLockThread2 = spawnThreadToAcquireLeftLock(releaseLeftLatch, sut);

        Future<Boolean> acquireRightLockTask = Executors.newSingleThreadExecutor().submit(() -> {
            if (sut.tryLockRight())
                throw new AssertionError("The left lock should be still held, I shouldn't be able to acquire right a this point.");
            printSynchronously("Going to be blocked on right lock");
            rightLockTaskIsRunning.countDown();
            sut.lockRight();
            printSynchronously("Lock acquired!");
            return true;
        });

        rightLockTaskIsRunning.await();

        releaseLeftLatch.countDown();
        leftLockThread1.join();
        leftLockThread2.join();

        assertTrue(acquireRightLockTask.get());
    }

    private synchronized void printSynchronously(String str) {

        System.out.println(logLineSequenceNumber++ + ")" + str);
        System.out.flush();
    }

    private Thread spawnThreadToAcquireLeftLock(CountDownLatch releaseLockLatch, LeftRightLock lock) throws InterruptedException {
        CountDownLatch lockAcquiredLatch = new CountDownLatch(1);
        final Thread thread = spawnThreadToAcquireLeftLock(releaseLockLatch, lockAcquiredLatch, lock);
        lockAcquiredLatch.await();
        return thread;
    }

    private Thread spawnThreadToAcquireLeftLock(CountDownLatch releaseLockLatch, CountDownLatch lockAcquiredLatch, LeftRightLock lock) {
        final Thread thread = new Thread(() -> {
            lock.lockLeft();
            printSynchronously("Thread " + Thread.currentThread() + " Acquired left lock");
            try {
                lockAcquiredLatch.countDown();
                releaseLockLatch.await();
            } catch (InterruptedException ignore) {
            } finally {
                lock.releaseLeft();
            }

            printSynchronously("Thread " + Thread.currentThread() + " RELEASED left lock");
        });
        thread.start();
        return thread;
    }
}

Upvotes: 3

vanOekel
vanOekel

Reputation: 6548

After my nth attempt to make a simple fair implementation, I think I understand why I could not find another library/example of the "mutual exclusive lock-pair": it requires a pretty specific user-case. As OP mentioned, you can get a long way with the ReadWriteLock and a fair lock-pair is only useful when there are many requests for a lock in quick succession (else you might as well use one normal lock).

The implementation below is more of a "permit dispenser": it is not re-entrant. It can be made re-entrant though (if not, I fear I failed to make the code simple and readable) but it requires some additional administration for various cases (e.g. one thread locking A twice, still needs to unlock A twice and the unlock-method needs to know when there are no more locks outstanding). An option to throw a deadlock error when one thread locks A and wants to lock B is probably a good idea.

The main idea is that there is an "active lock" that can only be changed by the lock-method when there are no (requests for) locks at all and can be changed by the unlock-method when the active locks outstanding reaches zero. The rest is basically keeping count of lock-requests and making threads wait until the active lock can be changed. Making threads wait involves working with InterruptedExceptions and I made a compromise there: I could not find a good solution that works well in all cases (e.g. application shutdown, one thread that gets interrupted, etc.).

I only did some basic testing (test class at the end), more validation is needed.

import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;

/**
 * A pair of mutual exclusive read-locks: many threads can hold a lock for A or B, but never A and B.
 * <br>Usage:<pre>
 * PairedLock plock = new PairedLock();
 * plock.lockA();
 * try {
 *     // do stuff
 * } finally {
 *     plock.unlockA();
 * }</pre>
 * This lock is not reentrant: a lock is not associated with a thread and a thread asking for the same lock 
 * might be blocked the second time (potentially causing a deadlock).
 * <p> 
 * When a lock for A is active, a lock for B will wait for all locks on A to be unlocked and vice versa.
 * <br>When a lock for A is active, and a lock for B is waiting, subsequent locks for A will wait 
 * until all (waiting) locks for B are unlocked.
 * I.e. locking is fair (in FIFO order).
 * <p>
 * See also
 * <a href="http://stackoverflow.com/questions/41358436">stackoverflow-java-concurrency-paired-locks-with-shared-access</a>
 * 
 * @author vanOekel
 *
 */
public class PairedLock {

    static final int MAX_LOCKS = 2;
    static final int CLOSE_PERMITS = 10_000;

    /** Use a fair lock to keep internal state instead of the {@code synchronized} keyword. */
    final ReentrantLock state = new ReentrantLock(true);

    /** Amount of threads that have locks. */
    final int[] activeLocks = new int[MAX_LOCKS];

    /** Amount of threads waiting to receive a lock. */
    final int[] waitingLocks = new int[MAX_LOCKS];

    /** Threads block on a semaphore until locks are available. */
    final Semaphore[] waiters = new Semaphore[MAX_LOCKS];

    int activeLock;
    volatile boolean closed;

    public PairedLock() {
        super();
        for (int i = 0; i < MAX_LOCKS; i++) {
            // no need for fair semaphore: unlocks are done for all in one go.
            waiters[i] = new Semaphore(0);
        }
    }

    public void lockA() throws InterruptedException { lock(0); }
    public void lockB() throws InterruptedException { lock(1); }

    public void lock(int lockNumber) throws InterruptedException {

        if (lockNumber < 0 || lockNumber >= MAX_LOCKS) {
            throw new IllegalArgumentException("Lock number must be 0 or less than " + MAX_LOCKS);
        } else if (isClosed()) {
            throw new IllegalStateException("Lock closed.");
        }
        boolean wait = false;
        state.lock();
        try {
            if (nextLockIsWaiting()) {
                wait = true;
            } else if (activeLock == lockNumber) {
                activeLocks[activeLock]++;
            } else if (activeLock != lockNumber && activeLocks[activeLock] == 0) {
                // nothing active and nobody waiting - safe to switch to another active lock
                activeLock = lockNumber;
                activeLocks[activeLock]++;
            } else {
                // with only two locks this means this is the first lock that needs an active-lock switch.
                // in other words:
                // activeLock != lockNumber && activeLocks[activeLock] > 0 && waitingLocks[lockNumber] == 0
                wait = true;
            }
            if (wait) {
                waitingLocks[lockNumber]++;
            }
        } finally {
            state.unlock();
        }
        if (wait) {
            waiters[lockNumber].acquireUninterruptibly();
            // there is no easy way to bring this lock back into a valid state when waiters do no get a lock.
            // so for now, use the closed state to make this lock unusable any further.
            if (closed) {
                throw new InterruptedException("Lock closed.");
            }
        }
    }

    protected boolean nextLockIsWaiting() {
        return (waitingLocks[nextLock(activeLock)] > 0);
    }

    protected int nextLock(int lockNumber) {
        return (lockNumber == 0 ? 1 : 0);
    }

    public void unlockA() { unlock(0); }
    public void unlockB() { unlock(1); }

    public void unlock(int lockNumber) {

        // unlock is called in a finally-block and should never throw an exception.
        if (lockNumber < 0 || lockNumber >= MAX_LOCKS) {
            System.out.println("Cannot unlock lock number " + lockNumber);
            return;
        }
        state.lock();
        try {
            if (activeLock != lockNumber) {
                System.out.println("ERROR: invalid lock state: no unlocks for inactive lock expected (active: " + activeLock + ", unlock: " + lockNumber + ").");
                return;
            }
            activeLocks[lockNumber]--;
            if (activeLocks[activeLock] == 0 && nextLockIsWaiting()) {
                activeLock = nextLock(lockNumber);
                waiters[activeLock].release(waitingLocks[activeLock]);
                activeLocks[activeLock] += waitingLocks[activeLock];
                waitingLocks[activeLock] = 0;
            } else if (activeLocks[lockNumber] < 0) {
                System.out.println("ERROR: to many unlocks for lock number " + lockNumber);
                activeLocks[lockNumber] = 0;
            }
        } finally {
            state.unlock();
        }
    }

    public boolean isClosed() { return closed; }

    /**
     * All threads waiting for a lock will be unblocked and an {@link InterruptedException} will be thrown.
     * Subsequent calls to the lock-method will throw an {@link IllegalStateException}.
     */
    public synchronized void close() {

        if (!closed) {
            closed = true;
            for (int i = 0; i < MAX_LOCKS; i++) {
                waiters[i].release(CLOSE_PERMITS);
            }
        }
    }

    @Override
    public String toString() {

        StringBuilder sb = new StringBuilder(this.getClass().getSimpleName());
        sb.append("=").append(this.hashCode());
        state.lock();
        try {
            sb.append(", active=").append(activeLock).append(", switching=").append(nextLockIsWaiting());
            sb.append(", lockA=").append(activeLocks[0]).append("/").append(waitingLocks[0]);
            sb.append(", lockB=").append(activeLocks[1]).append("/").append(waitingLocks[1]);
        } finally {
            state.unlock();
        }
        return sb.toString();
    }

}

The test class (YMMV - works fine on my system, but may deadlock on yours due to faster or slower starting and running of threads):

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PairedLockTest {

    private static final Logger log = LoggerFactory.getLogger(PairedLockTest.class);

    public static final ThreadPoolExecutor tp = (ThreadPoolExecutor) Executors.newCachedThreadPool();

    public static void main(String[] args) {

        try {
            new PairedLockTest().test();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            tp.shutdownNow();
        }
    }

    PairedLock mlock = new PairedLock();

    public void test() throws InterruptedException {

        CountDownLatch start = new CountDownLatch(1);
        CountDownLatch done = new CountDownLatch(2);
        mlock.lockA();
        try {
            logLock("la1 ");
            mlock.lockA();
            try {
                lockAsync(start, null, done, 1);
                await(start);
                logLock("la2 ");
            } finally {
                mlock.unlockA();
            }
            lockAsync(null, null, done, 0);
        } finally {
            mlock.unlockA();
        }
        await(done);
        logLock();
    }

    void lockAsync(CountDownLatch start, CountDownLatch locked, CountDownLatch unlocked, int lockNumber) {

        tp.execute(() -> {
            countDown(start);
            await(start);
            //log.info("Locking async " + lockNumber);
            try {
                mlock.lock(lockNumber);
                try {
                    countDown(locked);
                    logLock("async " + lockNumber + " ");
                } finally {
                    mlock.unlock(lockNumber);
                    //log.info("Unlocked async " + lockNumber);
                    //logLock("async " + lockNumber + " ");
                }
                countDown(unlocked);
            } catch (InterruptedException ie) {
                log.warn(ie.toString());
            }
        });
    }

    void logLock() {
        logLock("");
    }

    void logLock(String msg) {
        log.info(msg + mlock.toString());
    }

    static void countDown(CountDownLatch l) {
        if (l != null) {
            l.countDown();
        }
    }

    static void await(CountDownLatch l) {

        if (l == null) {
            return;
        }
        try {
            l.await();
        } catch (InterruptedException e) {
            log.error(e.toString(), e.getCause());
        }
    }

}

Upvotes: 0

Sergey Fedorov
Sergey Fedorov

Reputation: 2169

I don't know any library that does that you want. Even if there is such a library it possess little value because every time your request changes the library stops doing the magic.

The actual question here is "How to I implement my own lock with custom specification?"

Java provides tool for that named AbstractQueuedSynchronizer. It has extensive documentation. Apart from docs one would possibly like to look at CountDownLatch and ReentrantLock sources and use them as examples.

For your particular request see code below, but beware that it is 1) not fair 2) not tested

public class MultiReadWriteLock implements ReadWriteLock {

    private final Sync sync;
    private final Lock readLock;
    private final Lock writeLock;

    public MultiReadWriteLock() {
        this.sync = new Sync();
        this.readLock = new MultiLock(Sync.READ, sync);
        this.writeLock = new MultiLock(Sync.WRITE, sync);
    }

    @Override
    public Lock readLock() {
        return readLock;
    }

    @Override
    public Lock writeLock() {
        return writeLock;
    }

    private static final class Sync extends AbstractQueuedSynchronizer {

        private static final int READ = 1;
        private static final int WRITE = -1;

        @Override
        public int tryAcquireShared(int arg) {
            int state, result;
            do {
                state = getState();
                if (state >= 0 && arg == READ) {
                    // new read
                    result = state + 1;
                } else if (state <= 0 && arg == WRITE) {
                    // new write
                    result = state - 1;
                } else {
                    // blocked
                    return -1;
                }
            } while (!compareAndSetState(state, result));
            return 1;
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
            int state, result;
            do {
                state = getState();
                if (state == 0) {
                    return false;
                }
                if (state > 0 && arg == READ) {
                    result = state - 1;
                } else if (state < 0 && arg == WRITE) {
                    result = state + 1;
                } else {
                    throw new IllegalMonitorStateException();
                }
            } while (!compareAndSetState(state, result));
            return result == 0;
        }
    }

    private static class MultiLock implements Lock {

        private final int parameter;
        private final Sync sync;

        public MultiLock(int parameter, Sync sync) {
            this.parameter = parameter;
            this.sync = sync;
        }

        @Override
        public void lock() {
            sync.acquireShared(parameter);
        }

        @Override
        public void lockInterruptibly() throws InterruptedException {
            sync.acquireSharedInterruptibly(parameter);
        }

        @Override
        public boolean tryLock() {
            return sync.tryAcquireShared(parameter) > 0;
        }

        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            return sync.tryAcquireSharedNanos(parameter, unit.toNanos(time));
        }

        @Override
        public void unlock() {
            sync.releaseShared(parameter);
        }

        @Override
        public Condition newCondition() {
            throw new UnsupportedOperationException(
                "Conditions are unsupported as there are no exclusive access"
            );
        }
    }
}

Upvotes: 1

Related Questions