All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hortonworks.registries.common.SlotSynchronizer Maven / Gradle / Ivy

/**
 * Copyright 2016 Hortonworks.
 *
 * 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 com.hortonworks.registries.common;

import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

/**
 * A synchronization utility that allows a thread to take a mutual exclusion lock on a specific slot which is for a given Key K.
 * 

* Each instance of this class can have multiple slots and one can take a lock on a specific slot, execute statements and unlock. * So, there can be contention for each slot but no contention among different slots. *

* Locking and unlocking a slot using {@link SlotSynchronizer} can be done using try/finally block like below. *

 * class Service {
 *   private final SlotSynchronizer<K> slotSynchronizer = new SlotSynchronizer<>();
 *   // ...
 *
 *   public void invoke() {
 *     K key = ...
 *     Lock lock = slotSynchronizer.runInSlot(key)
 *     try {
 *       //  ... block of code
 *     } finally {
 *         lock.unlock();
 *     }
 *   }
 * }
 * 
*

* There is a way to run a block of code wrapped in {@link Runnable} with {@link #runInSlot(Object, Runnable)} like below. *

*

 * class Service {
 *   private final SlotSynchronizer<K> slotSynchronizer = new SlotSynchronizer<>();
 *   // ...
 *
 *   public void invoke() {
 *     K key = ...
 *     slotSynchronizer.runInSlot(key, new Runnable() {
 *       public void run() {
 *          // ... block of code
 *       }
 *     }
 *   }
 * }
 * 
* * @param the type of keys for a slot */ public class SlotSynchronizer { private static final Logger LOG = LoggerFactory.getLogger(SlotSynchronizer.class); private final ConcurrentHashMap locks = new ConcurrentHashMap<>(); public final class Lock { private final K k; private AtomicInteger count = new AtomicInteger(); private ReentrantLock reentrantLock = new ReentrantLock(); public Lock(K k) { Preconditions.checkNotNull(k, "Key k must not be null"); this.k = k; } private void lock() { count.incrementAndGet(); reentrantLock.lock(); } /** * Unlocks this lock for respective slot if the current thread holds this lock. * * Current thread should have hold this lock earlier with { SlotSynchronizer#lockSlot(K k)} for key {@code k}. * * @throws IllegalStateException if the current thread does not hold this lock. */ public void unlock() { if (!reentrantLock.isHeldByCurrentThread()) { String msg = String.format("Current thread [%s] does not hold the lock, unlock should have been called by " + "the thread which invoked lock earlier.", Thread.currentThread()); LOG.error(msg); throw new IllegalStateException(msg); } count.decrementAndGet(); // remove this slot if it's count is zero. if(count.get() == 0) { locks.remove(k, this); } reentrantLock.unlock(); } } /** * Returns the lock for the given slot {@code k} after taking a lock for the current thread if it available. * * If the lock is held by another thread then the current thread becomes disabled for thread scheduling * purposes and lies dormant until the lock has been acquired. * * @param k slot key for which lock to be taken. */ public Lock lockSlot(K k) { Preconditions.checkNotNull(k, "Key k must not be null"); while (true) { Lock newLock = new Lock(k); Lock lock = locks.putIfAbsent(k, newLock); if (lock == null) { lock = newLock; } // wait to acquire lock lock.lock(); // below is possible when current lock is incremented after removing that from slot by earlier {@link #unlock} operation. // if acquired lock for that slot is not same as the current lock, unlock the acquired lock and retry again to get a new lock. if (locks.get(k) != lock) { lock.unlock(); continue; } return lock; } } /** * @return no of slots currently used. */ public int occupiedSlots() { return locks.size(); } public void runInSlot(K k, Runnable runnable) { Lock lock = lockSlot(k); try { runnable.run(); } finally { lock.unlock(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy