net.sf.ehcache.concurrent.StripedReadWriteLockSync Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache-core Show documentation
Show all versions of ehcache-core Show documentation
This is the ehcache core module. Pair it with other modules for added functionality.
/**
* Copyright 2003-2010 Terracotta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.ehcache.concurrent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import net.sf.ehcache.CacheException;
/**
* Provides a number of Sync which allow fine-grained concurrency. Rather than locking a cache or a store,
* the individual elements or constituent objects can be locked. This dramatically increases
* the possible concurrency.
*
* The more stripes, the higher the concurrency. To be threadsafe, the instance of CacheLockProvider needs to be
* maintained for the entire life of the cache or store, so there is some added memory use.
*
* Though a new class, this code has been refactored from BlockingCache
, where it has been in use
* in highly concurrent production environments for years.
*
* Based on the lock striping concept from Brian Goetz. See Java Concurrency in Practice 11.4.3
* @author Alex Snaps
*/
public class StripedReadWriteLockSync implements CacheLockProvider {
/**
* The default number of locks to use. Must be a power of 2.
*
* The choice of 2048 enables 2048 concurrent operations per cache or cache store, which should be enough for most
* uses.
*/
public static final int DEFAULT_NUMBER_OF_MUTEXES = 2048;
private final ReadWriteLockSync[] mutexes;
private final int numberOfStripes;
/**
* Constructs a striped mutex with the default 2048 stripes.
*/
public StripedReadWriteLockSync() {
this(DEFAULT_NUMBER_OF_MUTEXES);
}
/**
* Constructs a striped mutex with the default 2048 stripes.
*
* The number of stripes determines the number of concurrent operations per cache or cache store.
* @param numberOfStripes - must be a factor of two
*/
public StripedReadWriteLockSync(int numberOfStripes) {
if (numberOfStripes % 2 != 0) {
throw new CacheException("Cannot create a CacheLockProvider with an odd number of stripes");
}
if (numberOfStripes == 0) {
throw new CacheException("A zero size CacheLockProvider does not have useful semantics.");
}
this.numberOfStripes = numberOfStripes;
mutexes = new ReadWriteLockSync[numberOfStripes];
for (int i = 0; i < numberOfStripes; i++) {
mutexes[i] = new ReadWriteLockSync();
}
}
/**
* Gets the Sync Stripe to use for a given key.
*
* This lookup must always return the same Sync for a given key.
*
* @param key the key
* @return one of a limited number of Sync's.
*/
public ReadWriteLockSync getSyncForKey(final Object key) {
int lockNumber = ConcurrencyUtil.selectLock(key, numberOfStripes);
return mutexes[lockNumber];
}
/**
* {@inheritDoc}
*/
public Sync[] getAndWriteLockAllSyncForKeys(Object... keys) {
SortedMap locks = getLockMap(keys);
Sync[] syncs = new Sync[locks.size()];
int i = 0;
for (Map.Entry entry : locks.entrySet()) {
while (entry.getValue().getAndDecrement() > 0) {
entry.getKey().lock(LockType.WRITE);
}
syncs[i++] = entry.getKey();
}
return syncs;
}
/**
* {@inheritDoc}
*/
public Sync[] getAndWriteLockAllSyncForKeys(long timeout, Object... keys) throws TimeoutException {
SortedMap locks = getLockMap(keys);
boolean lockHeld;
List heldLocks = new ArrayList();
Sync[] syncs = new Sync[locks.size()];
int i = 0;
for (Map.Entry entry : locks.entrySet()) {
while (entry.getValue().getAndDecrement() > 0) {
try {
ReadWriteLockSync writeLockSync = entry.getKey();
lockHeld = writeLockSync.tryLock(LockType.WRITE, timeout);
if (lockHeld) {
heldLocks.add(writeLockSync);
}
} catch (InterruptedException e) {
lockHeld = false;
}
if (!lockHeld) {
for (int j = heldLocks.size() - 1; j >= 0; j--) {
ReadWriteLockSync readWriteLockSync = heldLocks.get(j);
readWriteLockSync.unlock(LockType.WRITE);
}
throw new TimeoutException("could not acquire all locks in " + timeout + " ms");
}
}
syncs[i++] = entry.getKey();
}
return syncs;
}
/**
* {@inheritDoc}
*/
public void unlockWriteLockForAllKeys(Object... keys) {
SortedMap locks = getLockMap(keys);
for (Map.Entry entry : locks.entrySet()) {
while (entry.getValue().getAndDecrement() > 0) {
entry.getKey().unlock(LockType.WRITE);
}
}
}
private SortedMap getLockMap(final Object... keys) {
SortedMap locks = new TreeMap();
for (Object key : keys) {
ReadWriteLockSync syncForKey = getSyncForKey(key);
if (locks.containsKey(syncForKey)) {
locks.get(syncForKey).incrementAndGet();
} else {
locks.put(syncForKey, new AtomicInteger(1));
}
}
return locks;
}
}