001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.hbase.util;
020
021import java.util.Arrays;
022import java.util.LinkedHashMap;
023import java.util.Map;
024import java.util.Set;
025import java.util.concurrent.locks.Lock;
026import java.util.concurrent.locks.ReentrantLock;
027
028import org.apache.yetus.audience.InterfaceAudience;
029
030/**
031 * A utility class to manage a set of locks. Each lock is identified by a String which serves
032 * as a key. Typical usage is: <pre>
033 * class Example {
034 *   private final static KeyLocker&lt;String&gt; locker = new Locker&lt;String&gt;();
035 *   public void foo(String s){
036 *     Lock lock = locker.acquireLock(s);
037 *     try {
038 *       // whatever
039 *     }finally{
040 *       lock.unlock();
041 *     }
042 *   }
043 * }
044 * </pre>
045 */
046@InterfaceAudience.Private
047public class KeyLocker<K> {
048  // The number of lock we want to easily support. It's not a maximum.
049  private static final int NB_CONCURRENT_LOCKS = 1000;
050
051  private final WeakObjectPool<K, ReentrantLock> lockPool =
052      new WeakObjectPool<>(
053          new ObjectPool.ObjectFactory<K, ReentrantLock>() {
054            @Override
055            public ReentrantLock createObject(K key) {
056              return new ReentrantLock();
057            }
058          },
059          NB_CONCURRENT_LOCKS);
060
061  /**
062   * Return a lock for the given key. The lock is already locked.
063   *
064   * @param key
065   */
066  public ReentrantLock acquireLock(K key) {
067    if (key == null) throw new IllegalArgumentException("key must not be null");
068
069    lockPool.purge();
070    ReentrantLock lock = lockPool.get(key);
071
072    lock.lock();
073    return lock;
074  }
075
076  /**
077   * Acquire locks for a set of keys. The keys will be
078   * sorted internally to avoid possible deadlock.
079   *
080   * @throws ClassCastException if the given {@code keys}
081   *    contains elements that are not mutually comparable
082   */
083  public Map<K, Lock> acquireLocks(Set<? extends K> keys) {
084    Object[] keyArray = keys.toArray();
085    Arrays.sort(keyArray);
086
087    lockPool.purge();
088    Map<K, Lock> locks = new LinkedHashMap<>(keyArray.length);
089    for (Object o : keyArray) {
090      @SuppressWarnings("unchecked")
091      K key = (K)o;
092      ReentrantLock lock = lockPool.get(key);
093      locks.put(key, lock);
094    }
095
096    for (Lock lock : locks.values()) {
097      lock.lock();
098    }
099    return locks;
100  }
101}