001: // Copyright 2006, 2007 The Apache Software Foundation
002: //
003: // Licensed under the Apache License, Version 2.0 (the "License");
004: // you may not use this file except in compliance with the License.
005: // You may obtain a copy of the License at
006: //
007: // http://www.apache.org/licenses/LICENSE-2.0
008: //
009: // Unless required by applicable law or agreed to in writing, software
010: // distributed under the License is distributed on an "AS IS" BASIS,
011: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: // See the License for the specific language governing permissions and
013: // limitations under the License.
014:
015: package org.apache.tapestry.ioc.internal.util;
016:
017: import java.util.concurrent.locks.ReadWriteLock;
018: import java.util.concurrent.locks.ReentrantReadWriteLock;
019: import java.util.concurrent.TimeUnit;
020:
021: /**
022: * A barrier used to execute code in a context where it is guarded by read/write locks. In addition,
023: * handles upgrading read locks to write locks (and vice versa). Execution of code within a lock is
024: * in terms of a {@link Runnable} object (that returns no value), or a {@link Invokable} object
025: * (which does return a value).
026: */
027: public class ConcurrentBarrier {
028: private final ReadWriteLock _lock = new ReentrantReadWriteLock();
029:
030: /**
031: * This is, of course, a bit of a problem. We don't have an avenue for ensuring that this
032: * ThreadLocal is destroyed at the end of the request, and that means a thread can hold a
033: * reference to the class and the class loader which loaded it. This may cause redeployment
034: * problems (leaked classes and class loaders). Apparently JDK 1.6 provides the APIs to check to
035: * see if the current thread has a read lock. So, we tend to remove the TL, rather than set its
036: * value to false.
037: */
038: public static class ThreadBoolean extends ThreadLocal<Boolean> {
039: @Override
040: protected Boolean initialValue() {
041: return false;
042: }
043: }
044:
045: private final ThreadBoolean _threadHasReadLock = new ThreadBoolean();
046:
047: /**
048: * Invokes the object after acquiring the read lock (if necessary). If invoked when the read
049: * lock has not yet been acquired, then the lock is acquired for the duration of the call. If
050: * the lock has already been acquired, then the status of the lock is not changed.
051: * <p>
052: * TODO: Check to see if the write lock is acquired and <em>not</em> acquire the read lock in
053: * that situation. Currently this code is not re-entrant. If a write lock is already acquired
054: * and the thread attempts to get the read lock, then the thread will hang. For the moment, all
055: * the uses of ConcurrentBarrier are coded in such a way that reentrant locks are not a problem.
056: *
057: * @param <T>
058: * @param invokable
059: * @return the result of invoking the invokable
060: */
061: public <T> T withRead(Invokable<T> invokable) {
062: boolean readLockedAtEntry = _threadHasReadLock.get();
063:
064: if (!readLockedAtEntry) {
065: _lock.readLock().lock();
066:
067: _threadHasReadLock.set(true);
068: }
069:
070: try {
071: return invokable.invoke();
072: } finally {
073: if (!readLockedAtEntry) {
074: _lock.readLock().unlock();
075:
076: _threadHasReadLock.remove();
077: }
078: }
079: }
080:
081: /**
082: * As with {@link #withRead(Invokable)}, creating an {@link Invokable} wrapper around the
083: * runnable object.
084: */
085: public void withRead(final Runnable runnable) {
086: Invokable<Void> invokable = new Invokable<Void>() {
087: public Void invoke() {
088: runnable.run();
089:
090: return null;
091: }
092: };
093:
094: withRead(invokable);
095: }
096:
097: /**
098: * Acquires the exclusive write lock before invoking the Invokable. The code will be executed
099: * exclusively, no other reader or writer threads will exist (they will be blocked waiting for
100: * the lock). If the current thread has a read lock, it is released before attempting to acquire
101: * the write lock, and re-acquired after the write lock is released. Note that in that short
102: * window, between releasing the read lock and acquiring the write lock, it is entirely possible
103: * that some other thread will sneak in and do some work, so the {@link Invokable} object should
104: * be prepared for cases where the state has changed slightly, despite holding the read lock.
105: * This usually manifests as race conditions where either a) some parallel unrelated bit of work
106: * has occured or b) duplicate work has occured. The latter is only problematic if the operation
107: * is very expensive.
108: *
109: * @param <T>
110: * @param invokable
111: */
112: public <T> T withWrite(Invokable<T> invokable) {
113: boolean readLockedAtEntry = releaseReadLock();
114:
115: _lock.writeLock().lock();
116:
117: try {
118: return invokable.invoke();
119: } finally {
120: _lock.writeLock().unlock();
121: restoreReadLock(readLockedAtEntry);
122: }
123: }
124:
125: private boolean releaseReadLock() {
126: boolean readLockedAtEntry = _threadHasReadLock.get();
127:
128: if (readLockedAtEntry) {
129: _lock.readLock().unlock();
130:
131: _threadHasReadLock.set(false);
132: }
133: return readLockedAtEntry;
134: }
135:
136: private void restoreReadLock(boolean readLockedAtEntry) {
137: if (readLockedAtEntry) {
138: _lock.readLock().lock();
139:
140: _threadHasReadLock.set(true);
141: } else {
142: _threadHasReadLock.remove();
143: }
144: }
145:
146: /**
147: * As with {@link #withWrite(Invokable)}, creating an {@link Invokable} wrapper around the
148: * runnable object.
149: */
150: public void withWrite(final Runnable runnable) {
151: Invokable<Void> invokable = new Invokable<Void>() {
152: public Void invoke() {
153: runnable.run();
154:
155: return null;
156: }
157: };
158:
159: withWrite(invokable);
160: }
161:
162: /**
163: * Try to aquire the exclusive write lock and invoke the Runnable. If the write lock is obtained
164: * within the specfied timeout, then this method behaves as {@link #withWrite(Runnable)} and
165: * will return true. If the write lock is not obtained within the timeout then the runnable is
166: * never invoked and the method will return false.
167: *
168: * @param runnable
169: * Runnable object to execute inside the write lock.
170: * @param timeout
171: * Time to wait for write lock.
172: * @param timeoutUnit
173: * Units of timeout.
174: * @return true if lock was obtained & runnabled executed. False otherwise.
175: */
176: public boolean tryWithWrite(final Runnable runnable, long timeout,
177: TimeUnit timeoutUnit) {
178: boolean readLockedAtEntry = releaseReadLock();
179:
180: boolean obtainedLock = false;
181:
182: try {
183: try {
184: obtainedLock = _lock.writeLock().tryLock(timeout,
185: timeoutUnit);
186:
187: if (obtainedLock)
188: runnable.run();
189:
190: } catch (InterruptedException e) {
191: obtainedLock = false;
192: } finally {
193: if (obtainedLock)
194: _lock.writeLock().unlock();
195: }
196: } finally {
197: restoreReadLock(readLockedAtEntry);
198: }
199:
200: return obtainedLock;
201: }
202:
203: }
|