KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > nightlabs > util > RWLock


1 /* ************************************************************************** *
2  * Copyright (C) 2004 NightLabs GmbH, Marco Schulze *
3  * All rights reserved. *
4  * http://www.NightLabs.de *
5  * *
6  * This program and the accompanying materials are free software; you can re- *
7  * distribute it and/or modify it under the terms of the GNU General Public *
8  * License as published by the Free Software Foundation; either ver 2 of the *
9  * License, or any later version. *
10  * *
11  * This module is distributed in the hope that it will be useful, but WITHOUT *
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FIT- *
13  * NESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more *
14  * details. *
15  * *
16  * You should have received a copy of the GNU General Public License along *
17  * with this module; if not, write to the Free Software Foundation, Inc.: *
18  * 59 Temple Place, Suite 330 *
19  * Boston MA 02111-1307 *
20  * USA *
21  * *
22  * Or get it online: *
23  * http://www.opensource.org/licenses/gpl-license.php *
24  * *
25  * In case, you want to use this module or parts of it in a proprietary pro- *
26  * ject, you can purchase it under the NightLabs Commercial License. Please *
27  * contact NightLabs GmbH under info AT nightlabs DOT com for more infos or *
28  * visit http://www.NightLabs.com *
29  * ************************************************************************** */

30
31 package com.nightlabs.util;
32
33 import java.lang.InterruptedException JavaDoc;
34 import java.lang.IllegalThreadStateException JavaDoc;
35 import java.util.ArrayList JavaDoc;
36 import java.util.HashMap JavaDoc;
37
38 /**
39  * This class allows to synchronize a block for multiple reads and exclusive
40  * write. The default java way to synchronize a block is exclusive which slows
41  * down the program in some cases absolutely unnecessarily. Thus, you might want
42  * to use this class instead of a synchronized-block if you
43  * perform expensive multi-threaded read operations (e.g. in a server).
44  * <p>
45  * A thread trying to acquire a write lock obtains higher priority than threads
46  * trying to aquire read locks.
47  * <p>
48  * Use code like the following to perform thread protected read operations:
49  * <p><blockquote><pre>
50  * RWLock rwLock = new RWLock();
51  *
52  * :
53  * public void myMethod() {
54  * :
55  * :
56  * rwLock.acquireRead();
57  * try {
58  * :
59  * :
60  * } finally {
61  * rwLock.releaseLock();
62  * }
63  * :
64  * :
65  * }
66  * </pre></blockquote><p>
67  * <b>Never ever forget the releaseLock() and always use try-finally-blocks!!!</b>
68  * <p>
69  * The RWLock supports cascaded reads and writes. There is only one thing that
70  * you should be aware of: If a thread acquires a read lock, first, and without
71  * releasing it, it acquires a write lock, the protected data might be changed by another thread
72  * while the method <code>acquireWriteLock()</code> is waiting for the write access.
73  * This behaviour is to prevent dead locks. A corruption of the data is not
74  * possible, because only one is allowed to write at the same time and the
75  * other threads are suspended at the line where they execute
76  * <code>acquireWriteLock()</code>.
77  * <p><blockquote><pre>
78  * TestObject testObject = new TestObject();
79  * RWLock rwLock = new RWLock();
80  *
81  * public void myMethod()
82  * {
83  * rwLock.acquireReadLock();
84  * try {
85  * int tmpVal = testObject.getTmpVal();
86  * int tmpVal2;
87  * :
88  * :
89  * rwLock.acquireWriteLock(); // here the read locks get temporarily released to prevent dead locks
90  * try {
91  * if (tmpVal != testObject.getTmpVal())
92  * System.out.println("This can happen!!!");
93  * :
94  * :
95  * tmpVal2 = testObject.getTmpVal();
96  * } finally {
97  * rwLock.releaseLock(); // here the temporarily released read locks get re-established.
98  * }
99  * if (tmpVal2 != testObject.getTmpVal())
100  * throw new IllegalStateException("This can never happen, because the releaseLock() does not allow other threads to write.");
101  * :
102  * :
103  * } finally {
104  * rwLock.releaseLock();
105  * }
106  * }
107  * </pre></blockquote><p>
108  *
109  * @author Marco Schulze
110  * @author Marc Klinger - marc at nightlabs dot de (API documentation fixes)
111  * @version 1.0
112  */

113
114 public class RWLock implements RWLockable
115 {
116   /**
117    * Contains 0 if there is no lock existing, &gt; 0 for read locks and &lt; 0 for write
118    * locks. Because one thread may call acquireWriteLock multiple times, it
119    * is necessary to count the write locks even though only one thread can
120    * hold the write lock at the same time.
121    */

122   private int currentLocks = 0;
123
124   /**
125    * This method returns the current lock status. It equals 0, if there is
126    * no lock existing. It is &gt; 0, if there are read locks existing and it is
127    * &lt; 0, if there are write locks existing. Because locks can be cascaded and
128    * the same thread is allowed to execute <code>acquireWriteLock()</code> multiple
129    * times, there are values possible below -1.
130    *
131    * @return 0 if there is no lock; &lt; 0 if there is a write lock; &gt; 0 if there are read locks.
132    */

133   public int getCurrentLockStatus()
134   {
135     synchronized (mutex) { return currentLocks; }
136   }
137
138   /**
139    * If there is a write lock existing (currentLocks &lt; 0), this variable stores
140    * the thread that holds the lock.
141    */

142   private Thread JavaDoc currentWriteThread = null;
143
144   /**
145    * This map is used to allow cascaded reads without the danger of dead locks.
146    * Because writes have priority, it is necessary to allow a thread who already
147    * has acquired a read lock, to acquire more. Only new reading threads should
148    * be forced to wait.
149    * <p>
150    * key: Thread reader<br/>
151    * value: ReadLockCount readLockCount
152    */

153   private HashMap JavaDoc currentReadThreads = new HashMap JavaDoc();
154
155   /**
156    * This map is used to save the read locks while releasing them when
157    * aquiring a write lock. To prevent dead locks, acquireWrite() releases
158    * all read locks of the current thread and releaseLock() acquires them again.
159    */

160   private HashMap JavaDoc savedReadLocks = new HashMap JavaDoc();
161
162
163   /**
164    * This class exists to allow to put a changeable int into a Map.
165    * For each reading thread, we store how many read locks this thread owns.
166    * This is necessary to temporarily release all read locks when acquiring
167    * a write lock - and of course, to restore the read locks when releasing
168    * the write lock, too.
169    */

170   private static class ReadLockCount
171   {
172     public int readLockCount = 0;
173   }
174
175   /**
176    * This integer counts, how many threads are currently trying to acquire a
177    * write lock. This is used to give writers priority. This means: If a
178    * waiting writer exists, noone can acquire new read locks - except if the
179    * thread already owns at least one read or write lock.
180    */

181   private int waitingWriters = 0;
182
183   /**
184    * This is a counter to know how many threads are currently trying to acquire
185    * a read lock. This is only used for statistics and debugging.
186    */

187   private int waitingReaders = 0;
188
189   /**
190    * All the methods of this class use this object to synchronize.
191    */

192   private Object JavaDoc mutex = new Object JavaDoc();
193
194   private RWLockMan rwLockMan;
195
196   private String JavaDoc rwLockName = null;
197
198   public RWLock()
199   {
200     rwLockMan = RWLockMan.getRWLockMan();
201   }
202
203   /**
204    * Use this constructor to define a name for the lock. This name provides
205    * no functionality, but in case of a dead lock exception, you might be
206    * thankful to get names instead of memory addresses.
207    *
208    * @param _rwLockName The name for this lock.
209    */

210   public RWLock(String JavaDoc _rwLockName)
211   {
212     this();
213     this.rwLockName = _rwLockName;
214   }
215
216   /**
217    * Use this constructor to define a name for the lock. This name provides
218    * no functionality, but in case of a dead lock exception, you might be
219    * thankful to get names instead of memory addresses.
220    * <p>
221    * If you create semi-static rwLockNames (e.g. use the class name of the protected
222    * object), you might want to have the memory address additionally. Thus,
223    * you can set appendMemAddr true.
224    *
225    * @param _rwLockName The name for this lock.
226    * @param appendMemAddr Whether or not to append the memory address.
227    */

228   public RWLock(String JavaDoc _rwLockName, boolean appendMemAddr)
229   {
230     this();
231     if (appendMemAddr) {
232       StringBuffer JavaDoc sb = new StringBuffer JavaDoc(_rwLockName.length()+10);
233       sb.append(_rwLockName);
234       sb.append('@');
235       sb.append(Integer.toHexString(hashCode()));
236       this.rwLockName = sb.toString();
237     }
238     else
239       this.rwLockName = _rwLockName;
240   }
241
242   /**
243    * Use this method to find out, how many threads are currently waiting for
244    * a write lock.
245    *
246    * @return Number of threads trying to acquire a write lock.
247    */

248   public int getWaitingWriters()
249   {
250     synchronized (mutex) { return waitingWriters; }
251   }
252
253   /**
254    * Use this method to find out, how many threads are currently waiting for
255    * a read lock.
256    *
257    * @return Number of threads trying to acquire a read lock.
258    */

259   public int getWaitingReaders()
260   {
261     synchronized (mutex) { return waitingReaders; }
262   }
263
264   /**
265    * Use this method to find out, who owns the write lock. If there is no write
266    * lock existing, this method returns <code>null</code>
267    *
268    * @return The thread that owns the write lock or <code>null</code>.
269    */

270   public Runnable JavaDoc getCurrentWriteThread()
271   {
272     synchronized (mutex) { return currentWriteThread; }
273   }
274
275
276   /**
277    * Use this method to acquire a read lock. <b>Never forget to release the lock!!!</b>
278    * The best thing is to use try..finally-blocks to ensure the release.
279    *
280    * @throws DeadLockException If the try to acquire a lock would lead into a
281    * dead lock, a DeadLockException is thrown. Dead locks are detected immediately.
282    * Because DeadLockException is inherited from RuntimeException, you don't have
283    * to declare it.
284    *
285    * @see #releaseLock()
286    */

287   public void acquireReadLock()
288   throws DeadLockException
289   {
290     ReadLockCount rlc = null;
291
292     synchronized(mutex) {
293       Thread JavaDoc currentThread = Thread.currentThread();
294
295       waitingReaders++;
296       try {
297
298         // Wenn der aktuelle Thread ein WriteLock besitzt, darf natürlich nicht
299
// geprüft und gewartet werden. In dem Fall muß er natürlich sofort
300
// ein ReadLock zugestanden bekommen.
301
if (currentWriteThread != currentThread) {
302
303           // wenn der aktuelle Thread bereits ein Read-Lock hat, kann
304
// es kein *fremdes* Write-Lock geben (eigenes schon) und wir duerfen nicht
305
// testen & warten.
306
// Dies würde nämlich zu einem Deadlock führen, falls der gleiche
307
// Thread mehr readLocks will, aber ein anderer Thread auf einen WriteLock
308
// wartet, weil WriteLocks Priorität haben.
309
rlc = (ReadLockCount)currentReadThreads.get(currentThread);
310           if (rlc == null || rlc.readLockCount == 0) {
311
312             if ((currentLocks < 0) || (waitingWriters != 0)) {
313               rwLockMan.beginWaitForLock(this, RWLockMan.MODE_READ); // here, a DeadLockException might be thrown.
314
try {
315
316                 try {
317                   while ((currentLocks < 0) || (waitingWriters != 0))
318                     mutex.wait();
319                 } catch (InterruptedException JavaDoc x) {
320                   throw new IllegalThreadStateException JavaDoc("Waiting interrupted. Can't continue work in this situation, because threads my collide!");
321                 }
322
323               } finally {
324                 rwLockMan.endWaitForLock(this, RWLockMan.MODE_READ);
325               }
326             } // if ((currentLocks < 0) || (waitingWriters != 0)) {
327

328             if (rlc == null) {
329               rlc = new ReadLockCount();
330               currentReadThreads.put(currentThread, rlc);
331             } // if (rlc == null) {
332
} // if (rlc == null || rlc.readLockCount == 0) {
333

334         } // if (currentWriteThread != currentThread) {
335

336       } finally {
337         waitingReaders--;
338       }
339
340       rwLockMan.acquireLock(this, RWLockMan.MODE_READ, 1);
341
342       // register the current read lock to be able to release it.
343
ArrayList JavaDoc lockStack = (ArrayList JavaDoc)lockStacksByThread.get(currentThread);
344       if (lockStack == null) {
345         lockStack = new ArrayList JavaDoc();
346         lockStacksByThread.put(currentThread, lockStack);
347       }
348       lockStack.add(new Boolean JavaDoc(false)); // Boolean isWriteLock
349

350 // if (currentWriteThread != currentThread) {
351
if (currentLocks >= 0) {
352         currentLocks++;
353         rlc.readLockCount++;
354       }
355       else {
356         rlc = (ReadLockCount)savedReadLocks.get(currentThread);
357         if (rlc == null) {
358           rlc = new ReadLockCount();
359           savedReadLocks.put(currentThread, rlc);
360         }
361         rlc.readLockCount++;
362       } // if (currentWriteThread == currentThread) {
363

364     } // synchronized(mutex) {
365
}
366
367   /**
368    * Use this method to acquire a write lock. <b>Never forget to release the lock!!!</b>
369    * The best thing is to use try..finally-blocks to ensure the release.
370    *
371    * @throws DeadLockException If the try to acquire a lock would lead into a
372    * dead lock, a DeadLockException is thrown. Dead locks are detected immediately.
373    * Because DeadLockException is inherited from RuntimeException, you don't have
374    * to declare it.
375    *
376    * @see #releaseLock()
377    */

378   public void acquireWriteLock()
379   throws DeadLockException
380   {
381     synchronized(mutex) {
382       Thread JavaDoc currentThread = Thread.currentThread();
383
384       waitingWriters++;
385       try {
386
387         if (currentThread != currentWriteThread) {
388
389           // A dead lock exception might be thrown by rwLockMan.beginWaitForLock(...).
390
// Thus, we have to execute this method, before releasing the read locks
391
// to make sure, our lock doesn't get corrupted by the DeadLockException.
392
boolean endWaitInRWLockMan = false;
393           if (currentLocks != 0) {
394             rwLockMan.beginWaitForLock(this, RWLockMan.MODE_WRITE);
395             endWaitInRWLockMan = true;
396           }
397           try { // finally ends wait in rwLockMan
398

399             // aktuelle ReadLocks sichern und temporaer releasen
400
ReadLockCount rlc = (ReadLockCount)currentReadThreads.get(currentThread);
401             if (rlc != null && rlc.readLockCount > 0) {
402               savedReadLocks.put(currentThread, rlc);
403               currentReadThreads.remove(currentThread);
404               currentLocks -= rlc.readLockCount;
405               if (currentLocks < 0)
406                 throw new IllegalStateException JavaDoc(currentThread+": currentLocks < 0!!!");
407
408               rwLockMan.releaseLock(this, RWLockMan.MODE_READ, rlc.readLockCount);
409
410               mutex.notifyAll();
411             }
412
413             if (currentLocks != 0) {
414               try {
415                 while (currentLocks != 0)
416                   mutex.wait();
417               } catch (InterruptedException JavaDoc x) {
418                 throw new IllegalThreadStateException JavaDoc(currentThread+": Waiting interrupted. Can't continue work in this situation, because threads my collide!");
419               }
420             } // if (currentLocks != 0) {
421

422           } finally {
423             if (endWaitInRWLockMan)
424               rwLockMan.endWaitForLock(this, RWLockMan.MODE_WRITE);
425           }
426
427           currentWriteThread = currentThread;
428         } // if (currentThread != currentWriteThread) {
429

430       } finally {
431         waitingWriters--;
432       }
433
434       rwLockMan.acquireLock(this, RWLockMan.MODE_WRITE, 1);
435
436       ArrayList JavaDoc lockStack = (ArrayList JavaDoc)lockStacksByThread.get(currentThread);
437       if (lockStack == null) {
438         lockStack = new ArrayList JavaDoc();
439         lockStacksByThread.put(currentThread, lockStack);
440       }
441       lockStack.add(new Boolean JavaDoc(true)); // Boolean isWriteLock
442

443       currentLocks--;
444     } // synchronized(mutex) {
445
}
446
447   /**
448    * This map is needed to know in releaseLock(), whether we have to release
449    * a read or a write lock, because they can be cascaded.
450    * <p>
451    * key: Thread theTread<br/>
452    * value: Vector of (Boolean isWriteLock)
453    * <p>
454    * The last entry in the vector is always the newest.
455    */

456   private HashMap JavaDoc lockStacksByThread = new HashMap JavaDoc();
457
458
459   /**
460    * Use this method to release a lock. The method knows whether it must
461    * release a read or a write lock. You should always use try...finally-blocks
462    * in the following way:
463    * <p><blockquote><pre>
464    * RWLock rwLock = new RWLock();
465    * :
466    * :
467    * rwLock.acquireReadLock();
468    * try {
469    * :
470    * :
471    * } finally {
472    * rwLock.releaseLock();
473    * }
474    * </pre></blockquote>
475    * <p>
476    * Note, that there must be a call of releaseLock() for each corresponding acquire*Lock(),
477    * if you cascade them.
478    *
479    * @see #acquireReadLock()
480    * @see #acquireWriteLock()
481    */

482   public void releaseLock()
483   {
484     synchronized(mutex) {
485       Thread JavaDoc currentThread = Thread.currentThread();
486
487       if (currentLocks == 0)
488         throw new IllegalStateException JavaDoc(currentThread+": currentLocks == 0: releaseLock called without previous acquire!");
489
490       ArrayList JavaDoc lockStack = (ArrayList JavaDoc)lockStacksByThread.get(currentThread);
491       if (lockStack == null)
492         throw new IllegalStateException JavaDoc(currentThread+": No lock stack registered for current thread!");
493
494       if (lockStack.isEmpty())
495         throw new IllegalStateException JavaDoc(currentThread+": lock stack of current thread is empty!");
496
497       Boolean JavaDoc releaseWriteLock = (Boolean JavaDoc)lockStack.remove(lockStack.size()-1);
498
499       if (releaseWriteLock.booleanValue()) { // we release a write lock
500

501         if (currentLocks > 0)
502           throw new IllegalStateException JavaDoc(currentThread+": currentLocks > 0, but we are trying to release a write lock!");
503
504         currentLocks++;
505
506         if (currentLocks == 0) {
507           currentWriteThread = null;
508
509           // gesicherte ReadLocks wiederherstellen
510
ReadLockCount rlc = (ReadLockCount)savedReadLocks.remove(currentThread);
511           if (rlc != null) {
512             if (rlc.readLockCount > 0) {
513 // mutex.notifyAll(); // andere waiting writers wecken!
514
//
515
// try {
516
// while ((currentLocks < 0) || (waitingWriters != 0))
517
// mutex.wait();
518
// } catch (InterruptedException x) {
519
// throw new IllegalThreadStateException("Waiting interrupted. Can't continue work in this situation, because threads my collide!");
520
// }
521
//
522
// // restore, when it's save to do it.
523
// The above behaviour is not that logical and it is not necessary, thus we
524
// use the below.
525

526               // we restore the read locks immediately, without giving other writers
527
// the possibility to write, because it's more logical, that the
528
// protected variable(s) can't change the value during a releaseLock.
529
// It is necessary that others can write while one is trying to acquire
530
// a write lock, because we might run into deadlocks otherwise, but
531
// for the release lock it's not necessary.
532
currentLocks += rlc.readLockCount;
533               currentReadThreads.put(currentThread, rlc);
534
535               rwLockMan.acquireLock(this, RWLockMan.MODE_READ, rlc.readLockCount);
536             } // if (rlc.readLockCount > 0) {
537
} // if (rlc != null) {
538

539         } // if (currentLocks == 0) {
540

541         rwLockMan.releaseLock(this, RWLockMan.MODE_WRITE, 1);
542
543       }
544       else { // we release a read lock
545

546         if (currentLocks < 0) { // there is a write lock currently active! If everything is correct, this must be owned by the current thread!
547
if (currentThread != currentWriteThread)
548             throw new IllegalStateException JavaDoc(currentThread+": Current thread is not current write thread! Why are we here?");
549
550
551           ReadLockCount rlc = (ReadLockCount)savedReadLocks.get(currentThread);
552
553           if (rlc == null || rlc.readLockCount == 0)
554             throw new IllegalStateException JavaDoc(currentThread+": Current thread does not have a read lock set, but tries to release one!");
555
556           rlc.readLockCount--;
557         } // if (currentLocks < 0) {
558
else {
559           ReadLockCount rlc = (ReadLockCount)currentReadThreads.get(currentThread);
560           if (rlc == null || rlc.readLockCount == 0)
561             throw new IllegalStateException JavaDoc(currentThread+": Current thread does not have a read lock set, but tries to release one!");
562
563           currentLocks--;
564           rlc.readLockCount--;
565         } // if (currentLocks > 0) {
566

567         rwLockMan.releaseLock(this, RWLockMan.MODE_READ, 1);
568
569       }
570
571       mutex.notifyAll();
572     } // synchronized(mutex) {
573
}
574
575   /**
576    * Create a <code>String</code>-representation of this object.
577    * Overridden method.
578    *
579    * @see java.lang.Object#toString()
580    * @return The <code>String</code>-representation of this object.
581    */

582   public String JavaDoc toString()
583   {
584     synchronized(mutex) {
585       StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
586       sb.append(this.getClass().getName());
587
588       if (rwLockName == null) {
589         sb.append('@');
590         sb.append(Integer.toHexString(hashCode()));
591         sb.append('{');
592       }
593       else {
594         sb.append("{name=");
595         sb.append(rwLockName);
596         sb.append(',');
597       }
598       sb.append("status=");
599       sb.append(currentLocks);
600       sb.append(",waitingReaders=");
601       sb.append(waitingReaders);
602       sb.append(",waitingWriters=");
603       sb.append(waitingWriters);
604       sb.append(",currentWriteThread=");
605       sb.append(currentWriteThread);
606       sb.append('}');
607       return sb.toString();
608     } // synchronized(mutex) {
609
}
610
611 }
Popular Tags