diff --git a/pom.xml b/pom.xml index a3714bb..d96b279 100644 --- a/pom.xml +++ b/pom.xml @@ -3,8 +3,8 @@ 4.0.0 site.hellooo - hellooo-distributedlock - 0.0.5-GA + halo-distributedlock + 0.0.6-GA UTF8 @@ -21,7 +21,7 @@ jedis ${jedis.version} - + junit junit @@ -42,10 +42,10 @@ - + distributed-lock a simple but reliable distributed lock implementation - https://github.com/hellooo-stack/hellooo-distributedlock + https://github.com/hellooo-stack/halo-distributedlock Apache License, Version 2.0 @@ -53,9 +53,9 @@ - https://github.com/hellooo-stack/hellooo-distributedlock - https://github.com/hellooo-stack/hellooo-distributedlock.git - https://github.com/hellooo-stack/hellooo-distributedlock.git + https://github.com/hellooo-stack/halo-distributedlock + https://github.com/hellooo-stack/halo-distributedlock.git + https://github.com/hellooo-stack/halo-distributedlock.git HEAD @@ -66,7 +66,7 @@ - + ossrh https://s01.oss.sonatype.org/content/repositories/snapshots @@ -80,7 +80,7 @@ release - + org.apache.maven.plugins maven-source-plugin @@ -94,7 +94,7 @@ - + org.apache.maven.plugins maven-javadoc-plugin @@ -108,7 +108,7 @@ - + org.apache.maven.plugins maven-gpg-plugin @@ -123,7 +123,7 @@ - + org.sonatype.plugins nexus-staging-maven-plugin diff --git a/src/main/java/site/hellooo/distributedlock/LockCallback.java b/src/main/java/site/hellooo/distributedlock/LockCallback.java index 7b294ab..f3c3256 100644 --- a/src/main/java/site/hellooo/distributedlock/LockCallback.java +++ b/src/main/java/site/hellooo/distributedlock/LockCallback.java @@ -1,12 +1,12 @@ package site.hellooo.distributedlock; public interface LockCallback { - // execute immediately after the lock granted to current thread + // execute immediately after the lock granted to current thread void afterLocked(LockContext lockContext); - // execute before parking the current thread + // execute before parking the current thread void beforeParking(LockContext lockContext); - // execute immediately after the lock released + // execute immediately after the lock released void afterUnlocked(LockContext lockContext); } diff --git a/src/main/java/site/hellooo/distributedlock/LockContext.java b/src/main/java/site/hellooo/distributedlock/LockContext.java index 8f5f282..c990504 100644 --- a/src/main/java/site/hellooo/distributedlock/LockContext.java +++ b/src/main/java/site/hellooo/distributedlock/LockContext.java @@ -8,19 +8,18 @@ * context of the lock, holding the necessary message for handling the lock operation */ public interface LockContext { - String lockTarget(); - // the user config of this lock - LockOptions lockOptions(); + String target(); + // user options of the lock + LockOptions options(); - // thread which is holding the lock in current process -// return value should not be null -// todo add annotation + // thread which is holding the lock in current process + // return value should not be null AtomicReference holdingThread(); - // lockState of the holding thread in current process + // lockState of the holding thread in current process AtomicReference> holdingLockState(); - // operation handler for dealing with the coordinator + // operation handler for dealing with the coordinator LockHandler lockHandler(); LockCallback lockCallback(); diff --git a/src/main/java/site/hellooo/distributedlock/LockState.java b/src/main/java/site/hellooo/distributedlock/LockState.java index 93d0ad4..6719bf5 100644 --- a/src/main/java/site/hellooo/distributedlock/LockState.java +++ b/src/main/java/site/hellooo/distributedlock/LockState.java @@ -4,13 +4,13 @@ public interface LockState extends Serializable { - // get the identifier representing this state -// such as a redis key, a zookeeper path, a file name + // return the identifier representing this state + // such as a redis key, a zookeeper path, a file name, etc. String getIdentifier(); void setIdentifier(String identifier); - // get the value in the state + // return the value in the state T getValue(); void setValue(T value); diff --git a/src/main/java/site/hellooo/distributedlock/common/ProcessUtils.java b/src/main/java/site/hellooo/distributedlock/common/ProcessUtils.java index ee3f2c9..c897fd0 100644 --- a/src/main/java/site/hellooo/distributedlock/common/ProcessUtils.java +++ b/src/main/java/site/hellooo/distributedlock/common/ProcessUtils.java @@ -16,7 +16,7 @@ public static String getProcessId() { } -// if no process id returned, return the default one + // if no process id returned, return the default one return DEFAULT_FALLBACK_PROCESS_ID; } } diff --git a/src/main/java/site/hellooo/distributedlock/common/StringUtils.java b/src/main/java/site/hellooo/distributedlock/common/StringUtils.java index d80aaa0..8ab3a0c 100644 --- a/src/main/java/site/hellooo/distributedlock/common/StringUtils.java +++ b/src/main/java/site/hellooo/distributedlock/common/StringUtils.java @@ -10,7 +10,7 @@ public static boolean isEmpty(CharSequence charSequence) { } public static boolean isEmpty(String string) { - return string == null || string.length() == 0; + return string == null || string.isEmpty(); } public static boolean isNotEmpty(CharSequence charSequence) { diff --git a/src/main/java/site/hellooo/distributedlock/config/LockOptions.java b/src/main/java/site/hellooo/distributedlock/config/LockOptions.java index ba347ca..ee5e659 100644 --- a/src/main/java/site/hellooo/distributedlock/config/LockOptions.java +++ b/src/main/java/site/hellooo/distributedlock/config/LockOptions.java @@ -7,22 +7,20 @@ import java.io.Serializable; import java.util.StringJoiner; -// 提供用户使用的参数配置类 -// redis专用, 后续优化 +// Maybe a little hardcode for redis, It still needs to do some work if you want to use other coordinators, +// such as zookeeper, etcd, etc. public class LockOptions implements Reusable, Serializable { - // prefix of lock identifier + // prefix of lock identifier private final String identifierPrefix; - // suffix of lock identifier + // suffix of lock identifier private final String identifierSuffix; - // milliseconds of retry thread execute interval + // milliseconds of retry thread execute interval private final long retryIntervalMilliseconds; - // milliseconds of lease thread execute interval, should be smaller than leaseMilliseconds + // milliseconds of lease thread execute interval, should be smaller than leaseMilliseconds private final long leaseIntervalMilliseconds; - // milliseconds to lease per time + // milliseconds to lease per time private final long leaseMilliseconds; - - // todo private final Coordinator coordinator; private LockOptions(final String identifierPrefix, @@ -36,6 +34,7 @@ private LockOptions(final String identifierPrefix, ArgChecker.check(retryIntervalMilliseconds > 0, "retryIntervalMilliseconds is " + retryIntervalMilliseconds + " (expected > 0)."); ArgChecker.check(leaseIntervalMilliseconds > 0, "leaseIntervalMilliseconds is " + leaseIntervalMilliseconds + " (expected > 0)."); ArgChecker.check(leaseMilliseconds > 0, "leaseMilliseconds is " + leaseMilliseconds + " (expected > 0)."); + ArgChecker.check(leaseMilliseconds > leaseIntervalMilliseconds, "leaseMilliseconds less than leaseIntervalMilliseconds (expected greater than leaseIntervalMilliseconds)."); ArgChecker.check(coordinator != null, "coordinator is null (expected not null)."); this.identifierPrefix = identifierPrefix; @@ -99,13 +98,13 @@ public static class LockOptionsBuilder { private static final String DEFAULT_IDENTIFIER_PREFIX = ""; private static final String DEFAULT_IDENTIFIER_SUFFIX = ""; -// 默认20毫秒重试一次 + // 20 milliseconds by default private static final long DEFAULT_RETRY_INTERVAL_MILLISECONDS = 10L; -// 默认1秒续期一次 + // 1 second a lease by default private static final long DEFAULT_LEASE_INTERVAL_MILLISECONDS = 1000L; -// 默认每次续期都将过期时间设置为5秒 + // default lease time is 5 seconds private static final long DEFAULT_LEASE_MILLISECONDS = 5000L; - + // default coordinator is redis(singleton) private static final Coordinator DEFAULT_COORDINATOR = Coordinator.REDIS_SINGLETON; private String identifierPrefix = DEFAULT_IDENTIFIER_PREFIX; diff --git a/src/main/java/site/hellooo/distributedlock/enums/Coordinator.java b/src/main/java/site/hellooo/distributedlock/enums/Coordinator.java index 7cdf814..1215534 100644 --- a/src/main/java/site/hellooo/distributedlock/enums/Coordinator.java +++ b/src/main/java/site/hellooo/distributedlock/enums/Coordinator.java @@ -1,6 +1,5 @@ package site.hellooo.distributedlock.enums; -// todo add clone method, some reusable interface may use that method to do the clone public enum Coordinator { REDIS_SINGLETON("redis_singleton"), diff --git a/src/main/java/site/hellooo/distributedlock/impl/LockStateBuilder.java b/src/main/java/site/hellooo/distributedlock/impl/LockStateBuilder.java index 1a532c6..2aa6650 100644 --- a/src/main/java/site/hellooo/distributedlock/impl/LockStateBuilder.java +++ b/src/main/java/site/hellooo/distributedlock/impl/LockStateBuilder.java @@ -16,9 +16,6 @@ public class LockStateBuilder { private Coordinator coordinator = DEFAULT_COORDINATOR; private String identifier; - private Object value; - - public LockStateBuilder() { } @@ -38,23 +35,17 @@ public LockStateBuilder identifier(String identifier) { return this; } - public LockStateBuilder value(Object value) { - this.value = value; - return this; - } - public LockState build() { ArgChecker.check(!StringUtils.isEmpty(identifier), "identifier is empty (expected not empty)."); -// ArgChecker.check(value != null, "value is null (expected not null)."); String generatedIdentifier = lockOptions.getIdentifierPrefix() + this.identifier; generatedIdentifier = generatedIdentifier + lockOptions.getIdentifierSuffix(); switch (coordinator) { case REDIS_SINGLETON: - if (this.value != null && !(this.value instanceof String)) { - throw new IllegalArgumentException("Fatal: invalid type of 'value', type is " + value.getClass().getSimpleName()); - } + // if (this.value == null || !(this.value instanceof String)) { + // throw new IllegalArgumentException("Fatal: invalid type of 'value', type is " + (value == null ? "null" : value.getClass().getSimpleName())); + // } return new RedisLockState(generatedIdentifier); case REDIS_CLUSTER: diff --git a/src/main/java/site/hellooo/distributedlock/impl/ReentrantDistributedLock.java b/src/main/java/site/hellooo/distributedlock/impl/ReentrantDistributedLock.java index 7dbc942..2d31388 100644 --- a/src/main/java/site/hellooo/distributedlock/impl/ReentrantDistributedLock.java +++ b/src/main/java/site/hellooo/distributedlock/impl/ReentrantDistributedLock.java @@ -34,12 +34,12 @@ public ReentrantDistributedLock(LockOptions lockOptions, String lockTarget, Lock private final AtomicReference> holdingLockState = new AtomicReference<>(); @Override - public String lockTarget() { + public String target() { return lockTarget; } @Override - public LockOptions lockOptions() { + public LockOptions options() { return lockOptions; } @@ -88,7 +88,7 @@ private Node addWaiter() { return currentNode; } -// enqueue node and return the prev node + // enqueue node and return the prev node private Node enqueue(final Node node) { while (true) { @@ -115,12 +115,12 @@ private void acquireQueued(final Node node) { while (true) { final Node prev = node.prev.get(); -// only if prev node is head, and then we try to get the lock + // only if prev node is head, and then we try to get the lock if (prev == head.get() && tryLock()) { -// if tryLock success, it means that the prev node has release the lock, -// so we need to remove it from the queue + // if tryLock success, it means that the prev node has release the lock, + // so we need to remove it from the queue head.set(node); -// set to null, help with gc + // set to null, help with gc prev.next.set(null); break; } @@ -158,8 +158,6 @@ public boolean tryLock() { LockState lockState = new LockStateBuilder(lockOptions) .identifier(lockTarget) -// todo: need a value builder for difference state type -// .value() .build(); boolean locked = false; try { @@ -208,11 +206,12 @@ public void unlock() { } try { lockHandler.removeState(holdingLockState, lockContext); - lockCallback.afterUnlocked(lockContext); } catch (LockStateNotRemovedException ignored) { - } finally { + lockCallback.afterUnlocked(lockContext); + holdingThreadReference.compareAndSet(holdingThread, null); + holdingLockStateReference.compareAndSet(holdingLockState, null); unparkQueueHead(); } } diff --git a/src/main/java/site/hellooo/distributedlock/impl/ReentrantDistributedLockBuilder.java b/src/main/java/site/hellooo/distributedlock/impl/ReentrantDistributedLockBuilder.java index 6138b30..916ba1a 100644 --- a/src/main/java/site/hellooo/distributedlock/impl/ReentrantDistributedLockBuilder.java +++ b/src/main/java/site/hellooo/distributedlock/impl/ReentrantDistributedLockBuilder.java @@ -1,7 +1,6 @@ package site.hellooo.distributedlock.impl; -import redis.clients.jedis.Jedis; -import site.hellooo.distributedlock.LockCallback; +import redis.clients.jedis.JedisPool; import site.hellooo.distributedlock.LockHandler; import site.hellooo.distributedlock.common.StringUtils; import site.hellooo.distributedlock.config.LockOptions; @@ -18,9 +17,8 @@ public class ReentrantDistributedLockBuilder { private String lockTarget = null; private LockHandler lockHandler = null; - private LockCallback lockCallback = null; - private Jedis jedis; + private JedisPool jedisPool; public ReentrantDistributedLockBuilder lockOptions(LockOptions lockOptions) { this.lockOptions = lockOptions; @@ -37,13 +35,8 @@ public ReentrantDistributedLockBuilder lockHandler(LockHandler lockHandler) { return this; } - public ReentrantDistributedLockBuilder lockCallback(LockCallback lockCallback) { - this.lockCallback = lockCallback; - return this; - } - - public ReentrantDistributedLockBuilder jedis(Jedis jedis) { - this.jedis = jedis; + public ReentrantDistributedLockBuilder jedisPool(JedisPool jedisPool) { + this.jedisPool = jedisPool; return this; } @@ -56,17 +49,13 @@ public ReentrantDistributedLock build() { Coordinator coordinator = lockOptions.getCoordinator(); switch (coordinator) { case REDIS_SINGLETON: - if (this.jedis == null) { + if (this.jedisPool == null) { throw new BuilderEssentialFieldNotSetException("Fatal: miss essential component 'jedis'!"); } if (lockHandler == null) { - lockHandler = new RedisLockHandler(this.jedis); + lockHandler = new RedisLockHandler(this.jedisPool); } - -// if (lockCallback == null) { -// lockCallback = new RedisLockCallback(); -// } break; case REDIS_CLUSTER: case ZOOKEEPER: diff --git a/src/main/java/site/hellooo/distributedlock/impl/redis/RedisLockCallback.java b/src/main/java/site/hellooo/distributedlock/impl/redis/RedisLockCallback.java index e5b3527..59b18dd 100644 --- a/src/main/java/site/hellooo/distributedlock/impl/redis/RedisLockCallback.java +++ b/src/main/java/site/hellooo/distributedlock/impl/redis/RedisLockCallback.java @@ -33,7 +33,7 @@ private void startRetryLockThread() { while (retryLockThread == null || retryLockThread.getState() == Thread.State.TERMINATED) { if (retryLockThreadReference.compareAndSet(retryLockThread, new RemotingRetryLockThread(lockContext))) { retryLockThread = retryLockThreadReference.get(); -// one process have only one running retryLockThread + // one process have only one running retryLockThread retryLockThread.start(); } } @@ -72,7 +72,7 @@ public void afterLocked(LockContext lockContext) { @Override public void beforeParking(LockContext lockContext) { -// if current process not holding the lock, then we should start the retry lock thread + // if current process not holding the lock, then we should start the retry lock thread if (lockContext.holdingThread().get() == null) { startRetryLockThread(); } diff --git a/src/main/java/site/hellooo/distributedlock/impl/redis/RedisLockHandler.java b/src/main/java/site/hellooo/distributedlock/impl/redis/RedisLockHandler.java index 2513bd9..53f7d5d 100644 --- a/src/main/java/site/hellooo/distributedlock/impl/redis/RedisLockHandler.java +++ b/src/main/java/site/hellooo/distributedlock/impl/redis/RedisLockHandler.java @@ -1,6 +1,7 @@ package site.hellooo.distributedlock.impl.redis; import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; import redis.clients.jedis.params.SetParams; import site.hellooo.distributedlock.LockContext; import site.hellooo.distributedlock.LockHandler; @@ -18,10 +19,10 @@ public class RedisLockHandler implements LockHandler { - private final Jedis jedis; + private final JedisPool jedisPool; - public RedisLockHandler(Jedis jedis) { - this.jedis = jedis; + public RedisLockHandler(JedisPool jedisPool) { + this.jedisPool = jedisPool; } @Override @@ -30,17 +31,16 @@ public void setState(LockState lockState, LockContext lockContext) throws Loc ArgChecker.checkNotNull(lockState.getIdentifier(), "lockState.getIdentifier() is expected to be not null"); ArgChecker.checkNotNull(lockState.getValue(), "lockState.getValue() is expected to be not null"); ArgChecker.checkNotNull(lockContext, "lockContext is expected to be not null"); - ArgChecker.checkNotNull(lockContext.lockOptions(), "lockContext.lockOptions() is expected to be not null"); - ArgChecker.check(lockContext.lockOptions().getLeaseMilliseconds() > 0, "lockContext.lockOptions().getLeaseMilliseconds() is expected to be > 0"); + ArgChecker.checkNotNull(lockContext.options(), "lockContext.lockOptions() is expected to be not null"); + ArgChecker.check(lockContext.options().getLeaseMilliseconds() > 0, "lockContext.lockOptions().getLeaseMilliseconds() is expected to be > 0"); String lockResult; - try { -// SET key value [EX seconds] [PX milliseconds] [NX|XX] -// set abc 2 nx ex 10 -// set abc 2 nx px 10000 -// lockResult = this.jedis.set(lockState.getIdentifier(), (String) lockState.getValue(), "NX", "PX", lockContext.lockOptions().getLeaseMilliseconds()); - SetParams setParams = new SetParams().nx().px(lockContext.lockOptions().getLeaseMilliseconds()); - lockResult = this.jedis.set(lockState.getIdentifier(), (String) lockState.getValue(), setParams); + try (Jedis jedis = jedisPool.getResource()) { + // SET key value [EX seconds] [PX milliseconds] [NX|XX] + // set abc 2 nx ex 10 + // set abc 2 nx px 10000 + SetParams setParams = new SetParams().nx().px(lockContext.options().getLeaseMilliseconds()); + lockResult = jedis.set(lockState.getIdentifier(), (String) lockState.getValue(), setParams); } catch (Exception e) { throw new LockStateNotSetException("Fatal: executing redis command to set lock state for identifier [" + lockState.getIdentifier() + "] failed!!!", e); } @@ -61,12 +61,12 @@ public void removeState(LockState lockState, LockContext lockContext) throws ArgChecker.checkNotNull(lockState.getIdentifier(), "lockState.getIdentifier() is expected to be not null"); ArgChecker.checkNotNull(lockState.getValue(), "lockState.getValue() is expected to be not null"); ArgChecker.checkNotNull(lockContext, "lockContext is expected to be not null"); - ArgChecker.checkNotNull(lockContext.lockOptions(), "lockContext.lockOptions() is expected to be not null"); + ArgChecker.checkNotNull(lockContext.options(), "lockContext.lockOptions() is expected to be not null"); String removeStateScript = "if (redis.call('get', KEYS[1]) == ARGV[1]) then return redis.call('del', KEYS[1]); else return nil; end;"; Object unlockResult; - try { - unlockResult = this.jedis.eval(removeStateScript, Collections.singletonList(lockState.getIdentifier()), Collections.singletonList((String) lockState.getValue())); + try (Jedis jedis = jedisPool.getResource()) { + unlockResult = jedis.eval(removeStateScript, Collections.singletonList(lockState.getIdentifier()), Collections.singletonList((String) lockState.getValue())); } catch (Exception e) { throw new LockStateNotRemovedException("Fatal: executing redis command to remove lock state for identifier [" + lockState.getIdentifier() + "] failed!!!", e); } @@ -88,18 +88,17 @@ public Coordinator coordinatorType() { public boolean doLease(LockContext lockContext) { ArgChecker.checkNotNull(lockContext, "lockContext is expected to be not null"); - ArgChecker.checkNotNull(lockContext.lockOptions(), "lockContext.lockOptions() is expected to be not null"); - ArgChecker.check(lockContext.lockOptions().getLeaseMilliseconds() > 0, "leaseMilliseconds is " + lockContext.lockOptions().getLeaseMilliseconds() + " (expected > 0)."); + ArgChecker.checkNotNull(lockContext.options(), "lockContext.lockOptions() is expected to be not null"); + ArgChecker.check(lockContext.options().getLeaseMilliseconds() > 0, "leaseMilliseconds is " + lockContext.options().getLeaseMilliseconds() + " (expected > 0)."); String leaseScript = "if (redis.call('get', KEYS[1]) == ARGV[1]) then return redis.call('pexpire', KEYS[1], ARGV[2]); else return nil; end;"; Object leaseResult = null; - try { + try (Jedis jedis = jedisPool.getResource()) { LockState lockState = lockContext.holdingLockState().get(); -// if success, value will be "1" - leaseResult = jedis.eval(leaseScript, Collections.singletonList(lockState.getIdentifier()), Arrays.asList((String) lockState.getValue(), lockContext.lockOptions().getLeaseMilliseconds() + "")); + // if success, value will be "1" + leaseResult = jedis.eval(leaseScript, Collections.singletonList(lockState.getIdentifier()), Arrays.asList((String) lockState.getValue(), lockContext.options().getLeaseMilliseconds() + "")); } catch (Exception ignored) { - } return "1".equals(leaseResult); @@ -108,13 +107,12 @@ public boolean doLease(LockContext lockContext) { public boolean checkStateExists(String identifier) { boolean isStateExists = false; - try { + try (Jedis jedis = jedisPool.getResource()) { String lockState = jedis.get(identifier); if (StringUtils.isNotEmpty(lockState)) { isStateExists = true; } - } catch (Exception e) { - + } catch (Exception ignored) { } return isStateExists; diff --git a/src/main/java/site/hellooo/distributedlock/impl/redis/RemotingLeaseThread.java b/src/main/java/site/hellooo/distributedlock/impl/redis/RemotingLeaseThread.java index 7575997..a4bf143 100644 --- a/src/main/java/site/hellooo/distributedlock/impl/redis/RemotingLeaseThread.java +++ b/src/main/java/site/hellooo/distributedlock/impl/redis/RemotingLeaseThread.java @@ -15,7 +15,7 @@ protected void execute() throws InterruptedException { @Override protected long getExecuteInterval() { - return lockContext.lockOptions().getLeaseIntervalMilliseconds(); + return lockContext.options().getLeaseIntervalMilliseconds(); } private void doLease() throws InterruptedException { diff --git a/src/main/java/site/hellooo/distributedlock/impl/redis/RemotingRetryLockThread.java b/src/main/java/site/hellooo/distributedlock/impl/redis/RemotingRetryLockThread.java index d2d5384..3969b32 100644 --- a/src/main/java/site/hellooo/distributedlock/impl/redis/RemotingRetryLockThread.java +++ b/src/main/java/site/hellooo/distributedlock/impl/redis/RemotingRetryLockThread.java @@ -17,7 +17,7 @@ protected void execute() throws InterruptedException { @Override protected long getExecuteInterval() { - return lockContext.lockOptions().getRetryIntervalMilliseconds(); + return lockContext.options().getRetryIntervalMilliseconds(); } private void doRetry() throws InterruptedException { @@ -31,10 +31,10 @@ private void doRetry() throws InterruptedException { } RedisLockHandler lockHandler = (RedisLockHandler) lockContext.lockHandler(); - boolean isStateExists = lockHandler.checkStateExists(lockContext.lockTarget()); + boolean isStateExists = lockHandler.checkStateExists(lockContext.target()); if (!isStateExists) { -// lock hold by other process has been released, -// then we unpark queued head thread in current process +// lock hold by other process has been released, +// then we unpark queued head thread in current process ReentrantDistributedLock lock = (ReentrantDistributedLock) lockContext.currentLock(); lock.unparkQueueHead(); }