View Javadoc
1   /**
2    *  Copyright Terracotta, Inc.
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 info.magnolia.cache.concurrent;
17  
18  import java.util.Arrays;
19  import java.util.Collections;
20  import java.util.List;
21  import java.util.concurrent.locks.ReadWriteLock;
22  
23  /**
24   * Provides a number of Sync which allow fine-grained concurrency.
25   * Rather than locking a cache or a store,
26   * the individual elements or constituent objects can be locked. This dramatically increases
27   * the possible concurrency.
28   * <p/>
29   * The more stripes, the higher the concurrency. To be threadsafe, the instance of CacheLockProvider needs to be
30   * maintained for the entire life of the cache or store, so there is some added memory use.
31   * <p/>
32   * Though a new class, this code has been refactored from <code>BlockingCache</code>, where it has been in use
33   * in highly concurrent production environments for years.
34   * <p/>
35   * Based on the lock striping concept from Brian Goetz. See Java Concurrency in Practice 11.4.3
36   * @author Alex Snaps
37   */
38  public class StripedReadWriteLockSync {
39  
40      /**
41       * The default number of locks to use. Must be a power of 2.
42       * <p/>
43       * The choice of 2048 enables 2048 concurrent operations per cache or cache store, which should be enough for most
44       * uses.
45       */
46      public static final int DEFAULT_NUMBER_OF_MUTEXES = 2048;
47  
48      private final ReadWriteLockSync[] mutexes;
49      private final List<ReadWriteLockSync> mutexesAsList;
50      /**
51       * Constructs a striped mutex with the default 2048 stripes.
52       */
53      public StripedReadWriteLockSync() {
54          this(DEFAULT_NUMBER_OF_MUTEXES);
55      }
56  
57      /**
58       * Constructs a striped mutex with the default 2048 stripes.
59       * <p/>
60       * The number of stripes determines the number of concurrent operations per cache or cache store.
61       * @param numberOfStripes - must be a factor of two
62       */
63      public StripedReadWriteLockSync(int numberOfStripes) {
64          if ((numberOfStripes & (numberOfStripes - 1)) != 0) {
65              throw new IllegalArgumentException("Cannot create a CacheLockProvider with a non power-of-two number of stripes");
66          }
67          if (numberOfStripes == 0) {
68              throw new IllegalArgumentException("A zero size CacheLockProvider does not have useful semantics.");
69          }
70  
71          mutexes = new ReadWriteLockSync[numberOfStripes];
72  
73          for (int i = 0; i < mutexes.length; i++) {
74              mutexes[i] = new ReadWriteLockSync();
75          }
76          mutexesAsList = Collections.unmodifiableList(Arrays.asList(mutexes));
77      }
78  
79      /**
80       * Gets the Sync Stripe to use for a given key.
81       * <p/>
82       * This lookup must always return the same Sync for a given key.
83       * <p/>
84       * @param key the key
85       * @return one of a limited number of Sync's.
86       */
87      public ReadWriteLockSync getSyncForKey(final Object key) {
88          int lockNumber = ConcurrencyUtil.selectLock(key, mutexes.length);
89          return mutexes[lockNumber];
90      }
91  
92      /**
93       * Gets the RWL Stripe to use for a given key.
94       * <p/>
95       * This lookup must always return the same RWL for a given key.
96       * <p/>
97       * @param key the key
98       * @return one of a limited number of RWLs.
99       */
100     public ReadWriteLock getLockForKey(final Object key) {
101         int lockNumber = ConcurrencyUtil.selectLock(key, mutexes.length);
102         return mutexes[lockNumber].getReadWriteLock();
103     }
104 
105     /**
106      * Returns all internal syncs.
107      * @return all internal syncs
108      */
109     public List<ReadWriteLockSync> getAllSyncs() {
110         return mutexesAsList;
111     }
112 }