001: /*
002: File: SyncCollection.java
003:
004: Originally written by Doug Lea and released into the public domain.
005: This may be used for any purposes whatsoever without acknowledgment.
006: Thanks for the assistance and support of Sun Microsystems Labs,
007: and everyone contributing, testing, and using this code.
008:
009: History:
010: Date Who What
011: 1Aug1998 dl Create public version
012: */
013:
014: package EDU.oswego.cs.dl.util.concurrent;
015:
016: import java.util.*;
017:
018: /**
019: * SyncCollections wrap Sync-based control around java.util.Collections.
020: * They are similar in operation to those provided
021: * by java.util.Collection.synchronizedCollection, but have
022: * several extended capabilities.
023: * <p>
024: * The Collection interface is conceptually broken into two
025: * parts for purposes of synchronization control. The purely inspective
026: * reader operations are:
027: * <ul>
028: * <li> size
029: * <li> isEmpty
030: * <li> toArray
031: * <li> contains
032: * <li> containsAll
033: * <li> iterator
034: * </ul>
035: * The possibly mutative writer operations (which are also
036: * the set of operations that are allowed to throw
037: * UnsupportedOperationException) are:
038: * <ul>
039: * <li> add
040: * <li> addAll
041: * <li> remove
042: * <li> clear
043: * <li> removeAll
044: * <li> retainAll
045: * </ul>
046: *
047: * <p>
048: * SyncCollections can be used with either Syncs or ReadWriteLocks.
049: * When used with
050: * single Syncs, the same lock is used as both the reader and writer lock.
051: * The SyncCollection class cannot itself guarantee that using
052: * a pair of read/write locks will always correctly protect objects, since
053: * Collection implementations are not precluded from internally
054: * performing hidden unprotected state changes within conceptually read-only
055: * operations. However, they do work with current java.util implementations.
056: * (Hopefully, implementations that do not provide this natural
057: * guarantee will be clearly documentented as such.)
058: * <p>
059: * This class provides a straight implementation of Collections interface.
060: * In order to conform to this interface, sync failures
061: * due to interruption do NOT result in InterruptedExceptions.
062: * Instead, upon detection of interruption,
063: * <ul>
064: * <li> All mutative operations convert the interruption to
065: * an UnsupportedOperationException, while also propagating
066: * the interrupt status of the thread. Thus, unlike normal
067: * java.util.Collections, SyncCollections can <em>transiently</em>
068: * behave as if mutative operations are not supported.
069: * <li> All read-only operations
070: * attempt to return a result even upon interruption. In some contexts,
071: * such results will be meaningless due to interference, but
072: * provide best-effort status indications that can be useful during
073: * recovery. The cumulative number of synchronization failures encountered
074: * during such operations is accessible using method
075: * <code>synchronizationFailures()</code>.
076: * Non-zero values may indicate serious program errors.
077: * </ul>
078: * <p>
079: * The iterator() method returns a SyncCollectionIterator with
080: * properties and methods that are analogous to those of SyncCollection
081: * itself: hasNext and next are read-only, and remove is mutative.
082: * These methods allow fine-grained controlled access, but do <em>NOT</em>
083: * preclude concurrent modifications from being interleaved with traversals,
084: * which may lead to ConcurrentModificationExceptions.
085: * However, the class also supports method <code>unprotectedIterator</code>
086: * that can be used in conjunction with the <code>readerSync</code> or
087: * <code>writerSync</code> methods to perform locked traversals. For example,
088: * to protect a block of reads:
089: * <pre>
090: * Sync lock = coll.readerSync();
091: * try {
092: * lock.acquire();
093: * try {
094: * Iterator it = coll.unprotectedIterator();
095: * while (it.hasNext())
096: * System.out.println(it.next());
097: * }
098: * finally {
099: * lock.release();
100: * }
101: * }
102: * catch (InterruptedException ex) { ... }
103: * </pre>
104: * If you need to protect blocks of writes, you must use some
105: * form of <em>reentrant</em> lock (for example <code>ReentrantLock</code>
106: * or <code>ReentrantWriterPreferenceReadWriteLock</code>) as the Sync
107: * for the collection in order to allow mutative methods to proceed
108: * while the current thread holds the lock. For example, you might
109: * need to hold a write lock during an initialization sequence:
110: * <pre>
111: * Collection c = new SyncCollection(new ArrayList(),
112: * new ReentrantWriterPreferenceReadWriteLock());
113: * // ...
114: * c.writeLock().acquire();
115: * try {
116: * for (...) {
117: * Object x = someStream.readObject();
118: * c.add(x); // would block if writeLock not reentrant
119: * }
120: * }
121: * catch (IOException iox) {
122: * ...
123: * }
124: * finally {
125: * c.writeLock().release();
126: * }
127: * catch (InterruptedException ex) { ... }
128: * </pre>
129: * <p>
130: * (It would normally be better practice here to not make the
131: * collection accessible until initialization is complete.)
132: * <p>
133: * This class does not specifically support use of
134: * timed synchronization through the attempt method. However,
135: * you can obtain this effect via
136: * the TimeoutSync class. For example:
137: * <pre>
138: * Mutex lock = new Mutex();
139: * TimeoutSync timedLock = new TimeoutSync(lock, 1000); // 1 sec timeouts
140: * Collection c = new SyncCollection(new HashSet(), timedlock);
141: * </pre>
142: * <p>
143: * The same can be done with read-write locks:
144: * <pre>
145: * ReadWriteLock rwl = new WriterPreferenceReadWriteLock();
146: * Sync rlock = new TimeoutSync(rwl.readLock(), 100);
147: * Sync wlock = new TimeoutSync(rwl.writeLock(), 100);
148: * Collection c = new SyncCollection(new HashSet(), rlock, wlock);
149: * </pre>
150: * <p>
151: * In addition to synchronization control, SyncCollections
152: * may be useful in any context requiring before/after methods
153: * surrounding collections. For example, you can use ObservableSync
154: * to arrange notifications on method calls to collections, as in:
155: * <pre>
156: * class X {
157: * Collection c;
158: *
159: * static class CollectionObserver implements ObservableSync.SyncObserver {
160: * public void onAcquire(Object arg) {
161: * Collection coll = (Collection) arg;
162: * System.out.println("Starting operation on" + coll);
163: * // Other plausible responses include performing integrity
164: * // checks on the collection, updating displays, etc
165: * }
166: * public void onRelease(Object arg) {
167: * Collection coll = (Collection) arg;
168: * System.out.println("Finished operation on" + coll);
169: * }
170: * }
171: *
172: * X() {
173: * ObservableSync s = new ObservableSync();
174: * c = new SyncCollection(new HashSet(), s);
175: * s.setNotificationArgument(c);
176: * CollectionObserver obs = new CollectionObserver();
177: * s.attach(obs);
178: * }
179: * ...
180: * }
181: * </pre>
182: *
183: * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>]
184: * @see LayeredSync
185: * @see TimeoutSync
186: **/
187:
188: public class SyncCollection implements Collection {
189: protected final Collection c_; // Backing Collection
190: protected final Sync rd_; // sync for read-only methods
191: protected final Sync wr_; // sync for mutative methods
192:
193: protected final SynchronizedLong syncFailures_ = new SynchronizedLong(
194: 0);
195:
196: /**
197: * Create a new SyncCollection protecting the given collection,
198: * and using the given sync to control both reader and writer methods.
199: * Common, reasonable choices for the sync argument include
200: * Mutex, ReentrantLock, and Semaphores initialized to 1.
201: * <p>
202: * <b>Sample Usage</b>
203: * <pre>
204: * Collection c = new SyncCollection(new ArrayList(), new Mutex());
205: * </pre>
206: **/
207: public SyncCollection(Collection collection, Sync sync) {
208: this (collection, sync, sync);
209: }
210:
211: /**
212: * Create a new SyncCollection protecting the given collection,
213: * and using the given ReadWriteLock to control reader and writer methods.
214: * <p>
215: * <b>Sample Usage</b>
216: * <pre>
217: * Collection c = new SyncCollection(new HashSet(),
218: * new WriterPreferenceReadWriteLock());
219: * </pre>
220: **/
221: public SyncCollection(Collection collection, ReadWriteLock rwl) {
222: this (collection, rwl.readLock(), rwl.writeLock());
223: }
224:
225: /**
226: * Create a new SyncCollection protecting the given collection,
227: * and using the given pair of locks to control reader and writer methods.
228: **/
229: public SyncCollection(Collection collection, Sync readLock,
230: Sync writeLock) {
231: c_ = collection;
232: rd_ = readLock;
233: wr_ = writeLock;
234: }
235:
236: /**
237: * Return the Sync object managing read-only operations
238: **/
239:
240: public Sync readerSync() {
241: return rd_;
242: }
243:
244: /**
245: * Return the Sync object managing mutative operations
246: **/
247:
248: public Sync writerSync() {
249: return wr_;
250: }
251:
252: /**
253: * Return the number of synchronization failures for read-only operations
254: **/
255: public long syncFailures() {
256: return syncFailures_.get();
257: }
258:
259: /** Try to acquire sync before a reader operation; record failure **/
260: protected boolean beforeRead() {
261: try {
262: rd_.acquire();
263: return false;
264: } catch (InterruptedException ex) {
265: syncFailures_.increment();
266: return true;
267: }
268: }
269:
270: /** Clean up after a reader operation **/
271: protected void afterRead(boolean wasInterrupted) {
272: if (wasInterrupted) {
273: Thread.currentThread().interrupt();
274: } else
275: rd_.release();
276: }
277:
278: public int size() {
279: boolean wasInterrupted = beforeRead();
280: try {
281: return c_.size();
282: } finally {
283: afterRead(wasInterrupted);
284: }
285: }
286:
287: public boolean isEmpty() {
288: boolean wasInterrupted = beforeRead();
289: try {
290: return c_.isEmpty();
291: } finally {
292: afterRead(wasInterrupted);
293: }
294: }
295:
296: public boolean contains(Object o) {
297: boolean wasInterrupted = beforeRead();
298: try {
299: return c_.contains(o);
300: } finally {
301: afterRead(wasInterrupted);
302: }
303: }
304:
305: public Object[] toArray() {
306: boolean wasInterrupted = beforeRead();
307: try {
308: return c_.toArray();
309: } finally {
310: afterRead(wasInterrupted);
311: }
312: }
313:
314: public Object[] toArray(Object[] a) {
315: boolean wasInterrupted = beforeRead();
316: try {
317: return c_.toArray(a);
318: } finally {
319: afterRead(wasInterrupted);
320: }
321: }
322:
323: public boolean containsAll(Collection coll) {
324: boolean wasInterrupted = beforeRead();
325: try {
326: return c_.containsAll(coll);
327: } finally {
328: afterRead(wasInterrupted);
329: }
330: }
331:
332: public boolean add(Object o) {
333: try {
334: wr_.acquire();
335: try {
336: return c_.add(o);
337: } finally {
338: wr_.release();
339: }
340: } catch (InterruptedException ex) {
341: Thread.currentThread().interrupt();
342: throw new UnsupportedOperationException();
343: }
344: }
345:
346: public boolean remove(Object o) {
347: try {
348: wr_.acquire();
349: try {
350: return c_.remove(o);
351: } finally {
352: wr_.release();
353: }
354: } catch (InterruptedException ex) {
355: Thread.currentThread().interrupt();
356: throw new UnsupportedOperationException();
357: }
358: }
359:
360: public boolean addAll(Collection coll) {
361: try {
362: wr_.acquire();
363: try {
364: return c_.addAll(coll);
365: } finally {
366: wr_.release();
367: }
368: } catch (InterruptedException ex) {
369: Thread.currentThread().interrupt();
370: throw new UnsupportedOperationException();
371: }
372: }
373:
374: public boolean removeAll(Collection coll) {
375: try {
376: wr_.acquire();
377: try {
378: return c_.removeAll(coll);
379: } finally {
380: wr_.release();
381: }
382: } catch (InterruptedException ex) {
383: Thread.currentThread().interrupt();
384: throw new UnsupportedOperationException();
385: }
386: }
387:
388: public boolean retainAll(Collection coll) {
389: try {
390: wr_.acquire();
391: try {
392: return c_.retainAll(coll);
393: } finally {
394: wr_.release();
395: }
396: } catch (InterruptedException ex) {
397: Thread.currentThread().interrupt();
398: throw new UnsupportedOperationException();
399: }
400: }
401:
402: public void clear() {
403: try {
404: wr_.acquire();
405: try {
406: c_.clear();
407: } finally {
408: wr_.release();
409: }
410: } catch (InterruptedException ex) {
411: Thread.currentThread().interrupt();
412: throw new UnsupportedOperationException();
413: }
414: }
415:
416: /** Return the base iterator of the underlying collection **/
417: public Iterator unprotectedIterator() {
418: boolean wasInterrupted = beforeRead();
419: try {
420: return c_.iterator();
421: } finally {
422: afterRead(wasInterrupted);
423: }
424: }
425:
426: public Iterator iterator() {
427: boolean wasInterrupted = beforeRead();
428: try {
429: return new SyncCollectionIterator(c_.iterator());
430: } finally {
431: afterRead(wasInterrupted);
432: }
433: }
434:
435: public class SyncCollectionIterator implements Iterator {
436: protected final Iterator baseIterator_;
437:
438: SyncCollectionIterator(Iterator baseIterator) {
439: baseIterator_ = baseIterator;
440: }
441:
442: public boolean hasNext() {
443: boolean wasInterrupted = beforeRead();
444: try {
445: return baseIterator_.hasNext();
446: } finally {
447: afterRead(wasInterrupted);
448: }
449: }
450:
451: public Object next() {
452: boolean wasInterrupted = beforeRead();
453: try {
454: return baseIterator_.next();
455: } finally {
456: afterRead(wasInterrupted);
457: }
458: }
459:
460: public void remove() {
461: try {
462: wr_.acquire();
463: try {
464: baseIterator_.remove();
465: } finally {
466: wr_.release();
467: }
468: } catch (InterruptedException ex) {
469: Thread.currentThread().interrupt();
470: throw new UnsupportedOperationException();
471: }
472: }
473:
474: }
475: }
|