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 }