org.redisson.RedissonMultiLock Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redisson-all Show documentation
Show all versions of redisson-all Show documentation
Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC
/**
* Copyright (c) 2013-2021 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.api.RLockAsync;
import org.redisson.client.RedisResponseTimeoutException;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
/**
* Groups multiple independent locks and manages them as one lock.
*
* @author Nikita Koksharov
*
*/
public class RedissonMultiLock implements RLock {
class LockState {
private final long newLeaseTime;
private final long lockWaitTime;
private final List acquiredLocks;
private final long waitTime;
private final long threadId;
private final long leaseTime;
private final TimeUnit unit;
private long remainTime;
private long time = System.currentTimeMillis();
private int failedLocksLimit;
LockState(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
this.waitTime = waitTime;
this.leaseTime = leaseTime;
this.unit = unit;
this.threadId = threadId;
if (leaseTime != -1) {
if (waitTime == -1) {
newLeaseTime = unit.toMillis(leaseTime);
} else {
newLeaseTime = unit.toMillis(waitTime)*2;
}
} else {
newLeaseTime = -1;
}
remainTime = -1;
if (waitTime != -1) {
remainTime = unit.toMillis(waitTime);
}
lockWaitTime = calcLockWaitTime(remainTime);
failedLocksLimit = failedLocksLimit();
acquiredLocks = new ArrayList<>(locks.size());
}
void tryAcquireLockAsync(ListIterator iterator, RPromise result) {
if (!iterator.hasNext()) {
checkLeaseTimeAsync(result);
return;
}
RLock lock = iterator.next();
RFuture lockAcquiredFuture;
if (waitTime == -1 && leaseTime == -1) {
lockAcquiredFuture = lock.tryLockAsync(threadId);
} else {
long awaitTime = Math.min(lockWaitTime, remainTime);
lockAcquiredFuture = lock.tryLockAsync(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS, threadId);
}
lockAcquiredFuture.whenComplete((res, e) -> {
boolean lockAcquired = false;
if (res != null) {
lockAcquired = res;
}
if (e instanceof RedisResponseTimeoutException) {
unlockInnerAsync(Arrays.asList(lock), threadId);
}
if (lockAcquired) {
acquiredLocks.add(lock);
} else {
if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
checkLeaseTimeAsync(result);
return;
}
if (failedLocksLimit == 0) {
unlockInnerAsync(acquiredLocks, threadId).onComplete((r, ex) -> {
if (ex != null) {
result.tryFailure(ex);
return;
}
if (waitTime == -1) {
result.trySuccess(false);
return;
}
failedLocksLimit = failedLocksLimit();
acquiredLocks.clear();
// reset iterator
while (iterator.hasPrevious()) {
iterator.previous();
}
checkRemainTimeAsync(iterator, result);
});
return;
} else {
failedLocksLimit--;
}
}
checkRemainTimeAsync(iterator, result);
});
}
private void checkLeaseTimeAsync(RPromise result) {
if (leaseTime != -1) {
AtomicInteger counter = new AtomicInteger(acquiredLocks.size());
for (RLock rLock : acquiredLocks) {
RFuture future = ((RedissonLock) rLock).expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS);
future.onComplete((res, e) -> {
if (e != null) {
result.tryFailure(e);
return;
}
if (counter.decrementAndGet() == 0) {
result.trySuccess(true);
}
});
}
return;
}
result.trySuccess(true);
}
private void checkRemainTimeAsync(ListIterator iterator, RPromise result) {
if (remainTime != -1) {
remainTime += -(System.currentTimeMillis() - time);
time = System.currentTimeMillis();
if (remainTime <= 0) {
unlockInnerAsync(acquiredLocks, threadId).onComplete((res, e) -> {
if (e != null) {
result.tryFailure(e);
return;
}
result.trySuccess(false);
});
return;
}
}
tryAcquireLockAsync(iterator, result);
}
}
final List locks = new ArrayList<>();
/**
* Creates instance with multiple {@link RLock} objects.
* Each RLock object could be created by own Redisson instance.
*
* @param locks - array of locks
*/
public RedissonMultiLock(RLock... locks) {
if (locks.length == 0) {
throw new IllegalArgumentException("Lock objects are not defined");
}
this.locks.addAll(Arrays.asList(locks));
}
@Override
public void lock() {
try {
lockInterruptibly();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public void lock(long leaseTime, TimeUnit unit) {
try {
lockInterruptibly(leaseTime, unit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public RFuture lockAsync(long leaseTime, TimeUnit unit) {
return lockAsync(leaseTime, unit, Thread.currentThread().getId());
}
@Override
public RFuture lockAsync(long leaseTime, TimeUnit unit, long threadId) {
long baseWaitTime = locks.size() * 1500;
long waitTime = -1;
if (leaseTime == -1) {
waitTime = baseWaitTime;
} else {
leaseTime = unit.toMillis(leaseTime);
waitTime = leaseTime;
if (waitTime <= 2000) {
waitTime = 2000;
} else if (waitTime <= baseWaitTime) {
waitTime = ThreadLocalRandom.current().nextLong(waitTime/2, waitTime);
} else {
waitTime = ThreadLocalRandom.current().nextLong(baseWaitTime, waitTime);
}
}
RPromise result = new RedissonPromise();
tryLockAsync(threadId, leaseTime, TimeUnit.MILLISECONDS, waitTime, result);
return result;
}
protected void tryLockAsync(long threadId, long leaseTime, TimeUnit unit, long waitTime, RPromise result) {
tryLockAsync(waitTime, leaseTime, unit, threadId).onComplete((res, e) -> {
if (e != null) {
result.tryFailure(e);
return;
}
if (res) {
result.trySuccess(null);
} else {
tryLockAsync(threadId, leaseTime, unit, waitTime, result);
}
});
}
@Override
public void lockInterruptibly() throws InterruptedException {
lockInterruptibly(-1, null);
}
@Override
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
long baseWaitTime = locks.size() * 1500;
long waitTime = -1;
if (leaseTime == -1) {
waitTime = baseWaitTime;
} else {
leaseTime = unit.toMillis(leaseTime);
waitTime = leaseTime;
if (waitTime <= 2000) {
waitTime = 2000;
} else if (waitTime <= baseWaitTime) {
waitTime = ThreadLocalRandom.current().nextLong(waitTime/2, waitTime);
} else {
waitTime = ThreadLocalRandom.current().nextLong(baseWaitTime, waitTime);
}
}
while (true) {
if (tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS)) {
return;
}
}
}
@Override
public boolean tryLock() {
try {
return tryLock(-1, -1, null);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
protected void unlockInner(Collection locks) {
locks.stream()
.map(RLockAsync::unlockAsync)
.forEach(f -> {
try {
f.toCompletableFuture().join();
} catch (Exception e) {
// skip
}
});
}
protected RFuture unlockInnerAsync(Collection locks, long threadId) {
if (locks.isEmpty()) {
return RedissonPromise.newSucceededFuture(null);
}
RPromise result = new RedissonPromise();
AtomicInteger counter = new AtomicInteger(locks.size());
for (RLock lock : locks) {
lock.unlockAsync(threadId).onComplete((res, e) -> {
if (e != null) {
result.tryFailure(e);
return;
}
if (counter.decrementAndGet() == 0) {
result.trySuccess(null);
}
});
}
return result;
}
@Override
public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException {
return tryLock(waitTime, -1, unit);
}
protected int failedLocksLimit() {
return 0;
}
@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
// try {
// return tryLockAsync(waitTime, leaseTime, unit).get();
// } catch (ExecutionException e) {
// throw new IllegalStateException(e);
// }
long newLeaseTime = -1;
if (leaseTime != -1) {
if (waitTime == -1) {
newLeaseTime = unit.toMillis(leaseTime);
} else {
newLeaseTime = unit.toMillis(waitTime)*2;
}
}
long time = System.currentTimeMillis();
long remainTime = -1;
if (waitTime != -1) {
remainTime = unit.toMillis(waitTime);
}
long lockWaitTime = calcLockWaitTime(remainTime);
int failedLocksLimit = failedLocksLimit();
List acquiredLocks = new ArrayList<>(locks.size());
for (ListIterator iterator = locks.listIterator(); iterator.hasNext();) {
RLock lock = iterator.next();
boolean lockAcquired;
try {
if (waitTime == -1 && leaseTime == -1) {
lockAcquired = lock.tryLock();
} else {
long awaitTime = Math.min(lockWaitTime, remainTime);
lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);
}
} catch (RedisResponseTimeoutException e) {
unlockInner(Arrays.asList(lock));
lockAcquired = false;
} catch (Exception e) {
lockAcquired = false;
}
if (lockAcquired) {
acquiredLocks.add(lock);
} else {
if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
break;
}
if (failedLocksLimit == 0) {
unlockInner(acquiredLocks);
if (waitTime == -1) {
return false;
}
failedLocksLimit = failedLocksLimit();
acquiredLocks.clear();
// reset iterator
while (iterator.hasPrevious()) {
iterator.previous();
}
} else {
failedLocksLimit--;
}
}
if (remainTime != -1) {
remainTime -= System.currentTimeMillis() - time;
time = System.currentTimeMillis();
if (remainTime <= 0) {
unlockInner(acquiredLocks);
return false;
}
}
}
if (leaseTime != -1) {
acquiredLocks.stream()
.map(l -> (RedissonLock) l)
.map(l -> l.expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS))
.forEach(f -> f.toCompletableFuture().join());
}
return true;
}
@Override
public RFuture tryLockAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
RPromise result = new RedissonPromise();
LockState state = new LockState(waitTime, leaseTime, unit, threadId);
state.tryAcquireLockAsync(locks.listIterator(), result);
return result;
}
@Override
public RFuture tryLockAsync(long waitTime, long leaseTime, TimeUnit unit) {
return tryLockAsync(waitTime, leaseTime, unit, Thread.currentThread().getId());
}
protected long calcLockWaitTime(long remainTime) {
return remainTime;
}
@Override
public RFuture unlockAsync(long threadId) {
return unlockInnerAsync(locks, threadId);
}
@Override
public void unlock() {
List> futures = new ArrayList<>(locks.size());
for (RLock lock : locks) {
futures.add(lock.unlockAsync());
}
for (RFuture future : futures) {
future.toCompletableFuture().join();
}
}
@Override
public Condition newCondition() {
throw new UnsupportedOperationException();
}
@Override
public RFuture forceUnlockAsync() {
throw new UnsupportedOperationException();
}
@Override
public RFuture unlockAsync() {
return unlockAsync(Thread.currentThread().getId());
}
@Override
public RFuture tryLockAsync() {
return tryLockAsync(Thread.currentThread().getId());
}
@Override
public RFuture lockAsync() {
return lockAsync(Thread.currentThread().getId());
}
@Override
public RFuture lockAsync(long threadId) {
return lockAsync(-1, null, threadId);
}
@Override
public RFuture tryLockAsync(long threadId) {
return tryLockAsync(-1, -1, null, threadId);
}
@Override
public RFuture tryLockAsync(long waitTime, TimeUnit unit) {
return tryLockAsync(waitTime, -1, unit);
}
@Override
public RFuture getHoldCountAsync() {
throw new UnsupportedOperationException();
}
@Override
public String getName() {
throw new UnsupportedOperationException();
}
@Override
public boolean forceUnlock() {
throw new UnsupportedOperationException();
}
@Override
public boolean isLocked() {
throw new UnsupportedOperationException();
}
@Override
public RFuture isLockedAsync() {
throw new UnsupportedOperationException();
}
@Override
public boolean isHeldByThread(long threadId) {
throw new UnsupportedOperationException();
}
@Override
public boolean isHeldByCurrentThread() {
throw new UnsupportedOperationException();
}
@Override
public int getHoldCount() {
throw new UnsupportedOperationException();
}
@Override
public RFuture remainTimeToLiveAsync() {
throw new UnsupportedOperationException();
}
@Override
public long remainTimeToLive() {
throw new UnsupportedOperationException();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy