/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/eclipse-1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.jikesrvm.mm.mmtk; import org.jikesrvm.VM; import org.vmmagic.unboxed.*; import org.vmmagic.pragma.*; import org.jikesrvm.runtime.Entrypoints; import org.jikesrvm.runtime.Magic; import org.jikesrvm.scheduler.RVMThread; import org.jikesrvm.scheduler.ThreadQueue; /** * Adaptive mutex with a spinlock fast path. Designed for good performance * on native threaded systems. This implementation has the following specific * properties: * <ul> * <li>It behaves like a spinlock on the fast path (one CAS to lock, one CAS * to unlock, if there is no contention).</li> * <li>It has the ability to spin for some predetermined number of cycles * (see <code>SPIN_LIMIT</code>).</li> * <li>Adapts to contention by eventually placing contending threads on a * queue and parking them.</li> * <li>Has a weak fairness guarantee: contenders follow queue discipline, * except that new contenders may "beat" the thread at the head of the * queue if they arrive just as the lock becomes available and the thread * at the head of the queue just got scheduled.</li> * </ul> * @author Filip Pizlo */ @Uninterruptible public class Lock extends org.mmtk.vm.Lock { // Core Instance fields private String name; // logical name of lock private final int id; // lock id (based on a non-resetting counter) private static int lockCount; private static final int SPIN_LIMIT = 1000; /** Lock is not held and the queue is empty. When the lock is in this * state, there <i>may</i> be a thread that just got dequeued and is * about to enter into contention on the lock. */ private static final int CLEAR = 0; /** Lock is held and the queue is empty. */ private static final int LOCKED = 1; /** Lock is not held but the queue is non-empty. This state guarantees * that there is a thread that got dequeued and is about to contend on * the lock. */ private static final int CLEAR_QUEUED = 2; /** Lock is held and the queue is non-empty. */ private static final int LOCKED_QUEUED = 3; /** Some thread is currently engaged in an enqueue or dequeue operation, * and will return the lock to whatever it was in previously once that * operation is done. During this states any lock/unlock attempts will * spin until the lock reverts to some other state. */ private static final int QUEUEING = 4; private ThreadQueue queue; @Entrypoint private int state; // Diagnosis Instance fields @Untraced private RVMThread thread; // if locked, who locked it? private int where = -1; // how far along has the lock owner progressed? public Lock(String name) { this(); this.name = name; } public Lock() { id = lockCount++; queue = new ThreadQueue(); state = CLEAR; } public void setName(String str) { name = str; } public void acquire() { RVMThread me = RVMThread.getCurrentThread(); Offset offset=Entrypoints.lockStateField.getOffset(); boolean acquired=false; for (int i=0;i<SPIN_LIMIT;++i) { int oldState=Magic.prepareInt(this,offset); // NOTE: we could be smart here and break out of the spin if we see // that the state is CLEAR_QUEUED or LOCKED_QUEUED, or we could even // check the queue directly and see if there is anything on it; this // would make the lock slightly more fair. if ((oldState==CLEAR && Magic.attemptInt(this,offset,CLEAR,LOCKED)) || (oldState==CLEAR_QUEUED && Magic.attemptInt(this,offset,CLEAR_QUEUED,LOCKED_QUEUED))) { acquired=true; break; } } if (!acquired) { for (;;) { int oldState=Magic.prepareInt(this,offset); if ((oldState==CLEAR && Magic.attemptInt(this,offset,CLEAR,LOCKED)) || (oldState==CLEAR_QUEUED && Magic.attemptInt(this,offset,CLEAR_QUEUED,LOCKED_QUEUED))) { break; } else if ((oldState==LOCKED && Magic.attemptInt(this,offset,LOCKED,QUEUEING)) || (oldState==LOCKED_QUEUED && Magic.attemptInt(this,offset,LOCKED_QUEUED,QUEUEING))) { queue.enqueue(me); Magic.sync(); state=LOCKED_QUEUED; me.monitor().lockNoHandshake(); while (queue.isQueued(me)) { // use await instead of waitNicely because this is NOT a GC point! me.monitor().waitNoHandshake(); } me.monitor().unlock(); } } } thread = me; where = -1; Magic.isync(); } public void check(int w) { if (VM.VerifyAssertions) VM._assert(RVMThread.getCurrentThread() == thread); where = w; } public void release() { where=-1; thread=null; Magic.sync(); Offset offset=Entrypoints.lockStateField.getOffset(); for (;;) { int oldState=Magic.prepareInt(this,offset); if (VM.VerifyAssertions) VM._assert(oldState==LOCKED || oldState==LOCKED_QUEUED || oldState==QUEUEING); if (oldState==LOCKED && Magic.attemptInt(this,offset,LOCKED,CLEAR)) { break; } else if (oldState==LOCKED_QUEUED && Magic.attemptInt(this,offset,LOCKED_QUEUED,QUEUEING)) { RVMThread toAwaken=queue.dequeue(); if (VM.VerifyAssertions) VM._assert(toAwaken!=null); boolean queueEmpty=queue.isEmpty(); Magic.sync(); if (queueEmpty) { state=CLEAR; } else { state=CLEAR_QUEUED; } toAwaken.monitor().lockedBroadcastNoHandshake(); break; } } } }