/* * Copyright (c) 2008-2012, 2015, 2016 Eike Stepper (Loehne, Germany) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Simon McDuff - initial API and implementation * Eike Stepper - maintenance */ package org.eclipse.net4j.util.concurrent; import org.eclipse.net4j.util.ObjectUtil; import org.eclipse.net4j.util.collection.HashBag; import org.eclipse.net4j.util.lifecycle.Lifecycle; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * Support Multiple reads/no write and upgrade lock from read to write. Many context could request * {@link IRWLockManager.LockType#WRITE write} lock at the same time. It will privileges first context that has already * a {@link IRWLockManager.LockType#READ read} lock. If no one has any read lock, it's "first come first serve". * * @author Simon McDuff * @since 2.0 * @deprecated Use {@link RWOLockManager} */ @Deprecated public class RWLockManager extends Lifecycle implements IRWLockManager { private LockStrategy readLockStrategy = new LockStrategy() { public boolean isLocked(LockEntry entry, CONTEXT context) { return entry.isReadLock(context); } public boolean isLockedByOthers(LockEntry entry, CONTEXT context) { return entry.isReadLockByOthers(context); } public boolean canObtainLock(LockEntry entry, CONTEXT context) { return entry.canObtainReadLock(context); } public LockEntry lock(LockEntry entry, CONTEXT context) { return entry.readLock(context); } public LockEntry unlock(LockEntry entry, CONTEXT context) { return entry.readUnlock(context); } @Override public String toString() { return "ReadLockStrategy"; } }; private LockStrategy writeLockStrategy = new LockStrategy() { public boolean isLocked(LockEntry entry, CONTEXT context) { return entry.isWriteLock(context); } public boolean isLockedByOthers(LockEntry entry, CONTEXT context) { return entry.isWriteLockByOthers(context); } public boolean canObtainLock(LockEntry entry, CONTEXT context) { return entry.canObtainWriteLock(context); } public LockEntry lock(LockEntry entry, CONTEXT context) { return entry.writeLock(context); } public LockEntry unlock(LockEntry entry, CONTEXT context) { return entry.writeUnlock(context); } @Override public String toString() { return "WriteLockStrategy"; } }; private Map> lockEntries = new HashMap>(); private LockChanged lockChanged = new LockChanged(); /** * @since 3.0 */ public void lock(LockType type, CONTEXT context, Collection objectsToLock, long timeout) throws InterruptedException { LockStrategy lockingStrategy = getLockingStrategy(type); lock(lockingStrategy, context, objectsToLock, timeout); } /** * @since 3.0 */ public void lock(LockType type, CONTEXT context, OBJECT objectToLock, long timeout) throws InterruptedException { List objectsToLock = Collections.singletonList(objectToLock); lock(type, context, objectsToLock, timeout); } /** * Attempts to release for a given locktype, context and objects. * * @throws IllegalMonitorStateException * Unlocking objects without lock. * @since 3.0 */ public void unlock(LockType type, CONTEXT context, Collection objectsToUnlock) { LockStrategy lockingStrategy = getLockingStrategy(type); unlock(lockingStrategy, context, objectsToUnlock); } /** * Attempts to release all locks(read and write) for a given context. */ public void unlock(CONTEXT context) { synchronized (lockChanged) { List> lockEntrysToRemove = new ArrayList>(); List> lockEntrysToAdd = new ArrayList>(); for (Entry> entry : lockEntries.entrySet()) { LockEntry lockedContext = entry.getValue(); LockEntry newEntry = lockedContext.clearLock(context); if (newEntry == null) { lockEntrysToRemove.add(lockedContext); } else if (newEntry != entry) { lockEntrysToAdd.add(newEntry); } } for (LockEntry lockEntry : lockEntrysToRemove) { OBJECT object = lockEntry.getObject(); lockEntries.remove(object); } for (LockEntry lockEntry : lockEntrysToAdd) { OBJECT object = lockEntry.getObject(); lockEntries.put(object, lockEntry); } lockChanged.notifyAll(); } } /** * @since 3.0 */ public boolean hasLock(LockType type, CONTEXT context, OBJECT objectToLock) { LockStrategy lockingStrategy = getLockingStrategy(type); return hasLock(lockingStrategy, context, objectToLock); } /** * @since 3.0 */ public boolean hasLockByOthers(LockType type, CONTEXT context, OBJECT objectToLock) { LockStrategy lockingStrategy = getLockingStrategy(type); LockEntry entry = getLockEntry(objectToLock); return entry != null && lockingStrategy.isLockedByOthers(entry, context); } /** * @since 3.1 */ protected void handleLockEntries(CONTEXT context, LockEntryHandler handler) { synchronized (lockChanged) { for (LockEntry lockEntry : lockEntries.values()) { if (context == null || lockEntry.hasContext(context)) { if (!handler.handleLockEntry(lockEntry)) { break; } } } } } /** * @since 3.1 */ protected LockEntry getLockEntry(OBJECT objectToLock) { synchronized (lockChanged) { return lockEntries.get(objectToLock); } } /** * @since 3.1 */ protected LockStrategy getLockingStrategy(LockType type) { if (type == LockType.READ) { return readLockStrategy; } if (type == LockType.WRITE) { return writeLockStrategy; } throw new IllegalArgumentException("Invalid lock type: " + type); } /** * @since 3.1 */ protected void changeContext(CONTEXT oldContext, CONTEXT newContext) { synchronized (lockChanged) { for (LockEntry lockEntry : lockEntries.values()) { lockEntry.changeContext(oldContext, newContext); } } } /** * Attempts to release this lock. *

* If the number of context is now zero then the lock is made available for write lock attempts. * * @throws IllegalMonitorStateException * Unlocking object not locked. */ private void unlock(LockStrategy lockingStrategy, CONTEXT context, Collection objectsToLock) { synchronized (lockChanged) { List> lockEntrysToRemove = new ArrayList>(); List> lockEntrysToAdd = new ArrayList>(); for (OBJECT objectToLock : objectsToLock) { LockEntry entry = lockEntries.get(objectToLock); if (entry == null) { throw new IllegalMonitorStateException(); } LockEntry newEntry = lockingStrategy.unlock(entry, context); if (newEntry == null) { lockEntrysToRemove.add(entry); } else if (newEntry != entry) { lockEntrysToAdd.add(newEntry); } } for (LockEntry lockEntry : lockEntrysToRemove) { OBJECT object = lockEntry.getObject(); lockEntries.remove(object); } for (LockEntry lockEntry : lockEntrysToAdd) { OBJECT object = lockEntry.getObject(); lockEntries.put(object, lockEntry); } lockChanged.notifyAll(); } } private boolean hasLock(LockStrategy lockingStrategy, CONTEXT context, OBJECT objectToLock) { LockEntry entry = getLockEntry(objectToLock); return entry != null && lockingStrategy.isLocked(entry, context); } private void lock(LockStrategy lockStrategy, CONTEXT context, Collection objectsToLocks, long timeout) throws InterruptedException { long startTime = System.currentTimeMillis(); while (true) { synchronized (lockChanged) { OBJECT conflict = obtainLock(lockStrategy, context, objectsToLocks); if (conflict == null) { lockChanged.notifyAll(); return; } long elapsedTime = System.currentTimeMillis() - startTime; if (timeout != WAIT && elapsedTime > timeout) { throw new TimeoutRuntimeException("Could not lock " + conflict + " within " + timeout + " milli seconds"); //$NON-NLS-1$ } if (timeout == WAIT) { lockChanged.wait(); } else { lockChanged.wait(Math.max(1, timeout - elapsedTime)); } } } } private OBJECT obtainLock(LockStrategy lockingStrategy, CONTEXT context, Collection objectsToLock) { List> lockEntrys = new ArrayList>(); for (OBJECT objectToLock : objectsToLock) { LockEntry entry = lockEntries.get(objectToLock); if (entry == null) { entry = new NoLockEntry(objectToLock); } if (lockingStrategy.canObtainLock(entry, context)) { lockEntrys.add(entry); } else { return objectToLock; } } for (LockEntry lockEntry : lockEntrys) { OBJECT object = lockEntry.getObject(); LockEntry lock = lockingStrategy.lock(lockEntry, context); lockEntries.put(object, lock); } return null; } /** * @author Simon McDuff * @since 3.1 * @deprecated Use {@link RWOLockManager} */ @Deprecated protected interface LockStrategy { public boolean isLocked(LockEntry entry, CONTEXT context); public boolean isLockedByOthers(LockEntry entry, CONTEXT context); public boolean canObtainLock(LockEntry entry, CONTEXT context); public LockEntry lock(LockEntry entry, CONTEXT context); public LockEntry unlock(LockEntry entry, CONTEXT context); } /** * @author Simon McDuff * @since 3.1 * @deprecated Use {@link RWOLockManager} */ @Deprecated protected interface LockEntry { public OBJECT getObject(); public boolean isReadLock(CONTEXT context); public boolean isWriteLock(CONTEXT context); public boolean isReadLockByOthers(CONTEXT context); public boolean isWriteLockByOthers(CONTEXT context); public boolean canObtainReadLock(CONTEXT context); public boolean canObtainWriteLock(CONTEXT context); public LockEntry readLock(CONTEXT context); public LockEntry writeLock(CONTEXT context); public LockEntry readUnlock(CONTEXT context); public LockEntry writeUnlock(CONTEXT context); public LockEntry clearLock(CONTEXT context); /** * @since 3.1 */ public void changeContext(CONTEXT oldContext, CONTEXT newContext); /** * @since 3.1 */ public boolean hasContext(CONTEXT context); } /** * @author Eike Stepper * @since 3.1 * @deprecated Use {@link RWOLockManager} */ @Deprecated protected interface LockEntryHandler { public boolean handleLockEntry(LockEntry lockEntry); } /** * @author Simon McDuff */ private static final class ReadLockEntry implements LockEntry { private OBJECT object; private Set contexts = new HashBag(); public ReadLockEntry(OBJECT objectToLock, CONTEXT context) { this.object = objectToLock; contexts.add(context); } public OBJECT getObject() { return object; } public boolean isReadLock(CONTEXT context) { return contexts.contains(context); } public boolean isWriteLock(CONTEXT context) { return false; } public boolean isReadLockByOthers(CONTEXT context) { if (contexts.isEmpty()) { return false; } return contexts.size() > (isReadLock(context) ? 1 : 0); } public boolean isWriteLockByOthers(CONTEXT context) { return false; } public boolean canObtainReadLock(CONTEXT context) { return true; } public boolean canObtainWriteLock(CONTEXT context) { return contexts.size() == 1 && contexts.contains(context); } public LockEntry readLock(CONTEXT context) { contexts.add(context); return this; } public LockEntry writeLock(CONTEXT context) { return new WriteLockEntry(object, context, this); } public LockEntry readUnlock(CONTEXT context) { contexts.remove(context); return contexts.isEmpty() ? null : this; } public LockEntry writeUnlock(CONTEXT context) { throw new IllegalMonitorStateException(); } public LockEntry clearLock(CONTEXT context) { while (contexts.remove(context)) { } return contexts.isEmpty() ? null : this; } public void changeContext(CONTEXT oldContext, CONTEXT newContext) { if (contexts.remove(oldContext)) { contexts.add(newContext); } } public boolean hasContext(CONTEXT context) { return contexts.contains(context); } @Override public String toString() { return MessageFormat.format("ReadLockEntry[object={0}, contexts={1}]", object, contexts); } } /** * @author Simon McDuff */ private static final class WriteLockEntry implements LockEntry { private OBJECT object; private CONTEXT context; private int count; private ReadLockEntry readLock; public WriteLockEntry(OBJECT object, CONTEXT context, ReadLockEntry readLock) { this.object = object; this.context = context; this.readLock = readLock; this.count = 1; } public OBJECT getObject() { return object; } public boolean isReadLock(CONTEXT context) { return readLock != null ? readLock.isReadLock(context) : false; } public boolean isWriteLock(CONTEXT context) { return ObjectUtil.equals(this.context, context); } public boolean isReadLockByOthers(CONTEXT context) { return readLock != null ? readLock.isReadLockByOthers(context) : false; } public boolean isWriteLockByOthers(CONTEXT context) { return context != this.context; } public boolean canObtainWriteLock(CONTEXT context) { return ObjectUtil.equals(this.context, context); } public boolean canObtainReadLock(CONTEXT context) { return ObjectUtil.equals(this.context, context); } public LockEntry readLock(CONTEXT context) { ReadLockEntry lock = getReadLock(); lock.readLock(context); return this; } public LockEntry writeLock(CONTEXT context) { count++; return this; } public LockEntry readUnlock(CONTEXT context) { if (readLock != null) { if (readLock.readUnlock(context) == null) { readLock = null; } return this; } throw new IllegalMonitorStateException(); } public LockEntry writeUnlock(CONTEXT context) { return --count <= 0 ? readLock : this; } public LockEntry clearLock(CONTEXT context) { if (readLock != null) { if (readLock.clearLock(context) == null) { readLock = null; } } return ObjectUtil.equals(this.context, context) ? readLock : this; } public void changeContext(CONTEXT oldContext, CONTEXT newContext) { if (ObjectUtil.equals(context, oldContext)) { context = newContext; } } public boolean hasContext(CONTEXT context) { return ObjectUtil.equals(this.context, context); } @Override public String toString() { return MessageFormat.format("WriteLockEntry[object={0}, context={1}, count={2}]", object, context, count); } private ReadLockEntry getReadLock() { if (readLock == null) { readLock = new ReadLockEntry(object, context); } return readLock; } } /** * @author Simon McDuff */ private static final class NoLockEntry implements LockEntry { private OBJECT object; public NoLockEntry(OBJECT objectToLock) { this.object = objectToLock; } public OBJECT getObject() { return object; } public boolean isReadLock(CONTEXT context) { throw new UnsupportedOperationException(); } public boolean isWriteLock(CONTEXT context) { throw new UnsupportedOperationException(); } public boolean isReadLockByOthers(CONTEXT context) { throw new UnsupportedOperationException(); } public boolean isWriteLockByOthers(CONTEXT context) { throw new UnsupportedOperationException(); } public boolean canObtainWriteLock(CONTEXT context) { return true; } public boolean canObtainReadLock(CONTEXT context) { return true; } public LockEntry readLock(CONTEXT context) { return new ReadLockEntry(object, context); } public LockEntry writeLock(CONTEXT context) { return new WriteLockEntry(object, context, null); } public LockEntry readUnlock(CONTEXT context) { throw new UnsupportedOperationException(); } public LockEntry writeUnlock(CONTEXT context) { throw new UnsupportedOperationException(); } public LockEntry clearLock(CONTEXT context) { throw new UnsupportedOperationException(); } public void changeContext(CONTEXT oldContext, CONTEXT newContext) { // Do nothing } public boolean hasContext(CONTEXT context) { return false; } @Override public String toString() { return MessageFormat.format("NoLockEntry[object={0}]", object); } } /** * @author Eike Stepper */ private static final class LockChanged { } }