/* * JBoss, Home of Professional Open Source * Copyright 2009 Red Hat Inc. and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.util.concurrent.locks; import net.jcip.annotations.ThreadSafe; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * A simple implementation of lock striping, using cache entry keys to lock on, primarily used to help make {@link * org.infinispan.loaders.CacheLoader} implemtations thread safe. * <p/> * Backed by a set of {@link java.util.concurrent.locks.ReentrantReadWriteLock} instances, and using the key hashcodes * to determine buckets. * <p/> * Since buckets are used, it doesn't matter that the key in question is not removed from the lock map when no longer in * use, since the key is not referenced in this class. Rather, the hash code is used. * <p/> * * @author <a href="mailto:manik@jboss.org">Manik Surtani</a> * @author Mircea.Markus@jboss.com * @since 4.0 */ @ThreadSafe public class StripedLock { private static final Log log = LogFactory.getLog(StripedLock.class); private static final boolean trace = log.isTraceEnabled(); private static final int DEFAULT_CONCURRENCY = 20; private final int lockSegmentMask; private final int lockSegmentShift; final ReentrantReadWriteLock[] sharedLocks; /** * This constructor just calls {@link #StripedLock(int)} with a default concurrency value of 20. */ public StripedLock() { this(DEFAULT_CONCURRENCY); } /** * Creates a new StripedLock which uses a certain number of shared locks across all elements that need to be locked. * * @param concurrency number of threads expected to use this class concurrently. */ public StripedLock(int concurrency) { int tempLockSegShift = 0; int numLocks = 1; while (numLocks < concurrency) { ++tempLockSegShift; numLocks <<= 1; } lockSegmentShift = 32 - tempLockSegShift; lockSegmentMask = numLocks - 1; sharedLocks = new ReentrantReadWriteLock[numLocks]; for (int i = 0; i < numLocks; i++) { sharedLocks[i] = new ReentrantReadWriteLock(); } } /** * Blocks until a lock is acquired. * * @param exclusive if true, a write (exclusive) lock is attempted, otherwise a read (shared) lock is used. */ public void acquireLock(Object key, boolean exclusive) { ReentrantReadWriteLock lock = getLock(key); if (exclusive) { lock.writeLock().lock(); if (trace) log.tracef("WL acquired for '%s'", key); } else { lock.readLock().lock(); if (trace) log.tracef("RL acquired for '%s'", key); } } public boolean acquireLock(Object key, boolean exclusive, long millis) { ReentrantReadWriteLock lock = getLock(key); try { if (exclusive) { boolean success = lock.writeLock().tryLock(millis, TimeUnit.MILLISECONDS); if (success && trace) log.tracef("WL acquired for '%s'", key); return success; } else { boolean success = lock.readLock().tryLock(millis, TimeUnit.MILLISECONDS); if (success && trace) log.tracef("RL acquired for '%s'", key); return success; } } catch (InterruptedException e) { log.interruptedAcquiringLock(millis, e); return false; } } /** * Releases a lock the caller may be holding. This method is idempotent. */ public void releaseLock(Object key) { ReentrantReadWriteLock lock = getLock(key); if (lock.isWriteLockedByCurrentThread()) { lock.writeLock().unlock(); if (trace) log.tracef("WL released for '%s'", key); } else { lock.readLock().unlock(); if (trace) log.tracef("RL released for '%s'", key); } } public void upgradeLock(Object key) { ReentrantReadWriteLock lock = getLock(key); lock.readLock().unlock(); // another thread could come here and take the RL or WL, forcing us to wait lock.writeLock().lock(); if (trace) log.tracef("RL upgraded to WL for '%s'", key); } public void downgradeLock(Object key) { ReentrantReadWriteLock lock = getLock(key); lock.readLock().lock(); lock.writeLock().unlock(); if (trace) log.tracef("WL downgraded to RL for '%s'", key); } final ReentrantReadWriteLock getLock(Object o) { return sharedLocks[hashToIndex(o)]; } final int hashToIndex(Object o) { return hash(o) >>> lockSegmentShift & lockSegmentMask; } /** * Returns a hash code for non-null Object x. Uses the same hash code spreader as most other java.util hash tables, * except that this uses the string representation of the object passed in. * * @param x the object serving as a key * @return the hash code */ static final int hash(Object x) { int h = x.hashCode(); h ^= h >>> 20 ^ h >>> 12; return h ^ h >>> 7 ^ h >>> 4; } /** * Releases locks on all keys passed in. Makes multiple calls to {@link #releaseLock(Object)}. This method is * idempotent. * * @param keys keys to unlock */ public void releaseAllLocks(List<Object> keys) { for (Object k : keys) { releaseLock(k); } } /** * Acquires locks on keys passed in. Makes multiple calls to {@link #acquireLock(Object, boolean)} * * @param keys keys to unlock * @param exclusive whether locks are exclusive. */ public void acquireAllLocks(List<Object> keys, boolean exclusive) { for (Object k : keys) { acquireLock(k, exclusive); } } /** * Returns the total number of locks held by this class. */ public int getTotalLockCount() { int count = 0; for (ReentrantReadWriteLock lock : sharedLocks) { count += lock.getReadLockCount(); count += lock.isWriteLocked() ? 1 : 0; } return count; } /** * Acquires RL on all locks agregated by this StripedLock, in the given timeout. */ public boolean acquireGlobalLock(boolean exclusive, long timeout) { log.tracef("About to acquire global lock. Exclusive? %s", exclusive); boolean success = true; for (int i = 0; i < sharedLocks.length; i++) { Lock toAcquire = exclusive ? sharedLocks[i].writeLock() : sharedLocks[i].readLock(); try { success = toAcquire.tryLock(timeout, TimeUnit.MILLISECONDS); if (!success) { if (trace) log.tracef("Could not acquire lock on %s. Exclusive? %b", toAcquire, exclusive); break; } } catch (InterruptedException e) { if (trace) log.trace("Caught InterruptedException while trying to acquire global lock", e); success = false; Thread.currentThread().interrupt(); } finally { if (!success) { for (int j = 0; j < i; j++) { Lock toRelease = exclusive ? sharedLocks[j].writeLock() : sharedLocks[j].readLock(); toRelease.unlock(); } } } } return success; } public void releaseGlobalLock(boolean exclusive) { for (ReentrantReadWriteLock lock : sharedLocks) { Lock toRelease = exclusive ? lock.writeLock() : lock.readLock(); toRelease.unlock(); } } public int getTotalReadLockCount() { int count = 0; for (ReentrantReadWriteLock lock : sharedLocks) { count += lock.getReadLockCount(); } return count; } public int getSharedLockCount() { return sharedLocks.length; } public int getTotalWriteLockCount() { int count = 0; for (ReentrantReadWriteLock lock : sharedLocks) { count += lock.isWriteLocked() ? 1 : 0; } return count; } }