简体   繁体   English

是否存在带超时的Java Lock实现

[英]Is there a Java Lock implementation with timeouts

I have a spring application which can run in a clustered environment. 我有一个可以在集群环境中运行的spring应用程序。 In that environment I use Redis (and Redisson) as a distributed lock-service. 在那种环境中,我将Redis(和Redisson)用作分布式锁定服务。

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. 许多锁用于例如保护某些任务,这些任务一次只能运行一次,或者只能每X秒启动一次。

The application is also capable of running in standalone mode (without redis). 该应用程序还能够在独立模式下运行(无需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"). 我认为这将非常简单,因为我只需要在本地创建具有一定超时时间的Lock实例即可(例如,“最多2分钟只能运行一次动作”)。 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). 但是,当环顾四周时,我找不到支持设置超时设置的Java Lock接口的任何实现(这样,在该时间之后它会自动解锁)。

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: 该锁隐含的行为应如何:

  • Other threads are not able to lock it while it's active (as any other lock) 其他线程无法在其处于活动状态时对其进行锁定(与其他任何锁定一样)
  • Ideally, the owning thread should be able to call lock(long timoutMs) again to extend the lock (set the timeout to the given time again) 理想情况下,拥有线程应该能够再次调用lock(长timoutMs)以扩展锁(将超时重新设置为给定时间)

EDIT: It seems that a concrete example could help understand what I am looking for: 编辑:似乎一个具体的例子可以帮助理解我在寻找什么:

  • Imagine the server has an HTTP action "doExpesiveTask" 假设服务器具有HTTP操作“ doExpesiveTask”
  • whenever this task is called, my application goes to its ockService" and calls .tryAcquireLock("expensiveTaskId", 10, TimeUnit.Minutes) and gets back a boolean if it got the lock or not. 每当调用此任务时,我的应用程序都会转到其ockService”,并调用.tryAcquireLock("expensiveTaskId", 10, TimeUnit.Minutes)并在是否获得锁定的情况下返回一个布尔值。
  • if it got the lock it starts the task 如果获得了锁,它将启动任务
  • if it didn't get the lock it doesn't and shows the user "you have to be more patient" 如果没有锁,它不会显示并向用户显示“您必须更加耐心”

In a distributed setup the implementation of the lockService uses redis (and the Redisson library) distributed locks (this already works great)! 在分布式设置中, lockService的实现使用redis(和Redisson库)分布式锁(这已经很好用了!)! 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. 要在分布式和独立模式之间进行非常简单的切换,我只想实现不依赖任何外部服务的lockService实现。 Therefore I would simply need an implementation of a Lock which supports a timeout. 因此,我只需要实现支持超时的Lock即可。 With that I could simply have a ConcurrentHashMap inside the lockservice which maps lock-ids to these lock instances. 这样,我可以在锁服务内部简单地拥有一个ConcurrentHashMap,它将锁ID映射到这些锁实例。

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. 为什么不简单地使用将锁ID映射到时间对象的Map:因为我还需要防止其他线程重新锁定(延长生存期)被另一个线程获取的锁。

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. 由于您已经使用过Spring,因此可以查看其调度选项。 Recent versions allow you to use @Scheduled annotation to trigger that. 最新版本允许您使用@Scheduled注释来触发它。 @EnableScheduling fires up background task executor. @EnableScheduling启动后台任务执行程序。 You could combo that with Spring profiles, to ensure these kick in only when you pass a profile, for example as a JVM parameter. 您可以将其与Spring配置文件结合使用,以确保仅在您通过配置文件(例如,作为JVM参数)时才启动这些配置文件。

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: 这里有一个快速指南:

Spring Docs 春季文件

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) . Object类中有一个方法: public final void wait(long timeout) See http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#wait(long) 参见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): 作为示例(来自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: 如果有人感兴趣,这是我为“无集群”模式想到的LockService实现:

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. 因此,没有使用真正的锁,而是通过服务进行锁定,而TimeoutLock对象只是跟踪拥有的线程ID和超时。 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: 您可以尝试将超时放入ReentrantLock的await调用中,例如:

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. 使用该实用程序类,您可以使用await方法来等待特定条件,例如,等待Bar类完成初始化或等待测试中的特定步骤,然后使用signalAll方法终止等待条件并恢复正常操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM