/platform/util/src/com/intellij/util/concurrency/ReentrantWriterPreferenceReadWriteLock.java

https://github.com/machak/intellij-community · Java · 234 lines · 145 code · 26 blank · 63 comment · 36 complexity · fe946ab881d9135d247985ef3e46d537 MD5 · raw file

  1. /*
  2. * Copyright 2000-2009 JetBrains s.r.o.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.intellij.util.concurrency;
  17. import gnu.trove.TIntArrayList;
  18. import java.util.ArrayList;
  19. /**
  20. * A writer-preference ReadWriteLock that allows both readers and
  21. * writers to reacquire
  22. * read or write locks in the style of a ReentrantLock.
  23. * Readers are not allowed until all write locks held by
  24. * the writing thread have been released.
  25. * Among other applications, reentrancy can be useful when
  26. * write locks are held during calls or callbacks to methods that perform
  27. * reads under read locks.
  28. * <p>
  29. * <b>Sample usage</b>. Here is a code sketch showing how to exploit
  30. * reentrancy to perform lock downgrading after updating a cache:
  31. * <pre>
  32. * class CachedData {
  33. * Object data;
  34. * volatile boolean cacheValid;
  35. * ReentrantWriterPreferenceReadWriteLock rwl = ...
  36. *
  37. * void processCachedData() {
  38. * rwl.readLock().acquire();
  39. * if (!cacheValid) {
  40. *
  41. * // upgrade lock:
  42. * rwl.readLock().release(); // must release first to obtain writelock
  43. * rwl.writeLock().acquire();
  44. * if (!cacheValid) { // recheck
  45. * data = ...
  46. * cacheValid = true;
  47. * }
  48. * // downgrade lock
  49. * rwl.readLock().acquire(); // reacquire read without giving up lock
  50. * rwl.writeLock().release(); // release write, still hold read
  51. * }
  52. *
  53. * use(data);
  54. * rwl.readLock().release();
  55. * }
  56. * }
  57. * </pre>
  58. *
  59. *
  60. * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>]
  61. * @see ReentrantLock
  62. **/
  63. public class ReentrantWriterPreferenceReadWriteLock extends WriterPreferenceReadWriteLock {
  64. /** Number of acquires on write lock by activeWriter_ thread **/
  65. private long writeHolds_ = 0;
  66. private final ThreadToCountMap readers_ = new ThreadToCountMap();
  67. private final ThreadLocal<Boolean> hasReadLock = new ThreadLocal<Boolean>() {
  68. @Override
  69. protected Boolean initialValue() {
  70. return Boolean.FALSE;
  71. }
  72. };
  73. private final ThreadLocal<Boolean> hasWriteLock = new ThreadLocal<Boolean>() {
  74. @Override
  75. protected Boolean initialValue() {
  76. return Boolean.FALSE;
  77. }
  78. };
  79. public boolean isReadLockAcquired() {
  80. return hasReadLock.get();
  81. }
  82. public boolean isWriteLockAcquired() {
  83. return hasWriteLock.get();
  84. }
  85. public synchronized boolean isReadLockAcquired(Thread thread){
  86. return readers_.get(thread) > 0;
  87. }
  88. public synchronized boolean isWriteLockAcquired(Thread thread){
  89. return activeWriter_ == thread;
  90. }
  91. protected boolean allowReader() {
  92. // [Valentin] Changed policy so that readers are allowed while there are waiting writers
  93. // [cdr]: No more!
  94. return (activeWriter_ == null && waitingWriters_ == 0) ||
  95. activeWriter_ == Thread.currentThread();
  96. }
  97. protected synchronized boolean startRead() {
  98. Thread t = Thread.currentThread();
  99. int c = readers_.get(t);
  100. if (c > 0) { // already held -- just increment hold count
  101. readers_.put(t, c + 1);
  102. ++activeReaders_;
  103. return true;
  104. }
  105. else if (allowReader()) {
  106. hasReadLock.set(true);
  107. readers_.put(t, 1);
  108. ++activeReaders_;
  109. return true;
  110. }
  111. else
  112. return false;
  113. }
  114. protected synchronized boolean startWrite() {
  115. if (activeWriter_ == Thread.currentThread()) { // already held; re-acquire
  116. ++writeHolds_;
  117. return true;
  118. }
  119. else if (writeHolds_ == 0) {
  120. if (activeReaders_ == 0 ||
  121. (readers_.size() == 1 &&
  122. readers_.get(Thread.currentThread()) > 0)) {
  123. activeWriter_ = Thread.currentThread();
  124. hasWriteLock.set(true);
  125. writeHolds_ = 1;
  126. return true;
  127. }
  128. else
  129. return false;
  130. }
  131. else
  132. return false;
  133. }
  134. protected synchronized Signaller endRead() {
  135. --activeReaders_;
  136. Thread t = Thread.currentThread();
  137. int c = readers_.get(t);
  138. if (c != 1) { // more than one hold; decrement count
  139. readers_.put(t, c - 1);
  140. return null;
  141. }
  142. else {
  143. readers_.put(t, 0);
  144. hasReadLock.set(false);
  145. if (writeHolds_ > 0) { // a write lock is still held by current thread
  146. return null;
  147. }
  148. else if (/*activeReaders_ == 0 && */activeReaders_ <= 1 && waitingWriters_ > 0) {
  149. // [Valentin] commented out check for activeReaders == 0 - it's incorrect when waiting writer is already a reader!!
  150. return writerLock_;
  151. }
  152. else{
  153. return null;
  154. }
  155. }
  156. }
  157. protected synchronized Signaller endWrite() {
  158. --writeHolds_;
  159. if (writeHolds_ > 0) // still being held
  160. return null;
  161. else {
  162. activeWriter_ = null;
  163. hasWriteLock.set(false);
  164. if (waitingReaders_ > 0 && allowReader())
  165. return readerLock_;
  166. else if (waitingWriters_ > 0)
  167. return writerLock_;
  168. else
  169. return null;
  170. }
  171. }
  172. private static final class ThreadToCountMap{
  173. private final ArrayList<Thread> myThreads = new ArrayList<Thread>();
  174. private final TIntArrayList myCounters = new TIntArrayList();
  175. private Thread myLastThread = null; // optimization
  176. private int myLastCounter;
  177. public int get(Thread thread){
  178. if (thread == myLastThread) return myLastCounter;
  179. int index = myThreads.indexOf(thread);
  180. int result = index >= 0 ? myCounters.getQuick(index) : 0;
  181. myLastThread = thread;
  182. myLastCounter = result;
  183. return result;
  184. }
  185. public void put(Thread thread, int count){
  186. myLastThread = null;
  187. int index = myThreads.indexOf(thread);
  188. if (index >= 0){
  189. if (count == 0){
  190. myThreads.remove(index);
  191. myCounters.remove(index);
  192. }
  193. else{
  194. myCounters.setQuick(index, count);
  195. }
  196. }
  197. else{
  198. if (count != 0){
  199. myThreads.add(thread);
  200. myCounters.add(count);
  201. }
  202. }
  203. }
  204. public int size(){
  205. return myThreads.size();
  206. }
  207. }
  208. }