8187941: Add StampedLock stamp inspection methods
authordl
Fri, 13 Oct 2017 18:29:21 -0700
changeset 47343 75ee0b48ea63
parent 47342 bffcbf07ea88
child 47344 849e5737eb19
8187941: Add StampedLock stamp inspection methods Reviewed-by: martin, psandoz
src/java.base/share/classes/java/util/concurrent/locks/StampedLock.java
test/jdk/java/util/concurrent/tck/StampedLockTest.java
--- a/src/java.base/share/classes/java/util/concurrent/locks/StampedLock.java	Fri Oct 13 18:19:18 2017 -0700
+++ b/src/java.base/share/classes/java/util/concurrent/locks/StampedLock.java	Fri Oct 13 18:29:21 2017 -0700
@@ -140,7 +140,8 @@
  *   private double x, y;
  *   private final StampedLock sl = new StampedLock();
  *
- *   void move(double deltaX, double deltaY) { // an exclusively locked method
+ *   // an exclusively locked method
+ *   void move(double deltaX, double deltaY) {
  *     long stamp = sl.writeLock();
  *     try {
  *       x += deltaX;
@@ -150,25 +151,57 @@
  *     }
  *   }
  *
- *   double distanceFromOrigin() { // A read-only method
- *     double currentX, currentY;
+ *   // a read-only method
+ *   // upgrade from optimistic read to read lock
+ *   double distanceFromOrigin() {
  *     long stamp = sl.tryOptimisticRead();
- *     do {
- *       if (stamp == 0L)
- *         stamp = sl.readLock();
- *       try {
+ *     try {
+ *       retryHoldingLock: for (;; stamp = sl.readLock()) {
+ *         if (stamp == 0L)
+ *           continue retryHoldingLock;
  *         // possibly racy reads
- *         currentX = x;
- *         currentY = y;
- *       } finally {
- *         stamp = sl.tryConvertToOptimisticRead(stamp);
+ *         double currentX = x;
+ *         double currentY = y;
+ *         if (!sl.validate(stamp))
+ *           continue retryHoldingLock;
+ *         return Math.hypot(currentX, currentY);
  *       }
- *     } while (stamp == 0);
- *     return Math.hypot(currentX, currentY);
+ *     } finally {
+ *       if (StampedLock.isReadLockStamp(stamp))
+ *         sl.unlockRead(stamp);
+ *     }
  *   }
  *
- *   void moveIfAtOrigin(double newX, double newY) { // upgrade
- *     // Could instead start with optimistic, not read mode
+ *   // upgrade from optimistic read to write lock
+ *   void moveIfAtOrigin(double newX, double newY) {
+ *     long stamp = sl.tryOptimisticRead();
+ *     try {
+ *       retryHoldingLock: for (;; stamp = sl.writeLock()) {
+ *         if (stamp == 0L)
+ *           continue retryHoldingLock;
+ *         // possibly racy reads
+ *         double currentX = x;
+ *         double currentY = y;
+ *         if (!sl.validate(stamp))
+ *           continue retryHoldingLock;
+ *         if (currentX != 0.0 || currentY != 0.0)
+ *           break;
+ *         stamp = sl.tryConvertToWriteLock(stamp);
+ *         if (stamp == 0L)
+ *           continue retryHoldingLock;
+ *         // exclusive access
+ *         x = newX;
+ *         y = newY;
+ *         return;
+ *       }
+ *     } finally {
+ *       if (StampedLock.isWriteLockStamp(stamp))
+ *         sl.unlockWrite(stamp);
+ *     }
+ *   }
+ *
+ *   // Upgrade read lock to write lock
+ *   void moveIfAtOrigin(double newX, double newY) {
  *     long stamp = sl.readLock();
  *     try {
  *       while (x == 0.0 && y == 0.0) {
@@ -882,6 +915,92 @@
     }
 
     /**
+     * Tells whether a stamp represents holding a lock exclusively.
+     * This method may be useful in conjunction with
+     * {@link #tryConvertToWriteLock}, for example: <pre> {@code
+     * long stamp = sl.tryOptimisticRead();
+     * try {
+     *   ...
+     *   stamp = sl.tryConvertToWriteLock(stamp);
+     *   ...
+     * } finally {
+     *   if (StampedLock.isWriteLockStamp(stamp))
+     *     sl.unlockWrite(stamp);
+     * }}</pre>
+     *
+     * @param stamp a stamp returned by a previous StampedLock operation
+     * @return {@code true} if the stamp was returned by a successful
+     *   write-lock operation
+     * @since 10
+     */
+    public static boolean isWriteLockStamp(long stamp) {
+        return (stamp & ABITS) == WBIT;
+    }
+
+    /**
+     * Tells whether a stamp represents holding a lock non-exclusively.
+     * This method may be useful in conjunction with
+     * {@link #tryConvertToReadLock}, for example: <pre> {@code
+     * long stamp = sl.tryOptimisticRead();
+     * try {
+     *   ...
+     *   stamp = sl.tryConvertToReadLock(stamp);
+     *   ...
+     * } finally {
+     *   if (StampedLock.isReadLockStamp(stamp))
+     *     sl.unlockRead(stamp);
+     * }}</pre>
+     *
+     * @param stamp a stamp returned by a previous StampedLock operation
+     * @return {@code true} if the stamp was returned by a successful
+     *   read-lock operation
+     * @since 10
+     */
+    public static boolean isReadLockStamp(long stamp) {
+        return (stamp & RBITS) != 0L;
+    }
+
+    /**
+     * Tells whether a stamp represents holding a lock.
+     * This method may be useful in conjunction with
+     * {@link #tryConvertToReadLock} and {@link #tryConvertToWriteLock},
+     * for example: <pre> {@code
+     * long stamp = sl.tryOptimisticRead();
+     * try {
+     *   ...
+     *   stamp = sl.tryConvertToReadLock(stamp);
+     *   ...
+     *   stamp = sl.tryConvertToWriteLock(stamp);
+     *   ...
+     * } finally {
+     *   if (StampedLock.isLockStamp(stamp))
+     *     sl.unlock(stamp);
+     * }}</pre>
+     *
+     * @param stamp a stamp returned by a previous StampedLock operation
+     * @return {@code true} if the stamp was returned by a successful
+     *   read-lock or write-lock operation
+     * @since 10
+     */
+    public static boolean isLockStamp(long stamp) {
+        return (stamp & ABITS) != 0L;
+    }
+
+    /**
+     * Tells whether a stamp represents a successful optimistic read.
+     *
+     * @param stamp a stamp returned by a previous StampedLock operation
+     * @return {@code true} if the stamp was returned by a successful
+     *   optimistic read operation, that is, a non-zero return from
+     *   {@link #tryOptimisticRead()} or
+     *   {@link #tryConvertToOptimisticRead(long)}
+     * @since 10
+     */
+    public static boolean isOptimisticReadStamp(long stamp) {
+        return (stamp & ABITS) == 0L && stamp != 0L;
+    }
+
+    /**
      * Queries the number of read locks held for this lock. This
      * method is designed for use in monitoring system state, not for
      * synchronization control.
--- a/test/jdk/java/util/concurrent/tck/StampedLockTest.java	Fri Oct 13 18:19:18 2017 -0700
+++ b/test/jdk/java/util/concurrent/tck/StampedLockTest.java	Fri Oct 13 18:29:21 2017 -0700
@@ -35,6 +35,11 @@
 import static java.util.concurrent.TimeUnit.DAYS;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
+import static java.util.concurrent.locks.StampedLock.isLockStamp;
+import static java.util.concurrent.locks.StampedLock.isOptimisticReadStamp;
+import static java.util.concurrent.locks.StampedLock.isReadLockStamp;
+import static java.util.concurrent.locks.StampedLock.isWriteLockStamp;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -1286,11 +1291,115 @@
                 } while (stamp == 0);
                 return Math.hypot(currentX, currentY);
             }
+
+            double distanceFromOrigin2() {
+                long stamp = sl.tryOptimisticRead();
+                try {
+                    retryHoldingLock:
+                    for (;; stamp = sl.readLock()) {
+                        if (stamp == 0L)
+                            continue retryHoldingLock;
+                        // possibly racy reads
+                        double currentX = x;
+                        double currentY = y;
+                        if (!sl.validate(stamp))
+                            continue retryHoldingLock;
+                        return Math.hypot(currentX, currentY);
+                    }
+                } finally {
+                    if (StampedLock.isReadLockStamp(stamp))
+                        sl.unlockRead(stamp);
+                }
+            }
+
+            void moveIfAtOrigin(double newX, double newY) {
+                long stamp = sl.readLock();
+                try {
+                    while (x == 0.0 && y == 0.0) {
+                        long ws = sl.tryConvertToWriteLock(stamp);
+                        if (ws != 0L) {
+                            stamp = ws;
+                            x = newX;
+                            y = newY;
+                            return;
+                        }
+                        else {
+                            sl.unlockRead(stamp);
+                            stamp = sl.writeLock();
+                        }
+                    }
+                } finally {
+                    sl.unlock(stamp);
+                }
+            }
         }
 
         Point p = new Point();
         p.move(3.0, 4.0);
         assertEquals(5.0, p.distanceFromOrigin());
+        p.moveIfAtOrigin(5.0, 12.0);
+        assertEquals(5.0, p.distanceFromOrigin2());
+    }
+
+    /**
+     * Stamp inspection methods work as expected, and do not inspect
+     * the state of the lock itself.
+     */
+    public void testStampStateInspectionMethods() {
+        StampedLock lock = new StampedLock();
+
+        assertFalse(isWriteLockStamp(0L));
+        assertFalse(isReadLockStamp(0L));
+        assertFalse(isLockStamp(0L));
+        assertFalse(isOptimisticReadStamp(0L));
+
+        {
+            long stamp = lock.writeLock();
+            for (int i = 0; i < 2; i++) {
+                assertTrue(isWriteLockStamp(stamp));
+                assertFalse(isReadLockStamp(stamp));
+                assertTrue(isLockStamp(stamp));
+                assertFalse(isOptimisticReadStamp(stamp));
+                if (i == 0)
+                    lock.unlockWrite(stamp);
+            }
+        }
+
+        {
+            long stamp = lock.readLock();
+            for (int i = 0; i < 2; i++) {
+                assertFalse(isWriteLockStamp(stamp));
+                assertTrue(isReadLockStamp(stamp));
+                assertTrue(isLockStamp(stamp));
+                assertFalse(isOptimisticReadStamp(stamp));
+                if (i == 0)
+                    lock.unlockRead(stamp);
+            }
+        }
+
+        {
+            long optimisticStamp = lock.tryOptimisticRead();
+            long readStamp = lock.tryConvertToReadLock(optimisticStamp);
+            long writeStamp = lock.tryConvertToWriteLock(readStamp);
+            for (int i = 0; i < 2; i++) {
+                assertFalse(isWriteLockStamp(optimisticStamp));
+                assertFalse(isReadLockStamp(optimisticStamp));
+                assertFalse(isLockStamp(optimisticStamp));
+                assertTrue(isOptimisticReadStamp(optimisticStamp));
+
+                assertFalse(isWriteLockStamp(readStamp));
+                assertTrue(isReadLockStamp(readStamp));
+                assertTrue(isLockStamp(readStamp));
+                assertFalse(isOptimisticReadStamp(readStamp));
+
+                assertTrue(isWriteLockStamp(writeStamp));
+                assertFalse(isReadLockStamp(writeStamp));
+                assertTrue(isLockStamp(writeStamp));
+                assertFalse(isOptimisticReadStamp(writeStamp));
+                if (i == 0)
+                    lock.unlockWrite(writeStamp);
+            }
+        }
     }
 
 }