You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
471 lines
17 KiB
471 lines
17 KiB
/** |
|
* Copyright 2018 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 com.fr.third.org.redisson; |
|
|
|
import java.util.ArrayList; |
|
import java.util.Arrays; |
|
import java.util.Collection; |
|
import java.util.List; |
|
import java.util.ListIterator; |
|
import java.util.concurrent.TimeUnit; |
|
import java.util.concurrent.atomic.AtomicInteger; |
|
import java.util.concurrent.atomic.AtomicLong; |
|
import java.util.concurrent.locks.Condition; |
|
import java.util.concurrent.locks.Lock; |
|
|
|
import com.fr.third.org.redisson.api.RFuture; |
|
import com.fr.third.org.redisson.api.RLock; |
|
import com.fr.third.org.redisson.misc.RPromise; |
|
import com.fr.third.org.redisson.misc.RedissonPromise; |
|
import com.fr.third.org.redisson.misc.TransferListener; |
|
import com.fr.third.org.redisson.api.RFuture; |
|
import com.fr.third.org.redisson.api.RLock; |
|
import com.fr.third.org.redisson.misc.RPromise; |
|
import com.fr.third.org.redisson.misc.RedissonPromise; |
|
import com.fr.third.org.redisson.misc.TransferListener; |
|
|
|
import io.netty.util.concurrent.Future; |
|
import io.netty.util.concurrent.FutureListener; |
|
import io.netty.util.internal.ThreadLocalRandom; |
|
|
|
/** |
|
* Groups multiple independent locks and manages them as one lock. |
|
* |
|
* @author Nikita Koksharov |
|
* |
|
*/ |
|
public class RedissonMultiLock implements Lock { |
|
|
|
final List<RLock> locks = new ArrayList<RLock>(); |
|
|
|
/** |
|
* 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(); |
|
} |
|
} |
|
|
|
public void lock(long leaseTime, TimeUnit unit) { |
|
try { |
|
lockInterruptibly(leaseTime, unit); |
|
} catch (InterruptedException e) { |
|
Thread.currentThread().interrupt(); |
|
} |
|
} |
|
|
|
public RFuture<Void> lockAsync(long leaseTime, TimeUnit unit) { |
|
long baseWaitTime = locks.size() * 1500; |
|
long waitTime = -1; |
|
if (leaseTime == -1) { |
|
waitTime = baseWaitTime; |
|
unit = TimeUnit.MILLISECONDS; |
|
} else { |
|
waitTime = unit.toMillis(leaseTime); |
|
if (waitTime <= 2000) { |
|
waitTime = 2000; |
|
} else if (waitTime <= baseWaitTime) { |
|
waitTime = ThreadLocalRandom.current().nextLong(waitTime/2, waitTime); |
|
} else { |
|
waitTime = ThreadLocalRandom.current().nextLong(baseWaitTime, waitTime); |
|
} |
|
waitTime = unit.convert(waitTime, TimeUnit.MILLISECONDS); |
|
} |
|
|
|
RPromise<Void> result = new RedissonPromise<Void>(); |
|
tryLockAsync(leaseTime, unit, waitTime, result); |
|
return result; |
|
} |
|
|
|
protected void tryLockAsync(final long leaseTime, final TimeUnit unit, final long waitTime, final RPromise<Void> result) { |
|
tryLockAsync(waitTime, leaseTime, unit).addListener(new FutureListener<Boolean>() { |
|
@Override |
|
public void operationComplete(Future<Boolean> future) throws Exception { |
|
if (!future.isSuccess()) { |
|
result.tryFailure(future.cause()); |
|
return; |
|
} |
|
|
|
if (future.getNow()) { |
|
result.trySuccess(null); |
|
} else { |
|
tryLockAsync(leaseTime, unit, waitTime, result); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
|
|
@Override |
|
public void lockInterruptibly() throws InterruptedException { |
|
lockInterruptibly(-1, null); |
|
} |
|
|
|
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException { |
|
long baseWaitTime = locks.size() * 1500; |
|
long waitTime = -1; |
|
if (leaseTime == -1) { |
|
waitTime = baseWaitTime; |
|
unit = TimeUnit.MILLISECONDS; |
|
} else { |
|
waitTime = unit.toMillis(leaseTime); |
|
if (waitTime <= 2000) { |
|
waitTime = 2000; |
|
} else if (waitTime <= baseWaitTime) { |
|
waitTime = ThreadLocalRandom.current().nextLong(waitTime/2, waitTime); |
|
} else { |
|
waitTime = ThreadLocalRandom.current().nextLong(baseWaitTime, waitTime); |
|
} |
|
waitTime = unit.convert(waitTime, TimeUnit.MILLISECONDS); |
|
} |
|
|
|
while (true) { |
|
if (tryLock(waitTime, leaseTime, unit)) { |
|
return; |
|
} |
|
} |
|
} |
|
|
|
@Override |
|
public boolean tryLock() { |
|
try { |
|
return tryLock(-1, -1, null); |
|
} catch (InterruptedException e) { |
|
Thread.currentThread().interrupt(); |
|
return false; |
|
} |
|
} |
|
|
|
protected void unlockInner(Collection<RLock> locks) { |
|
List<RFuture<Void>> futures = new ArrayList<RFuture<Void>>(locks.size()); |
|
for (RLock lock : locks) { |
|
futures.add(lock.unlockAsync()); |
|
} |
|
|
|
for (RFuture<Void> unlockFuture : futures) { |
|
unlockFuture.awaitUninterruptibly(); |
|
} |
|
} |
|
|
|
protected RFuture<Void> unlockInnerAsync(Collection<RLock> locks, long threadId) { |
|
if (locks.isEmpty()) { |
|
return RedissonPromise.newSucceededFuture(null); |
|
} |
|
|
|
final RPromise<Void> result = new RedissonPromise<Void>(); |
|
final AtomicInteger counter = new AtomicInteger(locks.size()); |
|
for (RLock lock : locks) { |
|
lock.unlockAsync(threadId).addListener(new FutureListener<Void>() { |
|
@Override |
|
public void operationComplete(Future<Void> future) throws Exception { |
|
if (!future.isSuccess()) { |
|
result.tryFailure(future.cause()); |
|
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; |
|
} |
|
|
|
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) { |
|
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<RLock> acquiredLocks = new ArrayList<RLock>(locks.size()); |
|
for (ListIterator<RLock> 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 (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 && leaseTime == -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) { |
|
List<RFuture<Boolean>> futures = new ArrayList<RFuture<Boolean>>(acquiredLocks.size()); |
|
for (RLock rLock : acquiredLocks) { |
|
RFuture<Boolean> future = rLock.expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS); |
|
futures.add(future); |
|
} |
|
|
|
for (RFuture<Boolean> rFuture : futures) { |
|
rFuture.syncUninterruptibly(); |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
private void tryAcquireLockAsync(final ListIterator<RLock> iterator, final List<RLock> acquiredLocks, final RPromise<Boolean> result, |
|
final long lockWaitTime, final long waitTime, final long leaseTime, final long newLeaseTime, |
|
final AtomicLong remainTime, final AtomicLong time, final AtomicInteger failedLocksLimit, final TimeUnit unit, final long threadId) { |
|
if (!iterator.hasNext()) { |
|
checkLeaseTimeAsync(acquiredLocks, result, leaseTime, unit); |
|
return; |
|
} |
|
|
|
final RLock lock = iterator.next(); |
|
RPromise<Boolean> lockAcquired = new RedissonPromise<Boolean>(); |
|
if (waitTime == -1 && leaseTime == -1) { |
|
lock.tryLockAsync(threadId) |
|
.addListener(new TransferListener<Boolean>(lockAcquired)); |
|
} else { |
|
long awaitTime = Math.min(lockWaitTime, remainTime.get()); |
|
lock.tryLockAsync(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS, threadId) |
|
.addListener(new TransferListener<Boolean>(lockAcquired));; |
|
} |
|
|
|
lockAcquired.addListener(new FutureListener<Boolean>() { |
|
@Override |
|
public void operationComplete(Future<Boolean> future) throws Exception { |
|
boolean lockAcquired = false; |
|
if (future.getNow() != null) { |
|
lockAcquired = future.getNow(); |
|
} |
|
|
|
if (lockAcquired) { |
|
acquiredLocks.add(lock); |
|
} else { |
|
if (locks.size() - acquiredLocks.size() == failedLocksLimit()) { |
|
checkLeaseTimeAsync(acquiredLocks, result, leaseTime, unit); |
|
return; |
|
} |
|
|
|
if (failedLocksLimit.get() == 0) { |
|
unlockInnerAsync(acquiredLocks, threadId).addListener(new FutureListener<Void>() { |
|
@Override |
|
public void operationComplete(Future<Void> future) throws Exception { |
|
if (!future.isSuccess()) { |
|
result.tryFailure(future.cause()); |
|
return; |
|
} |
|
|
|
if (waitTime == -1 && leaseTime == -1) { |
|
result.trySuccess(false); |
|
return; |
|
} |
|
|
|
failedLocksLimit.set(failedLocksLimit()); |
|
acquiredLocks.clear(); |
|
// reset iterator |
|
while (iterator.hasPrevious()) { |
|
iterator.previous(); |
|
} |
|
|
|
checkRemainTimeAsync(iterator, acquiredLocks, result, |
|
lockWaitTime, waitTime, leaseTime, newLeaseTime, |
|
remainTime, time, failedLocksLimit, unit, threadId); |
|
} |
|
}); |
|
return; |
|
} else { |
|
failedLocksLimit.decrementAndGet(); |
|
} |
|
} |
|
|
|
checkRemainTimeAsync(iterator, acquiredLocks, result, |
|
lockWaitTime, waitTime, leaseTime, newLeaseTime, |
|
remainTime, time, failedLocksLimit, unit, threadId); |
|
} |
|
}); |
|
} |
|
|
|
private void checkLeaseTimeAsync(List<RLock> acquiredLocks, final RPromise<Boolean> result, long leaseTime, TimeUnit unit) { |
|
if (leaseTime != -1) { |
|
final AtomicInteger counter = new AtomicInteger(locks.size()); |
|
for (RLock rLock : acquiredLocks) { |
|
RFuture<Boolean> future = rLock.expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS); |
|
future.addListener(new FutureListener<Boolean>() { |
|
@Override |
|
public void operationComplete(Future<Boolean> future) throws Exception { |
|
if (!future.isSuccess()) { |
|
result.tryFailure(future.cause()); |
|
return; |
|
} |
|
|
|
if (counter.decrementAndGet() == 0) { |
|
result.trySuccess(true); |
|
} |
|
} |
|
}); |
|
} |
|
return; |
|
} |
|
|
|
result.trySuccess(true); |
|
} |
|
|
|
protected void checkRemainTimeAsync(ListIterator<RLock> iterator, List<RLock> acquiredLocks, final RPromise<Boolean> result, |
|
long lockWaitTime, long waitTime, long leaseTime, long newLeaseTime, |
|
AtomicLong remainTime, AtomicLong time, AtomicInteger failedLocksLimit, TimeUnit unit, long threadId) { |
|
if (remainTime.get() != -1) { |
|
remainTime.addAndGet(-(System.currentTimeMillis() - time.get())); |
|
time.set(System.currentTimeMillis());; |
|
if (remainTime.get() <= 0) { |
|
unlockInnerAsync(acquiredLocks, threadId).addListener(new FutureListener<Void>() { |
|
@Override |
|
public void operationComplete(Future<Void> future) throws Exception { |
|
if (!future.isSuccess()) { |
|
result.tryFailure(future.cause()); |
|
return; |
|
} |
|
|
|
result.trySuccess(false); |
|
} |
|
}); |
|
return; |
|
} |
|
} |
|
|
|
tryAcquireLockAsync(iterator, acquiredLocks, result, lockWaitTime, waitTime, |
|
leaseTime, newLeaseTime, remainTime, time, failedLocksLimit, unit, threadId); |
|
} |
|
|
|
public RFuture<Boolean> tryLockAsync(long waitTime, long leaseTime, TimeUnit unit) { |
|
RPromise<Boolean> result = new RedissonPromise<Boolean>(); |
|
long newLeaseTime = -1; |
|
if (leaseTime != -1) { |
|
newLeaseTime = unit.toMillis(waitTime)*2; |
|
} |
|
|
|
AtomicLong time = new AtomicLong(System.currentTimeMillis()); |
|
AtomicLong remainTime = new AtomicLong(-1); |
|
if (waitTime != -1) { |
|
remainTime.set(unit.toMillis(waitTime)); |
|
} |
|
long lockWaitTime = calcLockWaitTime(remainTime.get()); |
|
|
|
AtomicInteger failedLocksLimit = new AtomicInteger(failedLocksLimit()); |
|
List<RLock> acquiredLocks = new ArrayList<RLock>(locks.size()); |
|
long threadId = Thread.currentThread().getId(); |
|
tryAcquireLockAsync(locks.listIterator(), acquiredLocks, result, |
|
lockWaitTime, waitTime, leaseTime, newLeaseTime, |
|
remainTime, time, failedLocksLimit, unit, threadId); |
|
|
|
return result; |
|
} |
|
|
|
|
|
protected long calcLockWaitTime(long remainTime) { |
|
return remainTime; |
|
} |
|
|
|
public RFuture<Void> unlockAsync(long threadId) { |
|
return unlockInnerAsync(locks, threadId); |
|
} |
|
|
|
@Override |
|
public void unlock() { |
|
List<RFuture<Void>> futures = new ArrayList<RFuture<Void>>(locks.size()); |
|
|
|
for (RLock lock : locks) { |
|
futures.add(lock.unlockAsync()); |
|
} |
|
|
|
for (RFuture<Void> future : futures) { |
|
future.syncUninterruptibly(); |
|
} |
|
} |
|
|
|
|
|
@Override |
|
public Condition newCondition() { |
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
}
|
|
|