簡體   English   中英

是否存在帶超時的Java Lock實現

[英]Is there a Java Lock implementation with timeouts

我有一個可以在集群環境中運行的spring應用程序。 在那種環境中,我將Redis(和Redisson)用作分布式鎖定服務。

許多鎖用於例如保護某些任務,這些任務一次只能運行一次,或者只能每X秒啟動一次。

該應用程序還能夠在獨立模式下運行(無需redis)。

但是對於這種情況,我需要鎖服務的不同實現。 我認為這將非常簡單,因為我只需要在本地創建具有一定超時時間的Lock實例即可(例如,“最多2分鍾只能運行一次動作”)。 但是,當環顧四周時,我找不到支持設置超時設置的Java Lock接口的任何實現(這樣,在該時間之后它會自動解鎖)。

是否有這樣的事情,或者有一種我自己缺少的實現代碼的極其簡單的方法(就代碼行而言)?

該鎖隱含的行為應如何:

  • 其他線程無法在其處於活動狀態時對其進行鎖定(與其他任何鎖定一樣)
  • 理想情況下,擁有線程應該能夠再次調用lock(長timoutMs)以擴展鎖(將超時重新設置為給定時間)

編輯:似乎一個具體的例子可以幫助理解我在尋找什么:

  • 假設服務器具有HTTP操作“ doExpesiveTask”
  • 每當調用此任務時,我的應用程序都會轉到其ockService”,並調用.tryAcquireLock("expensiveTaskId", 10, TimeUnit.Minutes)並在是否獲得鎖定的情況下返回一個布爾值。
  • 如果獲得了鎖,它將啟動任務
  • 如果沒有鎖,它不會顯示並向用戶顯示“您必須更加耐心”

在分布式設置中, lockService的實現使用redis(和Redisson庫)分布式鎖(這已經很好用了!)! 要在分布式和獨立模式之間進行非常簡單的切換,我只想實現不依賴任何外部服務的lockService實現。 因此,我只需要實現支持超時的Lock即可。 這樣,我可以在鎖服務內部簡單地擁有一個ConcurrentHashMap,它將鎖ID映射到這些鎖實例。

為什么不簡單地使用將鎖ID映射到時間對象的Map:因為我還需要防止其他線程重新鎖定(延長生存期)被另一個線程獲取的鎖。

在談論鎖時,您的描述有些含糊,但實際上並沒有鎖定資源(或未提供示例)。 我覺得您的問題與安排有關。

由於您已經使用過Spring,因此可以查看其調度選項。 最新版本允許您使用@Scheduled注釋來觸發它。 @EnableScheduling啟動后台任務執行程序。 您可以將其與Spring配置文件結合使用,以確保僅在您通過配置文件(例如,作為JVM參數)時才啟動這些配置文件。

從文檔復制:

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()));
    }
}

並啟用:

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);
    }
}

這里有一個快速指南:

春季文件

服務代碼(您希望使用枚舉器,為了清楚起見使用字符串):

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 
        }
    }
}

Object類中有一個方法: public final void wait(long timeout) 參見http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#wait(long)

您只能在同步塊中調用它。

作為示例(來自javadoc):

synchronized (obj) {
         while (<condition does not hold>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
     }

如果有人感興趣,這是我為“無集群”模式想到的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());
        }
    }

}

因此,沒有使用真正的鎖,而是通過服務進行鎖定,而TimeoutLock對象只是跟蹤擁有的線程ID和超時。 為此編寫了一些測試,到目前為止一切都看起來不錯。

您可以嘗試將超時放入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();
  }
}

使用該實用程序類,您可以使用await方法來等待特定條件,例如,等待Bar類完成初始化或等待測試中的特定步驟,然后使用signalAll方法終止等待條件並恢復正常操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM