org.apache.commons.transaction.locking.GenericLock.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.commons.transaction.locking.GenericLock.java

Source

/*
 * 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.commons.transaction.locking;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.transaction.util.LoggerFacade;

/**
 * A generic implementain of a simple multi level lock.
 * 
 * <p>
 * The idea is to have an ascending number of lock levels ranging from
 * <code>0</code> to <code>maxLockLevel</code> as specified in
 * {@link #GenericLock(Object, int, LoggerFacade)}: the higher the lock level
 * the stronger and more restrictive the lock. To determine which lock may
 * coexist with other locks you have to imagine matching pairs of lock levels.
 * For each pair both parts allow for all lock levels less than or equal to the
 * matching other part. Pairs are composed by the lowest and highest level not
 * yet part of a pair and successively applying this method until no lock level
 * is left. For an even amount of levels each level is part of exactly one pair.
 * For an odd amount the middle level is paired with itself. The highst lock
 * level may coexist with the lowest one (<code>0</code>) which by
 * definition means <code>NO LOCK</code>. This implies that you will have to
 * specify at least one other lock level and thus set <code>maxLockLevel</code>
 * to at least <code>1</code>.
 * </p>
 * 
 * <p>
 * Although this may sound complicated, in practice this is quite simple. Let us
 * imagine you have three lock levels:
 * <ul>
 * <li><code>0</code>:<code>NO LOCK</code> (always needed by the
 * implementation of this lock)
 * <li><code>1</code>:<code>SHARED</code>
 * <li><code>2</code>:<code>EXCLUSIVE</code>
 * </ul>
 * Accordingly, you will have to set <code>maxLockLevel</code> to
 * <code>2</code>. Now, there are two pairs of levels
 * <ul>
 * <li><code>NO LOCK</code> with <code>EXCLUSIVE</code>
 * <li><code>SHARED</code> with <code>SHARED</code>
 * </ul>
 * This means when the current highest lock level is <code>NO LOCK</code>
 * everything less or equal to <code>EXCLUSIVE</code> is allowed - which means
 * every other lock level. On the other side <code>EXCLUSIVE</code> allows
 * exacly for <code>NO LOCK</code>- which means nothing else. In conclusion,
 * <code>SHARED</code> allows for <code>SHARED</code> or <code>NO
 * LOCK</code>,
 * but not for <code>EXCLUSIVE</code>. To make this very clear have a look at
 * this table, where <code>o</code> means compatible or can coexist and
 * <code>x</code> means incompatible or can not coexist:
 * </p>
 * <table><tbody>
 * <tr>
 * <td align="center"></td>
 * <td align="center">NO LOCK</td>
 * <td align="center">SHARED</td>
 * <td align="center">EXCLUSIVE</td>
 * </tr>
 * <tr>
 * <td align="center">NO LOCK</td>
 * <td align="center">o</td>
 * <td align="center">o</td>
 * <td align="center">o</td>
 * </tr>
 * <tr>
 * <td align="center">SHARED</td>
 * <td align="center">o</td>
 * <td align="center">o</td>
 * <td align="center">x</td>
 * </tr>
 * <tr>
 * <td align="center">EXCLUSIVE</td>
 * <td align="center" align="center">o</td>
 * <td align="center">x</td>
 * <td align="center">x</td>
 * </tr>
 * </tbody> </table>
 * 
 * </p>
 * <p>
 * Additionally, there are preferences for specific locks you can pass to
 * {@link #acquire(Object, int, boolean, int, boolean, long)}. 
 * This means whenever more thanone party
 * waits for a lock you can specify which one is to be preferred. This gives you
 * every freedom you might need to specifcy e.g. 
 * <ul>
 * <li>priority to parties either applying for higher or lower lock levels
 * <li>priority not only to higher or lower locks, but to a specific level
 * <li>completely random preferences
 * </ul>
 * </p>
 * 
 * @version $Id: GenericLock.java 493628 2007-01-07 01:42:48Z joerg $
 */
public class GenericLock implements MultiLevelLock2 {

    protected Object resourceId;
    // XXX needs to be synchronized to allow for unsynchronized access for deadlock detection
    // in getConflictingOwners to avoid deadlocks between lock to acquire and lock to check for
    // deadlocks
    protected Map owners = Collections.synchronizedMap(new HashMap());
    // XXX needs to be synchronized to allow for unsynchronized access for deadlock detection
    // in getConflictingWaiters to avoid deadlocks between lock to acquire and lock to check for
    // deadlocks
    // Note: having this as a list allows for fair mechanisms in sub classes
    protected List waitingOwners = Collections.synchronizedList(new ArrayList());
    private int maxLockLevel;
    protected LoggerFacade logger;
    protected int waiters = 0;

    /**
     * Creates a new lock.
     * 
     * @param resourceId identifier for the resource associated to this lock
     * @param maxLockLevel highest allowed lock level as described in class intro 
     * @param logger generic logger used for all kind of debug logging
     */
    public GenericLock(Object resourceId, int maxLockLevel, LoggerFacade logger) {
        if (maxLockLevel < 1)
            throw new IllegalArgumentException(
                    "The maximum lock level must be at least 1 (" + maxLockLevel + " was specified)");
        this.resourceId = resourceId;
        this.maxLockLevel = maxLockLevel;
        this.logger = logger;
    }

    public boolean equals(Object o) {
        if (o instanceof GenericLock) {
            return ((GenericLock) o).resourceId.equals(resourceId);
        }
        return false;
    }

    public int hashCode() {
        return resourceId.hashCode();
    }

    /**
     * @see MultiLevelLock2#test(Object, int, int)
     */
    public boolean test(Object ownerId, int targetLockLevel, int compatibility) {
        boolean success = tryLock(ownerId, targetLockLevel, compatibility, false, true);
        return success;
    }

    /**
     * @see MultiLevelLock2#has(Object, int)
     */
    public boolean has(Object ownerId, int lockLevel) {
        int level = getLockLevel(ownerId);
        return (lockLevel <= level);
    }

    /**
     * @see org.apache.commons.transaction.locking.MultiLevelLock#acquire(java.lang.Object,
     *      int, boolean, boolean, long)
     */
    public synchronized boolean acquire(Object ownerId, int targetLockLevel, boolean wait, boolean reentrant,
            long timeoutMSecs) throws InterruptedException {
        return acquire(ownerId, targetLockLevel, wait, reentrant ? COMPATIBILITY_REENTRANT : COMPATIBILITY_NONE,
                timeoutMSecs);
    }

    /**
     * @see #acquire(Object, int, boolean, int, boolean, long)
     */
    public synchronized boolean acquire(Object ownerId, int targetLockLevel, boolean wait, int compatibility,
            long timeoutMSecs) throws InterruptedException {
        return acquire(ownerId, targetLockLevel, wait, compatibility, false, timeoutMSecs);
    }

    /**
     * Tries to blockingly acquire a lock which can be preferred.
     * 
     * @see #acquire(Object, int, boolean, int, boolean, long) 
     * @since 1.1 
     */
    public synchronized boolean acquire(Object ownerId, int targetLockLevel, boolean preferred, long timeoutMSecs)
            throws InterruptedException {
        return acquire(ownerId, targetLockLevel, true, COMPATIBILITY_REENTRANT, preferred, timeoutMSecs);
    }

    /**
     * @see org.apache.commons.transaction.locking.MultiLevelLock2#acquire(Object,
     *      int, boolean, int, boolean, long)
     * @since 1.1 
     */
    public synchronized boolean acquire(Object ownerId, int targetLockLevel, boolean wait, int compatibility,
            boolean preferred, long timeoutMSecs) throws InterruptedException {

        if (logger.isFinerEnabled()) {
            logger.logFiner(ownerId.toString() + " trying to acquire lock for " + resourceId.toString()
                    + " at level " + targetLockLevel + " at " + System.currentTimeMillis());
        }

        if (tryLock(ownerId, targetLockLevel, compatibility, preferred)) {

            if (logger.isFinerEnabled()) {
                logger.logFiner(ownerId.toString() + " actually acquired lock for " + resourceId.toString() + " at "
                        + System.currentTimeMillis());
            }

            return true;
        } else {
            if (!wait) {
                return false;
            } else {
                long started = System.currentTimeMillis();
                for (long remaining = timeoutMSecs; remaining > 0; remaining = timeoutMSecs
                        - (System.currentTimeMillis() - started)) {

                    if (logger.isFinerEnabled()) {
                        logger.logFiner(ownerId.toString() + " waiting on " + resourceId.toString() + " for msecs "
                                + timeoutMSecs + " at " + System.currentTimeMillis());
                    }

                    LockOwner waitingOwner = new LockOwner(ownerId, targetLockLevel, compatibility, preferred);
                    try {
                        registerWaiter(waitingOwner);
                        if (preferred) {
                            // while waiting we already make our claim we are next
                            LockOwner oldLock = null;
                            try {
                                // we need to remember it to restore it after waiting
                                oldLock = (LockOwner) owners.get(ownerId);
                                // this creates a new owner, so we do not need to
                                // copy the old one
                                setLockLevel(ownerId, null, targetLockLevel, compatibility, preferred);

                                // finally wait
                                wait(remaining);

                            } finally {
                                // we need to restore the old lock in order not to
                                // interfere with the intention lock in the
                                // following check
                                // and not to have it in case of success either
                                // as there will be an ordinary lock then
                                if (oldLock != null) {
                                    owners.put(ownerId, oldLock);
                                } else {
                                    owners.remove(ownerId);
                                }
                            }

                        } else {
                            wait(remaining);
                        }
                    } finally {
                        unregisterWaiter(waitingOwner);
                    }

                    if (tryLock(ownerId, targetLockLevel, compatibility, preferred)) {

                        if (logger.isFinerEnabled()) {
                            logger.logFiner(ownerId.toString() + " waiting on " + resourceId.toString()
                                    + " eventually got the lock at " + System.currentTimeMillis());
                        }

                        return true;
                    }
                }
                return false;
            }
        }
    }

    protected void registerWaiter(LockOwner waitingOwner) {
        synchronized (waitingOwners) {
            unregisterWaiter(waitingOwner);
            waiters++;
            waitingOwners.add(waitingOwner);
        }
    }

    protected void unregisterWaiter(LockOwner waitingOwner) {
        synchronized (waitingOwners) {
            if (waitingOwners.remove(waitingOwner))
                waiters--;
        }
    }

    /**
     * @see org.apache.commons.transaction.locking.MultiLevelLock#release(Object)
     */
    public synchronized boolean release(Object ownerId) {
        if (owners.remove(ownerId) != null) {
            if (logger.isFinerEnabled()) {
                logger.logFiner(ownerId.toString() + " releasing lock for " + resourceId.toString() + " at "
                        + System.currentTimeMillis());
            }
            notifyAll();
            return true;
        }
        return false;
    }

    /**
     * @see org.apache.commons.transaction.locking.MultiLevelLock#getLockLevel(Object)
     */
    public int getLockLevel(Object ownerId) {
        LockOwner owner = (LockOwner) owners.get(ownerId);
        if (owner == null) {
            return 0;
        } else {
            return owner.lockLevel;
        }
    }

    /**
     * Gets the resource assotiated to this lock. 
     * 
     * @return identifier for the resource associated to this lock 
     */
    public Object getResourceId() {
        return resourceId;
    }

    /**
     * Gets the lowest lock level possible.
     * 
     * @return minimum lock level
     */
    public int getLevelMinLock() {
        return 0;
    }

    /**
     * Gets the highst lock level possible.
     * 
     * @return maximum lock level
     */
    public int getLevelMaxLock() {
        return maxLockLevel;
    }

    public Object getOwner() {
        LockOwner owner = getMaxLevelOwner();
        if (owner == null)
            return null;
        return owner.ownerId;
    }

    public synchronized String toString() {
        StringBuffer buf = new StringBuffer();
        buf.append(resourceId.toString()).append(":\n");

        for (Iterator it = owners.values().iterator(); it.hasNext();) {
            LockOwner owner = (LockOwner) it.next();
            buf.append("- ").append(owner.toString()).append("\n");
        }

        if (waiters != 0) {
            buf.append(waiters).append(" waiting:\n");
            for (Iterator it = waitingOwners.iterator(); it.hasNext();) {
                LockOwner owner = (LockOwner) it.next();
                buf.append("- ").append(owner.toString()).append("\n");
            }
        }

        return buf.toString();
    }

    protected synchronized LockOwner getMaxLevelOwner() {
        return getMaxLevelOwner(null, -1, false);
    }

    protected synchronized LockOwner getMaxLevelOwner(LockOwner reentrantOwner, boolean preferred) {
        return getMaxLevelOwner(reentrantOwner, -1, preferred);
    }

    protected synchronized LockOwner getMaxLevelOwner(int supportLockLevel, boolean preferred) {
        return getMaxLevelOwner(null, supportLockLevel, preferred);
    }

    protected synchronized LockOwner getMaxLevelOwner(LockOwner reentrantOwner, int supportLockLevel,
            boolean preferred) {
        LockOwner maxOwner = null;
        for (Iterator it = owners.values().iterator(); it.hasNext();) {
            LockOwner owner = (LockOwner) it.next();
            if (owner.lockLevel != supportLockLevel && !owner.equals(reentrantOwner)
                    && (maxOwner == null || maxOwner.lockLevel < owner.lockLevel)
                    // if we are a preferred lock we must not interfere with other intention
                    // locks as we otherwise might mututally lock without resolvation
                    && !(preferred && owner.intention)) {
                maxOwner = owner;
            }
        }
        return maxOwner;
    }

    protected synchronized void setLockLevel(Object ownerId, LockOwner lock, int targetLockLevel, int compatibility,
            boolean intention) {
        // be sure there exists at most one lock per owner
        if (lock != null) {
            if (logger.isFinestEnabled()) {
                logger.logFinest(ownerId.toString() + " upgrading lock for " + resourceId.toString() + " to level "
                        + targetLockLevel + " at " + System.currentTimeMillis());
            }
        } else {
            if (logger.isFinestEnabled()) {
                logger.logFinest(ownerId.toString() + " getting new lock for " + resourceId.toString()
                        + " at level " + targetLockLevel + " at " + System.currentTimeMillis());
            }
        }
        owners.put(ownerId, new LockOwner(ownerId, targetLockLevel, compatibility, intention));
    }

    protected boolean tryLock(Object ownerId, int targetLockLevel, int compatibility, boolean preferred) {
        return tryLock(ownerId, targetLockLevel, compatibility, preferred, false);
    }

    protected synchronized boolean tryLock(Object ownerId, int targetLockLevel, int compatibility,
            boolean preferred, boolean tryOnly) {

        LockOwner myLock = (LockOwner) owners.get(ownerId);

        // determine highest owner        
        LockOwner highestOwner;
        if (compatibility == COMPATIBILITY_REENTRANT) {
            if (myLock != null && targetLockLevel <= myLock.lockLevel) {
                // we already have it
                return true;
            } else {
                // our own lock will not be compromised by ourself
                highestOwner = getMaxLevelOwner(myLock, preferred);
            }
        } else if (compatibility == COMPATIBILITY_SUPPORT) {
            // we are compatible with any other lock owner holding
            // the same lock level
            highestOwner = getMaxLevelOwner(targetLockLevel, preferred);

        } else if (compatibility == COMPATIBILITY_REENTRANT_AND_SUPPORT) {
            if (myLock != null && targetLockLevel <= myLock.lockLevel) {
                // we already have it
                return true;
            } else {
                // our own lock will not be compromised by ourself and same lock level 
                highestOwner = getMaxLevelOwner(myLock, targetLockLevel, preferred);
            }
        } else {
            highestOwner = getMaxLevelOwner();
        }

        int i;
        // what is our current lock level?
        int currentLockLevel;
        if (highestOwner != null) {
            currentLockLevel = highestOwner.lockLevel;
        } else {
            currentLockLevel = getLevelMinLock();
        }

        // we are only allowed to acquire our locks if we do not compromise locks of any other lock owner
        if (isCompatible(targetLockLevel, currentLockLevel)) {
            if (!tryOnly) {
                // if we really have the lock, it no longer is an intention
                setLockLevel(ownerId, myLock, targetLockLevel, compatibility, false);
            }
            return true;
        } else {
            return false;
        }
    }

    protected boolean isCompatible(int targetLockLevel, int currentLockLevel) {
        return (targetLockLevel <= getLevelMaxLock() - currentLockLevel);
    }

    protected Set getConflictingOwners(Object ownerId, int targetLockLevel, int compatibility) {

        LockOwner myLock = (LockOwner) owners.get(ownerId);
        if (myLock != null && targetLockLevel <= myLock.lockLevel) {
            // shortcut as we already have the lock
            return null;
        }

        LockOwner testLock = new LockOwner(ownerId, targetLockLevel, compatibility, false);
        List ownersCopy;
        synchronized (owners) {
            ownersCopy = new ArrayList(owners.values());
        }
        return getConflictingOwners(testLock, ownersCopy);

    }

    protected Collection getConflictingWaiters(Object ownerId) {
        LockOwner owner = (LockOwner) owners.get(ownerId);
        if (owner != null) {
            List waiterCopy;
            synchronized (waitingOwners) {
                waiterCopy = new ArrayList(waitingOwners);
            }
            Collection conflicts = getConflictingOwners(owner, waiterCopy);
            return conflicts;
        }
        return null;
    }

    protected Set getConflictingOwners(LockOwner myOwner, Collection ownersToTest) {

        if (myOwner == null)
            return null;

        Set conflicts = new HashSet();

        // check if any locks conflict with ours
        for (Iterator it = ownersToTest.iterator(); it.hasNext();) {
            LockOwner owner = (LockOwner) it.next();

            // we do not interfere with ourselves, except when explicitely said so
            if ((myOwner.compatibility == COMPATIBILITY_REENTRANT
                    || myOwner.compatibility == COMPATIBILITY_REENTRANT_AND_SUPPORT)
                    && owner.ownerId.equals(myOwner.ownerId))
                continue;

            // otherwise find out the lock level of the owner and see if we conflict with it
            int onwerLockLevel = owner.lockLevel;

            if (myOwner.compatibility == COMPATIBILITY_SUPPORT
                    || myOwner.compatibility == COMPATIBILITY_REENTRANT_AND_SUPPORT
                            && myOwner.lockLevel == onwerLockLevel)
                continue;

            if (!isCompatible(myOwner.lockLevel, onwerLockLevel)) {
                conflicts.add(owner.ownerId);
            }
        }
        return (conflicts.isEmpty() ? null : conflicts);
    }

    protected static class LockOwner {
        public final Object ownerId;
        public final int lockLevel;
        public final boolean intention;
        public final int compatibility;

        public LockOwner(Object ownerId, int lockLevel, int compatibility, boolean intention) {
            this.ownerId = ownerId;
            this.lockLevel = lockLevel;
            this.intention = intention;
            this.compatibility = compatibility;
        }

        public String toString() {
            StringBuffer buf = new StringBuffer();
            buf.append(ownerId.toString()).append(": level ").append(lockLevel).append(", complevel ")
                    .append(compatibility).append(intention ? ", intention/preferred" : "");
            return buf.toString();
        }

        public boolean equals(Object o) {
            if (o instanceof LockOwner) {
                return ((LockOwner) o).ownerId.equals(ownerId);
            }
            return false;
        }

        public int hashCode() {
            return ownerId.hashCode();
        }
    }

}