001    /*
002     *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003     *
004     *  This file is licensed to You under the Eclipse Public License (EPL);
005     *  You may not use this file except in compliance with the License. You
006     *  may obtain a copy of the License at
007     *
008     *      http://www.opensource.org/licenses/eclipse-1.0.php
009     *
010     *  See the COPYRIGHT.txt file distributed with this work for information
011     *  regarding copyright ownership.
012     */
013    package org.jikesrvm.scheduler;
014    
015    import org.jikesrvm.VM;
016    import org.jikesrvm.Services;
017    import org.jikesrvm.objectmodel.ThinLockConstants;
018    import org.jikesrvm.runtime.Magic;
019    import org.vmmagic.pragma.Inline;
020    import org.vmmagic.pragma.NoInline;
021    import org.vmmagic.pragma.NoNullCheck;
022    import org.vmmagic.pragma.Uninterruptible;
023    import org.vmmagic.pragma.Unpreemptible;
024    import org.vmmagic.unboxed.Offset;
025    import org.vmmagic.unboxed.Word;
026    
027    /**
028     * Implementation of thin locks.
029     */
030    @Uninterruptible
031    public final class ThinLock implements ThinLockConstants {
032    
033      private static final boolean ENABLE_BIASED_LOCKING = true;
034    
035      @Inline
036      @NoNullCheck
037      @Unpreemptible
038      public static void inlineLock(Object o, Offset lockOffset) {
039        Word old = Magic.prepareWord(o, lockOffset); // FIXME: bad for PPC?
040        Word id = old.and(TL_THREAD_ID_MASK.or(TL_STAT_MASK));
041        Word tid = Word.fromIntSignExtend(RVMThread.getCurrentThread().getLockingId());
042        if (id.EQ(tid)) {
043          Word changed = old.plus(TL_LOCK_COUNT_UNIT);
044          if (!changed.and(TL_LOCK_COUNT_MASK).isZero()) {
045            setDedicatedU16(o, lockOffset, changed);
046            return;
047          }
048        } else if (id.EQ(TL_STAT_THIN)) {
049          // lock is thin and not held by anyone
050          if (Magic.attemptWord(o, lockOffset, old, old.or(tid))) {
051            Magic.isync();
052            return;
053          }
054        }
055        lock(o, lockOffset);
056      }
057    
058      @Inline
059      @NoNullCheck
060      @Unpreemptible
061      public static void inlineUnlock(Object o, Offset lockOffset) {
062        Word old = Magic.prepareWord(o, lockOffset); // FIXME: bad for PPC?
063        Word id = old.and(TL_THREAD_ID_MASK.or(TL_STAT_MASK));
064        Word tid = Word.fromIntSignExtend(RVMThread.getCurrentThread().getLockingId());
065        if (id.EQ(tid)) {
066          if (!old.and(TL_LOCK_COUNT_MASK).isZero()) {
067            setDedicatedU16(o, lockOffset, old.minus(TL_LOCK_COUNT_UNIT));
068            return;
069          }
070        } else if (old.xor(tid).rshl(TL_LOCK_COUNT_SHIFT).EQ(TL_STAT_THIN.rshl(TL_LOCK_COUNT_SHIFT))) {
071          Magic.sync();
072          if (Magic.attemptWord(o, lockOffset, old, old.and(TL_UNLOCK_MASK).or(TL_STAT_THIN))) {
073            return;
074          }
075        }
076        unlock(o, lockOffset);
077      }
078    
079      @NoInline
080      @NoNullCheck
081      @Unpreemptible
082      public static void lock(Object o, Offset lockOffset) {
083        if (STATS) fastLocks++;
084    
085        Word threadId = Word.fromIntZeroExtend(RVMThread.getCurrentThread().getLockingId());
086    
087        for (int cnt=0;;cnt++) {
088          Word old = Magic.getWordAtOffset(o, lockOffset);
089          Word stat = old.and(TL_STAT_MASK);
090          boolean tryToInflate=false;
091          if (stat.EQ(TL_STAT_BIASABLE)) {
092            Word id = old.and(TL_THREAD_ID_MASK);
093            if (id.isZero()) {
094              if (ENABLE_BIASED_LOCKING) {
095                // lock is unbiased, bias it in our favor and grab it
096                if (Synchronization.tryCompareAndSwap(
097                      o, lockOffset,
098                      old,
099                      old.or(threadId).plus(TL_LOCK_COUNT_UNIT))) {
100                  Magic.isync();
101                  return;
102                }
103              } else {
104                // lock is unbiased but biasing is NOT allowed, so turn it into
105                // a thin lock
106                if (Synchronization.tryCompareAndSwap(
107                      o, lockOffset,
108                      old,
109                      old.or(threadId).or(TL_STAT_THIN))) {
110                  Magic.isync();
111                  return;
112                }
113              }
114            } else if (id.EQ(threadId)) {
115              // lock is biased in our favor
116              Word changed = old.plus(TL_LOCK_COUNT_UNIT);
117              if (!changed.and(TL_LOCK_COUNT_MASK).isZero()) {
118                setDedicatedU16(o, lockOffset, changed);
119                return;
120              } else {
121                tryToInflate=true;
122              }
123            } else {
124              if (casFromBiased(o, lockOffset, old, biasBitsToThinBits(old), cnt)) {
125                continue; // don't spin, since it's thin now
126              }
127            }
128          } else if (stat.EQ(TL_STAT_THIN)) {
129            Word id = old.and(TL_THREAD_ID_MASK);
130            if (id.isZero()) {
131              if (Synchronization.tryCompareAndSwap(
132                    o, lockOffset, old, old.or(threadId))) {
133                Magic.isync();
134                return;
135              }
136            } else if (id.EQ(threadId)) {
137              Word changed = old.plus(TL_LOCK_COUNT_UNIT);
138              if (changed.and(TL_LOCK_COUNT_MASK).isZero()) {
139                tryToInflate=true;
140              } else if (Synchronization.tryCompareAndSwap(
141                           o, lockOffset, old, changed)) {
142                Magic.isync();
143                return;
144              }
145            } else if (cnt>retryLimit) {
146              tryToInflate=true;
147            }
148          } else {
149            if (VM.VerifyAssertions) VM._assert(stat.EQ(TL_STAT_FAT));
150            // lock is fat.  contend on it.
151            if (Lock.getLock(getLockIndex(old)).lockHeavy(o)) {
152              return;
153            }
154          }
155    
156          if (tryToInflate) {
157            if (STATS) slowLocks++;
158            // the lock is not fat, is owned by someone else, or else the count wrapped.
159            // attempt to inflate it (this may fail, in which case we'll just harmlessly
160            // loop around) and lock it (may also fail, if we get the wrong lock).  if it
161            // succeeds, we're done.
162            // NB: this calls into our attemptToMarkInflated() method, which will do the
163            // Right Thing if the lock is biased to someone else.
164            if (inflateAndLock(o, lockOffset)) {
165              return;
166            }
167          } else {
168            RVMThread.yieldNoHandshake();
169          }
170        }
171      }
172    
173      @NoInline
174      @NoNullCheck
175      @Unpreemptible
176      public static void unlock(Object o, Offset lockOffset) {
177        Word threadId = Word.fromIntZeroExtend(RVMThread.getCurrentThread().getLockingId());
178        for (int cnt=0;;cnt++) {
179          Word old = Magic.getWordAtOffset(o, lockOffset);
180          Word stat = old.and(TL_STAT_MASK);
181          if (stat.EQ(TL_STAT_BIASABLE)) {
182            Word id = old.and(TL_THREAD_ID_MASK);
183            if (id.EQ(threadId)) {
184              if (old.and(TL_LOCK_COUNT_MASK).isZero()) {
185                RVMThread.raiseIllegalMonitorStateException("biased unlocking: we own this object but the count is already zero", o);
186              }
187              setDedicatedU16(o, lockOffset, old.minus(TL_LOCK_COUNT_UNIT));
188              return;
189            } else {
190              RVMThread.raiseIllegalMonitorStateException("biased unlocking: we don't own this object", o);
191            }
192          } else if (stat.EQ(TL_STAT_THIN)) {
193            Magic.sync();
194            Word id = old.and(TL_THREAD_ID_MASK);
195            if (id.EQ(threadId)) {
196              Word changed;
197              if (old.and(TL_LOCK_COUNT_MASK).isZero()) {
198                changed = old.and(TL_UNLOCK_MASK).or(TL_STAT_THIN);
199              } else {
200                changed = old.minus(TL_LOCK_COUNT_UNIT);
201              }
202              if (Synchronization.tryCompareAndSwap(
203                    o, lockOffset, old, changed)) {
204                return;
205              }
206            } else {
207              if (false) {
208                VM.sysWriteln("threadId = ",threadId);
209                VM.sysWriteln("id = ",id);
210              }
211              RVMThread.raiseIllegalMonitorStateException("thin unlocking: we don't own this object", o);
212            }
213          } else {
214            if (VM.VerifyAssertions) VM._assert(stat.EQ(TL_STAT_FAT));
215            // fat unlock
216            Lock.getLock(getLockIndex(old)).unlockHeavy(o);
217            return;
218          }
219        }
220      }
221    
222      @Uninterruptible
223      @NoNullCheck
224      public static boolean holdsLock(Object o, Offset lockOffset, RVMThread thread) {
225        for (int cnt=0;;++cnt) {
226          int tid = thread.getLockingId();
227          Word bits = Magic.getWordAtOffset(o, lockOffset);
228          if (bits.and(TL_STAT_MASK).EQ(TL_STAT_BIASABLE)) {
229            // if locked, then it is locked with a thin lock
230            return
231              bits.and(TL_THREAD_ID_MASK).toInt() == tid &&
232              !bits.and(TL_LOCK_COUNT_MASK).isZero();
233          } else if (bits.and(TL_STAT_MASK).EQ(TL_STAT_THIN)) {
234            return bits.and(TL_THREAD_ID_MASK).toInt()==tid;
235          } else {
236            if (VM.VerifyAssertions) VM._assert(bits.and(TL_STAT_MASK).EQ(TL_STAT_FAT));
237            // if locked, then it is locked with a fat lock
238            Lock l=Lock.getLock(getLockIndex(bits));
239            if (l!=null) {
240              l.mutex.lock();
241              boolean result = (l.getOwnerId()==tid && l.getLockedObject()==o);
242              l.mutex.unlock();
243              return result;
244            }
245          }
246          RVMThread.yieldNoHandshake();
247        }
248      }
249    
250      @Inline
251      @Uninterruptible
252      public static boolean isFat(Word lockWord) {
253        return lockWord.and(TL_STAT_MASK).EQ(TL_STAT_FAT);
254      }
255    
256      /**
257       * Return the lock index for a given lock word.  Assert valid index
258       * ranges, that the fat lock bit is set, and that the lock entry
259       * exists.
260       *
261       * @param lockWord The lock word whose lock index is being established
262       * @return the lock index corresponding to the lock workd.
263       */
264      @Inline
265      @Uninterruptible
266      public static int getLockIndex(Word lockWord) {
267        int index = lockWord.and(TL_LOCK_ID_MASK).rshl(TL_LOCK_ID_SHIFT).toInt();
268        if (VM.VerifyAssertions) {
269          if (!(index > 0 && index < Lock.numLocks())) {
270            VM.sysWrite("Lock index out of range! Word: "); VM.sysWrite(lockWord);
271            VM.sysWrite(" index: "); VM.sysWrite(index);
272            VM.sysWrite(" locks: "); VM.sysWrite(Lock.numLocks());
273            VM.sysWriteln();
274          }
275          VM._assert(index > 0 && index < Lock.numLocks());  // index is in range
276          VM._assert(lockWord.and(TL_STAT_MASK).EQ(TL_STAT_FAT));        // fat lock bit is set
277        }
278        return index;
279      }
280    
281      @Inline
282      @Uninterruptible
283      public static int getLockOwner(Word lockWord) {
284        if (VM.VerifyAssertions) VM._assert(!isFat(lockWord));
285        if (lockWord.and(TL_STAT_MASK).EQ(TL_STAT_BIASABLE)) {
286          if (lockWord.and(TL_LOCK_COUNT_MASK).isZero()) {
287            return 0;
288          } else {
289            return lockWord.and(TL_THREAD_ID_MASK).toInt();
290          }
291        } else {
292          return lockWord.and(TL_THREAD_ID_MASK).toInt();
293        }
294      }
295    
296      @Inline
297      @Uninterruptible
298      public static int getRecCount(Word lockWord) {
299        if (VM.VerifyAssertions) VM._assert(getLockOwner(lockWord)!=0);
300        if (lockWord.and(TL_STAT_MASK).EQ(TL_STAT_BIASABLE)) {
301          return lockWord.and(TL_LOCK_COUNT_MASK).rshl(TL_LOCK_COUNT_SHIFT).toInt();
302        } else {
303          return lockWord.and(TL_LOCK_COUNT_MASK).rshl(TL_LOCK_COUNT_SHIFT).toInt()+1;
304        }
305      }
306    
307      /**
308       * Set only the dedicated locking 16-bit part of the given value. This is the only part
309       * that is allowed to be written without a CAS. This takes care of the shifting and
310       * storing of the value.
311       *
312       * @param o The object whose header is to be changed
313       * @param lockOffset The lock offset
314       * @param value The value which contains the 16-bit portion to be written.
315       */
316      @Inline
317      @Unpreemptible
318      private static void setDedicatedU16(Object o, Offset lockOffset, Word value) {
319        Magic.setCharAtOffset(o, lockOffset.plus(TL_DEDICATED_U16_OFFSET), (char)(value.toInt() >>> TL_DEDICATED_U16_SHIFT));
320      }
321    
322      @NoInline
323      @Unpreemptible
324      public static boolean casFromBiased(Object o, Offset lockOffset,
325                                          Word oldLockWord, Word changed,
326                                          int cnt) {
327        RVMThread me=RVMThread.getCurrentThread();
328        Word id=oldLockWord.and(TL_THREAD_ID_MASK);
329        if (id.isZero()) {
330          if (false) VM.sysWriteln("id is zero - easy case.");
331          return Synchronization.tryCompareAndSwap(o, lockOffset, oldLockWord, changed);
332        } else {
333          if (false) VM.sysWriteln("id = ",id);
334          int slot=id.toInt()>>TL_THREAD_ID_SHIFT;
335          if (false) VM.sysWriteln("slot = ",slot);
336          RVMThread owner=RVMThread.threadBySlot[slot];
337          if (owner==me /* I own it, so I can unbias it trivially.  This occurs
338                           when we are inflating due to, for example, wait() */ ||
339              owner==null /* the thread that owned it is dead, so it's safe to
340                             unbias. */) {
341            // note that we use a CAS here, but it's only needed in the case
342            // that owner==null, since in that case some other thread may also
343            // be unbiasing.
344            return Synchronization.tryCompareAndSwap(
345              o, lockOffset, oldLockWord, changed);
346          } else {
347            boolean result=false;
348    
349            // NB. this may stop a thread other than the one that had the bias,
350            // if that thread died and some other thread took its slot.  that's
351            // why we do a CAS below.  it's only needed if some other thread
352            // had seen the owner be null (which may happen if we came here after
353            // a new thread took the slot while someone else came here when the
354            // slot was still null).  if it was the case that everyone else had
355            // seen a non-null owner, then the pair handshake would serve as
356            // sufficient synchronization (the id would identify the set of threads
357            // that shared that id's communicationLock).  oddly, that means that
358            // this whole thing could be "simplified" to acquire the
359            // communicationLock even if the owner was null.  but that would be
360            // goofy.
361            if (false) VM.sysWriteln("entering pair handshake");
362            owner.beginPairHandshake();
363            if (false) VM.sysWriteln("done with that");
364    
365            Word newLockWord=Magic.getWordAtOffset(o, lockOffset);
366            result=Synchronization.tryCompareAndSwap(
367              o, lockOffset, oldLockWord, changed);
368            owner.endPairHandshake();
369            if (false) VM.sysWriteln("that worked.");
370    
371            return result;
372          }
373        }
374      }
375    
376      @Inline
377      @Unpreemptible
378      public static boolean attemptToMarkInflated(Object o, Offset lockOffset,
379                                                  Word oldLockWord,
380                                                  int lockId,
381                                                  int cnt) {
382        if (VM.VerifyAssertions) VM._assert(oldLockWord.and(TL_STAT_MASK).NE(TL_STAT_FAT));
383        if (false) VM.sysWriteln("attemptToMarkInflated with oldLockWord = ",oldLockWord);
384        // what this needs to do:
385        // 1) if the lock is thin, it's just a CAS
386        // 2) if the lock is unbiased, CAS in the inflation
387        // 3) if the lock is biased in our favor, store the lock without CAS
388        // 4) if the lock is biased but to someone else, enter the pair handshake
389        //    to unbias it and install the inflated lock
390        Word changed=
391          TL_STAT_FAT.or(Word.fromIntZeroExtend(lockId).lsh(TL_LOCK_ID_SHIFT))
392          .or(oldLockWord.and(TL_UNLOCK_MASK));
393        if (false && oldLockWord.and(TL_STAT_MASK).EQ(TL_STAT_THIN))
394          VM.sysWriteln("obj = ",Magic.objectAsAddress(o),
395                        ", old = ",oldLockWord,
396                        ", owner = ",getLockOwner(oldLockWord),
397                        ", rec = ",getLockOwner(oldLockWord)==0?0:getRecCount(oldLockWord),
398                        ", changed = ",changed,
399                        ", lockId = ",lockId);
400        if (false) VM.sysWriteln("changed = ",changed);
401        if (oldLockWord.and(TL_STAT_MASK).EQ(TL_STAT_THIN)) {
402          if (false) VM.sysWriteln("it's thin, inflating the easy way.");
403          return Synchronization.tryCompareAndSwap(
404            o, lockOffset, oldLockWord, changed);
405        } else {
406          return casFromBiased(o, lockOffset, oldLockWord, changed, cnt);
407        }
408      }
409    
410      /**
411       * Promotes a light-weight lock to a heavy-weight lock.  If this returns the lock
412       * that you gave it, its mutex will be locked; otherwise, its mutex will be unlocked.
413       * Hence, calls to this method should always be followed by a condition lock() or
414       * unlock() call.
415       *
416       * @param o the object to get a heavy-weight lock
417       * @param lockOffset the offset of the thin lock word in the object.
418       * @return the inflated lock; either the one you gave, or another one, if the lock
419       *         was inflated by some other thread.
420       */
421      @NoNullCheck
422      @Unpreemptible
423      protected static Lock attemptToInflate(Object o,
424                                             Offset lockOffset,
425                                             Lock l) {
426        if (false) VM.sysWriteln("l = ",Magic.objectAsAddress(l));
427        l.mutex.lock();
428        for (int cnt=0;;++cnt) {
429          Word bits = Magic.getWordAtOffset(o, lockOffset);
430          // check to see if another thread has already created a fat lock
431          if (isFat(bits)) {
432            if (trace) {
433              VM.sysWriteln("Thread #",RVMThread.getCurrentThreadSlot(),
434                            ": freeing lock ",Magic.objectAsAddress(l),
435                            " because we had a double-inflate");
436            }
437            Lock result = Lock.getLock(getLockIndex(bits));
438            if (result==null ||
439                result.lockedObject!=o) {
440              continue; /* this is nasty.  this will happen when a lock
441                           is deflated. */
442            }
443            Lock.free(l);
444            l.mutex.unlock();
445            return result;
446          }
447          if (VM.VerifyAssertions) VM._assert(l!=null);
448          if (attemptToMarkInflated(
449                o, lockOffset, bits, l.index, cnt)) {
450            l.setLockedObject(o);
451            l.setOwnerId(getLockOwner(bits));
452            if (l.getOwnerId() != 0) {
453              l.setRecursionCount(getRecCount(bits));
454            } else {
455              if (VM.VerifyAssertions) VM._assert(l.getRecursionCount()==0);
456            }
457            return l;
458          }
459          // contention detected, try again
460        }
461      }
462    
463      @Inline
464      @Uninterruptible
465      private static Word biasBitsToThinBits(Word bits) {
466        int lockOwner=getLockOwner(bits);
467    
468        Word changed=bits.and(TL_UNLOCK_MASK).or(TL_STAT_THIN);
469    
470        if (lockOwner!=0) {
471          int recCount=getRecCount(bits);
472          changed=changed
473            .or(Word.fromIntZeroExtend(lockOwner))
474            .or(Word.fromIntZeroExtend(recCount-1).lsh(TL_LOCK_COUNT_SHIFT));
475        }
476    
477        return changed;
478      }
479    
480      @Inline
481      @Uninterruptible
482      public static boolean attemptToMarkDeflated(Object o, Offset lockOffset,
483                                                  Word oldLockWord) {
484        // we allow concurrent modification of the lock word when it's thin or fat.
485        Word changed=oldLockWord.and(TL_UNLOCK_MASK).or(TL_STAT_THIN);
486        if (VM.VerifyAssertions) VM._assert(getLockOwner(changed)==0);
487        return Synchronization.tryCompareAndSwap(
488          o, lockOffset, oldLockWord, changed);
489      }
490    
491      @Uninterruptible
492      public static void markDeflated(Object o, Offset lockOffset, int id) {
493        for (;;) {
494          Word bits=Magic.getWordAtOffset(o, lockOffset);
495          if (VM.VerifyAssertions) VM._assert(isFat(bits));
496          if (VM.VerifyAssertions) VM._assert(getLockIndex(bits)==id);
497          if (attemptToMarkDeflated(o, lockOffset, bits)) {
498            return;
499          }
500        }
501      }
502    
503      ////////////////////////////////////////////////////////////////
504      /// Support for inflating (and deflating) heavy-weight locks ///
505      ////////////////////////////////////////////////////////////////
506    
507      /**
508       * Promotes a light-weight lock to a heavy-weight lock.  Note: the
509       * object is question will normally be locked by another thread,
510       * or it may be unlocked.  If there is already a heavy-weight lock
511       * on this object, that lock is returned.
512       *
513       * @param o the object to get a heavy-weight lock
514       * @param lockOffset the offset of the thin lock word in the object.
515       * @return the heavy-weight lock on this object
516       */
517      @Unpreemptible
518      private static Lock inflate(Object o, Offset lockOffset) {
519        Lock l = Lock.allocate();
520        if (VM.VerifyAssertions) {
521          VM._assert(l != null); // inflate called by wait (or notify) which shouldn't be called during GC
522        }
523        Lock rtn = attemptToInflate(o, lockOffset, l);
524        if (rtn == l)
525          l.mutex.unlock();
526        return rtn;
527      }
528    
529      /**
530       * Promotes a light-weight lock to a heavy-weight lock and locks it.
531       * Note: the object in question will normally be locked by another
532       * thread, or it may be unlocked.  If there is already a
533       * heavy-weight lock on this object, that lock is returned.
534       *
535       * @param o the object to get a heavy-weight lock
536       * @param lockOffset the offset of the thin lock word in the object.
537       * @return whether the object was successfully locked
538       */
539      @Unpreemptible
540      private static boolean inflateAndLock(Object o, Offset lockOffset) {
541        Lock l = Lock.allocate();
542        if (l == null) return false; // can't allocate locks during GC
543        Lock rtn = attemptToInflate(o, lockOffset, l);
544        if (l != rtn) {
545          l = rtn;
546          l.mutex.lock();
547        }
548        return l.lockHeavyLocked(o);
549      }
550    
551      ////////////////////////////////////////////////////////////////////////////
552      /// Get heavy-weight lock for an object; if thin, inflate it.
553      ////////////////////////////////////////////////////////////////////////////
554    
555      /**
556       * Obtains the heavy-weight lock, if there is one, associated with the
557       * indicated object.  Returns <code>null</code>, if there is no
558       * heavy-weight lock associated with the object.
559       *
560       * @param o the object from which a lock is desired
561       * @param lockOffset the offset of the thin lock word in the object.
562       * @param create if true, create heavy lock if none found
563       * @return the heavy-weight lock on the object (if any)
564       */
565      @Unpreemptible
566      public static Lock getHeavyLock(Object o, Offset lockOffset, boolean create) {
567        Word old = Magic.getWordAtOffset(o, lockOffset);
568        if (isFat(old)) { // already a fat lock in place
569          return Lock.getLock(getLockIndex(old));
570        } else if (create) {
571          return inflate(o, lockOffset);
572        } else {
573          return null;
574        }
575      }
576    
577      ///////////////////////////////////////////////////////////////
578      /// Support for debugging and performance tuning ///
579      ///////////////////////////////////////////////////////////////
580    
581      /**
582       * Number of times a thread yields before inflating the lock on a
583       * object to a heavy-weight lock.  The current value was for the
584       * portBOB benchmark on a 12-way SMP (AIX) in the Fall of '99.  FP
585       * confirmed that it's still optimal for JBB and DaCapo on 4-, 8-,
586       * and 16-way SMPs (Linux/ia32) in Spring '09.
587       */
588      private static final int retryLimit = 40;
589    
590      static final boolean STATS = Lock.STATS;
591    
592      static final boolean trace = false;
593    
594      static int fastLocks;
595      static int slowLocks;
596    
597      static void notifyAppRunStart(String app, int value) {
598        if (!STATS) return;
599        fastLocks = 0;
600        slowLocks = 0;
601      }
602    
603      static void notifyExit(int value) {
604        if (!STATS) return;
605        VM.sysWrite("ThinLocks: ");
606        VM.sysWrite(fastLocks);
607        VM.sysWrite(" fast locks");
608        Services.percentage(fastLocks, value, "all lock operations");
609        VM.sysWrite("ThinLocks: ");
610        VM.sysWrite(slowLocks);
611        VM.sysWrite(" slow locks");
612        Services.percentage(slowLocks, value, "all lock operations");
613      }
614    
615    }
616