【问题标题】:Java Concurrency: Paired locks with shared accessJava 并发:具有共享访问权限的配对锁
【发布时间】:2016-12-28 08:49:51
【问题描述】:

我正在寻找以下并发语义的 Java 实现。我想要类似于ReadWriteLock 的东西,除了对称之外,即读取和写入端都可以在多个线程之间共享,但读取不包括写入,反之亦然。

  1. 有两把锁,我们称它们为 A 和 B。
  2. 锁 A 是共享的,即可能有多个线程同时持有它。锁 B 也是共享的,可能有多个线程同时持有它。
  3. 如果 any 线程持有锁 A,则 no 线程可能会占用 B - 试图占用 B 的线程将阻塞,直到所有持有 A 的线程都释放 A。
  4. 如果 any 线程持有锁 B,则 no 线程可能会占用 A - 尝试占用 A 的线程将阻塞,直到所有持有 B 的线程都释放 B。

是否存在实现此目的的现有库类?目前,我已经用ReadWriteLock 近似了所需的功能,因为幸运的是,在锁 B 的上下文中完成的任务比较少见。不过感觉就像是 hack,它可能会影响我的程序在重负载下的性能。

【问题讨论】:

  • 这里有几个答案,但没有一个涉及公平。锁的一侧饥饿可能是一个严重的问题,特别是如果其中一个组比另一组运行得更频繁(这似乎是您的情况)。如果总是有一个 A 运行,则没有 B 将永远运行。

标签: java concurrency


【解决方案1】:

简答:

在标准库中,没有什么比得上您需要的了。

长答案:

要轻松实现自定义Lock,您应该继承或委托给AbstractQueuedSynchronizer。

下面的代码是一个non-fair 锁的例子,它实现了你需要的东西,包括一些(非耗尽的)测试。我将其称为 LeftRightLock 是因为您的要求具有二进制性质。

这个概念非常简单:

AbstractQueuedSynchronizer 公开了一个方法来原子地设置 int 的状态,使用 Compare and swap 成语(compareAndSetState(int expect, int update)),我们可以使用公开的状态来保持持有锁的线程数,将其设置为如果Right 锁被持有,则为正值;如果Left 锁被持有,则为负值。

我们只需确保以下条件: - 只有当内部AbstractQueuedSynchronizer 的状态为zero 或负数时,您才能锁定Left - 只有当内部AbstractQueuedSynchronizer 的状态为zero 或正时,您才能锁定Right

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

【讨论】:

    【解决方案2】:

    我不知道有任何图书馆可以满足您的要求。即使有这样的库,它也没有什么价值,因为每次您的请求更改时,库都会停止发挥作用。

    这里的实际问题是“如何使用自定义规范实现我自己的锁?”

    Java 为AbstractQueuedSynchronizer 提供了工具。它有大量的文档。除了文档之外,您可能还想查看 CountDownLatch 和 ReentrantLock 源并将它们用作示例。

    对于您的特定要求,请参阅下面的代码,但请注意它 1) 不公平 2) 未经测试

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

    【讨论】:

      【解决方案3】:

      在我第n次尝试进行简单的公平实现之后,我想我明白为什么我找不到“互斥锁对”的另一个库/示例:它需要一个非常具体的用户案例。正如 OP 所提到的,您可以使用 ReadWriteLock 获得很长的路要走,并且公平的锁对仅在有许多快速连续的锁请求时才有用(否则您不妨使用一个普通锁)。

      下面的实现更像是一个“许可分配器”:它不是可重入的。虽然它可以重入(如果不是,我担心我未能使代码简单易读)但它需要针对各种情况进行一些额外的管理(例如,一个线程锁定 A 两次,仍然需要解锁 A 两次和解锁-method 需要知道何时没有更多未完成的锁)。当一个线程锁定 A 并想要锁定 B 时抛出死锁错误的选项可能是一个好主意。

      主要思想是有一个“活动锁”,只有在根本没有(请求)锁时才能通过锁方法更改,并且当活动锁未完成时可以通过解锁方法更改达到零。其余的基本上是保持锁请求的计数并使线程等待直到可以更改活动锁。让线程等待涉及到使用InterruptedExceptions,我在那里做出了妥协:我找不到在所有情况下(例如应用程序关闭、一个线程被中断等)都能正常工作的好的解决方案。

      我只做了一些基本的测试(最后是测试类),还需要更多的验证。

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

      测试类(YMMV - 在我的系统上运行良好,但由于线程的启动和运行速度更快或更慢,您的系统可能会死锁):

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

      【讨论】:

        【解决方案4】:

        怎么样

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

        更新:至于“公平”(或者,更确切地说,非饥饿),OP 要求没有提及。为了实现 OPs 要求 + 某种形式的公平/非饥饿,应该明确指定它(你认为什么是公平的,当当前占主导地位和非占主导地位的锁的请求流进来时它应该如何表现等)。实现它的方法之一是:

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

        用于:

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

        这里的世代思想取自《Java Concurrency in Practice》Listing 14.9。

        【讨论】:

        • 您的代码在编译器优化后可能包含无限循环。使用volatile 变量来避免它
        • @SergeyFedorov 你能详细说明一下吗?我真的认为我对synchronized 的使用足以保证正确性
        • 可以缓存非易失性字段读取(例如,通过 JIT)。如果你的aHolders 将被缓存在锁定状态,你会得到一个无限循环
        • @SergeyFedorov 同步块将在测试的任一侧强制设置内存屏障。 while 循环将起作用,实际上是 good practice。
        • @SergeyFedorov 任何优化监视器锁的编译器都从根本上被破坏了
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-15
        • 1970-01-01
        • 2010-10-14
        • 2017-10-08
        相关资源
        最近更新 更多