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 */
018package org.apache.hadoop.hbase.util;
019
020import java.lang.ref.Reference;
021import java.util.concurrent.locks.ReentrantReadWriteLock;
022import org.apache.yetus.audience.InterfaceAudience;
023
024/**
025 * Allows multiple concurrent clients to lock on a numeric id with ReentrantReadWriteLock. The
026 * intended usage for read lock is as follows:
027 *
028 * <pre>
029 * ReentrantReadWriteLock lock = idReadWriteLock.getLock(id);
030 * try {
031 *   lock.readLock().lock();
032 *   // User code.
033 * } finally {
034 *   lock.readLock().unlock();
035 * }
036 * </pre>
037 *
038 * For write lock, use lock.writeLock()
039 */
040@InterfaceAudience.Private
041public class IdReadWriteLock<T> {
042  // The number of lock we want to easily support. It's not a maximum.
043  private static final int NB_CONCURRENT_LOCKS = 1000;
044  /**
045   * The pool to get entry from, entries are mapped by {@link Reference} and will be automatically
046   * garbage-collected by JVM
047   */
048  private final ObjectPool<T, ReentrantReadWriteLock> lockPool;
049  private final ReferenceType refType;
050
051  public IdReadWriteLock() {
052    this(ReferenceType.WEAK);
053  }
054
055  /**
056   * Constructor of IdReadWriteLock
057   * @param referenceType type of the reference used in lock pool, {@link ReferenceType#WEAK} by
058   *                      default. Use {@link ReferenceType#SOFT} if the key set is limited and the
059   *                      locks will be reused with a high frequency
060   */
061  public IdReadWriteLock(ReferenceType referenceType) {
062    this.refType = referenceType;
063    switch (referenceType) {
064      case SOFT:
065        lockPool = new SoftObjectPool<>(new ObjectPool.ObjectFactory<T, ReentrantReadWriteLock>() {
066          @Override
067          public ReentrantReadWriteLock createObject(T id) {
068            return new ReentrantReadWriteLock();
069          }
070        }, NB_CONCURRENT_LOCKS);
071        break;
072      case WEAK:
073      default:
074        lockPool = new WeakObjectPool<>(new ObjectPool.ObjectFactory<T, ReentrantReadWriteLock>() {
075          @Override
076          public ReentrantReadWriteLock createObject(T id) {
077            return new ReentrantReadWriteLock();
078          }
079        }, NB_CONCURRENT_LOCKS);
080    }
081  }
082
083  public static enum ReferenceType {
084    WEAK,
085    SOFT
086  }
087
088  /**
089   * Get the ReentrantReadWriteLock corresponding to the given id
090   * @param id an arbitrary number to identify the lock
091   */
092  public ReentrantReadWriteLock getLock(T id) {
093    lockPool.purge();
094    ReentrantReadWriteLock readWriteLock = lockPool.get(id);
095    return readWriteLock;
096  }
097
098  /** For testing */
099  int purgeAndGetEntryPoolSize() {
100    gc();
101    Threads.sleep(200);
102    lockPool.purge();
103    return lockPool.size();
104  }
105
106  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "DM_GC", justification = "Intentional")
107  private void gc() {
108    System.gc();
109  }
110
111  public void waitForWaiters(T id, int numWaiters) throws InterruptedException {
112    for (ReentrantReadWriteLock readWriteLock;;) {
113      readWriteLock = lockPool.get(id);
114      if (readWriteLock != null) {
115        synchronized (readWriteLock) {
116          if (readWriteLock.getQueueLength() >= numWaiters) {
117            return;
118          }
119        }
120      }
121      Thread.sleep(50);
122    }
123  }
124
125  public ReferenceType getReferenceType() {
126    return this.refType;
127  }
128}