
org.apache.tapestry.ioc.internal.util.ConcurrentBarrier Maven / Gradle / Ivy
// Copyright 2006, 2007 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry.ioc.internal.util;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* A barrier used to execute code in a context where it is guarded by read/write locks. In addition,
* handles upgrading read locks to write locks (and vice versa). Execution of code within a lock is
* in terms of a {@link Runnable} object (that returns no value), or a {@link Invokable} object
* (which does return a value).
*/
public class ConcurrentBarrier
{
private final ReadWriteLock _lock = new ReentrantReadWriteLock();
/**
* This is, of course, a bit of a problem. We don't have an avenue for ensuring that this
* ThreadLocal is destroyed at the end of the request, and that means a thread can hold a
* reference to the class and the class loader which loaded it. This may cause redeployment
* problems (leaked classes and class loaders). Apparently JDK 1.6 provides the APIs to check to
* see if the current thread has a read lock. So, we tend to remove the TL, rather than set its
* value to false.
*/
private static class ThreadBoolean extends ThreadLocal
{
@Override
protected Boolean initialValue()
{
return false;
}
}
private final ThreadBoolean _threadHasReadLock = new ThreadBoolean();
/**
* 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.
*
* TODO: Check to see if the write lock is acquired and not 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
* @param invokable
* @return the result of invoking the invokable
*/
public T withRead(Invokable invokable)
{
boolean readLockedAtEntry = _threadHasReadLock.get();
if (!readLockedAtEntry)
{
_lock.readLock().lock();
_threadHasReadLock.set(true);
}
try
{
return invokable.invoke();
}
finally
{
if (!readLockedAtEntry)
{
_lock.readLock().unlock();
_threadHasReadLock.remove();
}
}
}
/**
* As with {@link #withRead(Invokable)}, creating an {@link Invokable} wrapper around the
* runnable object.
*/
public void withRead(final Runnable runnable)
{
Invokable invokable = new Invokable()
{
public Void invoke()
{
runnable.run();
return null;
}
};
withRead(invokable);
}
/**
* 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
* @param invokable
*/
public T withWrite(Invokable invokable)
{
boolean readLockedAtEntry = releaseReadLock();
_lock.writeLock().lock();
try
{
return invokable.invoke();
}
finally
{
_lock.writeLock().unlock();
restoreReadLock(readLockedAtEntry);
}
}
private boolean releaseReadLock()
{
boolean readLockedAtEntry = _threadHasReadLock.get();
if (readLockedAtEntry)
{
_lock.readLock().unlock();
_threadHasReadLock.set(false);
}
return readLockedAtEntry;
}
private void restoreReadLock(boolean readLockedAtEntry)
{
if (readLockedAtEntry)
{
_lock.readLock().lock();
_threadHasReadLock.set(true);
}
else
{
_threadHasReadLock.remove();
}
}
/**
* As with {@link #withWrite(Invokable)}, creating an {@link Invokable} wrapper around the
* runnable object.
*/
public void withWrite(final Runnable runnable)
{
Invokable invokable = new Invokable()
{
public Void invoke()
{
runnable.run();
return null;
}
};
withWrite(invokable);
}
/**
* Try to aquire the exclusive write lock and invoke the Runnable. If the write lock is obtained
* within the specfied timeout, then this method behaves as {@link #withWrite(Runnable)} and
* will return true. If the write lock is not obtained within the timeout then the runnable is
* never invoked and the method will return false.
*
* @param runnable Runnable object to execute inside the write lock.
* @param timeout Time to wait for write lock.
* @param timeoutUnit Units of timeout.
* @return true if lock was obtained & runnabled executed. False otherwise.
*/
public boolean tryWithWrite(final Runnable runnable, long timeout, TimeUnit timeoutUnit)
{
boolean readLockedAtEntry = releaseReadLock();
boolean obtainedLock = false;
try
{
try
{
obtainedLock = _lock.writeLock().tryLock(timeout, timeoutUnit);
if (obtainedLock) runnable.run();
}
catch (InterruptedException e)
{
obtainedLock = false;
}
finally
{
if (obtainedLock) _lock.writeLock().unlock();
}
}
finally
{
restoreReadLock(readLockedAtEntry);
}
return obtainedLock;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy