All Downloads are FREE. Search and download functionalities are using the official Maven repository.

at.molindo.utils.concurrent.KeyLock Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
/**
 * Copyright 2010 Molindo GmbH
 *
 * 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 at.molindo.utils.concurrent;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;

/**
 * concurrency utility that locks execution of callable by a key, i.e. not using
 * key's identity but equality.
 * 
 * 
 * @param 
 *            key type
 * @param 
 *            return type of {@link Callable}
 * 
 * @author [email protected]
 */
public class KeyLock {

	private final ConcurrentHashMap _map = new ConcurrentHashMap();
	private boolean _wait;

	public static  KeyLock newKeyLock() {
		return new KeyLock();
	}

	public static  KeyLock newKeyLock(boolean wait) {
		return new KeyLock(wait);
	}

	public KeyLock() {
		this(true);
	}

	/**
	 * @param wait
	 *            should {@link #withLock(Object, Callable)} calls wait for
	 *            concurrent calls to finish or should they fail fast
	 */
	public KeyLock(boolean wait) {
		_wait = wait;
	}

	/**
	 * execute callable while locking concurrent execution for
	 * key
	 * 
	 * @param key
	 * @param callable
	 * @return
	 * @throws KeyLockedException
	 *             only if key lock configured not to wait and key locked by
	 *             other thread
	 * @throws Exception
	 *             any exception thrown by callable.call()
	 * @throws NullPointerException
	 *             if key or callable are
	 *             null
	 */
	public V withLock(final K key, final Callable callable) throws Exception {
		if (key == null) {
			throw new NullPointerException("key");
		}
		if (callable == null) {
			throw new NullPointerException("callable");
		}

		Task t = new Task(callable);
		try {
			final Task prev = _map.putIfAbsent(key, t);
			if (prev != null) {
				if (_wait) {
					t = prev;
				} else {
					t = null;
					throw new KeyLockedException(key);
				}
			}
			return t.perform();
		} finally {
			// first thread removes mapping
			if (t != null) {
				_map.remove(key);
			} else {
				// only with KeyLockedException
			}
		}
	}

	/**
	 * how to replace already computed results. by default it's null, as users
	 * might not want to get concurrency issues on shared results. As a
	 * replacement, one could perform a DB lookup if the task is to
	 * create/update a DB entity or do a deep clone of the object.
	 * 
	 * @param result
	 * @param ex
	 * @return
	 */
	protected V replace(final V result, final Exception ex) throws Exception {
		return null;
	}

	/**
	 * @return number of active keys
	 */
	public int activeCount() {
		return _map.size();
	}

	/**
	 * @return a newly created {@link List} of all active keys. Changes to this
	 *         list don't have any effect to this {@link KeyLock} (changes to
	 *         the values will have an undefined effect though).
	 */
	public List activeKeys() {
		return new ArrayList(_map.keySet());
	}

	private final class Task {

		private final Callable _callable;
		private Exception _ex;
		private boolean _done = false;
		private V _result;

		public Task(final Callable callable) {
			_callable = callable;
		}

		public synchronized V perform() throws Exception {
			if (!_done) {
				try {
					_result = _callable.call();
					return _result;
				} catch (final Exception e) {
					_ex = e;
					throw e;
				} finally {
					_done = true;
				}
			} else {
				return replace(_result, _ex);
			}
		}
	}

	public static final class KeyLockedException extends Exception {
		private static final long serialVersionUID = 1L;
		private final Object _key;

		private KeyLockedException(Object key) {
			super("key locked by different thread");
			_key = key;
		}

		public Object getKey() {
			return _key;
		}

	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy