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

org.kiwiproject.concurrent.StripedLock Maven / Gradle / Ivy

Go to download

Kiwi is a utility library. We really like Google's Guava, and also use Apache Commons. But if they don't have something we need, and we think it is useful, this is where we put it.

There is a newer version: 4.5.2
Show newest version
package org.kiwiproject.concurrent;

import static org.apache.commons.lang3.StringUtils.isBlank;

import com.google.common.util.concurrent.Striped;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.function.Supplier;

/**
 * {@link StripedLock} provides simple lambdas for encapsulating a block of code with a read/write lock.
 * 

* The number of "stripes" indicates the maximum concurrent processes where the lock "key" is hashed and is used to * select a lock. This is useful when you want tasks that operate in the same "context" to block one another without * blocking unrelated tasks. * * @implNote This {@link StripedLock} uses Guava's {@link Striped} under the covers. The {@link ReadWriteLock}s * are re-entrant, and read locks can be held by multiple readers, while write locks are exclusive. * @see Striped * @see ReadWriteLock */ @SuppressWarnings("UnstableApiUsage") // Guava's Striped is marked @Beta (but has been in Guava since 13, a long time) @Slf4j public class StripedLock { private static final String DEFAULT_KEY_WHEN_BLANK = "BLANK-LOCK-KEY"; private static final int DEFAULT_NUM_STRIPES = Runtime.getRuntime().availableProcessors() * 4; private final Striped lock; /** * Creates a new {@link StripedLock}, using {@link #DEFAULT_KEY_WHEN_BLANK} as the number of stripes. */ public StripedLock() { this(Striped.readWriteLock(DEFAULT_NUM_STRIPES)); } /** * Creates a new {@link StripedLock} with the given number of stripes. * * @param numStripes number of stripes * @see Striped#readWriteLock(int) */ public StripedLock(int numStripes) { this(Striped.readWriteLock(numStripes)); } /** * Create a new {@link StripedLock} using the given {@link ReadWriteLock}. This is useful if you have * multiple interdependent usages that you want to share across the same set of locks. * * @param lock the striped lock to use */ public StripedLock(Striped lock) { this.lock = lock; } /** * Execute a {@link Runnable} task using the provided lock key and associated a READ lock. *

* This implementation will block until the read lock is acquired. * * @param lockKey the lock key * @param task the task to run */ public void runWithReadLock(String lockKey, Runnable task) { supplyWithReadLock(lockKey, () -> { task.run(); return null; }); } /** * Execute a {@link Supplier} using the provided lock key and associated READ lock. *

* This implementation will block until the read lock is acquired. * * @param lockKey the lock key * @param task the task to supply a value * @param the type of object being supplied * @return the supplied value */ public T supplyWithReadLock(String lockKey, Supplier task) { var nonNullKey = ensureNonBlankKey(lockKey); var readWriteLock = lock.get(nonNullKey); var lockHashCode = extractHashCode(readWriteLock); LOG.trace("Locking read lock {} for key {}", lockHashCode, nonNullKey); readWriteLock.readLock().lock(); try { LOG.trace("Running task with read lock {} for key {}", lockHashCode, nonNullKey); return task.get(); } finally { LOG.trace("Unlocking read lock {} for key {}", lockHashCode, nonNullKey); readWriteLock.readLock().unlock(); } } /** * Execute a {@link Runnable} task using the provided lock key and associated a WRITE lock. *

* This implementation will block until the write lock is acquired. * * @param lockKey the lock key * @param task the task to run */ public void runWithWriteLock(String lockKey, Runnable task) { supplyWithWriteLock(lockKey, () -> { task.run(); return null; }); } /** * Execute a {@link Supplier} using the provided lock key and associated WRITE lock. *

* This implementation will block until the write lock is acquired. * * @param lockKey the lock key * @param task the task to supply a value * @param the type of object being supplied * @return the supplied value */ public T supplyWithWriteLock(String lockKey, Supplier task) { var nonNullKey = ensureNonBlankKey(lockKey); var readWriteLock = lock.get(nonNullKey); var lockHashCode = extractHashCode(readWriteLock); LOG.trace("Locking write lock {} for key {}", lockHashCode, nonNullKey); readWriteLock.writeLock().lock(); try { LOG.trace("Running task with write lock {} for key {}", lockHashCode, nonNullKey); return task.get(); } finally { LOG.trace("Unlocking write lock {} for key {}", lockHashCode, nonNullKey); readWriteLock.writeLock().unlock(); } } private String ensureNonBlankKey(String key) { return isBlank(key) ? DEFAULT_KEY_WHEN_BLANK : key; } private String extractHashCode(Object obj) { return "@" + Integer.toHexString(System.identityHashCode(obj)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy