/** * Invokes the object after acquiring the read lock (if necessary). If invoked when the read lock * has not yet been acquired, then the lock is acquired for the duration of the call. If the lock * has already been acquired, then the status of the lock is not changed. * * <p>TODO: Check to see if the write lock is acquired and <em>not</em> acquire the read lock in * that situation. Currently this code is not re-entrant. If a write lock is already acquired and * the thread attempts to get the read lock, then the thread will hang. For the moment, all the * uses of ConcurrentBarrier are coded in such a way that reentrant locks are not a problem. * * @param <T> * @param invokable * @return the result of invoking the invokable */ public <T> T withRead(Invokable<T> invokable) { boolean readLockedAtEntry; synchronized (threadHasReadLock) { readLockedAtEntry = threadHasReadLock.get(); } if (!readLockedAtEntry) { lock.readLock().lock(); synchronized (threadHasReadLock) { threadHasReadLock.set(true); } } try { return invokable.invoke(); } finally { if (!readLockedAtEntry) { lock.readLock().unlock(); synchronized (threadHasReadLock) { threadHasReadLock.remove(); } } } }
@Override public <T> Future<T> invoke(Invokable<T> invocable) { final T result = invocable.invoke(); return new Future<T>() { @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return true; } @Override public T get() throws InterruptedException, ExecutionException { return result; } @Override public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return result; } }; }
/** * Acquires the exclusive write lock before invoking the Invokable. The code will be executed * exclusively, no other reader or writer threads will exist (they will be blocked waiting for the * lock). If the current thread has a read lock, it is released before attempting to acquire the * write lock, and re-acquired after the write lock is released. Note that in that short window, * between releasing the read lock and acquiring the write lock, it is entirely possible that some * other thread will sneak in and do some work, so the {@link Invokable} object should be prepared * for cases where the state has changed slightly, despite holding the read lock. This usually * manifests as race conditions where either a) some parallel unrelated bit of work has occured or * b) duplicate work has occured. The latter is only problematic if the operation is very * expensive. * * @param <T> * @param invokable */ public <T> T withWrite(Invokable<T> invokable) { boolean readLockedAtEntry = releaseReadLock(); lock.writeLock().lock(); try { return invokable.invoke(); } finally { lock.writeLock().unlock(); restoreReadLock(readLockedAtEntry); } }
@Override public <T> T invoke(Class<T> proxyType, Invokable<T> invocable) { return invocable.invoke(); }