/* * JBoss, Home of Professional Open Source * Copyright 2009 Red Hat Inc. and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.util.concurrent.locks; import net.jcip.annotations.ThreadSafe; import org.infinispan.transaction.xa.GlobalTransaction; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.AbstractQueuedSynchronizer; import java.util.concurrent.locks.Lock; /** * A lock that supports reentrancy based on owner (and not on current thread). For this to work, the lock needs to be * constructed with a reference to the {@link org.infinispan.context.InvocationContextContainer}, so it is able to * determine whether the caller's "owner" reference is the current thread or a {@link * org.infinispan.transaction.xa.GlobalTransaction} instance. * <p/> * This makes this lock implementation very closely tied to Infinispan internals, but it provides for a very clean, * efficient and moreover familiar interface to work with, since it implements {@link java.util.concurrent.locks.Lock}. * <p/> * For the sake of performance, this lock only supports nonfair queueing. * <p/> * * @author Manik Surtani (<a href="mailto:manik@jboss.org">manik@jboss.org</a>) * @since 4.0 */ @ThreadSafe public class OwnableReentrantLock extends AbstractQueuedSynchronizer implements Lock { private static final Log log = LogFactory.getLog(OwnableReentrantLock.class); private static final long serialVersionUID = 4932974734462848792L; private transient Object owner; private final ThreadLocal<Object> requestorOnStack = new ThreadLocal<Object>(); /** * @return a GlobalTransaction instance if the current call is participating in a transaction, or the current thread * otherwise. */ protected final Object currentRequestor() { Object cr = requestorOnStack.get(); if (cr == null) throw new IllegalStateException("Should never get to this state!"); return cr; } protected void setCurrentRequestor(Object requestor) { requestorOnStack.set(requestor); } public void unsetCurrentRequestor() { requestorOnStack.remove(); } @Override public void lock() { throw new UnsupportedOperationException(); } @Override public void unlock() { throw new UnsupportedOperationException(); } public void lock(GlobalTransaction requestor) { setCurrentRequestor(requestor); try { if (log.isTraceEnabled()) { log.tracef("%s lock(%s)", requestor, System.identityHashCode(this)); } if (compareAndSetState(0, 1)) owner = requestor; else acquire(1); } finally { if (log.isTraceEnabled()) { log.tracef("%s lock(%s) => FINISH", requestor, System.identityHashCode(this)); } unsetCurrentRequestor(); } } @Override public void lockInterruptibly() throws InterruptedException { acquireInterruptibly(1); } @Override public boolean tryLock() { return tryAcquire(1); } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { throw new UnsupportedOperationException("Should never get here"); } public boolean tryLock(Object requestor, long time, TimeUnit unit) throws InterruptedException { if (log.isTraceEnabled()) { log.tracef("%s tryLock(%s)", requestor, System.identityHashCode(this)); } setCurrentRequestor(requestor); try { return tryAcquireNanos(1, unit.toNanos(time)); } finally { if (log.isTraceEnabled()) { log.tracef("%s tryLock(%s) => FINISH", requestor, System.identityHashCode(this)); } unsetCurrentRequestor(); } } public void unlock(Object requestor) { if (log.isTraceEnabled()) { log.tracef("%s unlock(%s)", requestor, System.identityHashCode(this)); } setCurrentRequestor(requestor); try { release(1); } catch (IllegalMonitorStateException imse) { // ignore? } finally { if (log.isTraceEnabled()) { log.tracef("%s unlock(%s) => FINISH", requestor, System.identityHashCode(this)); } unsetCurrentRequestor(); } } @Override public ConditionObject newCondition() { throw new UnsupportedOperationException("Not supported in this implementation!"); } @Override protected final boolean tryAcquire(int acquires) { final Object current = currentRequestor(); int c = getState(); if (log.isTraceEnabled()) { log.tracef("%s tryAcquire(%s)", current, System.identityHashCode(this)); } if (c == 0) { if (compareAndSetState(0, acquires)) { owner = current; if (log.isTraceEnabled()) { log.tracef("%s tryAcquire(%s) => SUCCESS", current, System.identityHashCode(this)); } return true; } } else if (current.equals(owner)) { setState(c + acquires); if (log.isTraceEnabled()) { log.tracef("%s tryAcquire(%s) => SUCCESS (reentrant)", current, System.identityHashCode(this)); } return true; } if (log.isTraceEnabled()) { log.tracef("%s tryAcquire(%s) => FAILED", current, System.identityHashCode(this)); } return false; } @Override protected final boolean tryRelease(int releases) { if (!currentRequestor().equals(owner)) { //throw new IllegalMonitorStateException(this.toString()); // lets be quiet about this if (log.isTraceEnabled()) { log.tracef("%s tryRelease(%s) => FAILED (Not Owner)", currentRequestor(), System.identityHashCode(this)); } return false; } int c = getState() - releases; boolean free = false; if (c == 0) { free = true; owner = null; } if (log.isTraceEnabled()) { log.tracef("%s tryRelease(%s) => free? %s", currentRequestor(), System.identityHashCode(this), free); } setState(c); return free; } @Override protected final boolean isHeldExclusively() { return getState() != 0 && currentRequestor().equals(owner); } /** * @return the owner of the lock, or null if it is currently unlocked. */ public final Object getOwner() { int c = getState(); Object o = owner; return (c == 0) ? null : o; } /** * @return the hold count of the current lock, or 0 if it is not locked. */ public final int getHoldCount(Object requestor) { int c = getState(); Object o = owner; return (requestor.equals(o)) ? c : 0; } /** * @return true if the lock is locked, false otherwise */ public final boolean isLocked() { return getState() != 0; } protected void resetState() { setState(0); } /** * Reconstitute this lock instance from a stream, resetting the lock to an unlocked state. * * @param s the stream */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); resetState(); } /** * Returns a string identifying this lock, as well as its lock state. The state, in brackets, includes either the * String "Unlocked" or the String "Locked by" followed by the String representation of the lock * owner. * * @return a string identifying this lock, as well as its lock state. */ public String toString() { Object owner = getOwner(); return super.toString() + ((owner == null) ? "[Unlocked]" : "[Locked by " + owner + "]"); } }