Java 实现基于Redis的分布式可重入锁
之前在 Java实现基于的Redis的分布式锁 这篇文章中,已经实现了加锁的逻辑,但是有个缺点,就是不可重入,任何重入锁的尝试都会导致死锁的发生,想了一下,这个问题可以解决。
Thinking
如何实现可重入?
首先锁信息(指redis中lockKey关联的value值) 必须得设计的能负载更多信息,之前non-reentrant时value直接就是一个超时时间,但是要实现可重入单超时时间是不够的,必须要标识锁是被谁持有的,也就是说要标识分布式环境中的线程,还要记录锁被入了多少次。
如何在分布式线程中标识唯一线程?
MAC地址 + jvm进程ID + 线程ID(或者线程地址都行),三者结合即可唯一分布式环境中的线程。
实现
锁的信息采用json存储,格式如下:
代码框架还是和之前实现的非重入的差不多,重点是lock方法,代码已有非常详细的注释
- package cc.lixiaohui.lock.redis;
- import java.io.IOException;
- import java.net.SocketAddress;
- import java.util.concurrent.TimeUnit;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import redis.clients.jedis.Jedis;
- import cc.lixiaohui.lock.AbstractLock;
- import cc.lixiaohui.lock.Lock;
- import cc.lixiaohui.lock.time.nio.client.TimeClient;
- import cc.lixiaohui.lock.util.LockInfo;
- /**
- * 基于Redis的SETNX操作实现的分布式锁, 获取锁时最好用tryLock(long time, TimeUnit unit), 以免网路问题而导致线程一直阻塞.
- * <a href="http://redis.io/commands/setnx">SETNC操作参考资料.</a>
- *
- * <p><b>可重入实现关键:</b>
- * <ul>
- * <li>在分布式环境中如何确定一个线程? <i><b>mac地址 + jvm pid + threadId</b></i> (mac地址唯一, jvm
- * pid在单机内唯一, threadId在单jvm内唯一)</li>
- * <li>任何一个线程从redis拿到value值后都需要能确定 该锁是否被自己持有, 因此value值要有以下特性: 保存持有锁的主机(mac), jvm
- * pid, 持有锁的线程ID, 重复持有锁的次数</li>
- * </ul></p>
- * <p>
- * redis中value设计如下(in json):
- * <pre>
- * {
- * expires : expire time in long
- * mac : mac address of lock holder's machine
- * pid : jvm process id
- * threadId : lock holder thread id
- * count : hold count(for use of reentrancy)
- * }
- * 由{@link LockInfo LockInfo}表示.
- * </pre>
- *
- * <b>Usage Example:</b>
- * <pre>
- * {@link Lock} lock = new {@link ReentrantLock}(jedis, "lockKey", lockExpires, timeServerAddr);
- * if (lock.tryLock(3, TimeUnit.SECONDS)) {
- * try {
- * // do something
- * } catch (Exception e) {
- * lock.unlock();
- * }
- * }
- * </pre>
- * </p>
- *
- * @author lixiaohui
- * @date 2016年9月15日 下午2:52:38
- *
- */
- public class ReentrantLock extends AbstractLock {
- private Jedis jedis;
- private TimeClient timeClient;
- // 锁的名字
- protected String lockKey;
- // 锁的有效时长(毫秒)
- protected long lockExpires;
- private static final Logger logger = LoggerFactory.getLogger(ReentrantLock.class);
- public ReentrantLock(Jedis jedis, String lockKey, long lockExpires, SocketAddress timeServerAddr) throws IOException {
- this.jedis = jedis;
- this.lockKey = lockKey;
- this.lockExpires = lockExpires;
- timeClient = new TimeClient(timeServerAddr);
- }
- // 阻塞式获取锁的实现
- protected boolean lock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt) throws InterruptedException {
- if (interrupt) {
- checkInterruption();
- }
- // 超时控制 的时间可以从本地获取, 因为这个和锁超时没有关系, 只是一段时间区间的控制
- long start = localTimeMillis();
- long timeout = unit.toMillis(time); // if !useTimeout, then it's useless
- // walkthrough
- // 1. lockKey未关联value, 直接设置lockKey, 成功获取到锁, return true
- // 2. lock 已过期, 用getset设置lockKey, 判断返回的旧的LockInfo
- // 2.1 若仍是超时的, 则成功获取到锁, return true
- // 2.2 若不是超时的, 则进入下一次循环重新开始 步骤1
- // 3. lock没过期, 判断是否是当前线程持有
- // 3.1 是, 则计数加 1, return true
- // 3.2 否, 则进入下一次循环重新开始 步骤1
- // note: 每次进入循环都检查 : 1.是否超时, 若是则return false; 2.是否检查中断(interrupt)被中断,
- // 若需检查中断且被中断, 则抛InterruptedException
- while (useTimeout ? !isTimeout(start, timeout) : true) {
- if (interrupt) {
- checkInterruption();
- }
- long lockExpireTime = serverTimeMillis() + lockExpires + 1;// 锁超时时间
- String newLockInfoJson = LockInfo.newForCurrThread(lockExpireTime).toString();
- if (jedis.setnx(lockKey, newLockInfoJson) == 1) { // 条件能成立的唯一情况就是redis中lockKey还未关联value
- // TODO 成功获取到锁, 设置相关标识
- logger.debug("{} get lock(new), lockInfo: {}", Thread.currentThread().getName(), newLockInfoJson);
- locked = true;
- return true;
- }
- // value已有值, 但不能说明锁被持有, 因为锁可能expired了
- String currLockInfoJson = jedis.get(lockKey);
- // 若这瞬间锁被delete了
- if (currLockInfoJson == null) {
- continue;
- }
- LockInfo currLockInfo = LockInfo.fromString(currLockInfoJson);
- // 竞争条件只可能出现在锁超时的情况, 因为如果没有超时, 线程发现锁并不是被自己持有, 线程就不会去动value
- if (isTimeExpired(currLockInfo.getExpires())) {
- // 锁超时了
- LockInfo oldLockInfo = LockInfo.fromString(jedis.getSet(lockKey, newLockInfoJson));
- if (oldLockInfo != null && isTimeExpired(oldLockInfo.getExpires())) {
- // TODO 成功获取到锁, 设置相关标识
- logger.debug("{} get lock(new), lockInfo: {}", Thread.currentThread().getName(), newLockInfoJson);
- locked = true;
- return true;
- }
- } else {
- // 锁未超时, 不会有竞争情况
- if (isHeldByCurrentThread(currLockInfo)) { // 当前线程持有
- // TODO 成功获取到锁, 设置相关标识
- currLockInfo.setExpires(serverTimeMillis() + lockExpires + 1); // 设置新的锁超时时间
- currLockInfo.incCount();
- jedis.set(lockKey, currLockInfo.toString());
- logger.debug("{} get lock(inc), lockInfo: {}", Thread.currentThread().getName(), currLockInfo);
- locked = true;
- return true;
- }
- }
- }
- locked = false;
- return false;
- }
- public boolean tryLock() {
- long lockExpireTime = serverTimeMillis() + lockExpires + 1;
- String newLockInfo = LockInfo.newForCurrThread(lockExpireTime).toString();
- if (jedis.setnx(lockKey, newLockInfo) == 1) {
- locked = true;
- return true;
- }
- String currLockInfoJson = jedis.get(lockKey);
- if (currLockInfoJson == null) {
- // 再一次尝试获取
- if (jedis.setnx(lockKey, newLockInfo) == 1) {
- locked = true;
- return true;
- } else {
- locked = false;
- return false;
- }
- }
- LockInfo currLockInfo = LockInfo.fromString(currLockInfoJson);
- if (isTimeExpired(currLockInfo.getExpires())) {
- LockInfo oldLockInfo = LockInfo.fromString(jedis.getSet(lockKey, newLockInfo));
- if (oldLockInfo != null && isTimeExpired(oldLockInfo.getExpires())) {
- locked = true;
- return true;
- }
- } else {
- if (isHeldByCurrentThread(currLockInfo)) {
- currLockInfo.setExpires(serverTimeMillis() + lockExpires + 1);
- currLockInfo.incCount();
- jedis.set(lockKey, currLockInfo.toString());
- locked = true;
- return true;
- }
- }
- locked = false;
- return false;
- }
- /**
- * Queries if this lock is held by any thread.
- *
- * @return {@code true} if any thread holds this lock and {@code false}
- * otherwise
- */
- public boolean isLocked() {
- // walkthrough
- // 1. lockKey未关联value, return false
- // 2. 若 lock 已过期, return false, 否则 return true
- if (!locked) { // 本地locked为false, 肯定没加锁
- return false;
- }
- String json = jedis.get(lockKey);
- if (json == null) {
- return false;
- }
- if (isTimeExpired(LockInfo.fromString(json).getExpires())) {
- return false;
- }
- return true;
- }
- @Override
- protected void unlock0() {
- // walkthrough
- // 1. 若锁过期, return
- // 2. 判断自己是否是锁的owner
- // 2.1 是, 若 count = 1, 则删除lockKey; 若 count > 1, 则计数减 1, return
- // 2.2 否, 则抛异常 IllegalMonitorStateException, reutrn
- // done, return
- LockInfo currLockInfo = LockInfo.fromString(jedis.get(lockKey));
- if (isTimeExpired(currLockInfo.getExpires())) {
- return;
- }
- if (isHeldByCurrentThread(currLockInfo)) {
- if (currLockInfo.getCount() == 1) {
- jedis.del(lockKey);
- logger.debug("{} unlock(del), lockInfo: null", Thread.currentThread().getName());
- } else {
- currLockInfo.decCount(); // 持有锁计数减1
- String json = currLockInfo.toString();
- jedis.set(lockKey, json);
- logger.debug("{} unlock(dec), lockInfo: {}", Thread.currentThread().getName(), json);
- }
- } else {
- throw new IllegalMonitorStateException(String.format("current thread[%s] does not holds the lock", Thread.currentThread().toString()));
- }
- }
- public void release() {
- jedis.close();
- timeClient.close();
- }
- public boolean isHeldByCurrentThread() {
- return isHeldByCurrentThread(LockInfo.fromString(jedis.get(lockKey)));
- }
- // ------------------- utility methods ------------------------
- private boolean isHeldByCurrentThread(LockInfo lockInfo) {
- return lockInfo.isCurrentThread();
- }
- private void checkInterruption() throws InterruptedException {
- if (Thread.currentThread().isInterrupted()) {
- throw new InterruptedException();
- }
- }
- private boolean isTimeExpired(long time) {
- return time < serverTimeMillis();
- }
- private boolean isTimeout(long start, long timeout) {
- // 这里拿本地的时间来比较
- return start + timeout < System.currentTimeMillis();
- }
- private long serverTimeMillis() {
- return timeClient.currentTimeMillis();
- }
- private long localTimeMillis() {
- return System.currentTimeMillis();
- }
- }
测试
5个线程,每个线程都是不同的jedis连接,模拟分布式环境,线程的任务就是不断的去尝试重入地获取锁,重入的次数为随机但在0-5之间。
代码
- package cc.lixiaohui.DistributedLock.DistributedLock;
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.net.SocketAddress;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Random;
- import java.util.concurrent.TimeUnit;
- import org.junit.Test;
- import redis.clients.jedis.Jedis;
- import cc.lixiaohui.lock.redis.ReentrantLock;
- /**
- * @author lixiaohui
- * @date 2016年9月28日 下午8:41:36
- *
- */
- public class ReentrantTest {
- final int EXPIRES = 10 * 1000;
- final String LOCK_KEY = "lock.lock";
- final SocketAddress TIME_SERVER_ADDR = new InetSocketAddress("localhost", 9999);
- @Test
- public void test() throws Exception {
- // 创建5个线程不停地去重入(随机次数n, 0 <= n <=5)获取锁
- List<Thread> threads = createThreads(5);
- //开始任务
- for (Thread t : threads) {
- t.start();
- }
- // 执行60秒
- Thread.sleep(60 * 1000);
- //停止所有线程
- Task.alive = false;
- // 等待所有线程终止
- for (Thread t : threads) {
- t.join();
- }
- }
- // 创建count个线程,每个线程都是不同的jedis连接以及不同的与时间服务器的连接
- private List<Thread> createThreads(int count) throws IOException {
- List<Thread> threads = new ArrayList<Thread>();
- for (int i = 0; i < count; i++) {
- Jedis jedis = new Jedis("localhost", 6379);
- ReentrantLock lock = new ReentrantLock(jedis, LOCK_KEY, EXPIRES, TIME_SERVER_ADDR);
- Task task = new Task(lock);
- Thread t = new Thread(task);
- threads.add(t);
- }
- return threads;
- }
- private static class Task implements Runnable {
- private ReentrantLock lock;
- private final int MAX_ENTRANT = 5;
- private final Random random = new Random();
- private static boolean alive = true;
- Task(ReentrantLock lock) {
- this.lock = lock;
- }
- public void run() {
- while (alive) {
- int times = random.nextInt(MAX_ENTRANT);
- doLock(times);
- }
- }
- private void doLock(int times) {
- if (lock.tryLock(5, TimeUnit.SECONDS)) {
- try {
- if (times > 0) {
- doLock(--times);
- }
- } finally {
- if (lock != null) {
- lock.unlock();
- }
- }
- }
- }
- }
- }
测试结果
下面是一部分日志输出:
- 21:09:22.328-[DEBUG] Thread-0 ReentrantLock - Thread-0 get lock(new), lockInfo: {"count":1,"expires":1475068172329,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.330-[DEBUG] Thread-0 ReentrantLock - Thread-0 get lock(inc), lockInfo: {"count":2,"expires":1475068172330,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.331-[DEBUG] Thread-0 ReentrantLock - Thread-0 get lock(inc), lockInfo: {"count":3,"expires":1475068172332,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.332-[DEBUG] Thread-0 ReentrantLock - Thread-0 get lock(inc), lockInfo: {"count":4,"expires":1475068172333,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.334-[DEBUG] Thread-0 ReentrantLock - Thread-0 get lock(inc), lockInfo: {"count":5,"expires":1475068172334,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.335-[DEBUG] Thread-0 ReentrantLock - Thread-0 unlock(dec), lockInfo: {"count":4,"expires":1475068172334,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.336-[DEBUG] Thread-0 ReentrantLock - Thread-0 unlock(dec), lockInfo: {"count":3,"expires":1475068172334,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.338-[DEBUG] Thread-0 ReentrantLock - Thread-0 unlock(dec), lockInfo: {"count":2,"expires":1475068172334,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.339-[DEBUG] Thread-0 ReentrantLock - Thread-0 unlock(dec), lockInfo: {"count":1,"expires":1475068172334,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.340-[DEBUG] Thread-0 ReentrantLock - Thread-0 unlock(del), lockInfo: null
- 21:09:22.341-[DEBUG] Thread-0 ReentrantLock - Thread-0 get lock(new), lockInfo: {"count":1,"expires":1475068172341,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.341-[DEBUG] Thread-0 ReentrantLock - Thread-0 unlock(del), lockInfo: null
- 21:09:22.342-[DEBUG] Thread-0 ReentrantLock - Thread-0 get lock(new), lockInfo: {"count":1,"expires":1475068172342,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.343-[DEBUG] Thread-0 ReentrantLock - Thread-0 get lock(inc), lockInfo: {"count":2,"expires":1475068172344,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.344-[DEBUG] Thread-0 ReentrantLock - Thread-0 unlock(dec), lockInfo: {"count":1,"expires":1475068172344,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.345-[DEBUG] Thread-0 ReentrantLock - Thread-0 unlock(del), lockInfo: null
- 21:09:22.345-[DEBUG] Thread-3 ReentrantLock - Thread-3 get lock(new), lockInfo: {"count":1,"expires":1475068172345,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":14}
- 21:09:22.346-[DEBUG] Thread-3 ReentrantLock - Thread-3 get lock(inc), lockInfo: {"count":2,"expires":1475068172347,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":14}
- 21:09:22.347-[DEBUG] Thread-3 ReentrantLock - Thread-3 get lock(inc), lockInfo: {"count":3,"expires":1475068172348,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":14}
- 21:09:22.349-[DEBUG] Thread-3 ReentrantLock - Thread-3 get lock(inc), lockInfo: {"count":4,"expires":1475068172350,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":14}
- 21:09:22.350-[DEBUG] Thread-3 ReentrantLock - Thread-3 unlock(dec), lockInfo: {"count":3,"expires":1475068172350,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":14}
- 21:09:22.350-[DEBUG] Thread-3 ReentrantLock - Thread-3 unlock(dec), lockInfo: {"count":2,"expires":1475068172350,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":14}
- 21:09:22.351-[DEBUG] Thread-3 ReentrantLock - Thread-3 unlock(dec), lockInfo: {"count":1,"expires":1475068172350,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":14}
- 21:09:22.351-[DEBUG] Thread-3 ReentrantLock - Thread-3 unlock(del), lockInfo: null
- 21:09:22.352-[DEBUG] Thread-3 ReentrantLock - Thread-3 get lock(new), lockInfo: {"count":1,"expires":1475068172352,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":14}
- 21:09:22.352-[DEBUG] Thread-3 ReentrantLock - Thread-3 unlock(del), lockInfo: null
- 21:09:22.352-[DEBUG] Thread-0 ReentrantLock - Thread-0 get lock(new), lockInfo: {"count":1,"expires":1475068172353,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.354-[DEBUG] Thread-0 ReentrantLock - Thread-0 get lock(inc), lockInfo: {"count":2,"expires":1475068172355,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.354-[DEBUG] Thread-0 ReentrantLock - Thread-0 unlock(dec), lockInfo: {"count":1,"expires":1475068172355,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.355-[DEBUG] Thread-0 ReentrantLock - Thread-0 unlock(del), lockInfo: null
- 21:09:22.355-[DEBUG] Thread-4 ReentrantLock - Thread-4 get lock(new), lockInfo: {"count":1,"expires":1475068172356,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":15}
- 21:09:22.357-[DEBUG] Thread-4 ReentrantLock - Thread-4 get lock(inc), lockInfo: {"count":2,"expires":1475068172357,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":15}
- 21:09:22.358-[DEBUG] Thread-4 ReentrantLock - Thread-4 get lock(inc), lockInfo: {"count":3,"expires":1475068172358,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":15}
- 21:09:22.359-[DEBUG] Thread-4 ReentrantLock - Thread-4 get lock(inc), lockInfo: {"count":4,"expires":1475068172360,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":15}
- 21:09:22.360-[DEBUG] Thread-4 ReentrantLock - Thread-4 unlock(dec), lockInfo: {"count":3,"expires":1475068172360,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":15}
- 21:09:22.361-[DEBUG] Thread-4 ReentrantLock - Thread-4 unlock(dec), lockInfo: {"count":2,"expires":1475068172360,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":15}
- 21:09:22.361-[DEBUG] Thread-4 ReentrantLock - Thread-4 unlock(dec), lockInfo: {"count":1,"expires":1475068172360,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":15}
- 21:09:22.362-[DEBUG] Thread-4 ReentrantLock - Thread-4 unlock(del), lockInfo: null
- 21:09:22.362-[DEBUG] Thread-0 ReentrantLock - Thread-0 get lock(new), lockInfo: {"count":1,"expires":1475068172363,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.363-[DEBUG] Thread-0 ReentrantLock - Thread-0 get lock(inc), lockInfo: {"count":2,"expires":1475068172364,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.365-[DEBUG] Thread-0 ReentrantLock - Thread-0 get lock(inc), lockInfo: {"count":3,"expires":1475068172365,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.365-[DEBUG] Thread-0 ReentrantLock - Thread-0 unlock(dec), lockInfo: {"count":2,"expires":1475068172365,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.366-[DEBUG] Thread-0 ReentrantLock - Thread-0 unlock(dec), lockInfo: {"count":1,"expires":1475068172365,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":11}
- 21:09:22.367-[DEBUG] Thread-0 ReentrantLock - Thread-0 unlock(del), lockInfo: null
- 21:09:22.367-[DEBUG] Thread-4 ReentrantLock - Thread-4 get lock(new), lockInfo: {"count":1,"expires":1475068172368,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":15}
- 21:09:22.368-[DEBUG] Thread-4 ReentrantLock - Thread-4 get lock(inc), lockInfo: {"count":2,"expires":1475068172369,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":15}
- 21:09:22.370-[DEBUG] Thread-4 ReentrantLock - Thread-4 unlock(dec), lockInfo: {"count":1,"expires":1475068172369,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":15}
- 21:09:22.372-[DEBUG] Thread-4 ReentrantLock - Thread-4 unlock(del), lockInfo: null
- 21:09:22.372-[DEBUG] Thread-1 ReentrantLock - Thread-1 get lock(new), lockInfo: {"count":1,"expires":1475068172373,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":12}
- 21:09:22.374-[DEBUG] Thread-1 ReentrantLock - Thread-1 get lock(inc), lockInfo: {"count":2,"expires":1475068172374,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":12}
- 21:09:22.375-[DEBUG] Thread-1 ReentrantLock - Thread-1 get lock(inc), lockInfo: {"count":3,"expires":1475068172376,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":12}
- 21:09:22.376-[DEBUG] Thread-1 ReentrantLock - Thread-1 get lock(inc), lockInfo: {"count":4,"expires":1475068172376,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":12}
- 21:09:22.376-[DEBUG] Thread-1 ReentrantLock - Thread-1 get lock(inc), lockInfo: {"count":5,"expires":1475068172377,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":12}
- 21:09:22.377-[DEBUG] Thread-1 ReentrantLock - Thread-1 unlock(dec), lockInfo: {"count":4,"expires":1475068172377,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":12}
- 21:09:22.377-[DEBUG] Thread-1 ReentrantLock - Thread-1 unlock(dec), lockInfo: {"count":3,"expires":1475068172377,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":12}
- 21:09:22.378-[DEBUG] Thread-1 ReentrantLock - Thread-1 unlock(dec), lockInfo: {"count":2,"expires":1475068172377,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":12}
- 21:09:22.378-[DEBUG] Thread-1 ReentrantLock - Thread-1 unlock(dec), lockInfo: {"count":1,"expires":1475068172377,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":12}
- 21:09:22.379-[DEBUG] Thread-1 ReentrantLock - Thread-1 unlock(del), lockInfo: null
- 21:09:22.379-[DEBUG] Thread-2 ReentrantLock - Thread-2 get lock(new), lockInfo: {"count":1,"expires":1475068172380,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":13}
- 21:09:22.380-[DEBUG] Thread-2 ReentrantLock - Thread-2 get lock(inc), lockInfo: {"count":2,"expires":1475068172380,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":13}
- 21:09:22.380-[DEBUG] Thread-2 ReentrantLock - Thread-2 unlock(dec), lockInfo: {"count":1,"expires":1475068172380,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":13}
- 21:09:22.380-[DEBUG] Thread-2 ReentrantLock - Thread-2 unlock(del), lockInfo: null
- 21:09:22.380-[DEBUG] Thread-3 ReentrantLock - Thread-3 get lock(new), lockInfo: {"count":1,"expires":1475068172381,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":14}
- 21:09:22.381-[DEBUG] Thread-3 ReentrantLock - Thread-3 get lock(inc), lockInfo: {"count":2,"expires":1475068172382,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":14}
- 21:09:22.381-[DEBUG] Thread-3 ReentrantLock - Thread-3 get lock(inc), lockInfo: {"count":3,"expires":1475068172382,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":14}
- 21:09:22.382-[DEBUG] Thread-3 ReentrantLock - Thread-3 unlock(dec), lockInfo: {"count":2,"expires":1475068172382,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":14}
- 21:09:22.382-[DEBUG] Thread-3 ReentrantLock - Thread-3 unlock(dec), lockInfo: {"count":1,"expires":1475068172382,"jvmPid":22224,"mac":"28-D2-44-0E-0D-9A","threadId":14}
- 21:09:22.382-[DEBUG] Thread-3 ReentrantLock - Thread-3 unlock(del), lockInfo: null
看日志输出,lockInfo对应的json就是redis中lockKey对应的值,可以看到这个重入锁正常工作(count先递增后递减,在count不为0时别的线程拿不到锁)。测试通过,性能上还待改善
Thinking again
如何实现读写锁?
目前想出的一种实现是redis中存两个键值对,一个读一个写,随之而来的一个问题是:对于加锁过程,可能会要求对这两个键值的更改(这里指一起更改两者)要具有原子性,因此得用一个额外的重入锁,来同步这个操作。但这样实现未免代价太大,仍思考解决方法中..........路过的大神请指条明路...
代码放这里了:戳我
相关推荐
lim_3ck_01b_0718
内容概要:本文介绍了一种新颖的变压器模型C2Former(Calibrated and Complementary Transformer),专门用于解决RGB图像和红外图像之间的物体检测难题。传统方法在进行多模态融合时面临两个主要问题——模态错位(Modality miscalibration)和融合不准确(fusion imprecision)。作者针对这两个问题提出采用互模交叉注意力模块(Inter-modality Cross-Attention, ICA)以及自适应特征采样模块(Adaptive Feature Sampling, AFS)来改善。具体来说,ICA可以获取对齐并且互补的特性,在特征层面进行更好的整合;而AFS则减少了计算成本。通过实验验证了基于C2Former的一阶段和二阶段检测器均能在现有公开数据集上达到最先进的表现。 适合人群:计算机视觉领域的研究人员和技术人员,特别是从事跨模态目标检测的研究人员,对Transformer架构有一定了解的开发者。 使用场景及目标:适用于需要将可见光和热成像传感器相结合的应用场合,例如全天候的视频监控系统、无人驾驶汽车、无人
pepeljugoski_01_0108
AB PLC例程代码项目案例 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我!
matlab程序代码项目案例 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我!
lusted_3ck_02_1118
内容概要:本文提出了用于虚拟同步发电机(VSG)在非对称电网故障期间的一种自适应低电压穿越(LVRT)方法。现有LVRT方法由于故障检测延迟可能导致逆变器损坏的问题以及新的电网标准(GCs)规定了逆变器基资源(IBRs)需要注入负序电流的需求。所提出的LVRT方法采用初始故障时刻电压控制环差补及有功功率环相角调整,使系统能够在正常运行时保持特性不受影响,并在出现不对称故障时立即限制输出电流。模拟结果显示,新方法不仅能迅速响应并抑制故障电流,在不同类型的不对称故障条件下亦能良好表现。此外,该方法不需要参数设计,且能够最大限度利用IBRs电流,同时维持IBRs电压源特性和符合新的GCs。 适合人群:电力电子与能源系统的学术研究人员和技术人员,熟悉虚拟同步发电技术以及LVRT相关背景的工程师。 使用场景及目标:主要适用于包含虚拟同步发电机的新能源系统中的故障穿越技术改进,以满足最新国际电气电子工程师协会(IEEE)的电气互连标准,并解决当前LVRT方法无法全面符合规范的问题,确保系统的安全可靠运行。该研究可用于改进新能源并网逆变器的设计。 其他说明:文章通过对现有的LVRT控制方法进行改进来
内容概要:本导读书籍《DeepSeek从入门到精通》旨在为读者提供对AI工具DeepSeek全面的认识。书籍首先概述了DeepSeek作为一个国产、开源的多功能AI平台的功能和特点,接着详细介绍了如何入门操作,并通过实战演示来帮助用户掌握不同应用场景下的高级提示词策略和使用技巧,如针对不同类型的提示语的设计方法和实际应用。特别是在‘发散’部分,对提示语进行了深入剖析,不仅探讨了提示词的高级技术和策略,还包括AI幻觉、人机协作以及知识生成等方面的讨论,有助于用户深入理解和灵活应用提示词,在各种任务中提高效率并获得更好的效果。最后一章展望了AI与人类在未来内容创作中的角色转变和发展趋势。这本详细的指导手册能帮助读者形成系统的AI工具认知和技术体系,快速上手并在实践中不断加深对该领域的理解。 适用人群:面向希望深入了解AI工具尤其是提示词设计的专业人士、开发者及所有有兴趣探索这一领域的个人;适合有一定技术背景的从业者。 使用场景及目标:帮助初学者快速启动DeepSeek的日常使用,为中高级用户提供更深入的高级功能理解和实践,特别是涉及提示词构建的技术细节,助力于提升特定任务处理效率和结果品质
下载 1. 单击“立即下载”,以下载该文件。 2. 出现“文件下载”窗口后,单击“保存”,以将文件保存到硬盘。 安装 1. 浏览至文件下载目标位置并双击新下载的文件。 2. 仔细阅读对话窗口中显示的发布信息。 3. 下载并安装对话窗口中标识的任何必备项,然后再继续。 4. 单击“Install”(安装)按钮。 5. 按照其余提示执行更新。 安装 1. 将解压的文件复制到可访问Windows的介质。 2. 将系统重新引导至Windows操作系统。 3. 打开“服务器管理器”->“设备管理器”->“存储控制器”,然后单击“PERC控制器”。 5. 单击“更新驱动程序软件”,并按照提示更新驱动程序。 4. 重新引导系统以使更改生效。
Saxena_01_0107
AB PLC例程代码项目案例 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我!
AB PLC例程代码项目案例 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我!
第8节模型预测控制在运动规划中的应用
覆盖了人口统计、经济发展、教育体系、环境资源等多个关键领域,旨在为研究人员、政策规划者及相关各界提供全面洞察社会现状与趋势的窗口。 2024年修订内容如下: 资源和环境–增加“倒塌房屋间数”。 农业–增加“大豆”、“花生”、“油菜籽”、“芝麻”“甘蔗”“甜英”业及辅助性活动”产值和指数。 工业–增加“智能手机”产量。 卫生和社会服务–增加“分地区儿童健康情况`和“分地区孕产妇健康情况”。 文化和体育–增加公共图书馆“少儿文献数”和国有博物馆“未成年人参观人次”情况。 城市、农村和区域发展–增加“乡村办水电站”“农村水电装机容量”“农村水电年发电量”情况。 科学技术–新增分行业新产品开发经费支出、新产品销售收入等指标。 运输、邮电和软件业–公路营业性客运量包括班车包车客运量、公共汽电车城际城乡客运量、出租汽车(含巡游出租汽车、网络预约出租汽车)城际城多客运量 运输、邮电和软件业–公路客运周转量包括班车包车客运周转量、公共汽电车城际城乡客运周转量、出租汽车(含巡游出租汽车、网络预约出租汽车)城际城乡客运周转量。 就业和工资、固定资产投资、工业、建筑业、批发和零售业、房地产、科学计算等修订主要内容: (1)拓展适用范围 为了更全面覆盖统计调查对象,新标准按照市场主体登记注册管理实际对分类范围作相应调整,从“企业”扩大至所有“市场主体”,增加了“农民专业合作社(联合社)”和“个体工户”等类别。 (2)取消相关类别 由于《中华人民共和国私营企业暂行条例》已被废止,根据《中华人民共和国公司法》《中华人民共和国个人独资企业法》《中华人民共和国合作企业法》,将相关“私营有限责任公司”、“私营股份有限公司”分别列入“有限责任公司、“股分有限公司”范围类别。引国,“私营独资企业”调整为“个人独资企业”,“私营合伙企业”调整为“合伙企业”。 (3)调整分类结构 一是关于“内资企业”。根据《中华人民共和国市场主体登记管理条例》规定,将原内资企业分类“国有企业”“集体企业”、“股份合作企业”、“联营企业”、“有限责任公司”、“股份有限公司”、“私营企业”和“其他企业”等8个类别调整为“有限责任公司”、“股份有限公司”、“非公司企业法人”、“个人独资企业”、合伙企业”和“其他内资企业”等6个类别。其中,原“国有企业”、“集体企业”、“股份合作企业”“联营企业”纳入新类别“非公司企业法人”下;原“私营企业”类别取消(上段已述)。二是关于“外商投资企业”和“港澳台投资企业”。根据《中华人民共和国外商投资法》规定,将原外商投资企业分类“中外合资经营企业”“中外合作经营企业”、“外资企业”、“外商投资股份有限公司”和“其他外商投资企业”等5个类别调整为“外商投资有限责任公司”、“外商投资股份有限公司”、“外商投资合伙企业”和“其他外商投资企业”等4个类别。港澳台投资企业参照外商投资企业分类方法调整。 (4)规范类别名称根据市场监管部门对登记注册管理的规范名称,分别将原“国有企业”、“集体企业”更名为“全民所有制企业(国有企业)”、“集体所有制企业(集体企业)” (5)统一内资范围根据《中华人民共和国外商投资法》和相关部门规定,将登记注册为内资公司的有限责任公司(外商投资企业投资)、登记注册为内资公司的股份有限公司(上市、外商投资企业投资)等市场主体,即外商投资企业市场主体在中国境内的再投资市场主体,由原标准中的“外商投资企业”调整为新标准中的“内资企业”相关类别。
AB PLC例程代码项目案例 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我!
AB PLC例程代码项目案例 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我!
基于comsol 6.2软件,建立了单晶生长的二维模型,用的是数学模块的PDE方法,涵盖了温度场和浓度场。
Gaussian_Filter
lim_3ck_01_0519
AB PLC例程代码项目案例 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我!