blob: 39070c35579c38afbc18de1be59b2bbdff1f548c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.ignite.internal.util;
import java.util.concurrent.ConcurrentMap;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.ConcurrentHashMap;
/**
* This is an utility class for 'splitting' locking of some resources.
* <p>
* Map resources values to some number of locks,
* and supply convenience methods to obtain and release these locks using key values.
*/
public class GridKeyLock {
/** Underlying locks. */
private final ConcurrentMap<Object, Sync> locks = new ConcurrentHashMap<>();
/**
* @param key Key to lock.
* @return Sync object that should be passed to {@link #unlock(Object, Object)} method.
* @throws InterruptedException If interrupted while acquiring lock.
*/
public <T> Object lockInterruptibly(T key) throws InterruptedException {
assert key != null;
Sync t = new Sync();
while (true) {
Sync old = locks.putIfAbsent(key, t);
if (old != null)
old.await();
else
return t;
}
}
/**
* @param key Key to lock.
* @return Sync object that should be passed to {@link #unlock(Object, Object)} method.
*/
public <T> Object lock(T key) {
assert key != null;
boolean interrupted = false;
Sync t = new Sync();
try {
while (true) {
Sync old = locks.putIfAbsent(key, t);
if (old != null) {
while (true) {
try {
old.await();
break;
}
catch (InterruptedException ignored) {
interrupted = true;
}
}
}
else
return t;
}
}
finally {
if (interrupted)
Thread.currentThread().interrupt();
}
}
/**
* @param key Key to lock.
* @return Sync object that should be passed to {@link #unlock(Object, Object)} method or
* {@code null} if try lock failed.
*/
@Nullable public <T> Object tryLock(T key) {
assert key != null;
Sync t = new Sync();
Sync old = locks.putIfAbsent(key, t);
return old != null ? null : t;
}
/**
* @param key Key.
* @param sync Sync that got from {@link #lockInterruptibly(Object)} call.
*/
public <T> void unlock(T key, Object sync) {
if (!locks.remove(key, sync))
throw new IllegalStateException("Lock has not been acquired for key: " + key);
((Sync)sync).finish();
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(GridKeyLock.class, this, "locksSize", locks.size());
}
/**
*
*/
private static class Sync {
/** */
private volatile boolean finished;
/**
* @throws InterruptedException If failed.
*/
void await() throws InterruptedException {
if (finished)
return;
synchronized (this) {
while (!finished)
wait();
}
}
/**
*
*/
synchronized void finish() {
finished = true;
notifyAll();
}
}
}