View Javadoc

1   /**
2    * Copyright 2008 Atlassian Pty Ltd 
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  
17  package com.atlassian.util.concurrent;
18  
19  import static com.atlassian.util.concurrent.Assertions.notNull;
20  
21  import java.lang.ref.ReferenceQueue;
22  import java.lang.ref.WeakReference;
23  import java.util.concurrent.ConcurrentHashMap;
24  import java.util.concurrent.ConcurrentMap;
25  import java.util.concurrent.locks.Lock;
26  import java.util.concurrent.locks.ReentrantLock;
27  
28  /**
29   * {@link WeakLockMap} holds a {@link Lock} per Descriptor.
30   * <p>
31   * Each {@link Lock} is {@link WeakReference weakly referenced} internally.
32   * 
33   * @param <D> comparable descriptor, should have a good hash function.
34   */
35  class WeakLockMap<D> implements Function<D, Lock> {
36      private final ConcurrentMap<D, LockReference<D>> locks;
37      private final ReferenceQueue<Lock> queue = new ReferenceQueue<Lock>();
38  
39      /**
40       * Construct a new {@link WeakLockMap} instance.
41       * 
42       * @param initialCapacity how large the internal map should be initially.
43       * @throws IllegalArgumentException if the initial capacity of elements is negative.
44       */
45      WeakLockMap(final int initialCapacity) {
46          locks = new ConcurrentHashMap<D, LockReference<D>>(initialCapacity);
47      }
48  
49      /**
50       * Get a Lock for the supplied Descriptor.
51       * 
52       * @param descriptor must not be null
53       * @return descriptor lock
54       */
55      public Lock get(final D descriptor) {
56          expungeStaleEntries();
57          notNull("descriptor", descriptor);
58          while (true) {
59              final LockReference<D> reference = locks.get(descriptor);
60              if (reference != null) {
61                  final Lock lock = reference.get();
62                  if (lock != null) {
63                      return lock;
64                  }
65                  locks.remove(descriptor, reference);
66              }
67              locks.putIfAbsent(descriptor, new LockReference<D>(descriptor, queue));
68          }
69      }
70  
71      // expunge descriptors whose lock reference has been collected
72      @SuppressWarnings("unchecked") private void expungeStaleEntries() {
73          LockReference<D> ref;
74          while ((ref = (LockReference<D>) queue.poll()) != null) {
75              final D descriptor = ref.getDescriptor();
76              if (descriptor == null) {
77                  // this should not be necessary as it should not be able to be null - but we have seen it!
78                  continue;
79              }
80              locks.remove(descriptor, ref);
81          }
82      }
83  
84      /**
85       * A weak reference that maintains a reference to the descriptor so that it can be removed from
86       * the map when garbage collected
87       */
88      static final class LockReference<D> extends WeakReference<Lock> {
89          private final D descriptor;
90  
91          public LockReference(final D descriptor, final ReferenceQueue<? super Lock> q) {
92              super(new ReentrantLock(), q);
93              this.descriptor = notNull("descriptor", descriptor);
94          }
95  
96          final D getDescriptor() {
97              return descriptor;
98          }
99      }
100 }