001: /* uDig - User Friendly Desktop Internet GIS client
002: * http://udig.refractions.net
003: * (C) 2004, Refractions Research Inc.
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation;
008: * version 2.1 of the License.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: */
015: package net.refractions.udig.ui;
016:
017: import java.util.concurrent.TimeUnit;
018: import java.util.concurrent.atomic.AtomicInteger;
019: import java.util.concurrent.locks.Condition;
020: import java.util.concurrent.locks.Lock;
021: import java.util.concurrent.locks.ReentrantLock;
022:
023: import net.refractions.udig.internal.ui.Trace;
024: import net.refractions.udig.internal.ui.UiPlugin;
025:
026: import org.eclipse.swt.widgets.Display;
027:
028: /**
029: * This lock is reentrant and guarantees that a display thread will not block,
030: * it will continue to call {@link Display#readAndDispatch()}. The Display thread
031: * gets priority over non-display threads.
032: *
033: * API is copied from the {@link ReentrantLock}
034: *
035: * @author Jesse
036: * @since 1.1.0
037: */
038: public class UDIGDisplaySafeLock implements Lock {
039: private static class NullLock extends UDIGDisplaySafeLock {
040: public NullLock(ReentrantLock internalLock) {
041: this .internalLock = internalLock;
042: }
043:
044: @Override
045: public boolean isHeldByCurrentThread() {
046: return true;
047: }
048:
049: @Override
050: public boolean isLocked() {
051: return true;
052: }
053:
054: @Override
055: public void unlock() {
056: internalLock.unlock();
057: }
058:
059: @Override
060: public void lock() {
061: internalLock.lock();
062: }
063:
064: public void lockInterruptibly() throws InterruptedException {
065: internalLock.lockInterruptibly();
066: }
067:
068: @Override
069: protected void init() {
070: // do nothing
071: }
072: };
073:
074: /**
075: * Indicates the number of times the lock has been locked.
076: */
077: private AtomicInteger lockCount;
078: ReentrantLock internalLock;
079: /**
080: * Thread that holds the lock.
081: */
082: volatile Thread lockThread;
083: /**
084: * The condition that is used for Display thread
085: */
086: private UDIGDisplaySafeCondition displayCondition;
087: /**
088: * condition used for other threads.
089: */
090: private UDIGDisplaySafeCondition condition;
091: private static int ID = 0;
092: private int id = ID++;;
093:
094: public UDIGDisplaySafeLock() {
095: init();
096: }
097:
098: protected void init() {
099: lockCount = new AtomicInteger(0);
100: internalLock = new ReentrantLock();
101: condition = new UDIGDisplaySafeCondition(new NullLock(
102: internalLock));
103: displayCondition = new UDIGDisplaySafeCondition(new NullLock(
104: internalLock));
105: }
106:
107: /**
108: * Queries the number of holds on this lock by the current thread.
109: *
110: * <p>A thread has a hold on a lock for each lock action that is not
111: * matched by an unlock action.
112: *
113: * <p>The hold count information is typically only used for testing and
114: * debugging purposes. For example, if a certain section of code should
115: * not be entered with the lock already held then we can assert that
116: * fact:
117: *
118: * <pre>
119: * class X {
120: * ReentrantLock lock = new ReentrantLock();
121: * // ...
122: * public void m() {
123: * assert lock.getHoldCount() == 0;
124: * lock.lock();
125: * try {
126: * // ... method body
127: * } finally {
128: * lock.unlock();
129: * }
130: * }
131: * }
132: * </pre>
133: *
134: * @return the number of holds on this lock by the current thread,
135: * or zero if this lock is not held by the current thread.
136: */
137: public int getHoldCount() {
138: internalLock.lock();
139: try {
140: return lockCount.get();
141: } finally {
142: internalLock.unlock();
143: }
144: }
145:
146: /**
147: * Returns an estimate of the number of threads waiting on the
148: * given condition associated with this lock. Note that because
149: * timeouts and interrupts may occur at any time, the estimate
150: * serves only as an upper bound on the actual number of waiters.
151: * This method is designed for use in monitoring of the system
152: * state, not for synchronization control.
153: * @param condition the condition
154: * @return the estimated number of waiting threads.
155: * @throws IllegalMonitorStateException if this lock
156: * is not held
157: * @throws IllegalArgumentException if the given condition is
158: * not associated with this lock
159: * @throws NullPointerException if condition null
160: */
161: public int getWaitQueueLength(Condition condition) {
162: internalLock.lock();
163: try {
164:
165: if (condition == null)
166: throw new NullPointerException(
167: "Condition cannot be null"); //$NON-NLS-1$
168: if (!(condition instanceof UDIGDisplaySafeCondition))
169: throw new IllegalStateException(
170: "Condition is not owned by this lock!"); //$NON-NLS-1$
171: UDIGDisplaySafeCondition casted = (UDIGDisplaySafeCondition) condition;
172: if (!casted.isOwner(this ))
173: throw new IllegalStateException(
174: "Condition is not owned by this lock!"); //$NON-NLS-1$
175: return casted.getWaitQueueLength();
176:
177: } finally {
178: internalLock.unlock();
179: }
180: }
181:
182: /**
183: * Queries whether any threads are waiting on the given condition
184: * associated with this lock. Note that because timeouts and
185: * interrupts may occur at any time, a <tt>true</tt> return does
186: * not guarantee that a future <tt>signal</tt> will awaken any
187: * threads. This method is designed primarily for use in
188: * monitoring of the system state.
189: * @param condition the condition
190: * @return <tt>true</tt> if there are any waiting threads.
191: * @throws IllegalMonitorStateException if this lock
192: * is not held
193: * @throws IllegalArgumentException if the given condition is
194: * not associated with this lock
195: * @throws NullPointerException if condition null
196: */
197: public boolean hasWaiters(Condition condition) {
198: internalLock.lock();
199: try {
200: return getWaitQueueLength(condition) > 0;
201: } finally {
202: internalLock.unlock();
203: }
204: }
205:
206: /**
207: * Queries if this lock is held by the current thread.
208: *
209: * <p>Analogous to the {@link Thread#holdsLock} method for built-in
210: * monitor locks, this method is typically used for debugging and
211: * testing. For example, a method that should only be called while
212: * a lock is held can assert that this is the case:
213: *
214: * <pre>
215: * class X {
216: * ReentrantLock lock = new ReentrantLock();
217: * // ...
218: *
219: * public void m() {
220: * assert lock.isHeldByCurrentThread();
221: * // ... method body
222: * }
223: * }
224: * </pre>
225: *
226: * <p>It can also be used to ensure that a reentrant lock is used
227: * in a non-reentrant manner, for example:
228: *
229: * <pre>
230: * class X {
231: * ReentrantLock lock = new ReentrantLock();
232: * // ...
233: *
234: * public void m() {
235: * assert !lock.isHeldByCurrentThread();
236: * lock.lock();
237: * try {
238: * // ... method body
239: * } finally {
240: * lock.unlock();
241: * }
242: * }
243: * }
244: * </pre>
245: * @return <tt>true</tt> if current thread holds this lock and
246: * <tt>false</tt> otherwise.
247: */
248: public boolean isHeldByCurrentThread() {
249: internalLock.lock();
250: try {
251: return Thread.currentThread() == lockThread;
252: } finally {
253: internalLock.unlock();
254: }
255:
256: }
257:
258: /**
259: * Returns true if locked by a different thread.
260: *
261: * @return true if locked by a different thread.
262: */
263: public boolean isLocked() {
264: internalLock.lock();
265: try {
266: return lockThread != null;
267: } finally {
268: internalLock.unlock();
269: }
270: }
271:
272: public void lock() {
273: try {
274: lock(false);
275: } catch (InterruptedException e) {
276: throw new IllegalStateException(
277: "This is illegal, interrupted exception should not occur here."); //$NON-NLS-1$
278: }
279: }
280:
281: private void lock(boolean allowInterrupts)
282: throws InterruptedException {
283: internalLock.lock();
284: try {
285: while (lockThread != null
286: && lockThread != Thread.currentThread()) {
287: wait(-1, null, allowInterrupts);
288: }
289: doLock();
290:
291: } finally {
292: internalLock.unlock();
293: }
294: }
295:
296: private void doLock() {
297: if (UiPlugin.isDebugging(Trace.UDIG_DISPLAY_SAFE_LOCK))
298: UiPlugin
299: .trace(
300: getClass(),
301: Thread.currentThread().getName()
302: + " is Locking " + id + ". Number of entrances: " + (lockCount.get() + 1), null); //$NON-NLS-1$ //$NON-NLS-2$
303: if (lockCount.compareAndSet(0, 1)) {
304: lockThread = Thread.currentThread();
305: } else {
306: if (lockThread == Thread.currentThread()) {
307: lockCount.incrementAndGet();
308: } else {
309: if (UiPlugin.isDebugging(Trace.UDIG_DISPLAY_SAFE_LOCK))
310: UiPlugin
311: .trace(
312: getClass(),
313: "Illegal state. Trying to increment lock count even though lock is not held by current Thread. " + //$NON-NLS-1$
314: "\n\tcurrentThread = "
315: + Thread.currentThread()
316: + " Lock thread==" + lockThread, null); //$NON-NLS-1$ //$NON-NLS-2$
317: throw new IllegalStateException(
318: "Illegal state. Trying to increment lock count even though lock is not held by current Thread. " + //$NON-NLS-1$
319: "\n\tcurrentThread = "
320: + Thread.currentThread()
321: + " Lock thread==" + lockThread); //$NON-NLS-1$ //$NON-NLS-2$
322: }
323: }
324: }
325:
326: private void wait(long timeout, TimeUnit unit,
327: boolean allowInterrupts) throws InterruptedException {
328: if (allowInterrupts && Thread.interrupted())
329: throw new InterruptedException(
330: "Thread has been interrupted"); //$NON-NLS-1$
331:
332: if (UiPlugin.isDebugging(Trace.UDIG_DISPLAY_SAFE_LOCK))
333: UiPlugin
334: .trace(
335: getClass(),
336: Thread.currentThread().getName()
337: + " is waiting for Lock " + id + ". Interruptible=" + allowInterrupts, null); //$NON-NLS-1$ //$NON-NLS-2$
338:
339: if (Display.getCurrent() != null) {
340: displayCondition.doAwait(timeout, unit, allowInterrupts);
341: } else
342: condition.doAwait(timeout, unit, allowInterrupts);
343: }
344:
345: public void lockInterruptibly() throws InterruptedException {
346: lock(true);
347: }
348:
349: public Condition newCondition() {
350: internalLock.lock();
351: try {
352: return new UDIGDisplaySafeCondition(this );
353: } finally {
354: internalLock.unlock();
355: }
356: }
357:
358: public boolean tryLock() {
359: internalLock.lock();
360: try {
361: if (!isLocked() || isHeldByCurrentThread()) {
362: lock();
363: return true;
364: }
365: return false;
366: } finally {
367: internalLock.unlock();
368: }
369: }
370:
371: public boolean tryLock(long timeout, TimeUnit unit)
372: throws InterruptedException {
373: internalLock.lock();
374: try {
375: if (tryLock()) {
376: return true;
377: } else {
378: this .wait(timeout, unit, true);
379: return tryLock();
380: }
381: } finally {
382: internalLock.unlock();
383: }
384: }
385:
386: public void unlock() {
387: internalLock.lock();
388: try {
389: if (this .lockThread != Thread.currentThread())
390: throw new IllegalStateException(
391: "Current thread does not own lock! Lock owner == " + lockThread); //$NON-NLS-1$
392:
393: if (UiPlugin.isDebugging(Trace.UDIG_DISPLAY_SAFE_LOCK))
394: UiPlugin
395: .trace(
396: getClass(),
397: Thread.currentThread().getName()
398: + " is unlocking Lock " + id + " remaining holds=" + (lockCount.get() - 1), null); //$NON-NLS-1$ //$NON-NLS-2$
399:
400: if (lockCount.get() > 0) {
401: if (displayCondition.getWaitQueueLength() > 0) {
402: displayCondition.signal();
403: } else {
404: condition.signal();
405: }
406: if (lockCount.decrementAndGet() == 0) {
407: lockThread = null;
408: }
409: }
410:
411: } finally {
412: internalLock.unlock();
413: }
414: }
415: }
|