I have a spring application which can run in a clustered environment. In that environment I use Redis (and Redisson) as a distributed lock-service.
Many of the locks are used to eg protect certain tasks which can only run once at a time, or which may only be started every X seconds.
The application is also capable of running in standalone mode (without redis).
However for this case I need a different implementation of the lockservice. I thought this would be extremely simple because I only need to create a Lock instance locally with a certain timeout (eg for the "only run action at most ever 2 minutes"). However when looking around I could not find any implementation of the Java Lock interface which supports setting a timeout for the lock (so that it automatically unlocks itself after that time).
Is there such a thing, or is there an extremely simple (in terms of lines-of-code) way how I can implement this myself, which I'm just missing?
How that lock impl should behave:
EDIT: It seems that a concrete example could help understand what I am looking for:
.tryAcquireLock("expensiveTaskId", 10, TimeUnit.Minutes)
and gets back a boolean if it got the lock or not. In a distributed setup the implementation of the lockService
uses redis (and the Redisson library) distributed locks (this already works great)! To have a very simple switch between distributed and standalone mode, I simply want to have an implementation of lockService
which doesn't rely on any external service. Therefore I would simply need an implementation of a Lock which supports a timeout. With that I could simply have a ConcurrentHashMap inside the lockservice which maps lock-ids to these lock instances.
Why not simply use a Map that maps lock-ids to time-objects: because I also need to prevent other threads from re-locking (extending the lifetime) of a lock which was acquired by another thread.
Your description is a little bit ambiguous, as you are talking about locks, but you are not actually locking a resource (or did not provide example). I feel your problem relates to scheduling.
Since you already use Spring, you could have a look at its scheduling options. Recent versions allow you to use @Scheduled annotation to trigger that. @EnableScheduling fires up background task executor. You could combo that with Spring profiles, to ensure these kick in only when you pass a profile, for example as a JVM parameter.
Copied from docs:
package hello;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class ScheduledTasks {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
System.out.println("The time is now " + dateFormat.format(new Date()));
}
}
and to enable:
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class);
}
}
There is a quick guide here:
Service code (you would want to go with enumerators, used strings for clarity):
import org.apache.commons.collections4.map.PassiveExpiringMap;
public class StandAloneLockService {
private Map ordinaryLocks;
private Map expiringLocks;
public StandAloneLockService() {
this.ordinaryLocks = new HashMap<String, Long>();
this.expiringLocks = new PassiveExpiringMap<String, Long>(2L,
TimeUnit.MINUTES);
}
public synchronized boolean accquireLock(String task) {
if (ordinaryLocks.containsKey("task")
|| expiringLocks.containsKey("task")) {
return false;
} else {
return handle("task");
}
}
private boolean handle(String jdk7) {
switch (jdk7) { // logic
}
}
private void releaseLock(String task) {
switch (task) { // logic
}
}
}
There is a method in the Object class: public final void wait(long timeout)
. See http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#wait(long)
You can only call it in a synchronized block.
As the example (from the javadoc):
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
If anyone is interested, this is the LockService implementation which I came up with for "clusterless" mode:
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import java.lang.management.ThreadInfo;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static org.slf4j.LoggerFactory.getLogger;
public class LocalLockServiceImpl implements LockService {
private static final Logger LOG = getLogger(ClusterLockServiceLocalImpl.class);
private ConcurrentMap<String, TimeoutLock> lockMap = Maps.newConcurrentMap();
private Object sync = new Object();
@Override
public boolean tryLockOrRelock(String lockName, long lockTimeMillis) {
synchronized (sync) {
TimeoutLock lock = lockMap.getOrDefault(lockName, new TimeoutLock());
lockMap.put(lockName, lock);
if (!lock.isExpired()) {
if (!lock.isHeldByCurrentThread()) {
LOG.debug("cannot lock " + lockName + " because it is held by a different thread");
return false;
}
}
lock.setExpiry(lockTimeMillis);
return true;
}
}
@Override
public void unlock(String lockName) {
synchronized (sync) {
TimeoutLock lock = lockMap.getOrDefault(lockName, null);
if (lock != null && lock.isHeldByCurrentThread()) {
lockMap.remove(lockName);
}
}
}
private static class TimeoutLock {
private LocalDateTime expiresAt;
private long threadId;
public TimeoutLock() {
expiresAt = LocalDateTime.now();
threadId = Thread.currentThread().getId();
}
public void setExpiry(long millisFromNow) {
expiresAt = LocalDateTime.now().plus(millisFromNow, ChronoUnit.MILLIS);
}
public boolean isHeldByCurrentThread() {
return threadId == Thread.currentThread().getId();
}
public boolean isExpired() {
return expiresAt.isBefore(LocalDateTime.now());
}
}
}
So no real Locks used, the locking happens via the service and the TimeoutLock objects simply keep track of owning thread-id and timeouts. Wrote a few tests for it and so far everything looks good.
You can try putting the timeout in the ReentrantLock's await call for example:
public class MessageUtil {
private static final Lock lock = new ReentrantLock();
public enum Conditions {
BAR_INIT(lock.newCondition()),
TEST_DELAY(lock.newCondition());
Condition condition;
private Conditions(Condition condition) {
this.condition = condition;
}
}
public static void await(Conditions condition, int timeout) throws Interrupted Exception {
lock.lock();
condition.condition.await(timeout, TimeUnit.SECONDS);
lock.unlock();
}
public static void signalAll(Conditions condtition) {
lock.lock();
condition.condition.signalAll();
lock.unlock();
}
}
This utility class allows you to use the await method to wait for a certain condition, say waiting for the Bar class to finish initializing or waiting for a certain step in a test then use the signalAll method to end the wait condition and resume normal operations.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.