diff --git a/JavaLibrary.mk b/JavaLibrary.mk index 21ad63539..756ec023c 100644 --- a/JavaLibrary.mk +++ b/JavaLibrary.mk @@ -215,7 +215,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := $(call all-test-java-files-under, jsr166-tests) LOCAL_JAVA_RESOURCE_DIRS := $(test_resource_dirs) LOCAL_NO_STANDARD_LIBRARIES := true -LOCAL_JAVA_LIBRARIES := core-oj core-libart core-junit +LOCAL_JAVA_LIBRARIES := core-oj core-libart core-lambda-stubs core-junit LOCAL_JAVACFLAGS := $(local_javac_flags) LOCAL_MODULE := jsr166-tests LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/JavaLibrary.mk diff --git a/jsr166-tests/src/test/java/jsr166/AbstractExecutorServiceTest.java b/jsr166-tests/src/test/java/jsr166/AbstractExecutorServiceTest.java index 9e83de2c9..c293f1379 100644 --- a/jsr166-tests/src/test/java/jsr166/AbstractExecutorServiceTest.java +++ b/jsr166-tests/src/test/java/jsr166/AbstractExecutorServiceTest.java @@ -18,6 +18,7 @@ import java.util.concurrent.AbstractExecutorService; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; @@ -38,7 +39,7 @@ public class AbstractExecutorServiceTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AbstractExecutorServiceTest.class); // } /** @@ -196,28 +197,25 @@ public void testSubmitNullCallable() { public void testInterruptedSubmit() throws InterruptedException { final CountDownLatch submitted = new CountDownLatch(1); final CountDownLatch quittingTime = new CountDownLatch(1); - final ExecutorService p - = new ThreadPoolExecutor(1,1,60, TimeUnit.SECONDS, - new ArrayBlockingQueue(10)); final Callable awaiter = new CheckedCallable() { public Void realCall() throws InterruptedException { - quittingTime.await(); + assertTrue(quittingTime.await(2*LONG_DELAY_MS, MILLISECONDS)); return null; }}; - try { - Thread t = new Thread(new CheckedInterruptedRunnable() { + final ExecutorService p + = new ThreadPoolExecutor(1,1,60, TimeUnit.SECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p, quittingTime)) { + Thread t = newStartedThread(new CheckedInterruptedRunnable() { public void realRun() throws Exception { Future future = p.submit(awaiter); submitted.countDown(); future.get(); }}); - t.start(); - submitted.await(); + + await(submitted); t.interrupt(); - t.join(); - } finally { - quittingTime.countDown(); - joinPool(p); + awaitTermination(t); } } @@ -226,34 +224,32 @@ public void realRun() throws Exception { * throws exception */ public void testSubmitEE() throws InterruptedException { - ThreadPoolExecutor p = + final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue(10)); - - Callable c = new Callable() { - public Object call() { throw new ArithmeticException(); }}; - - try { - p.submit(c).get(); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof ArithmeticException); + try (PoolCleaner cleaner = cleaner(p)) { + Callable c = new Callable() { + public Object call() { throw new ArithmeticException(); }}; + try { + p.submit(c).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof ArithmeticException); + } } - joinPool(p); } /** * invokeAny(null) throws NPE */ public void testInvokeAny1() throws Exception { - ExecutorService e = new DirectExecutorService(); - try { - e.invokeAny(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -261,13 +257,12 @@ public void testInvokeAny1() throws Exception { * invokeAny(empty collection) throws IAE */ public void testInvokeAny2() throws Exception { - ExecutorService e = new DirectExecutorService(); - try { - e.invokeAny(new ArrayList>()); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - joinPool(e); + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>()); + shouldThrow(); + } catch (IllegalArgumentException success) {} } } @@ -275,17 +270,16 @@ public void testInvokeAny2() throws Exception { * invokeAny(c) throws NPE if c has null elements */ public void testInvokeAny3() throws Exception { - ExecutorService e = new DirectExecutorService(); - List> l = new ArrayList>(); - l.add(new Callable() { - public Long call() { throw new ArithmeticException(); }}); - l.add(null); - try { - e.invokeAny(l); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new Callable() { + public Long call() { throw new ArithmeticException(); }}); + l.add(null); + try { + e.invokeAny(l); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -293,16 +287,16 @@ public void testInvokeAny3() throws Exception { * invokeAny(c) throws ExecutionException if no task in c completes */ public void testInvokeAny4() throws InterruptedException { - ExecutorService e = new DirectExecutorService(); - List> l = new ArrayList>(); - l.add(new NPETask()); - try { - e.invokeAny(l); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -310,15 +304,13 @@ public void testInvokeAny4() throws InterruptedException { * invokeAny(c) returns result of some task in c if at least one completes */ public void testInvokeAny5() throws Exception { - ExecutorService e = new DirectExecutorService(); - try { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); String result = e.invokeAny(l); assertSame(TEST_STRING, result); - } finally { - joinPool(e); } } @@ -326,13 +318,12 @@ public void testInvokeAny5() throws Exception { * invokeAll(null) throws NPE */ public void testInvokeAll1() throws InterruptedException { - ExecutorService e = new DirectExecutorService(); - try { - e.invokeAll(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -340,12 +331,10 @@ public void testInvokeAll1() throws InterruptedException { * invokeAll(empty collection) returns empty collection */ public void testInvokeAll2() throws InterruptedException { - ExecutorService e = new DirectExecutorService(); - try { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { List> r = e.invokeAll(new ArrayList>()); assertTrue(r.isEmpty()); - } finally { - joinPool(e); } } @@ -353,16 +342,15 @@ public void testInvokeAll2() throws InterruptedException { * invokeAll(c) throws NPE if c has null elements */ public void testInvokeAll3() throws InterruptedException { - ExecutorService e = new DirectExecutorService(); - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(null); - try { - e.invokeAll(l); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -370,8 +358,8 @@ public void testInvokeAll3() throws InterruptedException { * get of returned element of invokeAll(c) throws exception on failed task */ public void testInvokeAll4() throws Exception { - ExecutorService e = new DirectExecutorService(); - try { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new NPETask()); List> futures = e.invokeAll(l); @@ -382,8 +370,6 @@ public void testInvokeAll4() throws Exception { } catch (ExecutionException success) { assertTrue(success.getCause() instanceof NullPointerException); } - } finally { - joinPool(e); } } @@ -391,8 +377,8 @@ public void testInvokeAll4() throws Exception { * invokeAll(c) returns results of all completed tasks in c */ public void testInvokeAll5() throws Exception { - ExecutorService e = new DirectExecutorService(); - try { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); @@ -400,8 +386,6 @@ public void testInvokeAll5() throws Exception { assertEquals(2, futures.size()); for (Future future : futures) assertSame(TEST_STRING, future.get()); - } finally { - joinPool(e); } } @@ -409,13 +393,12 @@ public void testInvokeAll5() throws Exception { * timed invokeAny(null) throws NPE */ public void testTimedInvokeAny1() throws Exception { - ExecutorService e = new DirectExecutorService(); - try { - e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -423,15 +406,14 @@ public void testTimedInvokeAny1() throws Exception { * timed invokeAny(null time unit) throws NPE */ public void testTimedInvokeAnyNullTimeUnit() throws Exception { - ExecutorService e = new DirectExecutorService(); - List> l = new ArrayList>(); - l.add(new StringTask()); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -439,13 +421,13 @@ public void testTimedInvokeAnyNullTimeUnit() throws Exception { * timed invokeAny(empty collection) throws IAE */ public void testTimedInvokeAny2() throws Exception { - ExecutorService e = new DirectExecutorService(); - try { - e.invokeAny(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - joinPool(e); + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} } } @@ -453,17 +435,16 @@ public void testTimedInvokeAny2() throws Exception { * timed invokeAny(c) throws NPE if c has null elements */ public void testTimedInvokeAny3() throws Exception { - ExecutorService e = new DirectExecutorService(); - List> l = new ArrayList>(); - l.add(new Callable() { - public Long call() { throw new ArithmeticException(); }}); - l.add(null); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new Callable() { + public Long call() { throw new ArithmeticException(); }}); + l.add(null); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -471,16 +452,18 @@ public void testTimedInvokeAny3() throws Exception { * timed invokeAny(c) throws ExecutionException if no task completes */ public void testTimedInvokeAny4() throws Exception { - ExecutorService e = new DirectExecutorService(); - List> l = new ArrayList>(); - l.add(new NPETask()); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } } @@ -488,15 +471,15 @@ public void testTimedInvokeAny4() throws Exception { * timed invokeAny(c) returns result of some task in c */ public void testTimedInvokeAny5() throws Exception { - ExecutorService e = new DirectExecutorService(); - try { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); - String result = e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + String result = e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); assertSame(TEST_STRING, result); - } finally { - joinPool(e); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } } @@ -504,13 +487,12 @@ public void testTimedInvokeAny5() throws Exception { * timed invokeAll(null) throws NPE */ public void testTimedInvokeAll1() throws InterruptedException { - ExecutorService e = new DirectExecutorService(); - try { - e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -518,15 +500,14 @@ public void testTimedInvokeAll1() throws InterruptedException { * timed invokeAll(null time unit) throws NPE */ public void testTimedInvokeAllNullTimeUnit() throws InterruptedException { - ExecutorService e = new DirectExecutorService(); - List> l = new ArrayList>(); - l.add(new StringTask()); - try { - e.invokeAll(l, MEDIUM_DELAY_MS, null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -534,12 +515,10 @@ public void testTimedInvokeAllNullTimeUnit() throws InterruptedException { * timed invokeAll(empty collection) returns empty collection */ public void testTimedInvokeAll2() throws InterruptedException { - ExecutorService e = new DirectExecutorService(); - try { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { List> r = e.invokeAll(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); assertTrue(r.isEmpty()); - } finally { - joinPool(e); } } @@ -547,16 +526,15 @@ public void testTimedInvokeAll2() throws InterruptedException { * timed invokeAll(c) throws NPE if c has null elements */ public void testTimedInvokeAll3() throws InterruptedException { - ExecutorService e = new DirectExecutorService(); - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(null); - try { - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -564,12 +542,12 @@ public void testTimedInvokeAll3() throws InterruptedException { * get of returned element of invokeAll(c) throws exception on failed task */ public void testTimedInvokeAll4() throws Exception { - ExecutorService e = new DirectExecutorService(); - try { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new NPETask()); List> futures = - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); assertEquals(1, futures.size()); try { futures.get(0).get(); @@ -577,8 +555,6 @@ public void testTimedInvokeAll4() throws Exception { } catch (ExecutionException success) { assertTrue(success.getCause() instanceof NullPointerException); } - } finally { - joinPool(e); } } @@ -586,41 +562,51 @@ public void testTimedInvokeAll4() throws Exception { * timed invokeAll(c) returns results of all completed tasks in c */ public void testTimedInvokeAll5() throws Exception { - ExecutorService e = new DirectExecutorService(); - try { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); List> futures = - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); assertEquals(2, futures.size()); for (Future future : futures) assertSame(TEST_STRING, future.get()); - } finally { - joinPool(e); } } /** * timed invokeAll cancels tasks not completed by timeout */ - public void testTimedInvokeAll6() throws InterruptedException { - ExecutorService e = new DirectExecutorService(); - try { - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(Executors.callable(possiblyInterruptedRunnable(2 * SHORT_DELAY_MS), TEST_STRING)); - l.add(new StringTask()); - List> futures = - e.invokeAll(l, SHORT_DELAY_MS, MILLISECONDS); - assertEquals(l.size(), futures.size()); - for (Future future : futures) - assertTrue(future.isDone()); - assertFalse(futures.get(0).isCancelled()); - assertFalse(futures.get(1).isCancelled()); - assertTrue(futures.get(2).isCancelled()); - } finally { - joinPool(e); + public void testTimedInvokeAll6() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + for (long timeout = timeoutMillis();;) { + List> tasks = new ArrayList<>(); + tasks.add(new StringTask("0")); + tasks.add(Executors.callable(possiblyInterruptedRunnable(timeout), + TEST_STRING)); + tasks.add(new StringTask("2")); + long startTime = System.nanoTime(); + List> futures = + e.invokeAll(tasks, timeout, MILLISECONDS); + assertEquals(tasks.size(), futures.size()); + assertTrue(millisElapsedSince(startTime) >= timeout); + for (Future future : futures) + assertTrue(future.isDone()); + try { + assertEquals("0", futures.get(0).get()); + assertEquals(TEST_STRING, futures.get(1).get()); + } catch (CancellationException retryWithLongerTimeout) { + // unusual delay before starting second task + timeout *= 2; + if (timeout >= LONG_DELAY_MS / 2) + fail("expected exactly one task to be cancelled"); + continue; + } + assertTrue(futures.get(2).isCancelled()); + break; + } } } diff --git a/jsr166-tests/src/test/java/jsr166/AbstractQueueTest.java b/jsr166-tests/src/test/java/jsr166/AbstractQueueTest.java index 2aa73261b..bf25668d3 100644 --- a/jsr166-tests/src/test/java/jsr166/AbstractQueueTest.java +++ b/jsr166-tests/src/test/java/jsr166/AbstractQueueTest.java @@ -24,7 +24,7 @@ public class AbstractQueueTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AbstractQueueTest.class); // } static class Succeed extends AbstractQueue { @@ -158,7 +158,7 @@ public void testAddAll2() { public void testAddAll3() { Succeed q = new Succeed(); Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) + for (int i = 0; i < SIZE - 1; ++i) ints[i] = new Integer(i); try { q.addAll(Arrays.asList(ints)); diff --git a/jsr166-tests/src/test/java/jsr166/AbstractQueuedLongSynchronizerTest.java b/jsr166-tests/src/test/java/jsr166/AbstractQueuedLongSynchronizerTest.java index 8604d8676..c462c7386 100644 --- a/jsr166-tests/src/test/java/jsr166/AbstractQueuedLongSynchronizerTest.java +++ b/jsr166-tests/src/test/java/jsr166/AbstractQueuedLongSynchronizerTest.java @@ -29,7 +29,7 @@ public class AbstractQueuedLongSynchronizerTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AbstractQueuedLongSynchronizerTest.class); // } /** @@ -241,25 +241,33 @@ void await(ConditionObject c, AwaitMethod awaitMethod) */ void assertAwaitTimesOut(ConditionObject c, AwaitMethod awaitMethod) { long timeoutMillis = timeoutMillis(); - long startTime = System.nanoTime(); + long startTime; try { switch (awaitMethod) { case awaitTimed: + startTime = System.nanoTime(); assertFalse(c.await(timeoutMillis, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); break; case awaitNanos: + startTime = System.nanoTime(); long nanosTimeout = MILLISECONDS.toNanos(timeoutMillis); long nanosRemaining = c.awaitNanos(nanosTimeout); assertTrue(nanosRemaining <= 0); + assertTrue(nanosRemaining > -MILLISECONDS.toNanos(LONG_DELAY_MS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); break; case awaitUntil: + // We shouldn't assume that nanoTime and currentTimeMillis + // use the same time source, so don't use nanoTime here. + java.util.Date delayedDate = delayedDate(timeoutMillis()); assertFalse(c.awaitUntil(delayedDate(timeoutMillis))); + assertTrue(new java.util.Date().getTime() >= delayedDate.getTime()); break; default: throw new UnsupportedOperationException(); } } catch (InterruptedException ie) { threadUnexpectedException(ie); } - assertTrue(millisElapsedSince(startTime) >= timeoutMillis); } /** diff --git a/jsr166-tests/src/test/java/jsr166/AbstractQueuedSynchronizerTest.java b/jsr166-tests/src/test/java/jsr166/AbstractQueuedSynchronizerTest.java index b3c411029..d102fc63c 100644 --- a/jsr166-tests/src/test/java/jsr166/AbstractQueuedSynchronizerTest.java +++ b/jsr166-tests/src/test/java/jsr166/AbstractQueuedSynchronizerTest.java @@ -29,7 +29,7 @@ public class AbstractQueuedSynchronizerTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AbstractQueuedSynchronizerTest.class); // } /** @@ -244,25 +244,33 @@ void await(ConditionObject c, AwaitMethod awaitMethod) */ void assertAwaitTimesOut(ConditionObject c, AwaitMethod awaitMethod) { long timeoutMillis = timeoutMillis(); - long startTime = System.nanoTime(); + long startTime; try { switch (awaitMethod) { case awaitTimed: + startTime = System.nanoTime(); assertFalse(c.await(timeoutMillis, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); break; case awaitNanos: + startTime = System.nanoTime(); long nanosTimeout = MILLISECONDS.toNanos(timeoutMillis); long nanosRemaining = c.awaitNanos(nanosTimeout); assertTrue(nanosRemaining <= 0); + assertTrue(nanosRemaining > -MILLISECONDS.toNanos(LONG_DELAY_MS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); break; case awaitUntil: + // We shouldn't assume that nanoTime and currentTimeMillis + // use the same time source, so don't use nanoTime here. + java.util.Date delayedDate = delayedDate(timeoutMillis()); assertFalse(c.awaitUntil(delayedDate(timeoutMillis))); + assertTrue(new java.util.Date().getTime() >= delayedDate.getTime()); break; default: throw new UnsupportedOperationException(); } } catch (InterruptedException ie) { threadUnexpectedException(ie); } - assertTrue(millisElapsedSince(startTime) >= timeoutMillis); } /** diff --git a/jsr166-tests/src/test/java/jsr166/ArrayBlockingQueueTest.java b/jsr166-tests/src/test/java/jsr166/ArrayBlockingQueueTest.java index 247c90e70..902ae405e 100644 --- a/jsr166-tests/src/test/java/jsr166/ArrayBlockingQueueTest.java +++ b/jsr166-tests/src/test/java/jsr166/ArrayBlockingQueueTest.java @@ -26,7 +26,7 @@ public class ArrayBlockingQueueTest extends JSR166TestCase { - // android-note: These tests have been moved into their own separate + // android-note: These tests have been moved into their own separate // classes to work around CTS issues. // // public static class Fair extends BlockingQueueTest { @@ -34,17 +34,19 @@ public class ArrayBlockingQueueTest extends JSR166TestCase { // return new ArrayBlockingQueue(SIZE, true); // } // } - // + // public static class NonFair extends BlockingQueueTest { // protected BlockingQueue emptyCollection() { // return new ArrayBlockingQueue(SIZE, false); // } // } + + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. // // public static void main(String[] args) { // main(suite(), args); // } - // // public static Test suite() { // return newTestSuite(ArrayBlockingQueueTest.class, // new Fair().testSuite(), @@ -109,7 +111,7 @@ public void testConstructor4() { */ public void testConstructor5() { Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) + for (int i = 0; i < SIZE - 1; ++i) ints[i] = i; Collection elements = Arrays.asList(ints); try { @@ -171,7 +173,7 @@ public void testRemainingCapacity() { assertEquals(i, q.remove()); } for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.remainingCapacity()); + assertEquals(SIZE - i, q.remainingCapacity()); assertEquals(SIZE, q.size() + q.remainingCapacity()); assertTrue(q.add(i)); } @@ -219,7 +221,7 @@ public void testAddAllSelf() { public void testAddAll3() { ArrayBlockingQueue q = new ArrayBlockingQueue(SIZE); Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) + for (int i = 0; i < SIZE - 1; ++i) ints[i] = new Integer(i); try { q.addAll(Arrays.asList(ints)); @@ -456,25 +458,23 @@ public void testInterruptedTimedPoll() throws InterruptedException { final CountDownLatch aboutToWait = new CountDownLatch(1); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); for (int i = 0; i < SIZE; ++i) { - long t0 = System.nanoTime(); assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(millisElapsedSince(t0) < SMALL_DELAY_MS); } - long t0 = System.nanoTime(); aboutToWait.countDown(); try { - q.poll(MEDIUM_DELAY_MS, MILLISECONDS); + q.poll(LONG_DELAY_MS, MILLISECONDS); shouldThrow(); } catch (InterruptedException success) { - assertTrue(millisElapsedSince(t0) < MEDIUM_DELAY_MS); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } }}); - aboutToWait.await(); - waitForThreadToEnterWaitState(t, SMALL_DELAY_MS); + await(aboutToWait); + waitForThreadToEnterWaitState(t, LONG_DELAY_MS); t.interrupt(); - awaitTermination(t, MEDIUM_DELAY_MS); + awaitTermination(t); checkEmpty(q); } @@ -577,7 +577,7 @@ public void testRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.remove(); } } @@ -590,7 +590,7 @@ public void testRemoveAll() { ArrayBlockingQueue q = populatedQueue(SIZE); ArrayBlockingQueue p = populatedQueue(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { Integer x = (Integer)(p.remove()); assertFalse(q.contains(x)); @@ -624,23 +624,23 @@ public void testToArray() { checkToArray(q); assertEquals(i, q.poll()); checkToArray(q); - q.add(SIZE+i); + q.add(SIZE + i); } for (int i = 0; i < SIZE; i++) { checkToArray(q); - assertEquals(SIZE+i, q.poll()); + assertEquals(SIZE + i, q.poll()); } } void checkToArray2(ArrayBlockingQueue q) { int size = q.size(); - Integer[] a1 = size == 0 ? null : new Integer[size-1]; + Integer[] a1 = (size == 0) ? null : new Integer[size - 1]; Integer[] a2 = new Integer[size]; - Integer[] a3 = new Integer[size+2]; + Integer[] a3 = new Integer[size + 2]; if (size > 0) Arrays.fill(a1, 42); Arrays.fill(a2, 42); Arrays.fill(a3, 42); - Integer[] b1 = size == 0 ? null : (Integer[]) q.toArray(a1); + Integer[] b1 = (size == 0) ? null : (Integer[]) q.toArray(a1); Integer[] b2 = (Integer[]) q.toArray(a2); Integer[] b3 = (Integer[]) q.toArray(a3); assertSame(a2, b2); @@ -654,7 +654,7 @@ void checkToArray2(ArrayBlockingQueue q) { assertSame(b3[i], x); } assertNull(a3[size]); - assertEquals(42, (int) a3[size+1]); + assertEquals(42, (int) a3[size + 1]); if (size > 0) { assertNotSame(a1, b1); assertEquals(size, b1.length); @@ -678,11 +678,11 @@ public void testToArray2() { checkToArray2(q); assertEquals(i, q.poll()); checkToArray2(q); - q.add(SIZE+i); + q.add(SIZE + i); } for (int i = 0; i < SIZE; i++) { checkToArray2(q); - assertEquals(SIZE+i, q.poll()); + assertEquals(SIZE + i, q.poll()); } } @@ -793,24 +793,24 @@ public void testOfferInExecutor() { final ArrayBlockingQueue q = new ArrayBlockingQueue(2); q.add(one); q.add(two); - ExecutorService executor = Executors.newFixedThreadPool(2); final CheckedBarrier threadsStarted = new CheckedBarrier(2); - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - assertFalse(q.offer(three)); - threadsStarted.await(); - assertTrue(q.offer(three, LONG_DELAY_MS, MILLISECONDS)); - assertEquals(0, q.remainingCapacity()); - }}); - - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - threadsStarted.await(); - assertEquals(0, q.remainingCapacity()); - assertSame(one, q.take()); - }}); - - joinPool(executor); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(q.offer(three)); + threadsStarted.await(); + assertTrue(q.offer(three, LONG_DELAY_MS, MILLISECONDS)); + assertEquals(0, q.remainingCapacity()); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + assertEquals(0, q.remainingCapacity()); + assertSame(one, q.take()); + }}); + } } /** @@ -819,22 +819,22 @@ public void realRun() throws InterruptedException { public void testPollInExecutor() { final ArrayBlockingQueue q = new ArrayBlockingQueue(2); final CheckedBarrier threadsStarted = new CheckedBarrier(2); - ExecutorService executor = Executors.newFixedThreadPool(2); - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - assertNull(q.poll()); - threadsStarted.await(); - assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); - checkEmpty(q); - }}); - - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - threadsStarted.await(); - q.put(one); - }}); - - joinPool(executor); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertNull(q.poll()); + threadsStarted.await(); + assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); + checkEmpty(q); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + q.put(one); + }}); + } } /** @@ -886,7 +886,7 @@ public void testDrainToWithActivePut() throws InterruptedException { final ArrayBlockingQueue q = populatedQueue(SIZE); Thread t = new Thread(new CheckedRunnable() { public void realRun() throws InterruptedException { - q.put(new Integer(SIZE+1)); + q.put(new Integer(SIZE + 1)); }}); t.start(); @@ -903,7 +903,7 @@ public void realRun() throws InterruptedException { * drainTo(c, n) empties first min(n, size) elements of queue into c */ public void testDrainToN() { - ArrayBlockingQueue q = new ArrayBlockingQueue(SIZE*2); + ArrayBlockingQueue q = new ArrayBlockingQueue(SIZE * 2); for (int i = 0; i < SIZE + 2; ++i) { for (int j = 0; j < SIZE; j++) assertTrue(q.offer(new Integer(j))); @@ -911,7 +911,7 @@ public void testDrainToN() { q.drainTo(l, i); int k = (i < SIZE) ? i : SIZE; assertEquals(k, l.size()); - assertEquals(SIZE-k, q.size()); + assertEquals(SIZE - k, q.size()); for (int j = 0; j < k; ++j) assertEquals(l.get(j), new Integer(j)); do {} while (q.poll() != null); diff --git a/jsr166-tests/src/test/java/jsr166/ArrayDequeTest.java b/jsr166-tests/src/test/java/jsr166/ArrayDequeTest.java index 16290e9dc..23cc6b9d7 100644 --- a/jsr166-tests/src/test/java/jsr166/ArrayDequeTest.java +++ b/jsr166-tests/src/test/java/jsr166/ArrayDequeTest.java @@ -26,7 +26,7 @@ public class ArrayDequeTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ArrayDequeTest.class); // } /** @@ -65,8 +65,7 @@ public void testConstructor3() { */ public void testConstructor4() { try { - Integer[] ints = new Integer[SIZE]; - new ArrayDeque(Arrays.asList(ints)); + new ArrayDeque(Arrays.asList(new Integer[SIZE])); shouldThrow(); } catch (NullPointerException success) {} } @@ -75,10 +74,10 @@ public void testConstructor4() { * Initializing from Collection with some null elements throws NPE */ public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); try { - Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i); new ArrayDeque(Arrays.asList(ints)); shouldThrow(); } catch (NullPointerException success) {} @@ -116,7 +115,7 @@ public void testEmpty() { public void testSize() { ArrayDeque q = populatedDeque(SIZE); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); q.removeFirst(); } for (int i = 0; i < SIZE; ++i) { @@ -129,8 +128,8 @@ public void testSize() { * push(null) throws NPE */ public void testPushNull() { + ArrayDeque q = new ArrayDeque(1); try { - ArrayDeque q = new ArrayDeque(1); q.push(null); shouldThrow(); } catch (NullPointerException success) {} @@ -164,8 +163,8 @@ public void testPop() { * offer(null) throws NPE */ public void testOfferNull() { + ArrayDeque q = new ArrayDeque(); try { - ArrayDeque q = new ArrayDeque(); q.offer(null); shouldThrow(); } catch (NullPointerException success) {} @@ -175,8 +174,8 @@ public void testOfferNull() { * offerFirst(null) throws NPE */ public void testOfferFirstNull() { + ArrayDeque q = new ArrayDeque(); try { - ArrayDeque q = new ArrayDeque(); q.offerFirst(null); shouldThrow(); } catch (NullPointerException success) {} @@ -186,8 +185,8 @@ public void testOfferFirstNull() { * offerLast(null) throws NPE */ public void testOfferLastNull() { + ArrayDeque q = new ArrayDeque(); try { - ArrayDeque q = new ArrayDeque(); q.offerLast(null); shouldThrow(); } catch (NullPointerException success) {} @@ -230,8 +229,8 @@ public void testOfferLast() { * add(null) throws NPE */ public void testAddNull() { + ArrayDeque q = new ArrayDeque(); try { - ArrayDeque q = new ArrayDeque(); q.add(null); shouldThrow(); } catch (NullPointerException success) {} @@ -241,8 +240,8 @@ public void testAddNull() { * addFirst(null) throws NPE */ public void testAddFirstNull() { + ArrayDeque q = new ArrayDeque(); try { - ArrayDeque q = new ArrayDeque(); q.addFirst(null); shouldThrow(); } catch (NullPointerException success) {} @@ -252,8 +251,8 @@ public void testAddFirstNull() { * addLast(null) throws NPE */ public void testAddLastNull() { + ArrayDeque q = new ArrayDeque(); try { - ArrayDeque q = new ArrayDeque(); q.addLast(null); shouldThrow(); } catch (NullPointerException success) {} @@ -296,8 +295,8 @@ public void testAddLast() { * addAll(null) throws NPE */ public void testAddAll1() { + ArrayDeque q = new ArrayDeque(); try { - ArrayDeque q = new ArrayDeque(); q.addAll(null); shouldThrow(); } catch (NullPointerException success) {} @@ -307,10 +306,9 @@ public void testAddAll1() { * addAll of a collection with null elements throws NPE */ public void testAddAll2() { + ArrayDeque q = new ArrayDeque(); try { - ArrayDeque q = new ArrayDeque(); - Integer[] ints = new Integer[SIZE]; - q.addAll(Arrays.asList(ints)); + q.addAll(Arrays.asList(new Integer[SIZE])); shouldThrow(); } catch (NullPointerException success) {} } @@ -320,11 +318,11 @@ public void testAddAll2() { * possibly adding some elements */ public void testAddAll3() { + ArrayDeque q = new ArrayDeque(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); try { - ArrayDeque q = new ArrayDeque(); - Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i); q.addAll(Arrays.asList(ints)); shouldThrow(); } catch (NullPointerException success) {} @@ -361,7 +359,7 @@ public void testPollFirst() { */ public void testPollLast() { ArrayDeque q = populatedDeque(SIZE); - for (int i = SIZE-1; i >= 0; --i) { + for (int i = SIZE - 1; i >= 0; --i) { assertEquals(i, q.pollLast()); } assertNull(q.pollLast()); @@ -401,14 +399,14 @@ public void testRemoveElement() { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertTrue(q.contains(i-1)); + assertTrue(q.contains(i - 1)); } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertFalse(q.remove(i+1)); - assertFalse(q.contains(i+1)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); } assertTrue(q.isEmpty()); } @@ -446,7 +444,7 @@ public void testPeek() { */ public void testPeekLast() { ArrayDeque q = populatedDeque(SIZE); - for (int i = SIZE-1; i >= 0; --i) { + for (int i = SIZE - 1; i >= 0; --i) { assertEquals(i, q.peekLast()); assertEquals(i, q.pollLast()); assertTrue(q.peekLast() == null || @@ -490,7 +488,7 @@ public void testFirstElement() { */ public void testLastElement() { ArrayDeque q = populatedDeque(SIZE); - for (int i = SIZE-1; i >= 0; --i) { + for (int i = SIZE - 1; i >= 0; --i) { assertEquals(i, q.getLast()); assertEquals(i, q.pollLast()); } @@ -541,7 +539,7 @@ public void testRemoveFirstOccurrence() { } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.removeFirstOccurrence(new Integer(i))); - assertFalse(q.removeFirstOccurrence(new Integer(i+1))); + assertFalse(q.removeFirstOccurrence(new Integer(i + 1))); } assertTrue(q.isEmpty()); } @@ -556,7 +554,7 @@ public void testRemoveLastOccurrence() { } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.removeLastOccurrence(new Integer(i))); - assertFalse(q.removeLastOccurrence(new Integer(i+1))); + assertFalse(q.removeLastOccurrence(new Integer(i + 1))); } assertTrue(q.isEmpty()); } @@ -611,7 +609,7 @@ public void testRetainAll() { boolean changed = q.retainAll(p); assertEquals(changed, (i > 0)); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.removeFirst(); } } @@ -624,7 +622,7 @@ public void testRemoveAll() { ArrayDeque q = populatedDeque(SIZE); ArrayDeque p = populatedDeque(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { assertFalse(q.contains(p.removeFirst())); } @@ -656,23 +654,23 @@ public void testToArray() { for (int i = 0; i < SIZE; i++) { checkToArray(q); assertEquals(i, q.poll()); - q.addLast(SIZE+i); + q.addLast(SIZE + i); } for (int i = 0; i < SIZE; i++) { checkToArray(q); - assertEquals(SIZE+i, q.poll()); + assertEquals(SIZE + i, q.poll()); } } void checkToArray2(ArrayDeque q) { int size = q.size(); - Integer[] a1 = size == 0 ? null : new Integer[size-1]; + Integer[] a1 = (size == 0) ? null : new Integer[size - 1]; Integer[] a2 = new Integer[size]; - Integer[] a3 = new Integer[size+2]; + Integer[] a3 = new Integer[size + 2]; if (size > 0) Arrays.fill(a1, 42); Arrays.fill(a2, 42); Arrays.fill(a3, 42); - Integer[] b1 = size == 0 ? null : (Integer[]) q.toArray(a1); + Integer[] b1 = (size == 0) ? null : (Integer[]) q.toArray(a1); Integer[] b2 = (Integer[]) q.toArray(a2); Integer[] b3 = (Integer[]) q.toArray(a3); assertSame(a2, b2); @@ -686,7 +684,7 @@ void checkToArray2(ArrayDeque q) { assertSame(b3[i], x); } assertNull(a3[size]); - assertEquals(42, (int) a3[size+1]); + assertEquals(42, (int) a3[size + 1]); if (size > 0) { assertNotSame(a1, b1); assertEquals(size, b1.length); @@ -709,11 +707,11 @@ public void testToArray2() { for (int i = 0; i < SIZE; i++) { checkToArray2(q); assertEquals(i, q.poll()); - q.addLast(SIZE+i); + q.addLast(SIZE + i); } for (int i = 0; i < SIZE; i++) { checkToArray2(q); - assertEquals(SIZE+i, q.poll()); + assertEquals(SIZE + i, q.poll()); } } @@ -787,18 +785,18 @@ public void testIteratorRemove() { final Random rng = new Random(); for (int iters = 0; iters < 100; ++iters) { int max = rng.nextInt(5) + 2; - int split = rng.nextInt(max-1) + 1; + int split = rng.nextInt(max - 1) + 1; for (int j = 1; j <= max; ++j) q.add(new Integer(j)); Iterator it = q.iterator(); for (int j = 1; j <= split; ++j) assertEquals(it.next(), new Integer(j)); it.remove(); - assertEquals(it.next(), new Integer(split+1)); + assertEquals(it.next(), new Integer(split + 1)); for (int j = 1; j <= split; ++j) q.remove(new Integer(j)); it = q.iterator(); - for (int j = split+1; j <= max; ++j) { + for (int j = split + 1; j <= max; ++j) { assertEquals(it.next(), new Integer(j)); it.remove(); } @@ -855,18 +853,18 @@ public void testDescendingIteratorRemove() { final Random rng = new Random(); for (int iters = 0; iters < 100; ++iters) { int max = rng.nextInt(5) + 2; - int split = rng.nextInt(max-1) + 1; + int split = rng.nextInt(max - 1) + 1; for (int j = max; j >= 1; --j) q.add(new Integer(j)); Iterator it = q.descendingIterator(); for (int j = 1; j <= split; ++j) assertEquals(it.next(), new Integer(j)); it.remove(); - assertEquals(it.next(), new Integer(split+1)); + assertEquals(it.next(), new Integer(split + 1)); for (int j = 1; j <= split; ++j) q.remove(new Integer(j)); it = q.descendingIterator(); - for (int j = split+1; j <= max; ++j) { + for (int j = split + 1; j <= max; ++j) { assertEquals(it.next(), new Integer(j)); it.remove(); } diff --git a/jsr166-tests/src/test/java/jsr166/Atomic8Test.java b/jsr166-tests/src/test/java/jsr166/Atomic8Test.java new file mode 100644 index 000000000..f81c44fa4 --- /dev/null +++ b/jsr166-tests/src/test/java/jsr166/Atomic8Test.java @@ -0,0 +1,574 @@ +/* + * Written by Doug Lea and Martin Buchholz with assistance from + * members of JCP JSR-166 Expert Group and released to the public + * domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package jsr166; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicIntegerArray; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicLongArray; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class Atomic8Test extends JSR166TestCase { + + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. + // + // public static void main(String[] args) { + // main(suite(), args); + // } + // public static Test suite() { + // return new TestSuite(Atomic8Test.class); + // } + + /* + * Tests of atomic class methods accepting lambdas + * introduced in JDK8. + */ + + static long addLong17(long x) { return x + 17; } + static int addInt17(int x) { return x + 17; } + static Integer addInteger17(Integer x) { + return new Integer(x.intValue() + 17); + } + static Integer sumInteger(Integer x, Integer y) { + return new Integer(x.intValue() + y.intValue()); + } + + volatile long aLongField; + volatile int anIntField; + volatile Integer anIntegerField; + + AtomicLongFieldUpdater aLongFieldUpdater() { + return AtomicLongFieldUpdater.newUpdater + (Atomic8Test.class, "aLongField"); + } + + AtomicIntegerFieldUpdater anIntFieldUpdater() { + return AtomicIntegerFieldUpdater.newUpdater + (Atomic8Test.class, "anIntField"); + } + + AtomicReferenceFieldUpdater anIntegerFieldUpdater() { + return AtomicReferenceFieldUpdater.newUpdater + (Atomic8Test.class, Integer.class, "anIntegerField"); + } + + /** + * AtomicLong getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testLongGetAndUpdate() { + AtomicLong a = new AtomicLong(1L); + assertEquals(1L, a.getAndUpdate(Atomic8Test::addLong17)); + assertEquals(18L, a.getAndUpdate(Atomic8Test::addLong17)); + assertEquals(35L, a.get()); + } + + /** + * AtomicLong updateAndGet updates with supplied function and + * returns result. + */ + public void testLongUpdateAndGet() { + AtomicLong a = new AtomicLong(1L); + assertEquals(18L, a.updateAndGet(Atomic8Test::addLong17)); + assertEquals(35L, a.updateAndGet(Atomic8Test::addLong17)); + } + + /** + * AtomicLong getAndAccumulate returns previous value and updates + * with supplied function. + */ + public void testLongGetAndAccumulate() { + AtomicLong a = new AtomicLong(1L); + assertEquals(1L, a.getAndAccumulate(2L, Long::sum)); + assertEquals(3L, a.getAndAccumulate(3L, Long::sum)); + assertEquals(6L, a.get()); + } + + /** + * AtomicLong accumulateAndGet updates with supplied function and + * returns result. + */ + public void testLongAccumulateAndGet() { + AtomicLong a = new AtomicLong(1L); + assertEquals(7L, a.accumulateAndGet(6L, Long::sum)); + assertEquals(10L, a.accumulateAndGet(3L, Long::sum)); + assertEquals(10L, a.get()); + } + + /** + * AtomicInteger getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testIntGetAndUpdate() { + AtomicInteger a = new AtomicInteger(1); + assertEquals(1, a.getAndUpdate(Atomic8Test::addInt17)); + assertEquals(18, a.getAndUpdate(Atomic8Test::addInt17)); + assertEquals(35, a.get()); + } + + /** + * AtomicInteger updateAndGet updates with supplied function and + * returns result. + */ + public void testIntUpdateAndGet() { + AtomicInteger a = new AtomicInteger(1); + assertEquals(18, a.updateAndGet(Atomic8Test::addInt17)); + assertEquals(35, a.updateAndGet(Atomic8Test::addInt17)); + assertEquals(35, a.get()); + } + + /** + * AtomicInteger getAndAccumulate returns previous value and updates + * with supplied function. + */ + public void testIntGetAndAccumulate() { + AtomicInteger a = new AtomicInteger(1); + assertEquals(1, a.getAndAccumulate(2, Integer::sum)); + assertEquals(3, a.getAndAccumulate(3, Integer::sum)); + assertEquals(6, a.get()); + } + + /** + * AtomicInteger accumulateAndGet updates with supplied function and + * returns result. + */ + public void testIntAccumulateAndGet() { + AtomicInteger a = new AtomicInteger(1); + assertEquals(7, a.accumulateAndGet(6, Integer::sum)); + assertEquals(10, a.accumulateAndGet(3, Integer::sum)); + assertEquals(10, a.get()); + } + + /** + * AtomicReference getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testReferenceGetAndUpdate() { + AtomicReference a = new AtomicReference(one); + assertEquals(new Integer(1), a.getAndUpdate(Atomic8Test::addInteger17)); + assertEquals(new Integer(18), a.getAndUpdate(Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.get()); + } + + /** + * AtomicReference updateAndGet updates with supplied function and + * returns result. + */ + public void testReferenceUpdateAndGet() { + AtomicReference a = new AtomicReference(one); + assertEquals(new Integer(18), a.updateAndGet(Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.updateAndGet(Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.get()); + } + + /** + * AtomicReference getAndAccumulate returns previous value and updates + * with supplied function. + */ + public void testReferenceGetAndAccumulate() { + AtomicReference a = new AtomicReference(one); + assertEquals(new Integer(1), a.getAndAccumulate(2, Atomic8Test::sumInteger)); + assertEquals(new Integer(3), a.getAndAccumulate(3, Atomic8Test::sumInteger)); + assertEquals(new Integer(6), a.get()); + } + + /** + * AtomicReference accumulateAndGet updates with supplied function and + * returns result. + */ + public void testReferenceAccumulateAndGet() { + AtomicReference a = new AtomicReference(one); + assertEquals(new Integer(7), a.accumulateAndGet(6, Atomic8Test::sumInteger)); + assertEquals(new Integer(10), a.accumulateAndGet(3, Atomic8Test::sumInteger)); + assertEquals(new Integer(10), a.get()); + } + + /** + * AtomicLongArray getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testLongArrayGetAndUpdate() { + AtomicLongArray a = new AtomicLongArray(1); + a.set(0, 1); + assertEquals(1L, a.getAndUpdate(0, Atomic8Test::addLong17)); + assertEquals(18L, a.getAndUpdate(0, Atomic8Test::addLong17)); + assertEquals(35L, a.get(0)); + } + + /** + * AtomicLongArray updateAndGet updates with supplied function and + * returns result. + */ + public void testLongArrayUpdateAndGet() { + AtomicLongArray a = new AtomicLongArray(1); + a.set(0, 1); + assertEquals(18L, a.updateAndGet(0, Atomic8Test::addLong17)); + assertEquals(35L, a.updateAndGet(0, Atomic8Test::addLong17)); + assertEquals(35L, a.get(0)); + } + + /** + * AtomicLongArray getAndAccumulate returns previous value and updates + * with supplied function. + */ + public void testLongArrayGetAndAccumulate() { + AtomicLongArray a = new AtomicLongArray(1); + a.set(0, 1); + assertEquals(1L, a.getAndAccumulate(0, 2L, Long::sum)); + assertEquals(3L, a.getAndAccumulate(0, 3L, Long::sum)); + assertEquals(6L, a.get(0)); + } + + /** + * AtomicLongArray accumulateAndGet updates with supplied function and + * returns result. + */ + public void testLongArrayAccumulateAndGet() { + AtomicLongArray a = new AtomicLongArray(1); + a.set(0, 1); + assertEquals(7L, a.accumulateAndGet(0, 6L, Long::sum)); + assertEquals(10L, a.accumulateAndGet(0, 3L, Long::sum)); + assertEquals(10L, a.get(0)); + } + + /** + * AtomicIntegerArray getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testIntArrayGetAndUpdate() { + AtomicIntegerArray a = new AtomicIntegerArray(1); + a.set(0, 1); + assertEquals(1, a.getAndUpdate(0, Atomic8Test::addInt17)); + assertEquals(18, a.getAndUpdate(0, Atomic8Test::addInt17)); + assertEquals(35, a.get(0)); + } + + /** + * AtomicIntegerArray updateAndGet updates with supplied function and + * returns result. + */ + public void testIntArrayUpdateAndGet() { + AtomicIntegerArray a = new AtomicIntegerArray(1); + a.set(0, 1); + assertEquals(18, a.updateAndGet(0, Atomic8Test::addInt17)); + assertEquals(35, a.updateAndGet(0, Atomic8Test::addInt17)); + assertEquals(35, a.get(0)); + } + + /** + * AtomicIntegerArray getAndAccumulate returns previous value and updates + * with supplied function. + */ + public void testIntArrayGetAndAccumulate() { + AtomicIntegerArray a = new AtomicIntegerArray(1); + a.set(0, 1); + assertEquals(1, a.getAndAccumulate(0, 2, Integer::sum)); + assertEquals(3, a.getAndAccumulate(0, 3, Integer::sum)); + assertEquals(6, a.get(0)); + } + + /** + * AtomicIntegerArray accumulateAndGet updates with supplied function and + * returns result. + */ + public void testIntArrayAccumulateAndGet() { + AtomicIntegerArray a = new AtomicIntegerArray(1); + a.set(0, 1); + assertEquals(7, a.accumulateAndGet(0, 6, Integer::sum)); + assertEquals(10, a.accumulateAndGet(0, 3, Integer::sum)); + } + + /** + * AtomicReferenceArray getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testReferenceArrayGetAndUpdate() { + AtomicReferenceArray a = new AtomicReferenceArray(1); + a.set(0, one); + assertEquals(new Integer(1), a.getAndUpdate(0, Atomic8Test::addInteger17)); + assertEquals(new Integer(18), a.getAndUpdate(0, Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.get(0)); + } + + /** + * AtomicReferenceArray updateAndGet updates with supplied function and + * returns result. + */ + public void testReferenceArrayUpdateAndGet() { + AtomicReferenceArray a = new AtomicReferenceArray(1); + a.set(0, one); + assertEquals(new Integer(18), a.updateAndGet(0, Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.updateAndGet(0, Atomic8Test::addInteger17)); + } + + /** + * AtomicReferenceArray getAndAccumulate returns previous value and updates + * with supplied function. + */ + public void testReferenceArrayGetAndAccumulate() { + AtomicReferenceArray a = new AtomicReferenceArray(1); + a.set(0, one); + assertEquals(new Integer(1), a.getAndAccumulate(0, 2, Atomic8Test::sumInteger)); + assertEquals(new Integer(3), a.getAndAccumulate(0, 3, Atomic8Test::sumInteger)); + assertEquals(new Integer(6), a.get(0)); + } + + /** + * AtomicReferenceArray accumulateAndGet updates with supplied function and + * returns result. + */ + public void testReferenceArrayAccumulateAndGet() { + AtomicReferenceArray a = new AtomicReferenceArray(1); + a.set(0, one); + assertEquals(new Integer(7), a.accumulateAndGet(0, 6, Atomic8Test::sumInteger)); + assertEquals(new Integer(10), a.accumulateAndGet(0, 3, Atomic8Test::sumInteger)); + } + + /** + * AtomicLongFieldUpdater getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testLongFieldUpdaterGetAndUpdate() { + AtomicLongFieldUpdater a = aLongFieldUpdater(); + a.set(this, 1); + assertEquals(1L, a.getAndUpdate(this, Atomic8Test::addLong17)); + assertEquals(18L, a.getAndUpdate(this, Atomic8Test::addLong17)); + assertEquals(35L, a.get(this)); + assertEquals(35L, aLongField); + } + + /** + * AtomicLongFieldUpdater updateAndGet updates with supplied function and + * returns result. + */ + public void testLongFieldUpdaterUpdateAndGet() { + AtomicLongFieldUpdater a = aLongFieldUpdater(); + a.set(this, 1); + assertEquals(18L, a.updateAndGet(this, Atomic8Test::addLong17)); + assertEquals(35L, a.updateAndGet(this, Atomic8Test::addLong17)); + assertEquals(35L, a.get(this)); + assertEquals(35L, aLongField); + } + + /** + * AtomicLongFieldUpdater getAndAccumulate returns previous value + * and updates with supplied function. + */ + public void testLongFieldUpdaterGetAndAccumulate() { + AtomicLongFieldUpdater a = aLongFieldUpdater(); + a.set(this, 1); + assertEquals(1L, a.getAndAccumulate(this, 2L, Long::sum)); + assertEquals(3L, a.getAndAccumulate(this, 3L, Long::sum)); + assertEquals(6L, a.get(this)); + assertEquals(6L, aLongField); + } + + /** + * AtomicLongFieldUpdater accumulateAndGet updates with supplied + * function and returns result. + */ + public void testLongFieldUpdaterAccumulateAndGet() { + AtomicLongFieldUpdater a = aLongFieldUpdater(); + a.set(this, 1); + assertEquals(7L, a.accumulateAndGet(this, 6L, Long::sum)); + assertEquals(10L, a.accumulateAndGet(this, 3L, Long::sum)); + assertEquals(10L, a.get(this)); + assertEquals(10L, aLongField); + } + + /** + * AtomicIntegerFieldUpdater getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testIntegerFieldUpdaterGetAndUpdate() { + AtomicIntegerFieldUpdater a = anIntFieldUpdater(); + a.set(this, 1); + assertEquals(1, a.getAndUpdate(this, Atomic8Test::addInt17)); + assertEquals(18, a.getAndUpdate(this, Atomic8Test::addInt17)); + assertEquals(35, a.get(this)); + assertEquals(35, anIntField); + } + + /** + * AtomicIntegerFieldUpdater updateAndGet updates with supplied function and + * returns result. + */ + public void testIntegerFieldUpdaterUpdateAndGet() { + AtomicIntegerFieldUpdater a = anIntFieldUpdater(); + a.set(this, 1); + assertEquals(18, a.updateAndGet(this, Atomic8Test::addInt17)); + assertEquals(35, a.updateAndGet(this, Atomic8Test::addInt17)); + assertEquals(35, a.get(this)); + assertEquals(35, anIntField); + } + + /** + * AtomicIntegerFieldUpdater getAndAccumulate returns previous value + * and updates with supplied function. + */ + public void testIntegerFieldUpdaterGetAndAccumulate() { + AtomicIntegerFieldUpdater a = anIntFieldUpdater(); + a.set(this, 1); + assertEquals(1, a.getAndAccumulate(this, 2, Integer::sum)); + assertEquals(3, a.getAndAccumulate(this, 3, Integer::sum)); + assertEquals(6, a.get(this)); + assertEquals(6, anIntField); + } + + /** + * AtomicIntegerFieldUpdater accumulateAndGet updates with supplied + * function and returns result. + */ + public void testIntegerFieldUpdaterAccumulateAndGet() { + AtomicIntegerFieldUpdater a = anIntFieldUpdater(); + a.set(this, 1); + assertEquals(7, a.accumulateAndGet(this, 6, Integer::sum)); + assertEquals(10, a.accumulateAndGet(this, 3, Integer::sum)); + assertEquals(10, a.get(this)); + assertEquals(10, anIntField); + } + + /** + * AtomicReferenceFieldUpdater getAndUpdate returns previous value + * and updates result of supplied function + */ + public void testReferenceFieldUpdaterGetAndUpdate() { + AtomicReferenceFieldUpdater a = anIntegerFieldUpdater(); + a.set(this, one); + assertEquals(new Integer(1), a.getAndUpdate(this, Atomic8Test::addInteger17)); + assertEquals(new Integer(18), a.getAndUpdate(this, Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.get(this)); + assertEquals(new Integer(35), anIntegerField); + } + + /** + * AtomicReferenceFieldUpdater updateAndGet updates with supplied + * function and returns result. + */ + public void testReferenceFieldUpdaterUpdateAndGet() { + AtomicReferenceFieldUpdater a = anIntegerFieldUpdater(); + a.set(this, one); + assertEquals(new Integer(18), a.updateAndGet(this, Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.updateAndGet(this, Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.get(this)); + assertEquals(new Integer(35), anIntegerField); + } + + /** + * AtomicReferenceFieldUpdater returns previous value and updates + * with supplied function. + */ + public void testReferenceFieldUpdaterGetAndAccumulate() { + AtomicReferenceFieldUpdater a = anIntegerFieldUpdater(); + a.set(this, one); + assertEquals(new Integer(1), a.getAndAccumulate(this, 2, Atomic8Test::sumInteger)); + assertEquals(new Integer(3), a.getAndAccumulate(this, 3, Atomic8Test::sumInteger)); + assertEquals(new Integer(6), a.get(this)); + assertEquals(new Integer(6), anIntegerField); + } + + /** + * AtomicReferenceFieldUpdater accumulateAndGet updates with + * supplied function and returns result. + */ + public void testReferenceFieldUpdaterAccumulateAndGet() { + AtomicReferenceFieldUpdater a = anIntegerFieldUpdater(); + a.set(this, one); + assertEquals(new Integer(7), a.accumulateAndGet(this, 6, Atomic8Test::sumInteger)); + assertEquals(new Integer(10), a.accumulateAndGet(this, 3, Atomic8Test::sumInteger)); + assertEquals(new Integer(10), a.get(this)); + assertEquals(new Integer(10), anIntegerField); + } + + /** + * All Atomic getAndUpdate methods throw NullPointerException on + * null function argument + */ + public void testGetAndUpdateNPE() { + Runnable[] throwingActions = { + () -> new AtomicLong().getAndUpdate(null), + () -> new AtomicInteger().getAndUpdate(null), + () -> new AtomicReference().getAndUpdate(null), + () -> new AtomicLongArray(1).getAndUpdate(0, null), + () -> new AtomicIntegerArray(1).getAndUpdate(0, null), + () -> new AtomicReferenceArray(1).getAndUpdate(0, null), + () -> aLongFieldUpdater().getAndUpdate(this, null), + () -> anIntFieldUpdater().getAndUpdate(this, null), + () -> anIntegerFieldUpdater().getAndUpdate(this, null), + ////() -> aLongFieldUpdater().getAndUpdate(null, Atomic8Test::addLong17), + ////() -> anIntFieldUpdater().getAndUpdate(null, Atomic8Test::addInt17), + ////() -> anIntegerFieldUpdater().getAndUpdate(null, Atomic8Test::addInteger17), + }; + assertThrows(NullPointerException.class, throwingActions); + } + + /** + * All Atomic updateAndGet methods throw NullPointerException on null function argument + */ + public void testUpdateAndGetNPE() { + Runnable[] throwingActions = { + () -> new AtomicLong().updateAndGet(null), + () -> new AtomicInteger().updateAndGet(null), + () -> new AtomicReference().updateAndGet(null), + () -> new AtomicLongArray(1).updateAndGet(0, null), + () -> new AtomicIntegerArray(1).updateAndGet(0, null), + () -> new AtomicReferenceArray(1).updateAndGet(0, null), + () -> aLongFieldUpdater().updateAndGet(this, null), + () -> anIntFieldUpdater().updateAndGet(this, null), + () -> anIntegerFieldUpdater().updateAndGet(this, null), + }; + assertThrows(NullPointerException.class, throwingActions); + } + + /** + * All Atomic getAndAccumulate methods throw NullPointerException + * on null function argument + */ + public void testGetAndAccumulateNPE() { + Runnable[] throwingActions = { + () -> new AtomicLong().getAndAccumulate(1L, null), + () -> new AtomicInteger().getAndAccumulate(1, null), + () -> new AtomicReference().getAndAccumulate(one, null), + () -> new AtomicLongArray(1).getAndAccumulate(0, 1L, null), + () -> new AtomicIntegerArray(1).getAndAccumulate(0, 1, null), + () -> new AtomicReferenceArray(1).getAndAccumulate(0, one, null), + () -> aLongFieldUpdater().getAndAccumulate(this, 1L, null), + () -> anIntFieldUpdater().getAndAccumulate(this, 1, null), + () -> anIntegerFieldUpdater().getAndAccumulate(this, one, null), + }; + assertThrows(NullPointerException.class, throwingActions); + } + + /** + * All Atomic accumulateAndGet methods throw NullPointerException + * on null function argument + */ + public void testAccumulateAndGetNPE() { + Runnable[] throwingActions = { + () -> new AtomicLong().accumulateAndGet(1L, null), + () -> new AtomicInteger().accumulateAndGet(1, null), + () -> new AtomicReference().accumulateAndGet(one, null), + () -> new AtomicLongArray(1).accumulateAndGet(0, 1L, null), + () -> new AtomicIntegerArray(1).accumulateAndGet(0, 1, null), + () -> new AtomicReferenceArray(1).accumulateAndGet(0, one, null), + () -> aLongFieldUpdater().accumulateAndGet(this, 1L, null), + () -> anIntFieldUpdater().accumulateAndGet(this, 1, null), + () -> anIntegerFieldUpdater().accumulateAndGet(this, one, null), + }; + assertThrows(NullPointerException.class, throwingActions); + } + +} diff --git a/jsr166-tests/src/test/java/jsr166/AtomicBooleanTest.java b/jsr166-tests/src/test/java/jsr166/AtomicBooleanTest.java index bfe3fc661..6f5decf0e 100644 --- a/jsr166-tests/src/test/java/jsr166/AtomicBooleanTest.java +++ b/jsr166-tests/src/test/java/jsr166/AtomicBooleanTest.java @@ -21,7 +21,7 @@ public class AtomicBooleanTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AtomicBooleanTest.class); // } /** diff --git a/jsr166-tests/src/test/java/jsr166/AtomicIntegerArrayTest.java b/jsr166-tests/src/test/java/jsr166/AtomicIntegerArrayTest.java index 670b9ce9e..c9e32aaba 100644 --- a/jsr166-tests/src/test/java/jsr166/AtomicIntegerArrayTest.java +++ b/jsr166-tests/src/test/java/jsr166/AtomicIntegerArrayTest.java @@ -15,6 +15,7 @@ import junit.framework.TestSuite; public class AtomicIntegerArrayTest extends JSR166TestCase { + // android-note: Removed because the CTS runner does a bad job of // retrying tests that have suite() declarations. // @@ -22,7 +23,7 @@ public class AtomicIntegerArrayTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AtomicIntegerArrayTest.class); // } /** @@ -290,7 +291,7 @@ public void realRun() { assertTrue(v >= 0); if (v != 0) { done = false; - if (aa.compareAndSet(i, v, v-1)) + if (aa.compareAndSet(i, v, v - 1)) ++counts; } } diff --git a/jsr166-tests/src/test/java/jsr166/AtomicIntegerFieldUpdaterTest.java b/jsr166-tests/src/test/java/jsr166/AtomicIntegerFieldUpdaterTest.java index ef75b4633..c8d385672 100644 --- a/jsr166-tests/src/test/java/jsr166/AtomicIntegerFieldUpdaterTest.java +++ b/jsr166-tests/src/test/java/jsr166/AtomicIntegerFieldUpdaterTest.java @@ -15,8 +15,10 @@ public class AtomicIntegerFieldUpdaterTest extends JSR166TestCase { volatile int x = 0; + protected volatile int protectedField; + private volatile int privateField; int w; - long z; + float z; // android-note: Removed because the CTS runner does a bad job of // retrying tests that have suite() declarations. // @@ -24,7 +26,59 @@ public class AtomicIntegerFieldUpdaterTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AtomicIntegerFieldUpdaterTest.class); + // } + + // for testing subclass access + // android-note: Removed because android doesn't restrict reflection access + // static class AtomicIntegerFieldUpdaterTestSubclass extends AtomicIntegerFieldUpdaterTest { + // public void checkPrivateAccess() { + // try { + // AtomicIntegerFieldUpdater a = + // AtomicIntegerFieldUpdater.newUpdater + // (AtomicIntegerFieldUpdaterTest.class, "privateField"); + // shouldThrow(); + // } catch (RuntimeException success) { + // assertNotNull(success.getCause()); + // } + // } + + // public void checkCompareAndSetProtectedSub() { + // AtomicIntegerFieldUpdater a = + // AtomicIntegerFieldUpdater.newUpdater + // (AtomicIntegerFieldUpdaterTest.class, "protectedField"); + // this.protectedField = 1; + // assertTrue(a.compareAndSet(this, 1, 2)); + // assertTrue(a.compareAndSet(this, 2, -4)); + // assertEquals(-4, a.get(this)); + // assertFalse(a.compareAndSet(this, -5, 7)); + // assertEquals(-4, a.get(this)); + // assertTrue(a.compareAndSet(this, -4, 7)); + // assertEquals(7, a.get(this)); + // } + // } + + // static class UnrelatedClass { + // public void checkPackageAccess(AtomicIntegerFieldUpdaterTest obj) { + // obj.x = 72; + // AtomicIntegerFieldUpdater a = + // AtomicIntegerFieldUpdater.newUpdater + // (AtomicIntegerFieldUpdaterTest.class, "x"); + // assertEquals(72, a.get(obj)); + // assertTrue(a.compareAndSet(obj, 72, 73)); + // assertEquals(73, a.get(obj)); + // } + + // public void checkPrivateAccess(AtomicIntegerFieldUpdaterTest obj) { + // try { + // AtomicIntegerFieldUpdater a = + // AtomicIntegerFieldUpdater.newUpdater + // (AtomicIntegerFieldUpdaterTest.class, "privateField"); + // throw new AssertionError("should throw"); + // } catch (RuntimeException success) { + // assertNotNull(success.getCause()); + // } + // } // } AtomicIntegerFieldUpdater updaterFor(String fieldName) { @@ -64,6 +118,26 @@ public void testConstructor3() { } catch (IllegalArgumentException success) {} } + /** + * construction using private field from subclass throws RuntimeException + */ + // android-note: Removed because android doesn't restrict reflection access + // public void testPrivateFieldInSubclass() { + // AtomicIntegerFieldUpdaterTestSubclass s = + // new AtomicIntegerFieldUpdaterTestSubclass(); + // s.checkPrivateAccess(); + // } + + /** + * construction from unrelated class; package access is allowed, + * private access is not + */ + // android-note: Removed because android doesn't restrict reflection access + // public void testUnrelatedClassAccess() { + // new UnrelatedClass().checkPackageAccess(this); + // new UnrelatedClass().checkPrivateAccess(this); + // } + /** * get returns the last value set or assigned */ @@ -108,6 +182,34 @@ public void testCompareAndSet() { assertEquals(7, a.get(this)); } + /** + * compareAndSet succeeds in changing protected field value if + * equal to expected else fails + */ + public void testCompareAndSetProtected() { + AtomicIntegerFieldUpdater a; + a = updaterFor("protectedField"); + protectedField = 1; + assertTrue(a.compareAndSet(this, 1, 2)); + assertTrue(a.compareAndSet(this, 2, -4)); + assertEquals(-4, a.get(this)); + assertFalse(a.compareAndSet(this, -5, 7)); + assertEquals(-4, a.get(this)); + assertTrue(a.compareAndSet(this, -4, 7)); + assertEquals(7, a.get(this)); + } + + /** + * compareAndSet succeeds in changing protected field value if + * equal to expected else fails + */ + // android-note: Removed because android doesn't restrict reflection access + // public void testCompareAndSetProtectedInSubclass() { + // AtomicIntegerFieldUpdaterTestSubclass s = + // new AtomicIntegerFieldUpdaterTestSubclass(); + // s.checkCompareAndSetProtectedSub(); + // } + /** * compareAndSet in one thread enables another waiting for value * to succeed diff --git a/jsr166-tests/src/test/java/jsr166/AtomicIntegerTest.java b/jsr166-tests/src/test/java/jsr166/AtomicIntegerTest.java index cf73810bc..6392c549a 100644 --- a/jsr166-tests/src/test/java/jsr166/AtomicIntegerTest.java +++ b/jsr166-tests/src/test/java/jsr166/AtomicIntegerTest.java @@ -21,7 +21,7 @@ public class AtomicIntegerTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AtomicIntegerTest.class); // } final int[] VALUES = { diff --git a/jsr166-tests/src/test/java/jsr166/AtomicLongArrayTest.java b/jsr166-tests/src/test/java/jsr166/AtomicLongArrayTest.java index 08df01ed8..a60ecfdf7 100644 --- a/jsr166-tests/src/test/java/jsr166/AtomicLongArrayTest.java +++ b/jsr166-tests/src/test/java/jsr166/AtomicLongArrayTest.java @@ -22,7 +22,7 @@ public class AtomicLongArrayTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AtomicLongArrayTest.class); // } /** @@ -290,7 +290,7 @@ public void realRun() { assertTrue(v >= 0); if (v != 0) { done = false; - if (aa.compareAndSet(i, v, v-1)) + if (aa.compareAndSet(i, v, v - 1)) ++counts; } } diff --git a/jsr166-tests/src/test/java/jsr166/AtomicLongFieldUpdaterTest.java b/jsr166-tests/src/test/java/jsr166/AtomicLongFieldUpdaterTest.java index 204f814fa..d46280b88 100644 --- a/jsr166-tests/src/test/java/jsr166/AtomicLongFieldUpdaterTest.java +++ b/jsr166-tests/src/test/java/jsr166/AtomicLongFieldUpdaterTest.java @@ -15,9 +15,10 @@ public class AtomicLongFieldUpdaterTest extends JSR166TestCase { volatile long x = 0; - int z; + protected volatile long protectedField; + private volatile long privateField; long w; - + float z; // android-note: Removed because the CTS runner does a bad job of // retrying tests that have suite() declarations. // @@ -25,7 +26,59 @@ public class AtomicLongFieldUpdaterTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AtomicLongFieldUpdaterTest.class); + // } + + // for testing subclass access + // android-note: Removed because android doesn't restrict reflection access + // static class AtomicLongFieldUpdaterTestSubclass extends AtomicLongFieldUpdaterTest { + // public void checkPrivateAccess() { + // try { + // AtomicLongFieldUpdater a = + // AtomicLongFieldUpdater.newUpdater + // (AtomicLongFieldUpdaterTest.class, "privateField"); + // shouldThrow(); + // } catch (RuntimeException success) { + // assertNotNull(success.getCause()); + // } + // } + + // public void checkCompareAndSetProtectedSub() { + // AtomicLongFieldUpdater a = + // AtomicLongFieldUpdater.newUpdater + // (AtomicLongFieldUpdaterTest.class, "protectedField"); + // this.protectedField = 1; + // assertTrue(a.compareAndSet(this, 1, 2)); + // assertTrue(a.compareAndSet(this, 2, -4)); + // assertEquals(-4, a.get(this)); + // assertFalse(a.compareAndSet(this, -5, 7)); + // assertEquals(-4, a.get(this)); + // assertTrue(a.compareAndSet(this, -4, 7)); + // assertEquals(7, a.get(this)); + // } + // } + + // static class UnrelatedClass { + // public void checkPackageAccess(AtomicLongFieldUpdaterTest obj) { + // obj.x = 72L; + // AtomicLongFieldUpdater a = + // AtomicLongFieldUpdater.newUpdater + // (AtomicLongFieldUpdaterTest.class, "x"); + // assertEquals(72L, a.get(obj)); + // assertTrue(a.compareAndSet(obj, 72L, 73L)); + // assertEquals(73L, a.get(obj)); + // } + + // public void checkPrivateAccess(AtomicLongFieldUpdaterTest obj) { + // try { + // AtomicLongFieldUpdater a = + // AtomicLongFieldUpdater.newUpdater + // (AtomicLongFieldUpdaterTest.class, "privateField"); + // throw new AssertionError("should throw"); + // } catch (RuntimeException success) { + // assertNotNull(success.getCause()); + // } + // } // } AtomicLongFieldUpdater updaterFor(String fieldName) { @@ -65,6 +118,26 @@ public void testConstructor3() { } catch (IllegalArgumentException success) {} } + /** + * construction using private field from subclass throws RuntimeException + */ + // android-note: Removed because android doesn't restrict reflection access + // public void testPrivateFieldInSubclass() { + // AtomicLongFieldUpdaterTestSubclass s = + // new AtomicLongFieldUpdaterTestSubclass(); + // s.checkPrivateAccess(); + // } + + /** + * construction from unrelated class; package access is allowed, + * private access is not + */ + // android-note: Removed because android doesn't restrict reflection access + // public void testUnrelatedClassAccess() { + // new UnrelatedClass().checkPackageAccess(this); + // new UnrelatedClass().checkPrivateAccess(this); + // } + /** * get returns the last value set or assigned */ @@ -109,6 +182,34 @@ public void testCompareAndSet() { assertEquals(7, a.get(this)); } + /** + * compareAndSet succeeds in changing protected field value if + * equal to expected else fails + */ + public void testCompareAndSetProtected() { + AtomicLongFieldUpdater a; + a = updaterFor("protectedField"); + protectedField = 1; + assertTrue(a.compareAndSet(this, 1, 2)); + assertTrue(a.compareAndSet(this, 2, -4)); + assertEquals(-4, a.get(this)); + assertFalse(a.compareAndSet(this, -5, 7)); + assertEquals(-4, a.get(this)); + assertTrue(a.compareAndSet(this, -4, 7)); + assertEquals(7, a.get(this)); + } + + /** + * compareAndSet succeeds in changing protected field value if + * equal to expected else fails + */ + // android-note: Removed because android doesn't restrict reflection access + // public void testCompareAndSetProtectedInSubclass() { + // AtomicLongFieldUpdaterTestSubclass s = + // new AtomicLongFieldUpdaterTestSubclass(); + // s.checkCompareAndSetProtectedSub(); + // } + /** * compareAndSet in one thread enables another waiting for value * to succeed diff --git a/jsr166-tests/src/test/java/jsr166/AtomicLongTest.java b/jsr166-tests/src/test/java/jsr166/AtomicLongTest.java index b9c172224..a8ee7c610 100644 --- a/jsr166-tests/src/test/java/jsr166/AtomicLongTest.java +++ b/jsr166-tests/src/test/java/jsr166/AtomicLongTest.java @@ -21,7 +21,7 @@ public class AtomicLongTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AtomicLongTest.class); // } final long[] VALUES = { diff --git a/jsr166-tests/src/test/java/jsr166/AtomicMarkableReferenceTest.java b/jsr166-tests/src/test/java/jsr166/AtomicMarkableReferenceTest.java index 61b6b1ba2..bd4e8cb10 100644 --- a/jsr166-tests/src/test/java/jsr166/AtomicMarkableReferenceTest.java +++ b/jsr166-tests/src/test/java/jsr166/AtomicMarkableReferenceTest.java @@ -21,7 +21,7 @@ public class AtomicMarkableReferenceTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AtomicMarkableReferenceTest.class); // } /** diff --git a/jsr166-tests/src/test/java/jsr166/AtomicReferenceArrayTest.java b/jsr166-tests/src/test/java/jsr166/AtomicReferenceArrayTest.java index 1df2f9f55..f3aab44db 100644 --- a/jsr166-tests/src/test/java/jsr166/AtomicReferenceArrayTest.java +++ b/jsr166-tests/src/test/java/jsr166/AtomicReferenceArrayTest.java @@ -22,7 +22,7 @@ public class AtomicReferenceArrayTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AtomicReferenceArrayTest.class); // } /** diff --git a/jsr166-tests/src/test/java/jsr166/AtomicReferenceFieldUpdaterTest.java b/jsr166-tests/src/test/java/jsr166/AtomicReferenceFieldUpdaterTest.java index 4b0d946d1..9b2e9a9ad 100644 --- a/jsr166-tests/src/test/java/jsr166/AtomicReferenceFieldUpdaterTest.java +++ b/jsr166-tests/src/test/java/jsr166/AtomicReferenceFieldUpdaterTest.java @@ -15,6 +15,8 @@ public class AtomicReferenceFieldUpdaterTest extends JSR166TestCase { volatile Integer x = null; + protected volatile Integer protectedField; + private volatile Integer privateField; Object z; Integer w; volatile int i; @@ -26,10 +28,62 @@ public class AtomicReferenceFieldUpdaterTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AtomicReferenceFieldUpdaterTest.class); // } - AtomicReferenceFieldUpdater updaterFor(String fieldName) { + // for testing subclass access + // android-note: Removed because android doesn't restrict reflection access + // static class AtomicReferenceFieldUpdaterTestSubclass extends AtomicReferenceFieldUpdaterTest { + // public void checkPrivateAccess() { + // try { + // AtomicReferenceFieldUpdater a = + // AtomicReferenceFieldUpdater.newUpdater + // (AtomicReferenceFieldUpdaterTest.class, Integer.class, "privateField"); + // shouldThrow(); + // } catch (RuntimeException success) { + // assertNotNull(success.getCause()); + // } + // } + + // public void checkCompareAndSetProtectedSub() { + // AtomicReferenceFieldUpdater a = + // AtomicReferenceFieldUpdater.newUpdater + // (AtomicReferenceFieldUpdaterTest.class, Integer.class, "protectedField"); + // this.protectedField = one; + // assertTrue(a.compareAndSet(this, one, two)); + // assertTrue(a.compareAndSet(this, two, m4)); + // assertSame(m4, a.get(this)); + // assertFalse(a.compareAndSet(this, m5, seven)); + // assertFalse(seven == a.get(this)); + // assertTrue(a.compareAndSet(this, m4, seven)); + // assertSame(seven, a.get(this)); + // } + // } + + // static class UnrelatedClass { + // public void checkPackageAccess(AtomicReferenceFieldUpdaterTest obj) { + // obj.x = one; + // AtomicReferenceFieldUpdater a = + // AtomicReferenceFieldUpdater.newUpdater + // (AtomicReferenceFieldUpdaterTest.class, Integer.class, "x"); + // assertSame(one, a.get(obj)); + // assertTrue(a.compareAndSet(obj, one, two)); + // assertSame(two, a.get(obj)); + // } + + // public void checkPrivateAccess(AtomicReferenceFieldUpdaterTest obj) { + // try { + // AtomicReferenceFieldUpdater a = + // AtomicReferenceFieldUpdater.newUpdater + // (AtomicReferenceFieldUpdaterTest.class, Integer.class, "privateField"); + // throw new AssertionError("should throw"); + // } catch (RuntimeException success) { + // assertNotNull(success.getCause()); + // } + // } + // } + + static AtomicReferenceFieldUpdater updaterFor(String fieldName) { return AtomicReferenceFieldUpdater.newUpdater (AtomicReferenceFieldUpdaterTest.class, Integer.class, fieldName); } @@ -76,6 +130,26 @@ public void testConstructor4() { } catch (ClassCastException success) {} } + /** + * construction using private field from subclass throws RuntimeException + */ + // android-note: Removed because android doesn't restrict reflection access + // public void testPrivateFieldInSubclass() { + // AtomicReferenceFieldUpdaterTestSubclass s = + // new AtomicReferenceFieldUpdaterTestSubclass(); + // s.checkPrivateAccess(); + // } + + /** + * construction from unrelated class; package access is allowed, + * private access is not + */ + // android-note: Removed because android doesn't restrict reflection access + // public void testUnrelatedClassAccess() { + // new UnrelatedClass().checkPackageAccess(this); + // new UnrelatedClass().checkPrivateAccess(this); + // } + /** * get returns the last value set or assigned */ diff --git a/jsr166-tests/src/test/java/jsr166/AtomicReferenceTest.java b/jsr166-tests/src/test/java/jsr166/AtomicReferenceTest.java index 457182f28..47289707d 100644 --- a/jsr166-tests/src/test/java/jsr166/AtomicReferenceTest.java +++ b/jsr166-tests/src/test/java/jsr166/AtomicReferenceTest.java @@ -21,7 +21,7 @@ public class AtomicReferenceTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AtomicReferenceTest.class); // } /** diff --git a/jsr166-tests/src/test/java/jsr166/AtomicStampedReferenceTest.java b/jsr166-tests/src/test/java/jsr166/AtomicStampedReferenceTest.java index b3ff06abf..a2e8c7f83 100644 --- a/jsr166-tests/src/test/java/jsr166/AtomicStampedReferenceTest.java +++ b/jsr166-tests/src/test/java/jsr166/AtomicStampedReferenceTest.java @@ -21,7 +21,7 @@ public class AtomicStampedReferenceTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(AtomicStampedReferenceTest.class); // } /** diff --git a/jsr166-tests/src/test/java/jsr166/BlockingQueueTest.java b/jsr166-tests/src/test/java/jsr166/BlockingQueueTest.java index db0f03daf..1a188e161 100644 --- a/jsr166-tests/src/test/java/jsr166/BlockingQueueTest.java +++ b/jsr166-tests/src/test/java/jsr166/BlockingQueueTest.java @@ -39,9 +39,9 @@ public abstract class BlockingQueueTest extends JSR166TestCase { // android-note: Explicitly instantiated. // // public Test testSuite() { - // // TODO: filter the returned tests using the configuration - // // information provided by the subclass via protected methods. - // return new TestSuite(this.getClass()); + // // TODO: filter the returned tests using the configuration + // // information provided by the subclass via protected methods. + // return new TestSuite(this.getClass()); // } //---------------------------------------------------------------- @@ -239,6 +239,8 @@ public void realRun() throws InterruptedException { shouldThrow(); } catch (InterruptedException success) {} assertFalse(Thread.interrupted()); + + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); }}); barrier.await(); @@ -352,20 +354,20 @@ public void testRemoveElement() { assertEquals((pass == 0), q.contains(elts[i])); assertEquals((pass == 0), q.remove(elts[i])); assertFalse(q.contains(elts[i])); - assertTrue(q.contains(elts[i-1])); + assertTrue(q.contains(elts[i - 1])); if (i < size - 1) - assertTrue(q.contains(elts[i+1])); + assertTrue(q.contains(elts[i + 1])); } } if (size > 0) assertTrue(q.contains(elts[0])); - for (int i = size-2; i >= 0; i -= 2) { + for (int i = size - 2; i >= 0; i -= 2) { assertTrue(q.contains(elts[i])); - assertFalse(q.contains(elts[i+1])); + assertFalse(q.contains(elts[i + 1])); assertTrue(q.remove(elts[i])); assertFalse(q.contains(elts[i])); - assertFalse(q.remove(elts[i+1])); - assertFalse(q.contains(elts[i+1])); + assertFalse(q.remove(elts[i + 1])); + assertFalse(q.contains(elts[i + 1])); } checkEmpty(q); } diff --git a/jsr166-tests/src/test/java/jsr166/Collection8Test.java b/jsr166-tests/src/test/java/jsr166/Collection8Test.java new file mode 100644 index 000000000..634182b96 --- /dev/null +++ b/jsr166-tests/src/test/java/jsr166/Collection8Test.java @@ -0,0 +1,104 @@ +/* + * Written by Doug Lea and Martin Buchholz with assistance from + * members of JCP JSR-166 Expert Group and released to the public + * domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package jsr166; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; + +import junit.framework.Test; + +/** + * Contains tests applicable to all jdk8+ Collection implementations. + * An extension of CollectionTest. + */ +public class Collection8Test extends JSR166TestCase { + final CollectionImplementation impl; + + /** Tests are parameterized by a Collection implementation. */ + Collection8Test(CollectionImplementation impl, String methodName) { + super(methodName); + this.impl = impl; + } + + public static Test testSuite(CollectionImplementation impl) { + return parameterizedTestSuite(Collection8Test.class, + CollectionImplementation.class, + impl); + } + + /** + * stream().forEach returns elements in the collection + */ + // TODO(streams): + // public void testForEach() throws Throwable { + // final Collection c = impl.emptyCollection(); + // final AtomicLong count = new AtomicLong(0L); + // final Object x = impl.makeElement(1); + // final Object y = impl.makeElement(2); + // final ArrayList found = new ArrayList(); + // Consumer spy = (o) -> { found.add(o); }; + // c.stream().forEach(spy); + // assertTrue(found.isEmpty()); + + // assertTrue(c.add(x)); + // c.stream().forEach(spy); + // assertEquals(Collections.singletonList(x), found); + // found.clear(); + + // assertTrue(c.add(y)); + // c.stream().forEach(spy); + // assertEquals(2, found.size()); + // assertTrue(found.contains(x)); + // assertTrue(found.contains(y)); + // found.clear(); + + // c.clear(); + // c.stream().forEach(spy); + // assertTrue(found.isEmpty()); + // } + + // public void testForEachConcurrentStressTest() throws Throwable { + // if (!impl.isConcurrent()) return; + // final Collection c = impl.emptyCollection(); + // final long testDurationMillis = timeoutMillis(); + // final AtomicBoolean done = new AtomicBoolean(false); + // final Object elt = impl.makeElement(1); + // final Future f1, f2; + // final ExecutorService pool = Executors.newCachedThreadPool(); + // try (PoolCleaner cleaner = cleaner(pool, done)) { + // final CountDownLatch threadsStarted = new CountDownLatch(2); + // Runnable checkElt = () -> { + // threadsStarted.countDown(); + // while (!done.get()) + // c.stream().forEach((x) -> { assertSame(x, elt); }); }; + // Runnable addRemove = () -> { + // threadsStarted.countDown(); + // while (!done.get()) { + // assertTrue(c.add(elt)); + // assertTrue(c.remove(elt)); + // }}; + // f1 = pool.submit(checkElt); + // f2 = pool.submit(addRemove); + // Thread.sleep(testDurationMillis); + // } + // assertNull(f1.get(0L, MILLISECONDS)); + // assertNull(f2.get(0L, MILLISECONDS)); + // } + + // public void testCollection8DebugFail() { fail(); } +} diff --git a/jsr166-tests/src/test/java/jsr166/CollectionImplementation.java b/jsr166-tests/src/test/java/jsr166/CollectionImplementation.java new file mode 100644 index 000000000..4ba5bda7c --- /dev/null +++ b/jsr166-tests/src/test/java/jsr166/CollectionImplementation.java @@ -0,0 +1,21 @@ +/* + * Written by Doug Lea and Martin Buchholz with assistance from + * members of JCP JSR-166 Expert Group and released to the public + * domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package jsr166; + +import java.util.Collection; + +/** Allows tests to work with different Collection implementations. */ +public interface CollectionImplementation { + /** Returns the Collection class. */ + public Class klazz(); + /** Returns an empty collection. */ + public Collection emptyCollection(); + public Object makeElement(int i); + public boolean isConcurrent(); + public boolean permitsNulls(); +} diff --git a/jsr166-tests/src/test/java/jsr166/CollectionTest.java b/jsr166-tests/src/test/java/jsr166/CollectionTest.java new file mode 100644 index 000000000..44ef66d1e --- /dev/null +++ b/jsr166-tests/src/test/java/jsr166/CollectionTest.java @@ -0,0 +1,43 @@ +/* + * Written by Doug Lea and Martin Buchholz with assistance from + * members of JCP JSR-166 Expert Group and released to the public + * domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package jsr166; + +import java.util.Collection; + +import junit.framework.Test; + +/** + * Contains tests applicable to all Collection implementations. + */ +public class CollectionTest extends JSR166TestCase { + final CollectionImplementation impl; + + /** Tests are parameterized by a Collection implementation. */ + CollectionTest(CollectionImplementation impl, String methodName) { + super(methodName); + this.impl = impl; + } + + public static Test testSuite(CollectionImplementation impl) { + return newTestSuite + (parameterizedTestSuite(CollectionTest.class, + CollectionImplementation.class, + impl), + jdk8ParameterizedTestSuite(CollectionTest.class, + CollectionImplementation.class, + impl)); + } + + /** A test of the CollectionImplementation implementation ! */ + public void testEmptyMeansEmpty() { + assertTrue(impl.emptyCollection().isEmpty()); + assertEquals(0, impl.emptyCollection().size()); + } + + // public void testCollectionDebugFail() { fail(); } +} diff --git a/jsr166-tests/src/test/java/jsr166/CompletableFutureTest.java b/jsr166-tests/src/test/java/jsr166/CompletableFutureTest.java new file mode 100644 index 000000000..1372cc48d --- /dev/null +++ b/jsr166-tests/src/test/java/jsr166/CompletableFutureTest.java @@ -0,0 +1,3959 @@ +/* + * Written by Doug Lea and Martin Buchholz with assistance from + * members of JCP JSR-166 Expert Group and released to the public + * domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package jsr166; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.failedFuture; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +// TODO(streams): +//import java.util.stream.Collectors; +//import java.util.stream.Stream; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestSuite; + +public class CompletableFutureTest extends JSR166TestCase { + + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. + // + // public static void main(String[] args) { + // main(suite(), args); + // } + // public static Test suite() { + // return new TestSuite(CompletableFutureTest.class); + // } + + static class CFException extends RuntimeException {} + + void checkIncomplete(CompletableFuture f) { + assertFalse(f.isDone()); + assertFalse(f.isCancelled()); + assertTrue(f.toString().contains("Not completed")); + try { + assertNull(f.getNow(null)); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + f.get(0L, SECONDS); + shouldThrow(); + } + catch (TimeoutException success) {} + catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedNormally(CompletableFuture f, T value) { + checkTimedGet(f, value); + + try { + assertEquals(value, f.join()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + assertEquals(value, f.getNow(null)); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + assertEquals(value, f.get()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + assertTrue(f.isDone()); + assertFalse(f.isCancelled()); + assertFalse(f.isCompletedExceptionally()); + assertTrue(f.toString().contains("[Completed normally]")); + } + + /** + * Returns the "raw" internal exceptional completion of f, + * without any additional wrapping with CompletionException. + */ + Throwable exceptionalCompletion(CompletableFuture f) { + // handle (and whenComplete) can distinguish between "direct" + // and "wrapped" exceptional completion + return f.handle((U u, Throwable t) -> t).join(); + } + + void checkCompletedExceptionally(CompletableFuture f, + boolean wrapped, + Consumer checker) { + Throwable cause = exceptionalCompletion(f); + if (wrapped) { + assertTrue(cause instanceof CompletionException); + cause = cause.getCause(); + } + checker.accept(cause); + + long startTime = System.nanoTime(); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(cause, success.getCause()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS / 2); + + try { + f.join(); + shouldThrow(); + } catch (CompletionException success) { + assertSame(cause, success.getCause()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + f.getNow(null); + shouldThrow(); + } catch (CompletionException success) { + assertSame(cause, success.getCause()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + f.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(cause, success.getCause()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + assertFalse(f.isCancelled()); + assertTrue(f.isDone()); + assertTrue(f.isCompletedExceptionally()); + assertTrue(f.toString().contains("[Completed exceptionally]")); + } + + void checkCompletedWithWrappedCFException(CompletableFuture f) { + checkCompletedExceptionally(f, true, + (t) -> assertTrue(t instanceof CFException)); + } + + void checkCompletedWithWrappedCancellationException(CompletableFuture f) { + checkCompletedExceptionally(f, true, + (t) -> assertTrue(t instanceof CancellationException)); + } + + void checkCompletedWithTimeoutException(CompletableFuture f) { + checkCompletedExceptionally(f, false, + (t) -> assertTrue(t instanceof TimeoutException)); + } + + void checkCompletedWithWrappedException(CompletableFuture f, + Throwable ex) { + checkCompletedExceptionally(f, true, (t) -> assertSame(t, ex)); + } + + void checkCompletedExceptionally(CompletableFuture f, Throwable ex) { + checkCompletedExceptionally(f, false, (t) -> assertSame(t, ex)); + } + + void checkCancelled(CompletableFuture f) { + long startTime = System.nanoTime(); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS / 2); + + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) {} + try { + f.getNow(null); + shouldThrow(); + } catch (CancellationException success) {} + try { + f.get(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + assertTrue(exceptionalCompletion(f) instanceof CancellationException); + + assertTrue(f.isDone()); + assertTrue(f.isCompletedExceptionally()); + assertTrue(f.isCancelled()); + assertTrue(f.toString().contains("[Completed exceptionally]")); + } + + /** + * A newly constructed CompletableFuture is incomplete, as indicated + * by methods isDone, isCancelled, and getNow + */ + public void testConstructor() { + CompletableFuture f = new CompletableFuture<>(); + checkIncomplete(f); + } + + /** + * complete completes normally, as indicated by methods isDone, + * isCancelled, join, get, and getNow + */ + public void testComplete() { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + checkIncomplete(f); + assertTrue(f.complete(v1)); + assertFalse(f.complete(v1)); + checkCompletedNormally(f, v1); + }} + + /** + * completeExceptionally completes exceptionally, as indicated by + * methods isDone, isCancelled, join, get, and getNow + */ + public void testCompleteExceptionally() { + CompletableFuture f = new CompletableFuture<>(); + CFException ex = new CFException(); + checkIncomplete(f); + f.completeExceptionally(ex); + checkCompletedExceptionally(f, ex); + } + + /** + * cancel completes exceptionally and reports cancelled, as indicated by + * methods isDone, isCancelled, join, get, and getNow + */ + public void testCancel() { + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + { + CompletableFuture f = new CompletableFuture<>(); + checkIncomplete(f); + assertTrue(f.cancel(mayInterruptIfRunning)); + assertTrue(f.cancel(mayInterruptIfRunning)); + assertTrue(f.cancel(!mayInterruptIfRunning)); + checkCancelled(f); + }} + + /** + * obtrudeValue forces completion with given value + */ + public void testObtrudeValue() { + CompletableFuture f = new CompletableFuture<>(); + checkIncomplete(f); + assertTrue(f.complete(one)); + checkCompletedNormally(f, one); + f.obtrudeValue(three); + checkCompletedNormally(f, three); + f.obtrudeValue(two); + checkCompletedNormally(f, two); + f = new CompletableFuture<>(); + f.obtrudeValue(three); + checkCompletedNormally(f, three); + f.obtrudeValue(null); + checkCompletedNormally(f, null); + f = new CompletableFuture<>(); + f.completeExceptionally(new CFException()); + f.obtrudeValue(four); + checkCompletedNormally(f, four); + } + + /** + * obtrudeException forces completion with given exception + */ + public void testObtrudeException() { + for (Integer v1 : new Integer[] { 1, null }) + { + CFException ex; + CompletableFuture f; + + f = new CompletableFuture<>(); + assertTrue(f.complete(v1)); + for (int i = 0; i < 2; i++) { + f.obtrudeException(ex = new CFException()); + checkCompletedExceptionally(f, ex); + } + + f = new CompletableFuture<>(); + for (int i = 0; i < 2; i++) { + f.obtrudeException(ex = new CFException()); + checkCompletedExceptionally(f, ex); + } + + f = new CompletableFuture<>(); + f.completeExceptionally(ex = new CFException()); + f.obtrudeValue(v1); + checkCompletedNormally(f, v1); + f.obtrudeException(ex = new CFException()); + checkCompletedExceptionally(f, ex); + f.completeExceptionally(new CFException()); + checkCompletedExceptionally(f, ex); + assertFalse(f.complete(v1)); + checkCompletedExceptionally(f, ex); + }} + + /** + * getNumberOfDependents returns number of dependent tasks + */ + public void testGetNumberOfDependents() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + assertEquals(0, f.getNumberOfDependents()); + final CompletableFuture g = m.thenRun(f, new Noop(m)); + assertEquals(1, f.getNumberOfDependents()); + assertEquals(0, g.getNumberOfDependents()); + final CompletableFuture h = m.thenRun(f, new Noop(m)); + assertEquals(2, f.getNumberOfDependents()); + assertEquals(0, h.getNumberOfDependents()); + assertTrue(f.complete(v1)); + checkCompletedNormally(g, null); + checkCompletedNormally(h, null); + assertEquals(0, f.getNumberOfDependents()); + assertEquals(0, g.getNumberOfDependents()); + assertEquals(0, h.getNumberOfDependents()); + }} + + /** + * toString indicates current completion state + */ + public void testToString() { + CompletableFuture f; + + f = new CompletableFuture(); + assertTrue(f.toString().contains("[Not completed]")); + + assertTrue(f.complete("foo")); + assertTrue(f.toString().contains("[Completed normally]")); + + f = new CompletableFuture(); + assertTrue(f.completeExceptionally(new IndexOutOfBoundsException())); + assertTrue(f.toString().contains("[Completed exceptionally]")); + + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) { + f = new CompletableFuture(); + assertTrue(f.cancel(mayInterruptIfRunning)); + assertTrue(f.toString().contains("[Completed exceptionally]")); + } + } + + /** + * completedFuture returns a completed CompletableFuture with given value + */ + public void testCompletedFuture() { + CompletableFuture f = CompletableFuture.completedFuture("test"); + checkCompletedNormally(f, "test"); + } + + abstract class CheckedAction { + int invocationCount = 0; + final ExecutionMode m; + CheckedAction(ExecutionMode m) { this.m = m; } + void invoked() { + m.checkExecutionMode(); + assertEquals(0, invocationCount++); + } + void assertNotInvoked() { assertEquals(0, invocationCount); } + void assertInvoked() { assertEquals(1, invocationCount); } + } + + abstract class CheckedIntegerAction extends CheckedAction { + Integer value; + CheckedIntegerAction(ExecutionMode m) { super(m); } + void assertValue(Integer expected) { + assertInvoked(); + assertEquals(expected, value); + } + } + + class IntegerSupplier extends CheckedAction + implements Supplier + { + final Integer value; + IntegerSupplier(ExecutionMode m, Integer value) { + super(m); + this.value = value; + } + public Integer get() { + invoked(); + return value; + } + } + + // A function that handles and produces null values as well. + static Integer inc(Integer x) { + return (x == null) ? null : x + 1; + } + + class NoopConsumer extends CheckedIntegerAction + implements Consumer + { + NoopConsumer(ExecutionMode m) { super(m); } + public void accept(Integer x) { + invoked(); + value = x; + } + } + + class IncFunction extends CheckedIntegerAction + implements Function + { + IncFunction(ExecutionMode m) { super(m); } + public Integer apply(Integer x) { + invoked(); + return value = inc(x); + } + } + + // Choose non-commutative actions for better coverage + // A non-commutative function that handles and produces null values as well. + static Integer subtract(Integer x, Integer y) { + return (x == null && y == null) ? null : + ((x == null) ? 42 : x.intValue()) + - ((y == null) ? 99 : y.intValue()); + } + + class SubtractAction extends CheckedIntegerAction + implements BiConsumer + { + SubtractAction(ExecutionMode m) { super(m); } + public void accept(Integer x, Integer y) { + invoked(); + value = subtract(x, y); + } + } + + class SubtractFunction extends CheckedIntegerAction + implements BiFunction + { + SubtractFunction(ExecutionMode m) { super(m); } + public Integer apply(Integer x, Integer y) { + invoked(); + return value = subtract(x, y); + } + } + + class Noop extends CheckedAction implements Runnable { + Noop(ExecutionMode m) { super(m); } + public void run() { + invoked(); + } + } + + class FailingSupplier extends CheckedAction + implements Supplier + { + FailingSupplier(ExecutionMode m) { super(m); } + public Integer get() { + invoked(); + throw new CFException(); + } + } + + class FailingConsumer extends CheckedIntegerAction + implements Consumer + { + FailingConsumer(ExecutionMode m) { super(m); } + public void accept(Integer x) { + invoked(); + value = x; + throw new CFException(); + } + } + + class FailingBiConsumer extends CheckedIntegerAction + implements BiConsumer + { + FailingBiConsumer(ExecutionMode m) { super(m); } + public void accept(Integer x, Integer y) { + invoked(); + value = subtract(x, y); + throw new CFException(); + } + } + + class FailingFunction extends CheckedIntegerAction + implements Function + { + FailingFunction(ExecutionMode m) { super(m); } + public Integer apply(Integer x) { + invoked(); + value = x; + throw new CFException(); + } + } + + class FailingBiFunction extends CheckedIntegerAction + implements BiFunction + { + FailingBiFunction(ExecutionMode m) { super(m); } + public Integer apply(Integer x, Integer y) { + invoked(); + value = subtract(x, y); + throw new CFException(); + } + } + + class FailingRunnable extends CheckedAction implements Runnable { + FailingRunnable(ExecutionMode m) { super(m); } + public void run() { + invoked(); + throw new CFException(); + } + } + + class CompletableFutureInc extends CheckedIntegerAction + implements Function> + { + CompletableFutureInc(ExecutionMode m) { super(m); } + public CompletableFuture apply(Integer x) { + invoked(); + value = x; + CompletableFuture f = new CompletableFuture<>(); + assertTrue(f.complete(inc(x))); + return f; + } + } + + class FailingCompletableFutureFunction extends CheckedIntegerAction + implements Function> + { + FailingCompletableFutureFunction(ExecutionMode m) { super(m); } + public CompletableFuture apply(Integer x) { + invoked(); + value = x; + throw new CFException(); + } + } + + // Used for explicit executor tests + static final class ThreadExecutor implements Executor { + final AtomicInteger count = new AtomicInteger(0); + static final ThreadGroup tg = new ThreadGroup("ThreadExecutor"); + static boolean startedCurrentThread() { + return Thread.currentThread().getThreadGroup() == tg; + } + + public void execute(Runnable r) { + count.getAndIncrement(); + new Thread(tg, r).start(); + } + } + + static final boolean defaultExecutorIsCommonPool + = ForkJoinPool.getCommonPoolParallelism() > 1; + + /** + * Permits the testing of parallel code for the 3 different + * execution modes without copy/pasting all the test methods. + */ + enum ExecutionMode { + SYNC { + public void checkExecutionMode() { + assertFalse(ThreadExecutor.startedCurrentThread()); + assertNull(ForkJoinTask.getPool()); + } + public CompletableFuture runAsync(Runnable a) { + throw new UnsupportedOperationException(); + } + public CompletableFuture supplyAsync(Supplier a) { + throw new UnsupportedOperationException(); + } + public CompletableFuture thenRun + (CompletableFuture f, Runnable a) { + return f.thenRun(a); + } + public CompletableFuture thenAccept + (CompletableFuture f, Consumer a) { + return f.thenAccept(a); + } + public CompletableFuture thenApply + (CompletableFuture f, Function a) { + return f.thenApply(a); + } + public CompletableFuture thenCompose + (CompletableFuture f, + Function> a) { + return f.thenCompose(a); + } + public CompletableFuture handle + (CompletableFuture f, + BiFunction a) { + return f.handle(a); + } + public CompletableFuture whenComplete + (CompletableFuture f, + BiConsumer a) { + return f.whenComplete(a); + } + public CompletableFuture runAfterBoth + (CompletableFuture f, CompletableFuture g, Runnable a) { + return f.runAfterBoth(g, a); + } + public CompletableFuture thenAcceptBoth + (CompletableFuture f, + CompletionStage g, + BiConsumer a) { + return f.thenAcceptBoth(g, a); + } + public CompletableFuture thenCombine + (CompletableFuture f, + CompletionStage g, + BiFunction a) { + return f.thenCombine(g, a); + } + public CompletableFuture runAfterEither + (CompletableFuture f, + CompletionStage g, + java.lang.Runnable a) { + return f.runAfterEither(g, a); + } + public CompletableFuture acceptEither + (CompletableFuture f, + CompletionStage g, + Consumer a) { + return f.acceptEither(g, a); + } + public CompletableFuture applyToEither + (CompletableFuture f, + CompletionStage g, + Function a) { + return f.applyToEither(g, a); + } + }, + + ASYNC { + public void checkExecutionMode() { + assertEquals(defaultExecutorIsCommonPool, + (ForkJoinPool.commonPool() == ForkJoinTask.getPool())); + } + public CompletableFuture runAsync(Runnable a) { + return CompletableFuture.runAsync(a); + } + public CompletableFuture supplyAsync(Supplier a) { + return CompletableFuture.supplyAsync(a); + } + public CompletableFuture thenRun + (CompletableFuture f, Runnable a) { + return f.thenRunAsync(a); + } + public CompletableFuture thenAccept + (CompletableFuture f, Consumer a) { + return f.thenAcceptAsync(a); + } + public CompletableFuture thenApply + (CompletableFuture f, Function a) { + return f.thenApplyAsync(a); + } + public CompletableFuture thenCompose + (CompletableFuture f, + Function> a) { + return f.thenComposeAsync(a); + } + public CompletableFuture handle + (CompletableFuture f, + BiFunction a) { + return f.handleAsync(a); + } + public CompletableFuture whenComplete + (CompletableFuture f, + BiConsumer a) { + return f.whenCompleteAsync(a); + } + public CompletableFuture runAfterBoth + (CompletableFuture f, CompletableFuture g, Runnable a) { + return f.runAfterBothAsync(g, a); + } + public CompletableFuture thenAcceptBoth + (CompletableFuture f, + CompletionStage g, + BiConsumer a) { + return f.thenAcceptBothAsync(g, a); + } + public CompletableFuture thenCombine + (CompletableFuture f, + CompletionStage g, + BiFunction a) { + return f.thenCombineAsync(g, a); + } + public CompletableFuture runAfterEither + (CompletableFuture f, + CompletionStage g, + java.lang.Runnable a) { + return f.runAfterEitherAsync(g, a); + } + public CompletableFuture acceptEither + (CompletableFuture f, + CompletionStage g, + Consumer a) { + return f.acceptEitherAsync(g, a); + } + public CompletableFuture applyToEither + (CompletableFuture f, + CompletionStage g, + Function a) { + return f.applyToEitherAsync(g, a); + } + }, + + EXECUTOR { + public void checkExecutionMode() { + assertTrue(ThreadExecutor.startedCurrentThread()); + } + public CompletableFuture runAsync(Runnable a) { + return CompletableFuture.runAsync(a, new ThreadExecutor()); + } + public CompletableFuture supplyAsync(Supplier a) { + return CompletableFuture.supplyAsync(a, new ThreadExecutor()); + } + public CompletableFuture thenRun + (CompletableFuture f, Runnable a) { + return f.thenRunAsync(a, new ThreadExecutor()); + } + public CompletableFuture thenAccept + (CompletableFuture f, Consumer a) { + return f.thenAcceptAsync(a, new ThreadExecutor()); + } + public CompletableFuture thenApply + (CompletableFuture f, Function a) { + return f.thenApplyAsync(a, new ThreadExecutor()); + } + public CompletableFuture thenCompose + (CompletableFuture f, + Function> a) { + return f.thenComposeAsync(a, new ThreadExecutor()); + } + public CompletableFuture handle + (CompletableFuture f, + BiFunction a) { + return f.handleAsync(a, new ThreadExecutor()); + } + public CompletableFuture whenComplete + (CompletableFuture f, + BiConsumer a) { + return f.whenCompleteAsync(a, new ThreadExecutor()); + } + public CompletableFuture runAfterBoth + (CompletableFuture f, CompletableFuture g, Runnable a) { + return f.runAfterBothAsync(g, a, new ThreadExecutor()); + } + public CompletableFuture thenAcceptBoth + (CompletableFuture f, + CompletionStage g, + BiConsumer a) { + return f.thenAcceptBothAsync(g, a, new ThreadExecutor()); + } + public CompletableFuture thenCombine + (CompletableFuture f, + CompletionStage g, + BiFunction a) { + return f.thenCombineAsync(g, a, new ThreadExecutor()); + } + public CompletableFuture runAfterEither + (CompletableFuture f, + CompletionStage g, + java.lang.Runnable a) { + return f.runAfterEitherAsync(g, a, new ThreadExecutor()); + } + public CompletableFuture acceptEither + (CompletableFuture f, + CompletionStage g, + Consumer a) { + return f.acceptEitherAsync(g, a, new ThreadExecutor()); + } + public CompletableFuture applyToEither + (CompletableFuture f, + CompletionStage g, + Function a) { + return f.applyToEitherAsync(g, a, new ThreadExecutor()); + } + }; + + public abstract void checkExecutionMode(); + public abstract CompletableFuture runAsync(Runnable a); + public abstract CompletableFuture supplyAsync(Supplier a); + public abstract CompletableFuture thenRun + (CompletableFuture f, Runnable a); + public abstract CompletableFuture thenAccept + (CompletableFuture f, Consumer a); + public abstract CompletableFuture thenApply + (CompletableFuture f, Function a); + public abstract CompletableFuture thenCompose + (CompletableFuture f, + Function> a); + public abstract CompletableFuture handle + (CompletableFuture f, + BiFunction a); + public abstract CompletableFuture whenComplete + (CompletableFuture f, + BiConsumer a); + public abstract CompletableFuture runAfterBoth + (CompletableFuture f, CompletableFuture g, Runnable a); + public abstract CompletableFuture thenAcceptBoth + (CompletableFuture f, + CompletionStage g, + BiConsumer a); + public abstract CompletableFuture thenCombine + (CompletableFuture f, + CompletionStage g, + BiFunction a); + public abstract CompletableFuture runAfterEither + (CompletableFuture f, + CompletionStage g, + java.lang.Runnable a); + public abstract CompletableFuture acceptEither + (CompletableFuture f, + CompletionStage g, + Consumer a); + public abstract CompletableFuture applyToEither + (CompletableFuture f, + CompletionStage g, + Function a); + } + + /** + * exceptionally action is not invoked when source completes + * normally, and source result is propagated + */ + public void testExceptionally_normalCompletion() { + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final AtomicInteger a = new AtomicInteger(0); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) assertTrue(f.complete(v1)); + final CompletableFuture g = f.exceptionally + ((Throwable t) -> { + a.getAndIncrement(); + threadFail("should not be called"); + return null; // unreached + }); + if (createIncomplete) assertTrue(f.complete(v1)); + + checkCompletedNormally(g, v1); + checkCompletedNormally(f, v1); + assertEquals(0, a.get()); + }} + + /** + * exceptionally action completes with function value on source + * exception + */ + public void testExceptionally_exceptionalCompletion() { + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final AtomicInteger a = new AtomicInteger(0); + final CFException ex = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) f.completeExceptionally(ex); + final CompletableFuture g = f.exceptionally + ((Throwable t) -> { + ExecutionMode.SYNC.checkExecutionMode(); + threadAssertSame(t, ex); + a.getAndIncrement(); + return v1; + }); + if (createIncomplete) f.completeExceptionally(ex); + + checkCompletedNormally(g, v1); + assertEquals(1, a.get()); + }} + + /** + * If an "exceptionally action" throws an exception, it completes + * exceptionally with that exception + */ + public void testExceptionally_exceptionalCompletionActionFailed() { + for (boolean createIncomplete : new boolean[] { true, false }) + { + final AtomicInteger a = new AtomicInteger(0); + final CFException ex1 = new CFException(); + final CFException ex2 = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) f.completeExceptionally(ex1); + final CompletableFuture g = f.exceptionally + ((Throwable t) -> { + ExecutionMode.SYNC.checkExecutionMode(); + threadAssertSame(t, ex1); + a.getAndIncrement(); + throw ex2; + }); + if (createIncomplete) f.completeExceptionally(ex1); + + checkCompletedWithWrappedException(g, ex2); + checkCompletedExceptionally(f, ex1); + assertEquals(1, a.get()); + }} + + /** + * whenComplete action executes on normal completion, propagating + * source result. + */ + public void testWhenComplete_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final AtomicInteger a = new AtomicInteger(0); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) assertTrue(f.complete(v1)); + final CompletableFuture g = m.whenComplete + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertSame(result, v1); + threadAssertNull(t); + a.getAndIncrement(); + }); + if (createIncomplete) assertTrue(f.complete(v1)); + + checkCompletedNormally(g, v1); + checkCompletedNormally(f, v1); + assertEquals(1, a.get()); + }} + + /** + * whenComplete action executes on exceptional completion, propagating + * source result. + */ + public void testWhenComplete_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + { + final AtomicInteger a = new AtomicInteger(0); + final CFException ex = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) f.completeExceptionally(ex); + final CompletableFuture g = m.whenComplete + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertNull(result); + threadAssertSame(t, ex); + a.getAndIncrement(); + }); + if (createIncomplete) f.completeExceptionally(ex); + + checkCompletedWithWrappedException(g, ex); + checkCompletedExceptionally(f, ex); + assertEquals(1, a.get()); + }} + + /** + * whenComplete action executes on cancelled source, propagating + * CancellationException. + */ + public void testWhenComplete_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (boolean createIncomplete : new boolean[] { true, false }) + { + final AtomicInteger a = new AtomicInteger(0); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning)); + final CompletableFuture g = m.whenComplete + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertNull(result); + threadAssertTrue(t instanceof CancellationException); + a.getAndIncrement(); + }); + if (createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning)); + + checkCompletedWithWrappedCancellationException(g); + checkCancelled(f); + assertEquals(1, a.get()); + }} + + /** + * If a whenComplete action throws an exception when triggered by + * a normal completion, it completes exceptionally + */ + public void testWhenComplete_sourceCompletedNormallyActionFailed() { + for (boolean createIncomplete : new boolean[] { true, false }) + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final AtomicInteger a = new AtomicInteger(0); + final CFException ex = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) assertTrue(f.complete(v1)); + final CompletableFuture g = m.whenComplete + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertSame(result, v1); + threadAssertNull(t); + a.getAndIncrement(); + throw ex; + }); + if (createIncomplete) assertTrue(f.complete(v1)); + + checkCompletedWithWrappedException(g, ex); + checkCompletedNormally(f, v1); + assertEquals(1, a.get()); + }} + + /** + * If a whenComplete action throws an exception when triggered by + * a source completion that also throws an exception, the source + * exception takes precedence (unlike handle) + */ + public void testWhenComplete_sourceFailedActionFailed() { + for (boolean createIncomplete : new boolean[] { true, false }) + for (ExecutionMode m : ExecutionMode.values()) + { + final AtomicInteger a = new AtomicInteger(0); + final CFException ex1 = new CFException(); + final CFException ex2 = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + + if (!createIncomplete) f.completeExceptionally(ex1); + final CompletableFuture g = m.whenComplete + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertSame(t, ex1); + threadAssertNull(result); + a.getAndIncrement(); + throw ex2; + }); + if (createIncomplete) f.completeExceptionally(ex1); + + checkCompletedWithWrappedException(g, ex1); + checkCompletedExceptionally(f, ex1); + if (testImplementationDetails) { + assertEquals(1, ex1.getSuppressed().length); + assertSame(ex2, ex1.getSuppressed()[0]); + } + assertEquals(1, a.get()); + }} + + /** + * handle action completes normally with function value on normal + * completion of source + */ + public void testHandle_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final AtomicInteger a = new AtomicInteger(0); + if (!createIncomplete) assertTrue(f.complete(v1)); + final CompletableFuture g = m.handle + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertSame(result, v1); + threadAssertNull(t); + a.getAndIncrement(); + return inc(v1); + }); + if (createIncomplete) assertTrue(f.complete(v1)); + + checkCompletedNormally(g, inc(v1)); + checkCompletedNormally(f, v1); + assertEquals(1, a.get()); + }} + + /** + * handle action completes normally with function value on + * exceptional completion of source + */ + public void testHandle_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final AtomicInteger a = new AtomicInteger(0); + final CFException ex = new CFException(); + if (!createIncomplete) f.completeExceptionally(ex); + final CompletableFuture g = m.handle + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertNull(result); + threadAssertSame(t, ex); + a.getAndIncrement(); + return v1; + }); + if (createIncomplete) f.completeExceptionally(ex); + + checkCompletedNormally(g, v1); + checkCompletedExceptionally(f, ex); + assertEquals(1, a.get()); + }} + + /** + * handle action completes normally with function value on + * cancelled source + */ + public void testHandle_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final AtomicInteger a = new AtomicInteger(0); + if (!createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning)); + final CompletableFuture g = m.handle + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertNull(result); + threadAssertTrue(t instanceof CancellationException); + a.getAndIncrement(); + return v1; + }); + if (createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning)); + + checkCompletedNormally(g, v1); + checkCancelled(f); + assertEquals(1, a.get()); + }} + + /** + * If a "handle action" throws an exception when triggered by + * a normal completion, it completes exceptionally + */ + public void testHandle_sourceCompletedNormallyActionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final AtomicInteger a = new AtomicInteger(0); + final CFException ex = new CFException(); + if (!createIncomplete) assertTrue(f.complete(v1)); + final CompletableFuture g = m.handle + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertSame(result, v1); + threadAssertNull(t); + a.getAndIncrement(); + throw ex; + }); + if (createIncomplete) assertTrue(f.complete(v1)); + + checkCompletedWithWrappedException(g, ex); + checkCompletedNormally(f, v1); + assertEquals(1, a.get()); + }} + + /** + * If a "handle action" throws an exception when triggered by + * a source completion that also throws an exception, the action + * exception takes precedence (unlike whenComplete) + */ + public void testHandle_sourceFailedActionFailed() { + for (boolean createIncomplete : new boolean[] { true, false }) + for (ExecutionMode m : ExecutionMode.values()) + { + final AtomicInteger a = new AtomicInteger(0); + final CFException ex1 = new CFException(); + final CFException ex2 = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + + if (!createIncomplete) f.completeExceptionally(ex1); + final CompletableFuture g = m.handle + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertNull(result); + threadAssertSame(ex1, t); + a.getAndIncrement(); + throw ex2; + }); + if (createIncomplete) f.completeExceptionally(ex1); + + checkCompletedWithWrappedException(g, ex2); + checkCompletedExceptionally(f, ex1); + assertEquals(1, a.get()); + }} + + /** + * runAsync completes after running Runnable + */ + public void testRunAsync_normalCompletion() { + ExecutionMode[] executionModes = { + ExecutionMode.ASYNC, + ExecutionMode.EXECUTOR, + }; + for (ExecutionMode m : executionModes) + { + final Noop r = new Noop(m); + final CompletableFuture f = m.runAsync(r); + assertNull(f.join()); + checkCompletedNormally(f, null); + r.assertInvoked(); + }} + + /** + * failing runAsync completes exceptionally after running Runnable + */ + public void testRunAsync_exceptionalCompletion() { + ExecutionMode[] executionModes = { + ExecutionMode.ASYNC, + ExecutionMode.EXECUTOR, + }; + for (ExecutionMode m : executionModes) + { + final FailingRunnable r = new FailingRunnable(m); + final CompletableFuture f = m.runAsync(r); + checkCompletedWithWrappedCFException(f); + r.assertInvoked(); + }} + + /** + * supplyAsync completes with result of supplier + */ + public void testSupplyAsync_normalCompletion() { + ExecutionMode[] executionModes = { + ExecutionMode.ASYNC, + ExecutionMode.EXECUTOR, + }; + for (ExecutionMode m : executionModes) + for (Integer v1 : new Integer[] { 1, null }) + { + final IntegerSupplier r = new IntegerSupplier(m, v1); + final CompletableFuture f = m.supplyAsync(r); + assertSame(v1, f.join()); + checkCompletedNormally(f, v1); + r.assertInvoked(); + }} + + /** + * Failing supplyAsync completes exceptionally + */ + public void testSupplyAsync_exceptionalCompletion() { + ExecutionMode[] executionModes = { + ExecutionMode.ASYNC, + ExecutionMode.EXECUTOR, + }; + for (ExecutionMode m : executionModes) + { + FailingSupplier r = new FailingSupplier(m); + CompletableFuture f = m.supplyAsync(r); + checkCompletedWithWrappedCFException(f); + r.assertInvoked(); + }} + + // seq completion methods + + /** + * thenRun result completes normally after normal completion of source + */ + public void testThenRun_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final Noop[] rs = new Noop[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m); + + final CompletableFuture h0 = m.thenRun(f, rs[0]); + final CompletableFuture h1 = m.runAfterBoth(f, f, rs[1]); + final CompletableFuture h2 = m.runAfterEither(f, f, rs[2]); + checkIncomplete(h0); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(f.complete(v1)); + final CompletableFuture h3 = m.thenRun(f, rs[3]); + final CompletableFuture h4 = m.runAfterBoth(f, f, rs[4]); + final CompletableFuture h5 = m.runAfterEither(f, f, rs[5]); + + checkCompletedNormally(h0, null); + checkCompletedNormally(h1, null); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + checkCompletedNormally(h4, null); + checkCompletedNormally(h5, null); + checkCompletedNormally(f, v1); + for (Noop r : rs) r.assertInvoked(); + }} + + /** + * thenRun result completes exceptionally after exceptional + * completion of source + */ + public void testThenRun_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + { + final CFException ex = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + final Noop[] rs = new Noop[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m); + + final CompletableFuture h0 = m.thenRun(f, rs[0]); + final CompletableFuture h1 = m.runAfterBoth(f, f, rs[1]); + final CompletableFuture h2 = m.runAfterEither(f, f, rs[2]); + checkIncomplete(h0); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(f.completeExceptionally(ex)); + final CompletableFuture h3 = m.thenRun(f, rs[3]); + final CompletableFuture h4 = m.runAfterBoth(f, f, rs[4]); + final CompletableFuture h5 = m.runAfterEither(f, f, rs[5]); + + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + checkCompletedWithWrappedException(h4, ex); + checkCompletedWithWrappedException(h5, ex); + checkCompletedExceptionally(f, ex); + for (Noop r : rs) r.assertNotInvoked(); + }} + + /** + * thenRun result completes exceptionally if source cancelled + */ + public void testThenRun_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + { + final CompletableFuture f = new CompletableFuture<>(); + final Noop[] rs = new Noop[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m); + + final CompletableFuture h0 = m.thenRun(f, rs[0]); + final CompletableFuture h1 = m.runAfterBoth(f, f, rs[1]); + final CompletableFuture h2 = m.runAfterEither(f, f, rs[2]); + checkIncomplete(h0); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(f.cancel(mayInterruptIfRunning)); + final CompletableFuture h3 = m.thenRun(f, rs[3]); + final CompletableFuture h4 = m.runAfterBoth(f, f, rs[4]); + final CompletableFuture h5 = m.runAfterEither(f, f, rs[5]); + + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + checkCompletedWithWrappedCancellationException(h4); + checkCompletedWithWrappedCancellationException(h5); + checkCancelled(f); + for (Noop r : rs) r.assertNotInvoked(); + }} + + /** + * thenRun result completes exceptionally if action does + */ + public void testThenRun_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final FailingRunnable[] rs = new FailingRunnable[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new FailingRunnable(m); + + final CompletableFuture h0 = m.thenRun(f, rs[0]); + final CompletableFuture h1 = m.runAfterBoth(f, f, rs[1]); + final CompletableFuture h2 = m.runAfterEither(f, f, rs[2]); + assertTrue(f.complete(v1)); + final CompletableFuture h3 = m.thenRun(f, rs[3]); + final CompletableFuture h4 = m.runAfterBoth(f, f, rs[4]); + final CompletableFuture h5 = m.runAfterEither(f, f, rs[5]); + + checkCompletedWithWrappedCFException(h0); + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + checkCompletedWithWrappedCFException(h4); + checkCompletedWithWrappedCFException(h5); + checkCompletedNormally(f, v1); + }} + + /** + * thenApply result completes normally after normal completion of source + */ + public void testThenApply_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final IncFunction[] rs = new IncFunction[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.thenApply(f, rs[0]); + final CompletableFuture h1 = m.applyToEither(f, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + assertTrue(f.complete(v1)); + final CompletableFuture h2 = m.thenApply(f, rs[2]); + final CompletableFuture h3 = m.applyToEither(f, f, rs[3]); + + checkCompletedNormally(h0, inc(v1)); + checkCompletedNormally(h1, inc(v1)); + checkCompletedNormally(h2, inc(v1)); + checkCompletedNormally(h3, inc(v1)); + checkCompletedNormally(f, v1); + for (IncFunction r : rs) r.assertValue(inc(v1)); + }} + + /** + * thenApply result completes exceptionally after exceptional + * completion of source + */ + public void testThenApply_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + { + final CFException ex = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + final IncFunction[] rs = new IncFunction[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.thenApply(f, rs[0]); + final CompletableFuture h1 = m.applyToEither(f, f, rs[1]); + assertTrue(f.completeExceptionally(ex)); + final CompletableFuture h2 = m.thenApply(f, rs[2]); + final CompletableFuture h3 = m.applyToEither(f, f, rs[3]); + + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + checkCompletedExceptionally(f, ex); + for (IncFunction r : rs) r.assertNotInvoked(); + }} + + /** + * thenApply result completes exceptionally if source cancelled + */ + public void testThenApply_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + { + final CompletableFuture f = new CompletableFuture<>(); + final IncFunction[] rs = new IncFunction[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.thenApply(f, rs[0]); + final CompletableFuture h1 = m.applyToEither(f, f, rs[1]); + assertTrue(f.cancel(mayInterruptIfRunning)); + final CompletableFuture h2 = m.thenApply(f, rs[2]); + final CompletableFuture h3 = m.applyToEither(f, f, rs[3]); + + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + checkCancelled(f); + for (IncFunction r : rs) r.assertNotInvoked(); + }} + + /** + * thenApply result completes exceptionally if action does + */ + public void testThenApply_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final FailingFunction[] rs = new FailingFunction[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new FailingFunction(m); + + final CompletableFuture h0 = m.thenApply(f, rs[0]); + final CompletableFuture h1 = m.applyToEither(f, f, rs[1]); + assertTrue(f.complete(v1)); + final CompletableFuture h2 = m.thenApply(f, rs[2]); + final CompletableFuture h3 = m.applyToEither(f, f, rs[3]); + + checkCompletedWithWrappedCFException(h0); + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + checkCompletedNormally(f, v1); + }} + + /** + * thenAccept result completes normally after normal completion of source + */ + public void testThenAccept_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final NoopConsumer[] rs = new NoopConsumer[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m); + + final CompletableFuture h0 = m.thenAccept(f, rs[0]); + final CompletableFuture h1 = m.acceptEither(f, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + assertTrue(f.complete(v1)); + final CompletableFuture h2 = m.thenAccept(f, rs[2]); + final CompletableFuture h3 = m.acceptEither(f, f, rs[3]); + + checkCompletedNormally(h0, null); + checkCompletedNormally(h1, null); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + checkCompletedNormally(f, v1); + for (NoopConsumer r : rs) r.assertValue(v1); + }} + + /** + * thenAccept result completes exceptionally after exceptional + * completion of source + */ + public void testThenAccept_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + { + final CFException ex = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + final NoopConsumer[] rs = new NoopConsumer[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m); + + final CompletableFuture h0 = m.thenAccept(f, rs[0]); + final CompletableFuture h1 = m.acceptEither(f, f, rs[1]); + assertTrue(f.completeExceptionally(ex)); + final CompletableFuture h2 = m.thenAccept(f, rs[2]); + final CompletableFuture h3 = m.acceptEither(f, f, rs[3]); + + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + checkCompletedExceptionally(f, ex); + for (NoopConsumer r : rs) r.assertNotInvoked(); + }} + + /** + * thenAccept result completes exceptionally if source cancelled + */ + public void testThenAccept_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + { + final CompletableFuture f = new CompletableFuture<>(); + final NoopConsumer[] rs = new NoopConsumer[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m); + + final CompletableFuture h0 = m.thenAccept(f, rs[0]); + final CompletableFuture h1 = m.acceptEither(f, f, rs[1]); + assertTrue(f.cancel(mayInterruptIfRunning)); + final CompletableFuture h2 = m.thenAccept(f, rs[2]); + final CompletableFuture h3 = m.acceptEither(f, f, rs[3]); + + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + checkCancelled(f); + for (NoopConsumer r : rs) r.assertNotInvoked(); + }} + + /** + * thenAccept result completes exceptionally if action does + */ + public void testThenAccept_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final FailingConsumer[] rs = new FailingConsumer[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new FailingConsumer(m); + + final CompletableFuture h0 = m.thenAccept(f, rs[0]); + final CompletableFuture h1 = m.acceptEither(f, f, rs[1]); + assertTrue(f.complete(v1)); + final CompletableFuture h2 = m.thenAccept(f, rs[2]); + final CompletableFuture h3 = m.acceptEither(f, f, rs[3]); + + checkCompletedWithWrappedCFException(h0); + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + checkCompletedNormally(f, v1); + }} + + /** + * thenCombine result completes normally after normal completion + * of sources + */ + public void testThenCombine_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final SubtractFunction[] rs = new SubtractFunction[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new SubtractFunction(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Integer w1 = fFirst ? v1 : v2; + final Integer w2 = !fFirst ? v1 : v2; + + final CompletableFuture h0 = m.thenCombine(f, g, rs[0]); + final CompletableFuture h1 = m.thenCombine(fst, fst, rs[1]); + assertTrue(fst.complete(w1)); + final CompletableFuture h2 = m.thenCombine(f, g, rs[2]); + final CompletableFuture h3 = m.thenCombine(fst, fst, rs[3]); + checkIncomplete(h0); rs[0].assertNotInvoked(); + checkIncomplete(h2); rs[2].assertNotInvoked(); + checkCompletedNormally(h1, subtract(w1, w1)); + checkCompletedNormally(h3, subtract(w1, w1)); + rs[1].assertValue(subtract(w1, w1)); + rs[3].assertValue(subtract(w1, w1)); + assertTrue(snd.complete(w2)); + final CompletableFuture h4 = m.thenCombine(f, g, rs[4]); + + checkCompletedNormally(h0, subtract(v1, v2)); + checkCompletedNormally(h2, subtract(v1, v2)); + checkCompletedNormally(h4, subtract(v1, v2)); + rs[0].assertValue(subtract(v1, v2)); + rs[2].assertValue(subtract(v1, v2)); + rs[4].assertValue(subtract(v1, v2)); + + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * thenCombine result completes exceptionally after exceptional + * completion of either source + */ + public void testThenCombine_exceptionalCompletion() throws Throwable { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (boolean failFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final SubtractFunction r1 = new SubtractFunction(m); + final SubtractFunction r2 = new SubtractFunction(m); + final SubtractFunction r3 = new SubtractFunction(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Callable complete1 = failFirst ? + () -> fst.completeExceptionally(ex) : + () -> fst.complete(v1); + final Callable complete2 = failFirst ? + () -> snd.complete(v1) : + () -> snd.completeExceptionally(ex); + + final CompletableFuture h1 = m.thenCombine(f, g, r1); + assertTrue(complete1.call()); + final CompletableFuture h2 = m.thenCombine(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(complete2.call()); + final CompletableFuture h3 = m.thenCombine(f, g, r3); + + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + r3.assertNotInvoked(); + checkCompletedNormally(failFirst ? snd : fst, v1); + checkCompletedExceptionally(failFirst ? fst : snd, ex); + }} + + /** + * thenCombine result completes exceptionally if either source cancelled + */ + public void testThenCombine_sourceCancelled() throws Throwable { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (boolean fFirst : new boolean[] { true, false }) + for (boolean failFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final SubtractFunction r1 = new SubtractFunction(m); + final SubtractFunction r2 = new SubtractFunction(m); + final SubtractFunction r3 = new SubtractFunction(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Callable complete1 = failFirst ? + () -> fst.cancel(mayInterruptIfRunning) : + () -> fst.complete(v1); + final Callable complete2 = failFirst ? + () -> snd.complete(v1) : + () -> snd.cancel(mayInterruptIfRunning); + + final CompletableFuture h1 = m.thenCombine(f, g, r1); + assertTrue(complete1.call()); + final CompletableFuture h2 = m.thenCombine(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(complete2.call()); + final CompletableFuture h3 = m.thenCombine(f, g, r3); + + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + r3.assertNotInvoked(); + checkCompletedNormally(failFirst ? snd : fst, v1); + checkCancelled(failFirst ? fst : snd); + }} + + /** + * thenCombine result completes exceptionally if action does + */ + public void testThenCombine_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final FailingBiFunction r1 = new FailingBiFunction(m); + final FailingBiFunction r2 = new FailingBiFunction(m); + final FailingBiFunction r3 = new FailingBiFunction(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Integer w1 = fFirst ? v1 : v2; + final Integer w2 = !fFirst ? v1 : v2; + + final CompletableFuture h1 = m.thenCombine(f, g, r1); + assertTrue(fst.complete(w1)); + final CompletableFuture h2 = m.thenCombine(f, g, r2); + assertTrue(snd.complete(w2)); + final CompletableFuture h3 = m.thenCombine(f, g, r3); + + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + r1.assertInvoked(); + r2.assertInvoked(); + r3.assertInvoked(); + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * thenAcceptBoth result completes normally after normal + * completion of sources + */ + public void testThenAcceptBoth_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final SubtractAction r1 = new SubtractAction(m); + final SubtractAction r2 = new SubtractAction(m); + final SubtractAction r3 = new SubtractAction(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Integer w1 = fFirst ? v1 : v2; + final Integer w2 = !fFirst ? v1 : v2; + + final CompletableFuture h1 = m.thenAcceptBoth(f, g, r1); + assertTrue(fst.complete(w1)); + final CompletableFuture h2 = m.thenAcceptBoth(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + assertTrue(snd.complete(w2)); + final CompletableFuture h3 = m.thenAcceptBoth(f, g, r3); + + checkCompletedNormally(h1, null); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + r1.assertValue(subtract(v1, v2)); + r2.assertValue(subtract(v1, v2)); + r3.assertValue(subtract(v1, v2)); + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * thenAcceptBoth result completes exceptionally after exceptional + * completion of either source + */ + public void testThenAcceptBoth_exceptionalCompletion() throws Throwable { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (boolean failFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final SubtractAction r1 = new SubtractAction(m); + final SubtractAction r2 = new SubtractAction(m); + final SubtractAction r3 = new SubtractAction(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Callable complete1 = failFirst ? + () -> fst.completeExceptionally(ex) : + () -> fst.complete(v1); + final Callable complete2 = failFirst ? + () -> snd.complete(v1) : + () -> snd.completeExceptionally(ex); + + final CompletableFuture h1 = m.thenAcceptBoth(f, g, r1); + assertTrue(complete1.call()); + final CompletableFuture h2 = m.thenAcceptBoth(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(complete2.call()); + final CompletableFuture h3 = m.thenAcceptBoth(f, g, r3); + + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + r3.assertNotInvoked(); + checkCompletedNormally(failFirst ? snd : fst, v1); + checkCompletedExceptionally(failFirst ? fst : snd, ex); + }} + + /** + * thenAcceptBoth result completes exceptionally if either source cancelled + */ + public void testThenAcceptBoth_sourceCancelled() throws Throwable { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (boolean fFirst : new boolean[] { true, false }) + for (boolean failFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final SubtractAction r1 = new SubtractAction(m); + final SubtractAction r2 = new SubtractAction(m); + final SubtractAction r3 = new SubtractAction(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Callable complete1 = failFirst ? + () -> fst.cancel(mayInterruptIfRunning) : + () -> fst.complete(v1); + final Callable complete2 = failFirst ? + () -> snd.complete(v1) : + () -> snd.cancel(mayInterruptIfRunning); + + final CompletableFuture h1 = m.thenAcceptBoth(f, g, r1); + assertTrue(complete1.call()); + final CompletableFuture h2 = m.thenAcceptBoth(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(complete2.call()); + final CompletableFuture h3 = m.thenAcceptBoth(f, g, r3); + + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + r3.assertNotInvoked(); + checkCompletedNormally(failFirst ? snd : fst, v1); + checkCancelled(failFirst ? fst : snd); + }} + + /** + * thenAcceptBoth result completes exceptionally if action does + */ + public void testThenAcceptBoth_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final FailingBiConsumer r1 = new FailingBiConsumer(m); + final FailingBiConsumer r2 = new FailingBiConsumer(m); + final FailingBiConsumer r3 = new FailingBiConsumer(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Integer w1 = fFirst ? v1 : v2; + final Integer w2 = !fFirst ? v1 : v2; + + final CompletableFuture h1 = m.thenAcceptBoth(f, g, r1); + assertTrue(fst.complete(w1)); + final CompletableFuture h2 = m.thenAcceptBoth(f, g, r2); + assertTrue(snd.complete(w2)); + final CompletableFuture h3 = m.thenAcceptBoth(f, g, r3); + + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + r1.assertInvoked(); + r2.assertInvoked(); + r3.assertInvoked(); + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * runAfterBoth result completes normally after normal + * completion of sources + */ + public void testRunAfterBoth_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final Noop r1 = new Noop(m); + final Noop r2 = new Noop(m); + final Noop r3 = new Noop(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Integer w1 = fFirst ? v1 : v2; + final Integer w2 = !fFirst ? v1 : v2; + + final CompletableFuture h1 = m.runAfterBoth(f, g, r1); + assertTrue(fst.complete(w1)); + final CompletableFuture h2 = m.runAfterBoth(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + assertTrue(snd.complete(w2)); + final CompletableFuture h3 = m.runAfterBoth(f, g, r3); + + checkCompletedNormally(h1, null); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + r1.assertInvoked(); + r2.assertInvoked(); + r3.assertInvoked(); + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * runAfterBoth result completes exceptionally after exceptional + * completion of either source + */ + public void testRunAfterBoth_exceptionalCompletion() throws Throwable { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (boolean failFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final Noop r1 = new Noop(m); + final Noop r2 = new Noop(m); + final Noop r3 = new Noop(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Callable complete1 = failFirst ? + () -> fst.completeExceptionally(ex) : + () -> fst.complete(v1); + final Callable complete2 = failFirst ? + () -> snd.complete(v1) : + () -> snd.completeExceptionally(ex); + + final CompletableFuture h1 = m.runAfterBoth(f, g, r1); + assertTrue(complete1.call()); + final CompletableFuture h2 = m.runAfterBoth(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(complete2.call()); + final CompletableFuture h3 = m.runAfterBoth(f, g, r3); + + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + r3.assertNotInvoked(); + checkCompletedNormally(failFirst ? snd : fst, v1); + checkCompletedExceptionally(failFirst ? fst : snd, ex); + }} + + /** + * runAfterBoth result completes exceptionally if either source cancelled + */ + public void testRunAfterBoth_sourceCancelled() throws Throwable { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (boolean fFirst : new boolean[] { true, false }) + for (boolean failFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final Noop r1 = new Noop(m); + final Noop r2 = new Noop(m); + final Noop r3 = new Noop(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Callable complete1 = failFirst ? + () -> fst.cancel(mayInterruptIfRunning) : + () -> fst.complete(v1); + final Callable complete2 = failFirst ? + () -> snd.complete(v1) : + () -> snd.cancel(mayInterruptIfRunning); + + final CompletableFuture h1 = m.runAfterBoth(f, g, r1); + assertTrue(complete1.call()); + final CompletableFuture h2 = m.runAfterBoth(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(complete2.call()); + final CompletableFuture h3 = m.runAfterBoth(f, g, r3); + + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + r3.assertNotInvoked(); + checkCompletedNormally(failFirst ? snd : fst, v1); + checkCancelled(failFirst ? fst : snd); + }} + + /** + * runAfterBoth result completes exceptionally if action does + */ + public void testRunAfterBoth_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final FailingRunnable r1 = new FailingRunnable(m); + final FailingRunnable r2 = new FailingRunnable(m); + final FailingRunnable r3 = new FailingRunnable(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Integer w1 = fFirst ? v1 : v2; + final Integer w2 = !fFirst ? v1 : v2; + + final CompletableFuture h1 = m.runAfterBoth(f, g, r1); + assertTrue(fst.complete(w1)); + final CompletableFuture h2 = m.runAfterBoth(f, g, r2); + assertTrue(snd.complete(w2)); + final CompletableFuture h3 = m.runAfterBoth(f, g, r3); + + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + r1.assertInvoked(); + r2.assertInvoked(); + r3.assertInvoked(); + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * applyToEither result completes normally after normal completion + * of either source + */ + public void testApplyToEither_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final IncFunction[] rs = new IncFunction[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.applyToEither(f, g, rs[0]); + final CompletableFuture h1 = m.applyToEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.complete(v1); + checkCompletedNormally(h0, inc(v1)); + checkCompletedNormally(h1, inc(v1)); + final CompletableFuture h2 = m.applyToEither(f, g, rs[2]); + final CompletableFuture h3 = m.applyToEither(g, f, rs[3]); + checkCompletedNormally(h2, inc(v1)); + checkCompletedNormally(h3, inc(v1)); + g.complete(v2); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.applyToEither(f, g, rs[4]); + final CompletableFuture h5 = m.applyToEither(g, f, rs[5]); + rs[4].assertValue(h4.join()); + rs[5].assertValue(h5.join()); + assertTrue(Objects.equals(inc(v1), h4.join()) || + Objects.equals(inc(v2), h4.join())); + assertTrue(Objects.equals(inc(v1), h5.join()) || + Objects.equals(inc(v2), h5.join())); + + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + checkCompletedNormally(h0, inc(v1)); + checkCompletedNormally(h1, inc(v1)); + checkCompletedNormally(h2, inc(v1)); + checkCompletedNormally(h3, inc(v1)); + for (int i = 0; i < 4; i++) rs[i].assertValue(inc(v1)); + }} + + /** + * applyToEither result completes exceptionally after exceptional + * completion of either source + */ + public void testApplyToEither_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final IncFunction[] rs = new IncFunction[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.applyToEither(f, g, rs[0]); + final CompletableFuture h1 = m.applyToEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.completeExceptionally(ex); + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + final CompletableFuture h2 = m.applyToEither(f, g, rs[2]); + final CompletableFuture h3 = m.applyToEither(g, f, rs[3]); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + g.complete(v1); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.applyToEither(f, g, rs[4]); + final CompletableFuture h5 = m.applyToEither(g, f, rs[5]); + try { + assertEquals(inc(v1), h4.join()); + rs[4].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h4, ex); + rs[4].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h5.join()); + rs[5].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h5, ex); + rs[5].assertNotInvoked(); + } + + checkCompletedExceptionally(f, ex); + checkCompletedNormally(g, v1); + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + checkCompletedWithWrappedException(h4, ex); + for (int i = 0; i < 4; i++) rs[i].assertNotInvoked(); + }} + + public void testApplyToEither_exceptionalCompletion2() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final IncFunction[] rs = new IncFunction[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.applyToEither(f, g, rs[0]); + final CompletableFuture h1 = m.applyToEither(g, f, rs[1]); + assertTrue(fFirst ? f.complete(v1) : g.completeExceptionally(ex)); + assertTrue(!fFirst ? f.complete(v1) : g.completeExceptionally(ex)); + final CompletableFuture h2 = m.applyToEither(f, g, rs[2]); + final CompletableFuture h3 = m.applyToEither(g, f, rs[3]); + + // unspecified behavior - both source completions available + try { + assertEquals(inc(v1), h0.join()); + rs[0].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h0, ex); + rs[0].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h1.join()); + rs[1].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h1, ex); + rs[1].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h2.join()); + rs[2].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h2, ex); + rs[2].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h3.join()); + rs[3].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h3, ex); + rs[3].assertNotInvoked(); + } + + checkCompletedNormally(f, v1); + checkCompletedExceptionally(g, ex); + }} + + /** + * applyToEither result completes exceptionally if either source cancelled + */ + public void testApplyToEither_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final IncFunction[] rs = new IncFunction[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.applyToEither(f, g, rs[0]); + final CompletableFuture h1 = m.applyToEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.cancel(mayInterruptIfRunning); + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + final CompletableFuture h2 = m.applyToEither(f, g, rs[2]); + final CompletableFuture h3 = m.applyToEither(g, f, rs[3]); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + g.complete(v1); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.applyToEither(f, g, rs[4]); + final CompletableFuture h5 = m.applyToEither(g, f, rs[5]); + try { + assertEquals(inc(v1), h4.join()); + rs[4].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h4); + rs[4].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h5.join()); + rs[5].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h5); + rs[5].assertNotInvoked(); + } + + checkCancelled(f); + checkCompletedNormally(g, v1); + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + for (int i = 0; i < 4; i++) rs[i].assertNotInvoked(); + }} + + public void testApplyToEither_sourceCancelled2() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final IncFunction[] rs = new IncFunction[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.applyToEither(f, g, rs[0]); + final CompletableFuture h1 = m.applyToEither(g, f, rs[1]); + assertTrue(fFirst ? f.complete(v1) : g.cancel(mayInterruptIfRunning)); + assertTrue(!fFirst ? f.complete(v1) : g.cancel(mayInterruptIfRunning)); + final CompletableFuture h2 = m.applyToEither(f, g, rs[2]); + final CompletableFuture h3 = m.applyToEither(g, f, rs[3]); + + // unspecified behavior - both source completions available + try { + assertEquals(inc(v1), h0.join()); + rs[0].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h0); + rs[0].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h1.join()); + rs[1].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h1); + rs[1].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h2.join()); + rs[2].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h2); + rs[2].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h3.join()); + rs[3].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h3); + rs[3].assertNotInvoked(); + } + + checkCompletedNormally(f, v1); + checkCancelled(g); + }} + + /** + * applyToEither result completes exceptionally if action does + */ + public void testApplyToEither_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final FailingFunction[] rs = new FailingFunction[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new FailingFunction(m); + + final CompletableFuture h0 = m.applyToEither(f, g, rs[0]); + final CompletableFuture h1 = m.applyToEither(g, f, rs[1]); + f.complete(v1); + final CompletableFuture h2 = m.applyToEither(f, g, rs[2]); + final CompletableFuture h3 = m.applyToEither(g, f, rs[3]); + checkCompletedWithWrappedCFException(h0); + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + for (int i = 0; i < 4; i++) rs[i].assertValue(v1); + + g.complete(v2); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.applyToEither(f, g, rs[4]); + final CompletableFuture h5 = m.applyToEither(g, f, rs[5]); + + checkCompletedWithWrappedCFException(h4); + assertTrue(Objects.equals(v1, rs[4].value) || + Objects.equals(v2, rs[4].value)); + checkCompletedWithWrappedCFException(h5); + assertTrue(Objects.equals(v1, rs[5].value) || + Objects.equals(v2, rs[5].value)); + + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * acceptEither result completes normally after normal completion + * of either source + */ + public void testAcceptEither_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final NoopConsumer[] rs = new NoopConsumer[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m); + + final CompletableFuture h0 = m.acceptEither(f, g, rs[0]); + final CompletableFuture h1 = m.acceptEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.complete(v1); + checkCompletedNormally(h0, null); + checkCompletedNormally(h1, null); + rs[0].assertValue(v1); + rs[1].assertValue(v1); + final CompletableFuture h2 = m.acceptEither(f, g, rs[2]); + final CompletableFuture h3 = m.acceptEither(g, f, rs[3]); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + rs[2].assertValue(v1); + rs[3].assertValue(v1); + g.complete(v2); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.acceptEither(f, g, rs[4]); + final CompletableFuture h5 = m.acceptEither(g, f, rs[5]); + checkCompletedNormally(h4, null); + checkCompletedNormally(h5, null); + assertTrue(Objects.equals(v1, rs[4].value) || + Objects.equals(v2, rs[4].value)); + assertTrue(Objects.equals(v1, rs[5].value) || + Objects.equals(v2, rs[5].value)); + + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + checkCompletedNormally(h0, null); + checkCompletedNormally(h1, null); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + for (int i = 0; i < 4; i++) rs[i].assertValue(v1); + }} + + /** + * acceptEither result completes exceptionally after exceptional + * completion of either source + */ + public void testAcceptEither_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final NoopConsumer[] rs = new NoopConsumer[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m); + + final CompletableFuture h0 = m.acceptEither(f, g, rs[0]); + final CompletableFuture h1 = m.acceptEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.completeExceptionally(ex); + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + final CompletableFuture h2 = m.acceptEither(f, g, rs[2]); + final CompletableFuture h3 = m.acceptEither(g, f, rs[3]); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + + g.complete(v1); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.acceptEither(f, g, rs[4]); + final CompletableFuture h5 = m.acceptEither(g, f, rs[5]); + try { + assertNull(h4.join()); + rs[4].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h4, ex); + rs[4].assertNotInvoked(); + } + try { + assertNull(h5.join()); + rs[5].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h5, ex); + rs[5].assertNotInvoked(); + } + + checkCompletedExceptionally(f, ex); + checkCompletedNormally(g, v1); + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + checkCompletedWithWrappedException(h4, ex); + for (int i = 0; i < 4; i++) rs[i].assertNotInvoked(); + }} + + public void testAcceptEither_exceptionalCompletion2() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final NoopConsumer[] rs = new NoopConsumer[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m); + + final CompletableFuture h0 = m.acceptEither(f, g, rs[0]); + final CompletableFuture h1 = m.acceptEither(g, f, rs[1]); + assertTrue(fFirst ? f.complete(v1) : g.completeExceptionally(ex)); + assertTrue(!fFirst ? f.complete(v1) : g.completeExceptionally(ex)); + final CompletableFuture h2 = m.acceptEither(f, g, rs[2]); + final CompletableFuture h3 = m.acceptEither(g, f, rs[3]); + + // unspecified behavior - both source completions available + try { + assertEquals(null, h0.join()); + rs[0].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h0, ex); + rs[0].assertNotInvoked(); + } + try { + assertEquals(null, h1.join()); + rs[1].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h1, ex); + rs[1].assertNotInvoked(); + } + try { + assertEquals(null, h2.join()); + rs[2].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h2, ex); + rs[2].assertNotInvoked(); + } + try { + assertEquals(null, h3.join()); + rs[3].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h3, ex); + rs[3].assertNotInvoked(); + } + + checkCompletedNormally(f, v1); + checkCompletedExceptionally(g, ex); + }} + + /** + * acceptEither result completes exceptionally if either source cancelled + */ + public void testAcceptEither_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final NoopConsumer[] rs = new NoopConsumer[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m); + + final CompletableFuture h0 = m.acceptEither(f, g, rs[0]); + final CompletableFuture h1 = m.acceptEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.cancel(mayInterruptIfRunning); + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + final CompletableFuture h2 = m.acceptEither(f, g, rs[2]); + final CompletableFuture h3 = m.acceptEither(g, f, rs[3]); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + + g.complete(v1); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.acceptEither(f, g, rs[4]); + final CompletableFuture h5 = m.acceptEither(g, f, rs[5]); + try { + assertNull(h4.join()); + rs[4].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h4); + rs[4].assertNotInvoked(); + } + try { + assertNull(h5.join()); + rs[5].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h5); + rs[5].assertNotInvoked(); + } + + checkCancelled(f); + checkCompletedNormally(g, v1); + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + for (int i = 0; i < 4; i++) rs[i].assertNotInvoked(); + }} + + /** + * acceptEither result completes exceptionally if action does + */ + public void testAcceptEither_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final FailingConsumer[] rs = new FailingConsumer[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new FailingConsumer(m); + + final CompletableFuture h0 = m.acceptEither(f, g, rs[0]); + final CompletableFuture h1 = m.acceptEither(g, f, rs[1]); + f.complete(v1); + final CompletableFuture h2 = m.acceptEither(f, g, rs[2]); + final CompletableFuture h3 = m.acceptEither(g, f, rs[3]); + checkCompletedWithWrappedCFException(h0); + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + for (int i = 0; i < 4; i++) rs[i].assertValue(v1); + + g.complete(v2); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.acceptEither(f, g, rs[4]); + final CompletableFuture h5 = m.acceptEither(g, f, rs[5]); + + checkCompletedWithWrappedCFException(h4); + assertTrue(Objects.equals(v1, rs[4].value) || + Objects.equals(v2, rs[4].value)); + checkCompletedWithWrappedCFException(h5); + assertTrue(Objects.equals(v1, rs[5].value) || + Objects.equals(v2, rs[5].value)); + + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * runAfterEither result completes normally after normal completion + * of either source + */ + public void testRunAfterEither_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final Noop[] rs = new Noop[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m); + + final CompletableFuture h0 = m.runAfterEither(f, g, rs[0]); + final CompletableFuture h1 = m.runAfterEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.complete(v1); + checkCompletedNormally(h0, null); + checkCompletedNormally(h1, null); + rs[0].assertInvoked(); + rs[1].assertInvoked(); + final CompletableFuture h2 = m.runAfterEither(f, g, rs[2]); + final CompletableFuture h3 = m.runAfterEither(g, f, rs[3]); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + rs[2].assertInvoked(); + rs[3].assertInvoked(); + + g.complete(v2); + + final CompletableFuture h4 = m.runAfterEither(f, g, rs[4]); + final CompletableFuture h5 = m.runAfterEither(g, f, rs[5]); + + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + checkCompletedNormally(h0, null); + checkCompletedNormally(h1, null); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + checkCompletedNormally(h4, null); + checkCompletedNormally(h5, null); + for (int i = 0; i < 6; i++) rs[i].assertInvoked(); + }} + + /** + * runAfterEither result completes exceptionally after exceptional + * completion of either source + */ + public void testRunAfterEither_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final Noop[] rs = new Noop[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m); + + final CompletableFuture h0 = m.runAfterEither(f, g, rs[0]); + final CompletableFuture h1 = m.runAfterEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + assertTrue(f.completeExceptionally(ex)); + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + final CompletableFuture h2 = m.runAfterEither(f, g, rs[2]); + final CompletableFuture h3 = m.runAfterEither(g, f, rs[3]); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + + assertTrue(g.complete(v1)); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.runAfterEither(f, g, rs[4]); + final CompletableFuture h5 = m.runAfterEither(g, f, rs[5]); + try { + assertNull(h4.join()); + rs[4].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h4, ex); + rs[4].assertNotInvoked(); + } + try { + assertNull(h5.join()); + rs[5].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h5, ex); + rs[5].assertNotInvoked(); + } + + checkCompletedExceptionally(f, ex); + checkCompletedNormally(g, v1); + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + checkCompletedWithWrappedException(h4, ex); + for (int i = 0; i < 4; i++) rs[i].assertNotInvoked(); + }} + + public void testRunAfterEither_exceptionalCompletion2() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final Noop[] rs = new Noop[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m); + + final CompletableFuture h0 = m.runAfterEither(f, g, rs[0]); + final CompletableFuture h1 = m.runAfterEither(g, f, rs[1]); + assertTrue( fFirst ? f.complete(v1) : g.completeExceptionally(ex)); + assertTrue(!fFirst ? f.complete(v1) : g.completeExceptionally(ex)); + final CompletableFuture h2 = m.runAfterEither(f, g, rs[2]); + final CompletableFuture h3 = m.runAfterEither(g, f, rs[3]); + + // unspecified behavior - both source completions available + try { + assertEquals(null, h0.join()); + rs[0].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h0, ex); + rs[0].assertNotInvoked(); + } + try { + assertEquals(null, h1.join()); + rs[1].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h1, ex); + rs[1].assertNotInvoked(); + } + try { + assertEquals(null, h2.join()); + rs[2].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h2, ex); + rs[2].assertNotInvoked(); + } + try { + assertEquals(null, h3.join()); + rs[3].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h3, ex); + rs[3].assertNotInvoked(); + } + + checkCompletedNormally(f, v1); + checkCompletedExceptionally(g, ex); + }} + + /** + * runAfterEither result completes exceptionally if either source cancelled + */ + public void testRunAfterEither_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final Noop[] rs = new Noop[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m); + + final CompletableFuture h0 = m.runAfterEither(f, g, rs[0]); + final CompletableFuture h1 = m.runAfterEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.cancel(mayInterruptIfRunning); + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + final CompletableFuture h2 = m.runAfterEither(f, g, rs[2]); + final CompletableFuture h3 = m.runAfterEither(g, f, rs[3]); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + + assertTrue(g.complete(v1)); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.runAfterEither(f, g, rs[4]); + final CompletableFuture h5 = m.runAfterEither(g, f, rs[5]); + try { + assertNull(h4.join()); + rs[4].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h4); + rs[4].assertNotInvoked(); + } + try { + assertNull(h5.join()); + rs[5].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h5); + rs[5].assertNotInvoked(); + } + + checkCancelled(f); + checkCompletedNormally(g, v1); + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + for (int i = 0; i < 4; i++) rs[i].assertNotInvoked(); + }} + + /** + * runAfterEither result completes exceptionally if action does + */ + public void testRunAfterEither_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final FailingRunnable[] rs = new FailingRunnable[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new FailingRunnable(m); + + final CompletableFuture h0 = m.runAfterEither(f, g, rs[0]); + final CompletableFuture h1 = m.runAfterEither(g, f, rs[1]); + assertTrue(f.complete(v1)); + final CompletableFuture h2 = m.runAfterEither(f, g, rs[2]); + final CompletableFuture h3 = m.runAfterEither(g, f, rs[3]); + checkCompletedWithWrappedCFException(h0); + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + for (int i = 0; i < 4; i++) rs[i].assertInvoked(); + assertTrue(g.complete(v2)); + final CompletableFuture h4 = m.runAfterEither(f, g, rs[4]); + final CompletableFuture h5 = m.runAfterEither(g, f, rs[5]); + checkCompletedWithWrappedCFException(h4); + checkCompletedWithWrappedCFException(h5); + + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + for (int i = 0; i < 6; i++) rs[i].assertInvoked(); + }} + + /** + * thenCompose result completes normally after normal completion of source + */ + public void testThenCompose_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFutureInc r = new CompletableFutureInc(m); + if (!createIncomplete) assertTrue(f.complete(v1)); + final CompletableFuture g = m.thenCompose(f, r); + if (createIncomplete) assertTrue(f.complete(v1)); + + checkCompletedNormally(g, inc(v1)); + checkCompletedNormally(f, v1); + r.assertValue(v1); + }} + + /** + * thenCompose result completes exceptionally after exceptional + * completion of source + */ + public void testThenCompose_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + { + final CFException ex = new CFException(); + final CompletableFutureInc r = new CompletableFutureInc(m); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) f.completeExceptionally(ex); + final CompletableFuture g = m.thenCompose(f, r); + if (createIncomplete) f.completeExceptionally(ex); + + checkCompletedWithWrappedException(g, ex); + checkCompletedExceptionally(f, ex); + r.assertNotInvoked(); + }} + + /** + * thenCompose result completes exceptionally if action does + */ + public void testThenCompose_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final FailingCompletableFutureFunction r + = new FailingCompletableFutureFunction(m); + if (!createIncomplete) assertTrue(f.complete(v1)); + final CompletableFuture g = m.thenCompose(f, r); + if (createIncomplete) assertTrue(f.complete(v1)); + + checkCompletedWithWrappedCFException(g); + checkCompletedNormally(f, v1); + }} + + /** + * thenCompose result completes exceptionally if source cancelled + */ + public void testThenCompose_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFutureInc r = new CompletableFutureInc(m); + if (!createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning)); + final CompletableFuture g = m.thenCompose(f, r); + if (createIncomplete) { + checkIncomplete(g); + assertTrue(f.cancel(mayInterruptIfRunning)); + } + + checkCompletedWithWrappedCancellationException(g); + checkCancelled(f); + }} + + /** + * thenCompose result completes exceptionally if the result of the action does + */ + public void testThenCompose_actionReturnsFailingFuture() { + for (ExecutionMode m : ExecutionMode.values()) + for (int order = 0; order < 6; order++) + for (Integer v1 : new Integer[] { 1, null }) + { + final CFException ex = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CompletableFuture h; + // Test all permutations of orders + switch (order) { + case 0: + assertTrue(f.complete(v1)); + assertTrue(g.completeExceptionally(ex)); + h = m.thenCompose(f, (x -> g)); + break; + case 1: + assertTrue(f.complete(v1)); + h = m.thenCompose(f, (x -> g)); + assertTrue(g.completeExceptionally(ex)); + break; + case 2: + assertTrue(g.completeExceptionally(ex)); + assertTrue(f.complete(v1)); + h = m.thenCompose(f, (x -> g)); + break; + case 3: + assertTrue(g.completeExceptionally(ex)); + h = m.thenCompose(f, (x -> g)); + assertTrue(f.complete(v1)); + break; + case 4: + h = m.thenCompose(f, (x -> g)); + assertTrue(f.complete(v1)); + assertTrue(g.completeExceptionally(ex)); + break; + case 5: + h = m.thenCompose(f, (x -> g)); + assertTrue(f.complete(v1)); + assertTrue(g.completeExceptionally(ex)); + break; + default: throw new AssertionError(); + } + + checkCompletedExceptionally(g, ex); + checkCompletedWithWrappedException(h, ex); + checkCompletedNormally(f, v1); + }} + + // other static methods + + /** + * allOf(no component futures) returns a future completed normally + * with the value null + */ + public void testAllOf_empty() throws Exception { + CompletableFuture f = CompletableFuture.allOf(); + checkCompletedNormally(f, null); + } + + /** + * allOf returns a future completed normally with the value null + * when all components complete normally + */ + public void testAllOf_normal() throws Exception { + for (int k = 1; k < 10; k++) { + CompletableFuture[] fs + = (CompletableFuture[]) new CompletableFuture[k]; + for (int i = 0; i < k; i++) + fs[i] = new CompletableFuture<>(); + CompletableFuture f = CompletableFuture.allOf(fs); + for (int i = 0; i < k; i++) { + checkIncomplete(f); + checkIncomplete(CompletableFuture.allOf(fs)); + fs[i].complete(one); + } + checkCompletedNormally(f, null); + checkCompletedNormally(CompletableFuture.allOf(fs), null); + } + } + + public void testAllOf_backwards() throws Exception { + for (int k = 1; k < 10; k++) { + CompletableFuture[] fs + = (CompletableFuture[]) new CompletableFuture[k]; + for (int i = 0; i < k; i++) + fs[i] = new CompletableFuture<>(); + CompletableFuture f = CompletableFuture.allOf(fs); + for (int i = k - 1; i >= 0; i--) { + checkIncomplete(f); + checkIncomplete(CompletableFuture.allOf(fs)); + fs[i].complete(one); + } + checkCompletedNormally(f, null); + checkCompletedNormally(CompletableFuture.allOf(fs), null); + } + } + + public void testAllOf_exceptional() throws Exception { + for (int k = 1; k < 10; k++) { + CompletableFuture[] fs + = (CompletableFuture[]) new CompletableFuture[k]; + CFException ex = new CFException(); + for (int i = 0; i < k; i++) + fs[i] = new CompletableFuture<>(); + CompletableFuture f = CompletableFuture.allOf(fs); + for (int i = 0; i < k; i++) { + checkIncomplete(f); + checkIncomplete(CompletableFuture.allOf(fs)); + if (i != k / 2) { + fs[i].complete(i); + checkCompletedNormally(fs[i], i); + } else { + fs[i].completeExceptionally(ex); + checkCompletedExceptionally(fs[i], ex); + } + } + checkCompletedWithWrappedException(f, ex); + checkCompletedWithWrappedException(CompletableFuture.allOf(fs), ex); + } + } + + /** + * anyOf(no component futures) returns an incomplete future + */ + public void testAnyOf_empty() throws Exception { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = CompletableFuture.anyOf(); + checkIncomplete(f); + + f.complete(v1); + checkCompletedNormally(f, v1); + }} + + /** + * anyOf returns a future completed normally with a value when + * a component future does + */ + public void testAnyOf_normal() throws Exception { + for (int k = 0; k < 10; k++) { + CompletableFuture[] fs = new CompletableFuture[k]; + for (int i = 0; i < k; i++) + fs[i] = new CompletableFuture<>(); + CompletableFuture f = CompletableFuture.anyOf(fs); + checkIncomplete(f); + for (int i = 0; i < k; i++) { + fs[i].complete(i); + checkCompletedNormally(f, 0); + int x = (int) CompletableFuture.anyOf(fs).join(); + assertTrue(0 <= x && x <= i); + } + } + } + public void testAnyOf_normal_backwards() throws Exception { + for (int k = 0; k < 10; k++) { + CompletableFuture[] fs = new CompletableFuture[k]; + for (int i = 0; i < k; i++) + fs[i] = new CompletableFuture<>(); + CompletableFuture f = CompletableFuture.anyOf(fs); + checkIncomplete(f); + for (int i = k - 1; i >= 0; i--) { + fs[i].complete(i); + checkCompletedNormally(f, k - 1); + int x = (int) CompletableFuture.anyOf(fs).join(); + assertTrue(i <= x && x <= k - 1); + } + } + } + + /** + * anyOf result completes exceptionally when any component does. + */ + public void testAnyOf_exceptional() throws Exception { + for (int k = 0; k < 10; k++) { + CompletableFuture[] fs = new CompletableFuture[k]; + CFException[] exs = new CFException[k]; + for (int i = 0; i < k; i++) { + fs[i] = new CompletableFuture<>(); + exs[i] = new CFException(); + } + CompletableFuture f = CompletableFuture.anyOf(fs); + checkIncomplete(f); + for (int i = 0; i < k; i++) { + fs[i].completeExceptionally(exs[i]); + checkCompletedWithWrappedException(f, exs[0]); + checkCompletedWithWrappedCFException(CompletableFuture.anyOf(fs)); + } + } + } + + public void testAnyOf_exceptional_backwards() throws Exception { + for (int k = 0; k < 10; k++) { + CompletableFuture[] fs = new CompletableFuture[k]; + CFException[] exs = new CFException[k]; + for (int i = 0; i < k; i++) { + fs[i] = new CompletableFuture<>(); + exs[i] = new CFException(); + } + CompletableFuture f = CompletableFuture.anyOf(fs); + checkIncomplete(f); + for (int i = k - 1; i >= 0; i--) { + fs[i].completeExceptionally(exs[i]); + checkCompletedWithWrappedException(f, exs[k - 1]); + checkCompletedWithWrappedCFException(CompletableFuture.anyOf(fs)); + } + } + } + + /** + * Completion methods throw NullPointerException with null arguments + */ + public void testNPE() { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture g = new CompletableFuture<>(); + CompletableFuture nullFuture = (CompletableFuture)null; + ThreadExecutor exec = new ThreadExecutor(); + + Runnable[] throwingActions = { + () -> CompletableFuture.supplyAsync(null), + () -> CompletableFuture.supplyAsync(null, exec), + () -> CompletableFuture.supplyAsync(new IntegerSupplier(ExecutionMode.SYNC, 42), null), + + () -> CompletableFuture.runAsync(null), + () -> CompletableFuture.runAsync(null, exec), + () -> CompletableFuture.runAsync(() -> {}, null), + + () -> f.completeExceptionally(null), + + () -> f.thenApply(null), + () -> f.thenApplyAsync(null), + () -> f.thenApplyAsync((x) -> x, null), + () -> f.thenApplyAsync(null, exec), + + () -> f.thenAccept(null), + () -> f.thenAcceptAsync(null), + () -> f.thenAcceptAsync((x) -> {} , null), + () -> f.thenAcceptAsync(null, exec), + + () -> f.thenRun(null), + () -> f.thenRunAsync(null), + () -> f.thenRunAsync(() -> {} , null), + () -> f.thenRunAsync(null, exec), + + () -> f.thenCombine(g, null), + () -> f.thenCombineAsync(g, null), + () -> f.thenCombineAsync(g, null, exec), + () -> f.thenCombine(nullFuture, (x, y) -> x), + () -> f.thenCombineAsync(nullFuture, (x, y) -> x), + () -> f.thenCombineAsync(nullFuture, (x, y) -> x, exec), + () -> f.thenCombineAsync(g, (x, y) -> x, null), + + () -> f.thenAcceptBoth(g, null), + () -> f.thenAcceptBothAsync(g, null), + () -> f.thenAcceptBothAsync(g, null, exec), + () -> f.thenAcceptBoth(nullFuture, (x, y) -> {}), + () -> f.thenAcceptBothAsync(nullFuture, (x, y) -> {}), + () -> f.thenAcceptBothAsync(nullFuture, (x, y) -> {}, exec), + () -> f.thenAcceptBothAsync(g, (x, y) -> {}, null), + + () -> f.runAfterBoth(g, null), + () -> f.runAfterBothAsync(g, null), + () -> f.runAfterBothAsync(g, null, exec), + () -> f.runAfterBoth(nullFuture, () -> {}), + () -> f.runAfterBothAsync(nullFuture, () -> {}), + () -> f.runAfterBothAsync(nullFuture, () -> {}, exec), + () -> f.runAfterBothAsync(g, () -> {}, null), + + () -> f.applyToEither(g, null), + () -> f.applyToEitherAsync(g, null), + () -> f.applyToEitherAsync(g, null, exec), + () -> f.applyToEither(nullFuture, (x) -> x), + () -> f.applyToEitherAsync(nullFuture, (x) -> x), + () -> f.applyToEitherAsync(nullFuture, (x) -> x, exec), + () -> f.applyToEitherAsync(g, (x) -> x, null), + + () -> f.acceptEither(g, null), + () -> f.acceptEitherAsync(g, null), + () -> f.acceptEitherAsync(g, null, exec), + () -> f.acceptEither(nullFuture, (x) -> {}), + () -> f.acceptEitherAsync(nullFuture, (x) -> {}), + () -> f.acceptEitherAsync(nullFuture, (x) -> {}, exec), + () -> f.acceptEitherAsync(g, (x) -> {}, null), + + () -> f.runAfterEither(g, null), + () -> f.runAfterEitherAsync(g, null), + () -> f.runAfterEitherAsync(g, null, exec), + () -> f.runAfterEither(nullFuture, () -> {}), + () -> f.runAfterEitherAsync(nullFuture, () -> {}), + () -> f.runAfterEitherAsync(nullFuture, () -> {}, exec), + () -> f.runAfterEitherAsync(g, () -> {}, null), + + () -> f.thenCompose(null), + () -> f.thenComposeAsync(null), + () -> f.thenComposeAsync(new CompletableFutureInc(ExecutionMode.EXECUTOR), null), + () -> f.thenComposeAsync(null, exec), + + () -> f.exceptionally(null), + + () -> f.handle(null), + + () -> CompletableFuture.allOf((CompletableFuture)null), + () -> CompletableFuture.allOf((CompletableFuture[])null), + () -> CompletableFuture.allOf(f, null), + () -> CompletableFuture.allOf(null, f), + + () -> CompletableFuture.anyOf((CompletableFuture)null), + () -> CompletableFuture.anyOf((CompletableFuture[])null), + () -> CompletableFuture.anyOf(f, null), + () -> CompletableFuture.anyOf(null, f), + + () -> f.obtrudeException(null), + + () -> CompletableFuture.delayedExecutor(1L, SECONDS, null), + () -> CompletableFuture.delayedExecutor(1L, null, new ThreadExecutor()), + () -> CompletableFuture.delayedExecutor(1L, null), + + () -> f.orTimeout(1L, null), + () -> f.completeOnTimeout(42, 1L, null), + + () -> CompletableFuture.failedFuture(null), + () -> CompletableFuture.failedStage(null), + }; + + assertThrows(NullPointerException.class, throwingActions); + assertEquals(0, exec.count.get()); + } + + /** + * toCompletableFuture returns this CompletableFuture. + */ + public void testToCompletableFuture() { + CompletableFuture f = new CompletableFuture<>(); + assertSame(f, f.toCompletableFuture()); + } + + // jdk9 + + /** + * newIncompleteFuture returns an incomplete CompletableFuture + */ + public void testNewIncompleteFuture() { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture g = f.newIncompleteFuture(); + checkIncomplete(f); + checkIncomplete(g); + f.complete(v1); + checkCompletedNormally(f, v1); + checkIncomplete(g); + g.complete(v1); + checkCompletedNormally(g, v1); + assertSame(g.getClass(), CompletableFuture.class); + }} + + /** + * completedStage returns a completed CompletionStage + */ + public void testCompletedStage() { + AtomicInteger x = new AtomicInteger(0); + AtomicReference r = new AtomicReference(); + CompletionStage f = CompletableFuture.completedStage(1); + f.whenComplete((v, e) -> {if (e != null) r.set(e); else x.set(v);}); + assertEquals(x.get(), 1); + assertNull(r.get()); + } + + /** + * defaultExecutor by default returns the commonPool if + * it supports more than one thread. + */ + public void testDefaultExecutor() { + CompletableFuture f = new CompletableFuture<>(); + Executor e = f.defaultExecutor(); + Executor c = ForkJoinPool.commonPool(); + if (ForkJoinPool.getCommonPoolParallelism() > 1) + assertSame(e, c); + else + assertNotSame(e, c); + } + + /** + * failedFuture returns a CompletableFuture completed + * exceptionally with the given Exception + */ + public void testFailedFuture() { + CFException ex = new CFException(); + CompletableFuture f = CompletableFuture.failedFuture(ex); + checkCompletedExceptionally(f, ex); + } + + /** + * failedFuture(null) throws NPE + */ + public void testFailedFuture_null() { + try { + CompletableFuture f = CompletableFuture.failedFuture(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * copy returns a CompletableFuture that is completed normally, + * with the same value, when source is. + */ + public void testCopy() { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture g = f.copy(); + checkIncomplete(f); + checkIncomplete(g); + f.complete(1); + checkCompletedNormally(f, 1); + checkCompletedNormally(g, 1); + } + + /** + * copy returns a CompletableFuture that is completed exceptionally + * when source is. + */ + public void testCopy2() { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture g = f.copy(); + checkIncomplete(f); + checkIncomplete(g); + CFException ex = new CFException(); + f.completeExceptionally(ex); + checkCompletedExceptionally(f, ex); + checkCompletedWithWrappedException(g, ex); + } + + /** + * minimalCompletionStage returns a CompletableFuture that is + * completed normally, with the same value, when source is. + */ + public void testMinimalCompletionStage() { + CompletableFuture f = new CompletableFuture<>(); + CompletionStage g = f.minimalCompletionStage(); + AtomicInteger x = new AtomicInteger(0); + AtomicReference r = new AtomicReference(); + checkIncomplete(f); + g.whenComplete((v, e) -> {if (e != null) r.set(e); else x.set(v);}); + f.complete(1); + checkCompletedNormally(f, 1); + assertEquals(x.get(), 1); + assertNull(r.get()); + } + + /** + * minimalCompletionStage returns a CompletableFuture that is + * completed exceptionally when source is. + */ + public void testMinimalCompletionStage2() { + CompletableFuture f = new CompletableFuture<>(); + CompletionStage g = f.minimalCompletionStage(); + AtomicInteger x = new AtomicInteger(0); + AtomicReference r = new AtomicReference(); + g.whenComplete((v, e) -> {if (e != null) r.set(e); else x.set(v);}); + checkIncomplete(f); + CFException ex = new CFException(); + f.completeExceptionally(ex); + checkCompletedExceptionally(f, ex); + assertEquals(x.get(), 0); + assertEquals(r.get().getCause(), ex); + } + + /** + * failedStage returns a CompletionStage completed + * exceptionally with the given Exception + */ + public void testFailedStage() { + CFException ex = new CFException(); + CompletionStage f = CompletableFuture.failedStage(ex); + AtomicInteger x = new AtomicInteger(0); + AtomicReference r = new AtomicReference(); + f.whenComplete((v, e) -> {if (e != null) r.set(e); else x.set(v);}); + assertEquals(x.get(), 0); + assertEquals(r.get(), ex); + } + + /** + * completeAsync completes with value of given supplier + */ + public void testCompleteAsync() { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + f.completeAsync(() -> v1); + f.join(); + checkCompletedNormally(f, v1); + }} + + /** + * completeAsync completes exceptionally if given supplier throws + */ + public void testCompleteAsync2() { + CompletableFuture f = new CompletableFuture<>(); + CFException ex = new CFException(); + f.completeAsync(() -> {if (true) throw ex; return 1;}); + try { + f.join(); + shouldThrow(); + } catch (CompletionException success) {} + checkCompletedWithWrappedException(f, ex); + } + + /** + * completeAsync with given executor completes with value of given supplier + */ + public void testCompleteAsync3() { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + ThreadExecutor executor = new ThreadExecutor(); + f.completeAsync(() -> v1, executor); + assertSame(v1, f.join()); + checkCompletedNormally(f, v1); + assertEquals(1, executor.count.get()); + }} + + /** + * completeAsync with given executor completes exceptionally if + * given supplier throws + */ + public void testCompleteAsync4() { + CompletableFuture f = new CompletableFuture<>(); + CFException ex = new CFException(); + ThreadExecutor executor = new ThreadExecutor(); + f.completeAsync(() -> {if (true) throw ex; return 1;}, executor); + try { + f.join(); + shouldThrow(); + } catch (CompletionException success) {} + checkCompletedWithWrappedException(f, ex); + assertEquals(1, executor.count.get()); + } + + /** + * orTimeout completes with TimeoutException if not complete + */ + public void testOrTimeout_timesOut() { + long timeoutMillis = timeoutMillis(); + CompletableFuture f = new CompletableFuture<>(); + long startTime = System.nanoTime(); + f.orTimeout(timeoutMillis, MILLISECONDS); + checkCompletedWithTimeoutException(f); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + } + + /** + * orTimeout completes normally if completed before timeout + */ + public void testOrTimeout_completed() { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture g = new CompletableFuture<>(); + long startTime = System.nanoTime(); + f.complete(v1); + f.orTimeout(LONG_DELAY_MS, MILLISECONDS); + g.orTimeout(LONG_DELAY_MS, MILLISECONDS); + g.complete(v1); + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v1); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS / 2); + }} + + /** + * completeOnTimeout completes with given value if not complete + */ + public void testCompleteOnTimeout_timesOut() { + testInParallel(() -> testCompleteOnTimeout_timesOut(42), + () -> testCompleteOnTimeout_timesOut(null)); + } + + public void testCompleteOnTimeout_timesOut(Integer v) { + long timeoutMillis = timeoutMillis(); + CompletableFuture f = new CompletableFuture<>(); + long startTime = System.nanoTime(); + f.completeOnTimeout(v, timeoutMillis, MILLISECONDS); + assertSame(v, f.join()); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + f.complete(99); // should have no effect + checkCompletedNormally(f, v); + } + + /** + * completeOnTimeout has no effect if completed within timeout + */ + public void testCompleteOnTimeout_completed() { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture g = new CompletableFuture<>(); + long startTime = System.nanoTime(); + f.complete(v1); + f.completeOnTimeout(-1, LONG_DELAY_MS, MILLISECONDS); + g.completeOnTimeout(-1, LONG_DELAY_MS, MILLISECONDS); + g.complete(v1); + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v1); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS / 2); + }} + + /** + * delayedExecutor returns an executor that delays submission + */ + public void testDelayedExecutor() { + testInParallel(() -> testDelayedExecutor(null, null), + () -> testDelayedExecutor(null, 1), + () -> testDelayedExecutor(new ThreadExecutor(), 1), + () -> testDelayedExecutor(new ThreadExecutor(), 1)); + } + + public void testDelayedExecutor(Executor executor, Integer v) throws Exception { + long timeoutMillis = timeoutMillis(); + // Use an "unreasonably long" long timeout to catch lingering threads + long longTimeoutMillis = 1000 * 60 * 60 * 24; + final Executor delayer, longDelayer; + if (executor == null) { + delayer = CompletableFuture.delayedExecutor(timeoutMillis, MILLISECONDS); + longDelayer = CompletableFuture.delayedExecutor(longTimeoutMillis, MILLISECONDS); + } else { + delayer = CompletableFuture.delayedExecutor(timeoutMillis, MILLISECONDS, executor); + longDelayer = CompletableFuture.delayedExecutor(longTimeoutMillis, MILLISECONDS, executor); + } + long startTime = System.nanoTime(); + CompletableFuture f = + CompletableFuture.supplyAsync(() -> v, delayer); + CompletableFuture g = + CompletableFuture.supplyAsync(() -> v, longDelayer); + + assertNull(g.getNow(null)); + + assertSame(v, f.get(LONG_DELAY_MS, MILLISECONDS)); + long millisElapsed = millisElapsedSince(startTime); + assertTrue(millisElapsed >= timeoutMillis); + assertTrue(millisElapsed < LONG_DELAY_MS / 2); + + checkCompletedNormally(f, v); + + checkIncomplete(g); + assertTrue(g.cancel(true)); + } + + //--- tests of implementation details; not part of official tck --- + + Object resultOf(CompletableFuture f) { + try { + java.lang.reflect.Field resultField + = CompletableFuture.class.getDeclaredField("result"); + resultField.setAccessible(true); + return resultField.get(f); + } catch (Throwable t) { throw new AssertionError(t); } + } + + public void testExceptionPropagationReusesResultObject() { + if (!testImplementationDetails) return; + for (ExecutionMode m : ExecutionMode.values()) + { + final CFException ex = new CFException(); + final CompletableFuture v42 = CompletableFuture.completedFuture(42); + final CompletableFuture incomplete = new CompletableFuture<>(); + + List, CompletableFuture>> funs + = new ArrayList<>(); + + funs.add((y) -> m.thenRun(y, new Noop(m))); + funs.add((y) -> m.thenAccept(y, new NoopConsumer(m))); + funs.add((y) -> m.thenApply(y, new IncFunction(m))); + + funs.add((y) -> m.runAfterEither(y, incomplete, new Noop(m))); + funs.add((y) -> m.acceptEither(y, incomplete, new NoopConsumer(m))); + funs.add((y) -> m.applyToEither(y, incomplete, new IncFunction(m))); + + funs.add((y) -> m.runAfterBoth(y, v42, new Noop(m))); + funs.add((y) -> m.thenAcceptBoth(y, v42, new SubtractAction(m))); + funs.add((y) -> m.thenCombine(y, v42, new SubtractFunction(m))); + + funs.add((y) -> m.whenComplete(y, (Integer r, Throwable t) -> {})); + + funs.add((y) -> m.thenCompose(y, new CompletableFutureInc(m))); + + funs.add((y) -> CompletableFuture.allOf(new CompletableFuture[] {y, v42})); + funs.add((y) -> CompletableFuture.anyOf(new CompletableFuture[] {y, incomplete})); + + for (Function, CompletableFuture> + fun : funs) { + CompletableFuture f = new CompletableFuture<>(); + f.completeExceptionally(ex); + CompletableFuture src = m.thenApply(f, new IncFunction(m)); + checkCompletedWithWrappedException(src, ex); + CompletableFuture dep = fun.apply(src); + checkCompletedWithWrappedException(dep, ex); + assertSame(resultOf(src), resultOf(dep)); + } + + for (Function, CompletableFuture> + fun : funs) { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture src = m.thenApply(f, new IncFunction(m)); + CompletableFuture dep = fun.apply(src); + f.completeExceptionally(ex); + checkCompletedWithWrappedException(src, ex); + checkCompletedWithWrappedException(dep, ex); + assertSame(resultOf(src), resultOf(dep)); + } + + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (Function, CompletableFuture> + fun : funs) { + CompletableFuture f = new CompletableFuture<>(); + f.cancel(mayInterruptIfRunning); + checkCancelled(f); + CompletableFuture src = m.thenApply(f, new IncFunction(m)); + checkCompletedWithWrappedCancellationException(src); + CompletableFuture dep = fun.apply(src); + checkCompletedWithWrappedCancellationException(dep); + assertSame(resultOf(src), resultOf(dep)); + } + + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (Function, CompletableFuture> + fun : funs) { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture src = m.thenApply(f, new IncFunction(m)); + CompletableFuture dep = fun.apply(src); + f.cancel(mayInterruptIfRunning); + checkCancelled(f); + checkCompletedWithWrappedCancellationException(src); + checkCompletedWithWrappedCancellationException(dep); + assertSame(resultOf(src), resultOf(dep)); + } + }} + + /** + * Minimal completion stages throw UOE for all non-CompletionStage methods + */ + // TODO(streams): + // public void testMinimalCompletionStage_minimality() { + // if (!testImplementationDetails) return; + // Function toSignature = + // (method) -> method.getName() + Arrays.toString(method.getParameterTypes()); + // Predicate isNotStatic = + // (method) -> (method.getModifiers() & Modifier.STATIC) == 0; + // List minimalMethods = + // Stream.of(Object.class, CompletionStage.class) + // .flatMap((klazz) -> Stream.of(klazz.getMethods())) + // .filter(isNotStatic) + // .collect(Collectors.toList()); + // // Methods from CompletableFuture permitted NOT to throw UOE + // String[] signatureWhitelist = { + // "newIncompleteFuture[]", + // "defaultExecutor[]", + // "minimalCompletionStage[]", + // "copy[]", + // }; + // Set permittedMethodSignatures = + // Stream.concat(minimalMethods.stream().map(toSignature), + // Stream.of(signatureWhitelist)) + // .collect(Collectors.toSet()); + // List allMethods = Stream.of(CompletableFuture.class.getMethods()) + // .filter(isNotStatic) + // .filter((method) -> !permittedMethodSignatures.contains(toSignature.apply(method))) + // .collect(Collectors.toList()); + + // CompletionStage minimalStage = + // new CompletableFuture().minimalCompletionStage(); + + // List bugs = new ArrayList<>(); + // for (Method method : allMethods) { + // Class[] parameterTypes = method.getParameterTypes(); + // Object[] args = new Object[parameterTypes.length]; + // // Manufacture boxed primitives for primitive params + // for (int i = 0; i < args.length; i++) { + // Class type = parameterTypes[i]; + // if (parameterTypes[i] == boolean.class) + // args[i] = false; + // else if (parameterTypes[i] == int.class) + // args[i] = 0; + // else if (parameterTypes[i] == long.class) + // args[i] = 0L; + // } + // try { + // method.invoke(minimalStage, args); + // bugs.add(method); + // } + // catch (java.lang.reflect.InvocationTargetException expected) { + // if (! (expected.getCause() instanceof UnsupportedOperationException)) { + // bugs.add(method); + // // expected.getCause().printStackTrace(); + // } + // } + // catch (ReflectiveOperationException bad) { throw new Error(bad); } + // } + // if (!bugs.isEmpty()) + // throw new Error("Methods did not throw UOE: " + bugs.toString()); + // } + + static class Monad { + static class ZeroException extends RuntimeException { + public ZeroException() { super("monadic zero"); } + } + // "return", "unit" + static CompletableFuture unit(T value) { + return completedFuture(value); + } + // monadic zero ? + static CompletableFuture zero() { + return failedFuture(new ZeroException()); + } + // >=> + static Function> compose + (Function> f, + Function> g) { + return (x) -> f.apply(x).thenCompose(g); + } + + static void assertZero(CompletableFuture f) { + try { + f.getNow(null); + throw new AssertionFailedError("should throw"); + } catch (CompletionException success) { + assertTrue(success.getCause() instanceof ZeroException); + } + } + + static void assertFutureEquals(CompletableFuture f, + CompletableFuture g) { + T fval = null, gval = null; + Throwable fex = null, gex = null; + + try { fval = f.get(); } + catch (ExecutionException ex) { fex = ex.getCause(); } + catch (Throwable ex) { fex = ex; } + + try { gval = g.get(); } + catch (ExecutionException ex) { gex = ex.getCause(); } + catch (Throwable ex) { gex = ex; } + + if (fex != null || gex != null) + assertSame(fex.getClass(), gex.getClass()); + else + assertEquals(fval, gval); + } + + static class PlusFuture extends CompletableFuture { + AtomicReference firstFailure = new AtomicReference<>(null); + } + + /** Implements "monadic plus". */ + static CompletableFuture plus(CompletableFuture f, + CompletableFuture g) { + PlusFuture plus = new PlusFuture(); + BiConsumer action = (T result, Throwable ex) -> { + try { + if (ex == null) { + if (plus.complete(result)) + if (plus.firstFailure.get() != null) + plus.firstFailure.set(null); + } + else if (plus.firstFailure.compareAndSet(null, ex)) { + if (plus.isDone()) + plus.firstFailure.set(null); + } + else { + // first failure has precedence + Throwable first = plus.firstFailure.getAndSet(null); + + // may fail with "Self-suppression not permitted" + try { first.addSuppressed(ex); } + catch (Exception ignored) {} + + plus.completeExceptionally(first); + } + } catch (Throwable unexpected) { + plus.completeExceptionally(unexpected); + } + }; + f.whenComplete(action); + g.whenComplete(action); + return plus; + } + } + + /** + * CompletableFuture is an additive monad - sort of. + * https://en.wikipedia.org/wiki/Monad_(functional_programming)#Additive_monads + */ + public void testAdditiveMonad() throws Throwable { + Function> unit = Monad::unit; + CompletableFuture zero = Monad.zero(); + + // Some mutually non-commutative functions + Function> triple + = (x) -> Monad.unit(3 * x); + Function> inc + = (x) -> Monad.unit(x + 1); + + // unit is a right identity: m >>= unit === m + Monad.assertFutureEquals(inc.apply(5L).thenCompose(unit), + inc.apply(5L)); + // unit is a left identity: (unit x) >>= f === f x + Monad.assertFutureEquals(unit.apply(5L).thenCompose(inc), + inc.apply(5L)); + + // associativity: (m >>= f) >>= g === m >>= ( \x -> (f x >>= g) ) + Monad.assertFutureEquals( + unit.apply(5L).thenCompose(inc).thenCompose(triple), + unit.apply(5L).thenCompose((x) -> inc.apply(x).thenCompose(triple))); + + // The case for CompletableFuture as an additive monad is weaker... + + // zero is a monadic zero + Monad.assertZero(zero); + + // left zero: zero >>= f === zero + Monad.assertZero(zero.thenCompose(inc)); + // right zero: f >>= (\x -> zero) === zero + Monad.assertZero(inc.apply(5L).thenCompose((x) -> zero)); + + // f plus zero === f + Monad.assertFutureEquals(Monad.unit(5L), + Monad.plus(Monad.unit(5L), zero)); + // zero plus f === f + Monad.assertFutureEquals(Monad.unit(5L), + Monad.plus(zero, Monad.unit(5L))); + // zero plus zero === zero + Monad.assertZero(Monad.plus(zero, zero)); + { + CompletableFuture f = Monad.plus(Monad.unit(5L), + Monad.unit(8L)); + // non-determinism + assertTrue(f.get() == 5L || f.get() == 8L); + } + + CompletableFuture godot = new CompletableFuture<>(); + // f plus godot === f (doesn't wait for godot) + Monad.assertFutureEquals(Monad.unit(5L), + Monad.plus(Monad.unit(5L), godot)); + // godot plus f === f (doesn't wait for godot) + Monad.assertFutureEquals(Monad.unit(5L), + Monad.plus(godot, Monad.unit(5L))); + } + +// static U join(CompletionStage stage) { +// CompletableFuture f = new CompletableFuture<>(); +// stage.whenComplete((v, ex) -> { +// if (ex != null) f.completeExceptionally(ex); else f.complete(v); +// }); +// return f.join(); +// } + +// static boolean isDone(CompletionStage stage) { +// CompletableFuture f = new CompletableFuture<>(); +// stage.whenComplete((v, ex) -> { +// if (ex != null) f.completeExceptionally(ex); else f.complete(v); +// }); +// return f.isDone(); +// } + +// static U join2(CompletionStage stage) { +// return stage.toCompletableFuture().copy().join(); +// } + +// static boolean isDone2(CompletionStage stage) { +// return stage.toCompletableFuture().copy().isDone(); +// } + +} diff --git a/jsr166-tests/src/test/java/jsr166/ConcurrentHashMap8Test.java b/jsr166-tests/src/test/java/jsr166/ConcurrentHashMap8Test.java new file mode 100644 index 000000000..60de8b9de --- /dev/null +++ b/jsr166-tests/src/test/java/jsr166/ConcurrentHashMap8Test.java @@ -0,0 +1,1096 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package jsr166; + +import static java.util.Spliterator.CONCURRENT; +import static java.util.Spliterator.DISTINCT; +import static java.util.Spliterator.NONNULL; + +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Spliterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.LongAdder; +import java.util.function.BiFunction; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ConcurrentHashMap8Test extends JSR166TestCase { + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. + // + // public static void main(String[] args) { + // main(suite(), args); + // } + // public static Test suite() { + // return new TestSuite(ConcurrentHashMap8Test.class); + // } + + /** + * Returns a new map from Integers 1-5 to Strings "A"-"E". + */ + private static ConcurrentHashMap map5() { + ConcurrentHashMap map = new ConcurrentHashMap(5); + assertTrue(map.isEmpty()); + map.put(one, "A"); + map.put(two, "B"); + map.put(three, "C"); + map.put(four, "D"); + map.put(five, "E"); + assertFalse(map.isEmpty()); + assertEquals(5, map.size()); + return map; + } + + /** + * getOrDefault returns value if present, else default + */ + public void testGetOrDefault() { + ConcurrentHashMap map = map5(); + assertEquals(map.getOrDefault(one, "Z"), "A"); + assertEquals(map.getOrDefault(six, "Z"), "Z"); + } + + /** + * computeIfAbsent adds when the given key is not present + */ + public void testComputeIfAbsent() { + ConcurrentHashMap map = map5(); + map.computeIfAbsent(six, (x) -> "Z"); + assertTrue(map.containsKey(six)); + } + + /** + * computeIfAbsent does not replace if the key is already present + */ + public void testComputeIfAbsent2() { + ConcurrentHashMap map = map5(); + assertEquals("A", map.computeIfAbsent(one, (x) -> "Z")); + } + + /** + * computeIfAbsent does not add if function returns null + */ + public void testComputeIfAbsent3() { + ConcurrentHashMap map = map5(); + map.computeIfAbsent(six, (x) -> null); + assertFalse(map.containsKey(six)); + } + + /** + * computeIfPresent does not replace if the key is already present + */ + public void testComputeIfPresent() { + ConcurrentHashMap map = map5(); + map.computeIfPresent(six, (x, y) -> "Z"); + assertFalse(map.containsKey(six)); + } + + /** + * computeIfPresent adds when the given key is not present + */ + public void testComputeIfPresent2() { + ConcurrentHashMap map = map5(); + assertEquals("Z", map.computeIfPresent(one, (x, y) -> "Z")); + } + + /** + * compute does not replace if the function returns null + */ + public void testCompute() { + ConcurrentHashMap map = map5(); + map.compute(six, (x, y) -> null); + assertFalse(map.containsKey(six)); + } + + /** + * compute adds when the given key is not present + */ + public void testCompute2() { + ConcurrentHashMap map = map5(); + assertEquals("Z", map.compute(six, (x, y) -> "Z")); + } + + /** + * compute replaces when the given key is present + */ + public void testCompute3() { + ConcurrentHashMap map = map5(); + assertEquals("Z", map.compute(one, (x, y) -> "Z")); + } + + /** + * compute removes when the given key is present and function returns null + */ + public void testCompute4() { + ConcurrentHashMap map = map5(); + map.compute(one, (x, y) -> null); + assertFalse(map.containsKey(one)); + } + + /** + * merge adds when the given key is not present + */ + public void testMerge1() { + ConcurrentHashMap map = map5(); + assertEquals("Y", map.merge(six, "Y", (x, y) -> "Z")); + } + + /** + * merge replaces when the given key is present + */ + public void testMerge2() { + ConcurrentHashMap map = map5(); + assertEquals("Z", map.merge(one, "Y", (x, y) -> "Z")); + } + + /** + * merge removes when the given key is present and function returns null + */ + public void testMerge3() { + ConcurrentHashMap map = map5(); + map.merge(one, "Y", (x, y) -> null); + assertFalse(map.containsKey(one)); + } + + static Set populatedSet(int n) { + Set a = ConcurrentHashMap.newKeySet(); + assertTrue(a.isEmpty()); + for (int i = 0; i < n; i++) + assertTrue(a.add(i)); + assertEquals(n == 0, a.isEmpty()); + assertEquals(n, a.size()); + return a; + } + + static Set populatedSet(Integer[] elements) { + Set a = ConcurrentHashMap.newKeySet(); + assertTrue(a.isEmpty()); + for (int i = 0; i < elements.length; i++) + assertTrue(a.add(elements[i])); + assertFalse(a.isEmpty()); + assertEquals(elements.length, a.size()); + return a; + } + + /** + * replaceAll replaces all matching values. + */ + public void testReplaceAll() { + ConcurrentHashMap map = map5(); + map.replaceAll((x, y) -> { return x > 3 ? "Z" : y; }); + assertEquals("A", map.get(one)); + assertEquals("B", map.get(two)); + assertEquals("C", map.get(three)); + assertEquals("Z", map.get(four)); + assertEquals("Z", map.get(five)); + } + + /** + * Default-constructed set is empty + */ + public void testNewKeySet() { + Set a = ConcurrentHashMap.newKeySet(); + assertTrue(a.isEmpty()); + } + + /** + * keySet.add adds the key with the established value to the map; + * remove removes it. + */ + public void testKeySetAddRemove() { + ConcurrentHashMap map = map5(); + Set set1 = map.keySet(); + Set set2 = map.keySet(true); + set2.add(six); + assertTrue(((ConcurrentHashMap.KeySetView)set2).getMap() == map); + assertTrue(((ConcurrentHashMap.KeySetView)set1).getMap() == map); + assertEquals(set2.size(), map.size()); + assertEquals(set1.size(), map.size()); + assertTrue((Boolean)map.get(six)); + assertTrue(set1.contains(six)); + assertTrue(set2.contains(six)); + set2.remove(six); + assertNull(map.get(six)); + assertFalse(set1.contains(six)); + assertFalse(set2.contains(six)); + } + + /** + * keySet.addAll adds each element from the given collection + */ + public void testAddAll() { + Set full = populatedSet(3); + assertTrue(full.addAll(Arrays.asList(three, four, five))); + assertEquals(6, full.size()); + assertFalse(full.addAll(Arrays.asList(three, four, five))); + assertEquals(6, full.size()); + } + + /** + * keySet.addAll adds each element from the given collection that did not + * already exist in the set + */ + public void testAddAll2() { + Set full = populatedSet(3); + // "one" is duplicate and will not be added + assertTrue(full.addAll(Arrays.asList(three, four, one))); + assertEquals(5, full.size()); + assertFalse(full.addAll(Arrays.asList(three, four, one))); + assertEquals(5, full.size()); + } + + /** + * keySet.add will not add the element if it already exists in the set + */ + public void testAdd2() { + Set full = populatedSet(3); + assertFalse(full.add(one)); + assertEquals(3, full.size()); + } + + /** + * keySet.add adds the element when it does not exist in the set + */ + public void testAdd3() { + Set full = populatedSet(3); + assertTrue(full.add(three)); + assertTrue(full.contains(three)); + assertFalse(full.add(three)); + assertTrue(full.contains(three)); + } + + /** + * keySet.add throws UnsupportedOperationException if no default + * mapped value + */ + public void testAdd4() { + Set full = map5().keySet(); + try { + full.add(three); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + } + + /** + * keySet.add throws NullPointerException if the specified key is + * null + */ + public void testAdd5() { + Set full = populatedSet(3); + try { + full.add(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * KeySetView.getMappedValue returns the map's mapped value + */ + public void testGetMappedValue() { + ConcurrentHashMap map = map5(); + assertNull(map.keySet().getMappedValue()); + try { + map.keySet(null); + shouldThrow(); + } catch (NullPointerException success) {} + ConcurrentHashMap.KeySetView set = map.keySet(one); + assertFalse(set.add(one)); + assertTrue(set.add(six)); + assertTrue(set.add(seven)); + assertTrue(set.getMappedValue() == one); + assertTrue(map.get(one) != one); + assertTrue(map.get(six) == one); + assertTrue(map.get(seven) == one); + } + + void checkSpliteratorCharacteristics(Spliterator sp, + int requiredCharacteristics) { + assertEquals(requiredCharacteristics, + requiredCharacteristics & sp.characteristics()); + } + + /** + * KeySetView.spliterator returns spliterator over the elements in this set + */ + public void testKeySetSpliterator() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap map = map5(); + Set set = map.keySet(); + Spliterator sp = set.spliterator(); + checkSpliteratorCharacteristics(sp, CONCURRENT | DISTINCT | NONNULL); + assertEquals(sp.estimateSize(), map.size()); + Spliterator sp2 = sp.trySplit(); + sp.forEachRemaining((Integer x) -> adder.add(x.longValue())); + long v = adder.sumThenReset(); + sp2.forEachRemaining((Integer x) -> adder.add(x.longValue())); + long v2 = adder.sum(); + assertEquals(v + v2, 15); + } + + /** + * keyset.clear removes all elements from the set + */ + public void testClear() { + Set full = populatedSet(3); + full.clear(); + assertEquals(0, full.size()); + } + + /** + * keyset.contains returns true for added elements + */ + public void testContains() { + Set full = populatedSet(3); + assertTrue(full.contains(one)); + assertFalse(full.contains(five)); + } + + /** + * KeySets with equal elements are equal + */ + public void testEquals() { + Set a = populatedSet(3); + Set b = populatedSet(3); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertEquals(a.hashCode(), b.hashCode()); + a.add(m1); + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + b.add(m1); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertEquals(a.hashCode(), b.hashCode()); + } + + /** + * KeySet.containsAll returns true for collections with subset of elements + */ + public void testContainsAll() { + Collection full = populatedSet(3); + assertTrue(full.containsAll(Arrays.asList())); + assertTrue(full.containsAll(Arrays.asList(one))); + assertTrue(full.containsAll(Arrays.asList(one, two))); + assertFalse(full.containsAll(Arrays.asList(one, two, six))); + assertFalse(full.containsAll(Arrays.asList(six))); + } + + /** + * KeySet.isEmpty is true when empty, else false + */ + public void testIsEmpty() { + assertTrue(populatedSet(0).isEmpty()); + assertFalse(populatedSet(3).isEmpty()); + } + + /** + * KeySet.iterator() returns an iterator containing the elements of the + * set + */ + public void testIterator() { + Collection empty = ConcurrentHashMap.newKeySet(); + int size = 20; + assertFalse(empty.iterator().hasNext()); + try { + empty.iterator().next(); + shouldThrow(); + } catch (NoSuchElementException success) {} + + Integer[] elements = new Integer[size]; + for (int i = 0; i < size; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedSet(elements); + + Iterator it = full.iterator(); + for (int j = 0; j < size; j++) { + assertTrue(it.hasNext()); + it.next(); + } + assertIteratorExhausted(it); + } + + /** + * iterator of empty collections has no elements + */ + public void testEmptyIterator() { + assertIteratorExhausted(ConcurrentHashMap.newKeySet().iterator()); + assertIteratorExhausted(new ConcurrentHashMap().entrySet().iterator()); + assertIteratorExhausted(new ConcurrentHashMap().values().iterator()); + assertIteratorExhausted(new ConcurrentHashMap().keySet().iterator()); + } + + /** + * KeySet.iterator.remove removes current element + */ + public void testIteratorRemove() { + Set q = populatedSet(3); + Iterator it = q.iterator(); + Object removed = it.next(); + it.remove(); + + it = q.iterator(); + assertFalse(it.next().equals(removed)); + assertFalse(it.next().equals(removed)); + assertFalse(it.hasNext()); + } + + /** + * KeySet.toString holds toString of elements + */ + public void testToString() { + assertEquals("[]", ConcurrentHashMap.newKeySet().toString()); + Set full = populatedSet(3); + String s = full.toString(); + for (int i = 0; i < 3; ++i) + assertTrue(s.contains(String.valueOf(i))); + } + + /** + * KeySet.removeAll removes all elements from the given collection + */ + public void testRemoveAll() { + Set full = populatedSet(3); + assertTrue(full.removeAll(Arrays.asList(one, two))); + assertEquals(1, full.size()); + assertFalse(full.removeAll(Arrays.asList(one, two))); + assertEquals(1, full.size()); + } + + /** + * KeySet.remove removes an element + */ + public void testRemove() { + Set full = populatedSet(3); + full.remove(one); + assertFalse(full.contains(one)); + assertEquals(2, full.size()); + } + + /** + * keySet.size returns the number of elements + */ + public void testSize() { + Set empty = ConcurrentHashMap.newKeySet(); + Set full = populatedSet(3); + assertEquals(3, full.size()); + assertEquals(0, empty.size()); + } + + /** + * KeySet.toArray() returns an Object array containing all elements from + * the set + */ + public void testToArray() { + Object[] a = ConcurrentHashMap.newKeySet().toArray(); + assertTrue(Arrays.equals(new Object[0], a)); + assertSame(Object[].class, a.getClass()); + int size = 20; + Integer[] elements = new Integer[size]; + for (int i = 0; i < size; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedSet(elements); + + assertTrue(Arrays.asList(elements).containsAll(Arrays.asList(full.toArray()))); + assertTrue(full.containsAll(Arrays.asList(full.toArray()))); + assertSame(Object[].class, full.toArray().getClass()); + } + + /** + * toArray(Integer array) returns an Integer array containing all + * elements from the set + */ + public void testToArray2() { + Collection empty = ConcurrentHashMap.newKeySet(); + Integer[] a; + int size = 20; + + a = new Integer[0]; + assertSame(a, empty.toArray(a)); + + a = new Integer[size / 2]; + Arrays.fill(a, 42); + assertSame(a, empty.toArray(a)); + assertNull(a[0]); + for (int i = 1; i < a.length; i++) + assertEquals(42, (int) a[i]); + + Integer[] elements = new Integer[size]; + for (int i = 0; i < size; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedSet(elements); + + Arrays.fill(a, 42); + assertTrue(Arrays.asList(elements).containsAll(Arrays.asList(full.toArray(a)))); + for (int i = 0; i < a.length; i++) + assertEquals(42, (int) a[i]); + assertSame(Integer[].class, full.toArray(a).getClass()); + + a = new Integer[size]; + Arrays.fill(a, 42); + assertSame(a, full.toArray(a)); + assertTrue(Arrays.asList(elements).containsAll(Arrays.asList(full.toArray(a)))); + } + + /** + * A deserialized serialized set is equal + */ + public void testSerialization() throws Exception { + int size = 20; + Set x = populatedSet(size); + Set y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x, y); + assertEquals(y, x); + } + + static final int SIZE = 10000; + static ConcurrentHashMap longMap; + + static ConcurrentHashMap longMap() { + if (longMap == null) { + longMap = new ConcurrentHashMap(SIZE); + for (int i = 0; i < SIZE; ++i) + longMap.put(Long.valueOf(i), Long.valueOf(2 *i)); + } + return longMap; + } + + // explicit function class to avoid type inference problems + static class AddKeys implements BiFunction, Map.Entry, Map.Entry> { + public Map.Entry apply(Map.Entry x, Map.Entry y) { + return new AbstractMap.SimpleEntry + (Long.valueOf(x.getKey().longValue() + y.getKey().longValue()), + Long.valueOf(1L)); + } + } + + /** + * forEachKeySequentially traverses all keys + */ + public void testForEachKeySequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachKey(Long.MAX_VALUE, (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), SIZE * (SIZE - 1) / 2); + } + + /** + * forEachValueSequentially traverses all values + */ + public void testForEachValueSequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachValue(Long.MAX_VALUE, (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), SIZE * (SIZE - 1)); + } + + /** + * forEachSequentially traverses all mappings + */ + public void testForEachSequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEach(Long.MAX_VALUE, (Long x, Long y) -> adder.add(x.longValue() + y.longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * forEachEntrySequentially traverses all entries + */ + public void testForEachEntrySequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachEntry(Long.MAX_VALUE, (Map.Entry e) -> adder.add(e.getKey().longValue() + e.getValue().longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * forEachKeyInParallel traverses all keys + */ + public void testForEachKeyInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachKey(1L, (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), SIZE * (SIZE - 1) / 2); + } + + /** + * forEachValueInParallel traverses all values + */ + public void testForEachValueInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachValue(1L, (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), SIZE * (SIZE - 1)); + } + + /** + * forEachInParallel traverses all mappings + */ + public void testForEachInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEach(1L, (Long x, Long y) -> adder.add(x.longValue() + y.longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * forEachEntryInParallel traverses all entries + */ + public void testForEachEntryInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachEntry(1L, (Map.Entry e) -> adder.add(e.getKey().longValue() + e.getValue().longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped forEachKeySequentially traverses the given + * transformations of all keys + */ + public void testMappedForEachKeySequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachKey(Long.MAX_VALUE, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 4 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped forEachValueSequentially traverses the given + * transformations of all values + */ + public void testMappedForEachValueSequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachValue(Long.MAX_VALUE, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 4 * SIZE * (SIZE - 1)); + } + + /** + * Mapped forEachSequentially traverses the given + * transformations of all mappings + */ + public void testMappedForEachSequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEach(Long.MAX_VALUE, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped forEachEntrySequentially traverses the given + * transformations of all entries + */ + public void testMappedForEachEntrySequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachEntry(Long.MAX_VALUE, (Map.Entry e) -> Long.valueOf(e.getKey().longValue() + e.getValue().longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped forEachKeyInParallel traverses the given + * transformations of all keys + */ + public void testMappedForEachKeyInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachKey(1L, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 4 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped forEachValueInParallel traverses the given + * transformations of all values + */ + public void testMappedForEachValueInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachValue(1L, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 4 * SIZE * (SIZE - 1)); + } + + /** + * Mapped forEachInParallel traverses the given + * transformations of all mappings + */ + public void testMappedForEachInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEach(1L, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped forEachEntryInParallel traverses the given + * transformations of all entries + */ + public void testMappedForEachEntryInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachEntry(1L, (Map.Entry e) -> Long.valueOf(e.getKey().longValue() + e.getValue().longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * reduceKeysSequentially accumulates across all keys, + */ + public void testReduceKeysSequentially() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.reduceKeys(Long.MAX_VALUE, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceValuesSequentially accumulates across all values + */ + public void testReduceValuesSequentially() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.reduceKeys(Long.MAX_VALUE, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceEntriesSequentially accumulates across all entries + */ + public void testReduceEntriesSequentially() { + ConcurrentHashMap m = longMap(); + Map.Entry r; + r = m.reduceEntries(Long.MAX_VALUE, new AddKeys()); + assertEquals(r.getKey().longValue(), (long)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceKeysInParallel accumulates across all keys + */ + public void testReduceKeysInParallel() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.reduceKeys(1L, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceValuesInParallel accumulates across all values + */ + public void testReduceValuesInParallel() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.reduceValues(1L, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)SIZE * (SIZE - 1)); + } + + /** + * reduceEntriesInParallel accumulate across all entries + */ + public void testReduceEntriesInParallel() { + ConcurrentHashMap m = longMap(); + Map.Entry r; + r = m.reduceEntries(1L, new AddKeys()); + assertEquals(r.getKey().longValue(), (long)SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped reduceKeysSequentially accumulates mapped keys + */ + public void testMapReduceKeysSequentially() { + ConcurrentHashMap m = longMap(); + Long r = m.reduceKeys(Long.MAX_VALUE, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)4 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped reduceValuesSequentially accumulates mapped values + */ + public void testMapReduceValuesSequentially() { + ConcurrentHashMap m = longMap(); + Long r = m.reduceValues(Long.MAX_VALUE, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)4 * SIZE * (SIZE - 1)); + } + + /** + * reduceSequentially accumulates across all transformed mappings + */ + public void testMappedReduceSequentially() { + ConcurrentHashMap m = longMap(); + Long r = m.reduce(Long.MAX_VALUE, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()), + (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + + assertEquals((long)r, (long)3 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped reduceKeysInParallel, accumulates mapped keys + */ + public void testMapReduceKeysInParallel() { + ConcurrentHashMap m = longMap(); + Long r = m.reduceKeys(1L, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)4 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped reduceValuesInParallel accumulates mapped values + */ + public void testMapReduceValuesInParallel() { + ConcurrentHashMap m = longMap(); + Long r = m.reduceValues(1L, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)4 * SIZE * (SIZE - 1)); + } + + /** + * reduceInParallel accumulate across all transformed mappings + */ + public void testMappedReduceInParallel() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.reduce(1L, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()), + (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)3 * SIZE * (SIZE - 1) / 2); + } + + /** + * reduceKeysToLongSequentially accumulates mapped keys + */ + public void testReduceKeysToLongSequentially() { + ConcurrentHashMap m = longMap(); + long lr = m.reduceKeysToLong(Long.MAX_VALUE, (Long x) -> x.longValue(), 0L, Long::sum); + assertEquals(lr, (long)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceKeysToIntSequentially accumulates mapped keys + */ + public void testReduceKeysToIntSequentially() { + ConcurrentHashMap m = longMap(); + int ir = m.reduceKeysToInt(Long.MAX_VALUE, (Long x) -> x.intValue(), 0, Integer::sum); + assertEquals(ir, SIZE * (SIZE - 1) / 2); + } + + /** + * reduceKeysToDoubleSequentially accumulates mapped keys + */ + public void testReduceKeysToDoubleSequentially() { + ConcurrentHashMap m = longMap(); + double dr = m.reduceKeysToDouble(Long.MAX_VALUE, (Long x) -> x.doubleValue(), 0.0, Double::sum); + assertEquals(dr, (double)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceValuesToLongSequentially accumulates mapped values + */ + public void testReduceValuesToLongSequentially() { + ConcurrentHashMap m = longMap(); + long lr = m.reduceValuesToLong(Long.MAX_VALUE, (Long x) -> x.longValue(), 0L, Long::sum); + assertEquals(lr, (long)SIZE * (SIZE - 1)); + } + + /** + * reduceValuesToIntSequentially accumulates mapped values + */ + public void testReduceValuesToIntSequentially() { + ConcurrentHashMap m = longMap(); + int ir = m.reduceValuesToInt(Long.MAX_VALUE, (Long x) -> x.intValue(), 0, Integer::sum); + assertEquals(ir, SIZE * (SIZE - 1)); + } + + /** + * reduceValuesToDoubleSequentially accumulates mapped values + */ + public void testReduceValuesToDoubleSequentially() { + ConcurrentHashMap m = longMap(); + double dr = m.reduceValuesToDouble(Long.MAX_VALUE, (Long x) -> x.doubleValue(), 0.0, Double::sum); + assertEquals(dr, (double)SIZE * (SIZE - 1)); + } + + /** + * reduceKeysToLongInParallel accumulates mapped keys + */ + public void testReduceKeysToLongInParallel() { + ConcurrentHashMap m = longMap(); + long lr = m.reduceKeysToLong(1L, (Long x) -> x.longValue(), 0L, Long::sum); + assertEquals(lr, (long)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceKeysToIntInParallel accumulates mapped keys + */ + public void testReduceKeysToIntInParallel() { + ConcurrentHashMap m = longMap(); + int ir = m.reduceKeysToInt(1L, (Long x) -> x.intValue(), 0, Integer::sum); + assertEquals(ir, SIZE * (SIZE - 1) / 2); + } + + /** + * reduceKeysToDoubleInParallel accumulates mapped values + */ + public void testReduceKeysToDoubleInParallel() { + ConcurrentHashMap m = longMap(); + double dr = m.reduceKeysToDouble(1L, (Long x) -> x.doubleValue(), 0.0, Double::sum); + assertEquals(dr, (double)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceValuesToLongInParallel accumulates mapped values + */ + public void testReduceValuesToLongInParallel() { + ConcurrentHashMap m = longMap(); + long lr = m.reduceValuesToLong(1L, (Long x) -> x.longValue(), 0L, Long::sum); + assertEquals(lr, (long)SIZE * (SIZE - 1)); + } + + /** + * reduceValuesToIntInParallel accumulates mapped values + */ + public void testReduceValuesToIntInParallel() { + ConcurrentHashMap m = longMap(); + int ir = m.reduceValuesToInt(1L, (Long x) -> x.intValue(), 0, Integer::sum); + assertEquals(ir, SIZE * (SIZE - 1)); + } + + /** + * reduceValuesToDoubleInParallel accumulates mapped values + */ + public void testReduceValuesToDoubleInParallel() { + ConcurrentHashMap m = longMap(); + double dr = m.reduceValuesToDouble(1L, (Long x) -> x.doubleValue(), 0.0, Double::sum); + assertEquals(dr, (double)SIZE * (SIZE - 1)); + } + + /** + * searchKeysSequentially returns a non-null result of search + * function, or null if none + */ + public void testSearchKeysSequentially() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.searchKeys(Long.MAX_VALUE, (Long x) -> x.longValue() == (long)(SIZE/2) ? x : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.searchKeys(Long.MAX_VALUE, (Long x) -> x.longValue() < 0L ? x : null); + assertNull(r); + } + + /** + * searchValuesSequentially returns a non-null result of search + * function, or null if none + */ + public void testSearchValuesSequentially() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.searchValues(Long.MAX_VALUE, + (Long x) -> (x.longValue() == (long)(SIZE/2)) ? x : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.searchValues(Long.MAX_VALUE, + (Long x) -> (x.longValue() < 0L) ? x : null); + assertNull(r); + } + + /** + * searchSequentially returns a non-null result of search + * function, or null if none + */ + public void testSearchSequentially() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.search(Long.MAX_VALUE, (Long x, Long y) -> x.longValue() == (long)(SIZE/2) ? x : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.search(Long.MAX_VALUE, (Long x, Long y) -> x.longValue() < 0L ? x : null); + assertNull(r); + } + + /** + * searchEntriesSequentially returns a non-null result of search + * function, or null if none + */ + public void testSearchEntriesSequentially() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.searchEntries(Long.MAX_VALUE, (Map.Entry e) -> e.getKey().longValue() == (long)(SIZE/2) ? e.getKey() : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.searchEntries(Long.MAX_VALUE, (Map.Entry e) -> e.getKey().longValue() < 0L ? e.getKey() : null); + assertNull(r); + } + + /** + * searchKeysInParallel returns a non-null result of search + * function, or null if none + */ + public void testSearchKeysInParallel() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.searchKeys(1L, (Long x) -> x.longValue() == (long)(SIZE/2) ? x : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.searchKeys(1L, (Long x) -> x.longValue() < 0L ? x : null); + assertNull(r); + } + + /** + * searchValuesInParallel returns a non-null result of search + * function, or null if none + */ + public void testSearchValuesInParallel() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.searchValues(1L, (Long x) -> x.longValue() == (long)(SIZE/2) ? x : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.searchValues(1L, (Long x) -> x.longValue() < 0L ? x : null); + assertNull(r); + } + + /** + * searchInParallel returns a non-null result of search function, + * or null if none + */ + public void testSearchInParallel() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.search(1L, (Long x, Long y) -> x.longValue() == (long)(SIZE/2) ? x : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.search(1L, (Long x, Long y) -> x.longValue() < 0L ? x : null); + assertNull(r); + } + + /** + * searchEntriesInParallel returns a non-null result of search + * function, or null if none + */ + public void testSearchEntriesInParallel() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.searchEntries(1L, (Map.Entry e) -> e.getKey().longValue() == (long)(SIZE/2) ? e.getKey() : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.searchEntries(1L, (Map.Entry e) -> e.getKey().longValue() < 0L ? e.getKey() : null); + assertNull(r); + } + +} diff --git a/jsr166-tests/src/test/java/jsr166/ConcurrentHashMapTest.java b/jsr166-tests/src/test/java/jsr166/ConcurrentHashMapTest.java index 4650f4140..f4271279b 100644 --- a/jsr166-tests/src/test/java/jsr166/ConcurrentHashMapTest.java +++ b/jsr166-tests/src/test/java/jsr166/ConcurrentHashMapTest.java @@ -30,7 +30,7 @@ public class ConcurrentHashMapTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ConcurrentHashMapTest.class); // } /** @@ -50,7 +50,9 @@ private static ConcurrentHashMap map5() { } /** Re-implement Integer.compare for old java versions */ - static int compare(int x, int y) { return x < y ? -1 : x > y ? 1 : 0; } + static int compare(int x, int y) { + return (x < y) ? -1 : (x > y) ? 1 : 0; + } // classes for testing Comparable fallbacks static class BI implements Comparable { @@ -538,7 +540,7 @@ public void testConstructor1() { /** * Constructor (initialCapacity, loadFactor) throws * IllegalArgumentException if either argument is negative - */ + */ public void testConstructor2() { try { new ConcurrentHashMap(-1, .75f); diff --git a/jsr166-tests/src/test/java/jsr166/ConcurrentLinkedDequeTest.java b/jsr166-tests/src/test/java/jsr166/ConcurrentLinkedDequeTest.java index c44595732..6625e7e4f 100644 --- a/jsr166-tests/src/test/java/jsr166/ConcurrentLinkedDequeTest.java +++ b/jsr166-tests/src/test/java/jsr166/ConcurrentLinkedDequeTest.java @@ -29,7 +29,7 @@ public class ConcurrentLinkedDequeTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ConcurrentLinkedDequeTest.class); // } /** @@ -69,8 +69,7 @@ public void testConstructor3() { */ public void testConstructor4() { try { - Integer[] ints = new Integer[SIZE]; - new ConcurrentLinkedDeque(Arrays.asList(ints)); + new ConcurrentLinkedDeque(Arrays.asList(new Integer[SIZE])); shouldThrow(); } catch (NullPointerException success) {} } @@ -79,10 +78,10 @@ public void testConstructor4() { * Initializing from Collection with some null elements throws NPE */ public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); try { - Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i); new ConcurrentLinkedDeque(Arrays.asList(ints)); shouldThrow(); } catch (NullPointerException success) {} @@ -120,7 +119,7 @@ public void testEmpty() { public void testSize() { ConcurrentLinkedDeque q = populatedDeque(SIZE); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); q.remove(); } for (int i = 0; i < SIZE; ++i) { @@ -133,8 +132,8 @@ public void testSize() { * push(null) throws NPE */ public void testPushNull() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); try { - ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); q.push(null); shouldThrow(); } catch (NullPointerException success) {} @@ -168,8 +167,8 @@ public void testPop() { * offer(null) throws NPE */ public void testOfferNull() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); try { - ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); q.offer(null); shouldThrow(); } catch (NullPointerException success) {} @@ -179,8 +178,8 @@ public void testOfferNull() { * offerFirst(null) throws NPE */ public void testOfferFirstNull() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); try { - ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); q.offerFirst(null); shouldThrow(); } catch (NullPointerException success) {} @@ -190,8 +189,8 @@ public void testOfferFirstNull() { * offerLast(null) throws NPE */ public void testOfferLastNull() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); try { - ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); q.offerLast(null); shouldThrow(); } catch (NullPointerException success) {} @@ -234,8 +233,8 @@ public void testOfferLast() { * add(null) throws NPE */ public void testAddNull() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); try { - ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); q.add(null); shouldThrow(); } catch (NullPointerException success) {} @@ -245,8 +244,8 @@ public void testAddNull() { * addFirst(null) throws NPE */ public void testAddFirstNull() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); try { - ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); q.addFirst(null); shouldThrow(); } catch (NullPointerException success) {} @@ -256,8 +255,8 @@ public void testAddFirstNull() { * addLast(null) throws NPE */ public void testAddLastNull() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); try { - ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); q.addLast(null); shouldThrow(); } catch (NullPointerException success) {} @@ -300,8 +299,8 @@ public void testAddLast() { * addAll(null) throws NPE */ public void testAddAll1() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); try { - ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); q.addAll(null); shouldThrow(); } catch (NullPointerException success) {} @@ -311,8 +310,8 @@ public void testAddAll1() { * addAll(this) throws IAE */ public void testAddAllSelf() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); try { - ConcurrentLinkedDeque q = populatedDeque(SIZE); q.addAll(q); shouldThrow(); } catch (IllegalArgumentException success) {} @@ -322,10 +321,9 @@ public void testAddAllSelf() { * addAll of a collection with null elements throws NPE */ public void testAddAll2() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); try { - ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); - Integer[] ints = new Integer[SIZE]; - q.addAll(Arrays.asList(ints)); + q.addAll(Arrays.asList(new Integer[SIZE])); shouldThrow(); } catch (NullPointerException success) {} } @@ -335,11 +333,11 @@ public void testAddAll2() { * possibly adding some elements */ public void testAddAll3() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); try { - ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); - Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i); q.addAll(Arrays.asList(ints)); shouldThrow(); } catch (NullPointerException success) {} @@ -376,7 +374,7 @@ public void testPollFirst() { */ public void testPollLast() { ConcurrentLinkedDeque q = populatedDeque(SIZE); - for (int i = SIZE-1; i >= 0; --i) { + for (int i = SIZE - 1; i >= 0; --i) { assertEquals(i, q.pollLast()); } assertNull(q.pollLast()); @@ -445,14 +443,14 @@ public void testRemoveElement() { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertTrue(q.contains(i-1)); + assertTrue(q.contains(i - 1)); } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertFalse(q.remove(i+1)); - assertFalse(q.contains(i+1)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); } assertTrue(q.isEmpty()); } @@ -476,7 +474,7 @@ public void testPeekFirst() { */ public void testPeekLast() { ConcurrentLinkedDeque q = populatedDeque(SIZE); - for (int i = SIZE-1; i >= 0; --i) { + for (int i = SIZE - 1; i >= 0; --i) { assertEquals(i, q.peekLast()); assertEquals(i, q.pollLast()); assertTrue(q.peekLast() == null || @@ -505,7 +503,7 @@ public void testFirstElement() { */ public void testLastElement() { ConcurrentLinkedDeque q = populatedDeque(SIZE); - for (int i = SIZE-1; i >= 0; --i) { + for (int i = SIZE - 1; i >= 0; --i) { assertEquals(i, q.getLast()); assertEquals(i, q.pollLast()); } @@ -556,7 +554,7 @@ public void testRemoveFirstOccurrence() { } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.removeFirstOccurrence(new Integer(i))); - assertFalse(q.removeFirstOccurrence(new Integer(i+1))); + assertFalse(q.removeFirstOccurrence(new Integer(i + 1))); } assertTrue(q.isEmpty()); } @@ -571,7 +569,7 @@ public void testRemoveLastOccurrence() { } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.removeLastOccurrence(new Integer(i))); - assertFalse(q.removeLastOccurrence(new Integer(i+1))); + assertFalse(q.removeLastOccurrence(new Integer(i + 1))); } assertTrue(q.isEmpty()); } @@ -630,7 +628,7 @@ public void testRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.remove(); } } @@ -643,7 +641,7 @@ public void testRemoveAll() { ConcurrentLinkedDeque q = populatedDeque(SIZE); ConcurrentLinkedDeque p = populatedDeque(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { Integer x = (Integer)(p.remove()); assertFalse(q.contains(x)); @@ -759,18 +757,18 @@ public void testIteratorRemove() { final Random rng = new Random(); for (int iters = 0; iters < 100; ++iters) { int max = rng.nextInt(5) + 2; - int split = rng.nextInt(max-1) + 1; + int split = rng.nextInt(max - 1) + 1; for (int j = 1; j <= max; ++j) q.add(new Integer(j)); Iterator it = q.iterator(); for (int j = 1; j <= split; ++j) assertEquals(it.next(), new Integer(j)); it.remove(); - assertEquals(it.next(), new Integer(split+1)); + assertEquals(it.next(), new Integer(split + 1)); for (int j = 1; j <= split; ++j) q.remove(new Integer(j)); it = q.iterator(); - for (int j = split+1; j <= max; ++j) { + for (int j = split + 1; j <= max; ++j) { assertEquals(it.next(), new Integer(j)); it.remove(); } @@ -827,18 +825,18 @@ public void testDescendingIteratorRemove() { final Random rng = new Random(); for (int iters = 0; iters < 100; ++iters) { int max = rng.nextInt(5) + 2; - int split = rng.nextInt(max-1) + 1; + int split = rng.nextInt(max - 1) + 1; for (int j = max; j >= 1; --j) q.add(new Integer(j)); Iterator it = q.descendingIterator(); for (int j = 1; j <= split; ++j) assertEquals(it.next(), new Integer(j)); it.remove(); - assertEquals(it.next(), new Integer(split+1)); + assertEquals(it.next(), new Integer(split + 1)); for (int j = 1; j <= split; ++j) q.remove(new Integer(j)); it = q.descendingIterator(); - for (int j = split+1; j <= max; ++j) { + for (int j = split + 1; j <= max; ++j) { assertEquals(it.next(), new Integer(j)); it.remove(); } diff --git a/jsr166-tests/src/test/java/jsr166/ConcurrentLinkedQueueTest.java b/jsr166-tests/src/test/java/jsr166/ConcurrentLinkedQueueTest.java index d3f5b1f83..70519a4c1 100644 --- a/jsr166-tests/src/test/java/jsr166/ConcurrentLinkedQueueTest.java +++ b/jsr166-tests/src/test/java/jsr166/ConcurrentLinkedQueueTest.java @@ -27,7 +27,7 @@ public class ConcurrentLinkedQueueTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ConcurrentLinkedQueueTest.class); // } /** @@ -66,8 +66,7 @@ public void testConstructor3() { */ public void testConstructor4() { try { - Integer[] ints = new Integer[SIZE]; - new ConcurrentLinkedQueue(Arrays.asList(ints)); + new ConcurrentLinkedQueue(Arrays.asList(new Integer[SIZE])); shouldThrow(); } catch (NullPointerException success) {} } @@ -76,10 +75,10 @@ public void testConstructor4() { * Initializing from Collection with some null elements throws NPE */ public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); try { - Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i); new ConcurrentLinkedQueue(Arrays.asList(ints)); shouldThrow(); } catch (NullPointerException success) {} @@ -117,7 +116,7 @@ public void testEmpty() { public void testSize() { ConcurrentLinkedQueue q = populatedQueue(SIZE); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); q.remove(); } for (int i = 0; i < SIZE; ++i) { @@ -130,8 +129,8 @@ public void testSize() { * offer(null) throws NPE */ public void testOfferNull() { + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); try { - ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); q.offer(null); shouldThrow(); } catch (NullPointerException success) {} @@ -141,8 +140,8 @@ public void testOfferNull() { * add(null) throws NPE */ public void testAddNull() { + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); try { - ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); q.add(null); shouldThrow(); } catch (NullPointerException success) {} @@ -172,8 +171,8 @@ public void testAdd() { * addAll(null) throws NPE */ public void testAddAll1() { + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); try { - ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); q.addAll(null); shouldThrow(); } catch (NullPointerException success) {} @@ -183,8 +182,8 @@ public void testAddAll1() { * addAll(this) throws IAE */ public void testAddAllSelf() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); try { - ConcurrentLinkedQueue q = populatedQueue(SIZE); q.addAll(q); shouldThrow(); } catch (IllegalArgumentException success) {} @@ -194,10 +193,9 @@ public void testAddAllSelf() { * addAll of a collection with null elements throws NPE */ public void testAddAll2() { + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); try { - ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); - Integer[] ints = new Integer[SIZE]; - q.addAll(Arrays.asList(ints)); + q.addAll(Arrays.asList(new Integer[SIZE])); shouldThrow(); } catch (NullPointerException success) {} } @@ -207,11 +205,11 @@ public void testAddAll2() { * possibly adding some elements */ public void testAddAll3() { + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); try { - ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); - Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i); q.addAll(Arrays.asList(ints)); shouldThrow(); } catch (NullPointerException success) {} @@ -295,14 +293,14 @@ public void testRemoveElement() { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertTrue(q.contains(i-1)); + assertTrue(q.contains(i - 1)); } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertFalse(q.remove(i+1)); - assertFalse(q.contains(i+1)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); } assertTrue(q.isEmpty()); } @@ -361,7 +359,7 @@ public void testRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.remove(); } } @@ -374,7 +372,7 @@ public void testRemoveAll() { ConcurrentLinkedQueue q = populatedQueue(SIZE); ConcurrentLinkedQueue p = populatedQueue(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { Integer x = (Integer)(p.remove()); assertFalse(q.contains(x)); diff --git a/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListMapTest.java b/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListMapTest.java index 0aadd23e5..f53a446ac 100644 --- a/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListMapTest.java +++ b/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListMapTest.java @@ -30,7 +30,7 @@ public class ConcurrentSkipListMapTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ConcurrentSkipListMapTest.class); // } /** @@ -1278,7 +1278,7 @@ static void assertEq(Integer i, int j) { } static boolean eq(Integer i, int j) { - return i == null ? j == -1 : i == j; + return (i == null) ? j == -1 : i == j; } } diff --git a/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSetTest.java b/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSetTest.java index 41f883503..959d70357 100644 --- a/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSetTest.java +++ b/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSetTest.java @@ -29,7 +29,7 @@ public class ConcurrentSkipListSetTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ConcurrentSkipListSetTest.class); // } static class MyReverseComparator implements Comparator { @@ -46,7 +46,7 @@ private ConcurrentSkipListSet populatedSet(int n) { ConcurrentSkipListSet q = new ConcurrentSkipListSet(); assertTrue(q.isEmpty()); - for (int i = n-1; i >= 0; i -= 2) + for (int i = n - 1; i >= 0; i -= 2) assertTrue(q.add(new Integer(i))); for (int i = (n & 1); i < n; i += 2) assertTrue(q.add(new Integer(i))); @@ -92,8 +92,7 @@ public void testConstructor3() { */ public void testConstructor4() { try { - Integer[] ints = new Integer[SIZE]; - new ConcurrentSkipListSet(Arrays.asList(ints)); + new ConcurrentSkipListSet(Arrays.asList(new Integer[SIZE])); shouldThrow(); } catch (NullPointerException success) {} } @@ -102,10 +101,10 @@ public void testConstructor4() { * Initializing from Collection with some null elements throws NPE */ public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); try { - Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i); new ConcurrentSkipListSet(Arrays.asList(ints)); shouldThrow(); } catch (NullPointerException success) {} @@ -134,7 +133,7 @@ public void testConstructor7() { for (int i = 0; i < SIZE; ++i) ints[i] = new Integer(i); q.addAll(Arrays.asList(ints)); - for (int i = SIZE-1; i >= 0; --i) + for (int i = SIZE - 1; i >= 0; --i) assertEquals(ints[i], q.pollFirst()); } @@ -158,7 +157,7 @@ public void testEmpty() { public void testSize() { ConcurrentSkipListSet q = populatedSet(SIZE); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); q.pollFirst(); } for (int i = 0; i < SIZE; ++i) { @@ -238,7 +237,7 @@ public void testAddAll2() { public void testAddAll3() { ConcurrentSkipListSet q = new ConcurrentSkipListSet(); Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) + for (int i = 0; i < SIZE - 1; ++i) ints[i] = new Integer(i); try { q.addAll(Arrays.asList(ints)); @@ -253,7 +252,7 @@ public void testAddAll5() { Integer[] empty = new Integer[0]; Integer[] ints = new Integer[SIZE]; for (int i = 0; i < SIZE; ++i) - ints[i] = new Integer(SIZE-1-i); + ints[i] = new Integer(SIZE - 1 - i); ConcurrentSkipListSet q = new ConcurrentSkipListSet(); assertFalse(q.addAll(Arrays.asList(empty))); assertTrue(q.addAll(Arrays.asList(ints))); @@ -277,7 +276,7 @@ public void testPollFirst() { */ public void testPollLast() { ConcurrentSkipListSet q = populatedSet(SIZE); - for (int i = SIZE-1; i >= 0; --i) { + for (int i = SIZE - 1; i >= 0; --i) { assertEquals(i, q.pollLast()); } assertNull(q.pollFirst()); @@ -292,14 +291,14 @@ public void testRemoveElement() { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertTrue(q.contains(i-1)); + assertTrue(q.contains(i - 1)); } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertFalse(q.remove(i+1)); - assertFalse(q.contains(i+1)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); } assertTrue(q.isEmpty()); } @@ -358,7 +357,7 @@ public void testRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.pollFirst(); } } @@ -371,7 +370,7 @@ public void testRemoveAll() { ConcurrentSkipListSet q = populatedSet(SIZE); ConcurrentSkipListSet p = populatedSet(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { Integer x = (Integer)(p.pollFirst()); assertFalse(q.contains(x)); @@ -980,7 +979,7 @@ static void assertEq(Integer i, int j) { } static boolean eq(Integer i, int j) { - return i == null ? j == -1 : i == j; + return (i == null) ? j == -1 : i == j; } } diff --git a/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSubMapTest.java b/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSubMapTest.java index 5315bcba6..a6510f551 100644 --- a/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSubMapTest.java +++ b/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSubMapTest.java @@ -28,7 +28,7 @@ public class ConcurrentSkipListSubMapTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ConcurrentSkipListSubMapTest.class); // } /** diff --git a/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSubSetTest.java b/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSubSetTest.java index f1c4aae73..220a09266 100644 --- a/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSubSetTest.java +++ b/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSubSetTest.java @@ -24,7 +24,7 @@ public class ConcurrentSkipListSubSetTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ConcurrentSkipListSubSetTest.class); // } static class MyReverseComparator implements Comparator { @@ -42,7 +42,7 @@ private NavigableSet populatedSet(int n) { new ConcurrentSkipListSet(); assertTrue(q.isEmpty()); - for (int i = n-1; i >= 0; i -= 2) + for (int i = n - 1; i >= 0; i -= 2) assertTrue(q.add(new Integer(i))); for (int i = (n & 1); i < n; i += 2) assertTrue(q.add(new Integer(i))); @@ -127,7 +127,7 @@ public void testEmpty() { public void testSize() { NavigableSet q = populatedSet(SIZE); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); q.pollFirst(); } for (int i = 0; i < SIZE; ++i) { @@ -206,8 +206,8 @@ public void testAddAll2() { public void testAddAll3() { NavigableSet q = set0(); Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i+SIZE); + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i + SIZE); try { q.addAll(Arrays.asList(ints)); shouldThrow(); @@ -221,7 +221,7 @@ public void testAddAll5() { Integer[] empty = new Integer[0]; Integer[] ints = new Integer[SIZE]; for (int i = 0; i < SIZE; ++i) - ints[i] = new Integer(SIZE-1- i); + ints[i] = new Integer(SIZE - 1 - i); NavigableSet q = set0(); assertFalse(q.addAll(Arrays.asList(empty))); assertTrue(q.addAll(Arrays.asList(ints))); @@ -249,14 +249,14 @@ public void testRemoveElement() { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertTrue(q.contains(i-1)); + assertTrue(q.contains(i - 1)); } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertFalse(q.remove(i+1)); - assertFalse(q.contains(i+1)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); } assertTrue(q.isEmpty()); } @@ -315,7 +315,7 @@ public void testRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.pollFirst(); } } @@ -328,7 +328,7 @@ public void testRemoveAll() { NavigableSet q = populatedSet(SIZE); NavigableSet p = populatedSet(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { Integer x = (Integer)(p.pollFirst()); assertFalse(q.contains(x)); @@ -623,7 +623,7 @@ public void testTailSetContents() { public void testDescendingSize() { NavigableSet q = populatedSet(SIZE); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); q.pollFirst(); } for (int i = 0; i < SIZE; ++i) { @@ -702,8 +702,8 @@ public void testDescendingAddAll2() { public void testDescendingAddAll3() { NavigableSet q = dset0(); Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i+SIZE); + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i + SIZE); try { q.addAll(Arrays.asList(ints)); shouldThrow(); @@ -717,7 +717,7 @@ public void testDescendingAddAll5() { Integer[] empty = new Integer[0]; Integer[] ints = new Integer[SIZE]; for (int i = 0; i < SIZE; ++i) - ints[i] = new Integer(SIZE-1- i); + ints[i] = new Integer(SIZE - 1 - i); NavigableSet q = dset0(); assertFalse(q.addAll(Arrays.asList(empty))); assertTrue(q.addAll(Arrays.asList(ints))); @@ -746,7 +746,7 @@ public void testDescendingRemoveElement() { } for (int i = 0; i < SIZE; i += 2 ) { assertTrue(q.remove(new Integer(i))); - assertFalse(q.remove(new Integer(i+1))); + assertFalse(q.remove(new Integer(i + 1))); } assertTrue(q.isEmpty()); } @@ -805,7 +805,7 @@ public void testDescendingRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.pollFirst(); } } @@ -818,7 +818,7 @@ public void testDescendingRemoveAll() { NavigableSet q = populatedSet(SIZE); NavigableSet p = populatedSet(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { Integer x = (Integer)(p.pollFirst()); assertFalse(q.contains(x)); diff --git a/jsr166-tests/src/test/java/jsr166/CopyOnWriteArrayListTest.java b/jsr166-tests/src/test/java/jsr166/CopyOnWriteArrayListTest.java index 658268a41..8a37d0304 100644 --- a/jsr166-tests/src/test/java/jsr166/CopyOnWriteArrayListTest.java +++ b/jsr166-tests/src/test/java/jsr166/CopyOnWriteArrayListTest.java @@ -31,7 +31,7 @@ public class CopyOnWriteArrayListTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(CopyOnWriteArrayListTest.class); // } static CopyOnWriteArrayList populatedArray(int n) { @@ -67,7 +67,7 @@ public void testConstructor() { */ public void testConstructor2() { Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) + for (int i = 0; i < SIZE - 1; ++i) ints[i] = new Integer(i); CopyOnWriteArrayList a = new CopyOnWriteArrayList(ints); for (int i = 0; i < SIZE; ++i) @@ -79,7 +79,7 @@ public void testConstructor2() { */ public void testConstructor3() { Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) + for (int i = 0; i < SIZE - 1; ++i) ints[i] = new Integer(i); CopyOnWriteArrayList a = new CopyOnWriteArrayList(Arrays.asList(ints)); for (int i = 0; i < SIZE; ++i) @@ -181,18 +181,26 @@ public void testEquals() { CopyOnWriteArrayList b = populatedArray(3); assertTrue(a.equals(b)); assertTrue(b.equals(a)); + assertTrue(a.containsAll(b)); + assertTrue(b.containsAll(a)); assertEquals(a.hashCode(), b.hashCode()); a.add(m1); assertFalse(a.equals(b)); assertFalse(b.equals(a)); + assertTrue(a.containsAll(b)); + assertFalse(b.containsAll(a)); b.add(m1); assertTrue(a.equals(b)); assertTrue(b.equals(a)); + assertTrue(a.containsAll(b)); + assertTrue(b.containsAll(a)); assertEquals(a.hashCode(), b.hashCode()); + + assertFalse(a.equals(null)); } /** - * containsAll returns true for collection with subset of elements + * containsAll returns true for collections with subset of elements */ public void testContainsAll() { CopyOnWriteArrayList full = populatedArray(3); @@ -201,6 +209,11 @@ public void testContainsAll() { assertTrue(full.containsAll(Arrays.asList(one, two))); assertFalse(full.containsAll(Arrays.asList(one, two, six))); assertFalse(full.containsAll(Arrays.asList(six))); + + try { + full.containsAll(null); + shouldThrow(); + } catch (NullPointerException success) {} } /** @@ -342,7 +355,7 @@ public void testListIterator2() { ListIterator i = full.listIterator(1); int j; for (j = 0; i.hasNext(); j++) - assertEquals(j+1, i.next()); + assertEquals(j + 1, i.next()); assertEquals(2, j); } @@ -441,7 +454,7 @@ public void testToArray2() { a = new Integer[0]; assertSame(a, empty.toArray(a)); - a = new Integer[SIZE/2]; + a = new Integer[SIZE / 2]; Arrays.fill(a, 42); assertSame(a, empty.toArray(a)); assertNull(a[0]); @@ -465,7 +478,7 @@ public void testToArray2() { assertSame(a, full.toArray(a)); assertTrue(Arrays.equals(elements, a)); - a = new Integer[2*SIZE]; + a = new Integer[2 * SIZE]; Arrays.fill(a, 42); assertSame(a, full.toArray(a)); assertTrue(Arrays.equals(elements, Arrays.copyOf(a, SIZE))); diff --git a/jsr166-tests/src/test/java/jsr166/CopyOnWriteArraySetTest.java b/jsr166-tests/src/test/java/jsr166/CopyOnWriteArraySetTest.java index 281080232..a486c6ae5 100644 --- a/jsr166-tests/src/test/java/jsr166/CopyOnWriteArraySetTest.java +++ b/jsr166-tests/src/test/java/jsr166/CopyOnWriteArraySetTest.java @@ -28,7 +28,7 @@ public class CopyOnWriteArraySetTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(CopyOnWriteArraySetTest.class); // } static CopyOnWriteArraySet populatedSet(int n) { @@ -64,7 +64,7 @@ public void testConstructor() { */ public void testConstructor3() { Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) + for (int i = 0; i < SIZE - 1; ++i) ints[i] = new Integer(i); CopyOnWriteArraySet a = new CopyOnWriteArraySet(Arrays.asList(ints)); for (int i = 0; i < SIZE; ++i) @@ -139,14 +139,46 @@ public void testEquals() { CopyOnWriteArraySet b = populatedSet(3); assertTrue(a.equals(b)); assertTrue(b.equals(a)); + assertTrue(a.containsAll(b)); + assertTrue(b.containsAll(a)); assertEquals(a.hashCode(), b.hashCode()); + assertEquals(a.size(), b.size()); + a.add(m1); assertFalse(a.equals(b)); assertFalse(b.equals(a)); + assertTrue(a.containsAll(b)); + assertFalse(b.containsAll(a)); b.add(m1); assertTrue(a.equals(b)); assertTrue(b.equals(a)); + assertTrue(a.containsAll(b)); + assertTrue(b.containsAll(a)); + assertEquals(a.hashCode(), b.hashCode()); + + Object x = a.iterator().next(); + a.remove(x); + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + assertFalse(a.containsAll(b)); + assertTrue(b.containsAll(a)); + a.add(x); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertTrue(a.containsAll(b)); + assertTrue(b.containsAll(a)); assertEquals(a.hashCode(), b.hashCode()); + assertEquals(a.size(), b.size()); + + CopyOnWriteArraySet empty1 = new CopyOnWriteArraySet(Arrays.asList()); + CopyOnWriteArraySet empty2 = new CopyOnWriteArraySet(Arrays.asList()); + assertTrue(empty1.equals(empty1)); + assertTrue(empty1.equals(empty2)); + + assertFalse(empty1.equals(a)); + assertFalse(a.equals(empty1)); + + assertFalse(a.equals(null)); } /** @@ -154,11 +186,24 @@ public void testEquals() { */ public void testContainsAll() { Collection full = populatedSet(3); + assertTrue(full.containsAll(full)); assertTrue(full.containsAll(Arrays.asList())); assertTrue(full.containsAll(Arrays.asList(one))); assertTrue(full.containsAll(Arrays.asList(one, two))); assertFalse(full.containsAll(Arrays.asList(one, two, six))); assertFalse(full.containsAll(Arrays.asList(six))); + + CopyOnWriteArraySet empty1 = new CopyOnWriteArraySet(Arrays.asList()); + CopyOnWriteArraySet empty2 = new CopyOnWriteArraySet(Arrays.asList()); + assertTrue(empty1.containsAll(empty2)); + assertTrue(empty1.containsAll(empty1)); + assertFalse(empty1.containsAll(full)); + assertTrue(full.containsAll(empty1)); + + try { + full.containsAll(null); + shouldThrow(); + } catch (NullPointerException success) {} } /** @@ -289,7 +334,7 @@ public void testToArray2() { a = new Integer[0]; assertSame(a, empty.toArray(a)); - a = new Integer[SIZE/2]; + a = new Integer[SIZE / 2]; Arrays.fill(a, 42); assertSame(a, empty.toArray(a)); assertNull(a[0]); @@ -313,7 +358,7 @@ public void testToArray2() { assertSame(a, full.toArray(a)); assertTrue(Arrays.equals(elements, a)); - a = new Integer[2*SIZE]; + a = new Integer[2 * SIZE]; Arrays.fill(a, 42); assertSame(a, full.toArray(a)); assertTrue(Arrays.equals(elements, Arrays.copyOf(a, SIZE))); @@ -327,10 +372,10 @@ public void testToArray2() { * not store the objects inside the set */ public void testToArray_ArrayStoreException() { + CopyOnWriteArraySet c = new CopyOnWriteArraySet(); + c.add("zfasdfsdf"); + c.add("asdadasd"); try { - CopyOnWriteArraySet c = new CopyOnWriteArraySet(); - c.add("zfasdfsdf"); - c.add("asdadasd"); c.toArray(new Long[5]); shouldThrow(); } catch (ArrayStoreException success) {} diff --git a/jsr166-tests/src/test/java/jsr166/CountDownLatchTest.java b/jsr166-tests/src/test/java/jsr166/CountDownLatchTest.java index da1ebb408..e764c9ebb 100644 --- a/jsr166-tests/src/test/java/jsr166/CountDownLatchTest.java +++ b/jsr166-tests/src/test/java/jsr166/CountDownLatchTest.java @@ -23,7 +23,7 @@ public class CountDownLatchTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(CountDownLatchTest.class); // } /** diff --git a/jsr166-tests/src/test/java/jsr166/CountedCompleterTest.java b/jsr166-tests/src/test/java/jsr166/CountedCompleterTest.java index 80d7b3bf5..8a38efff9 100644 --- a/jsr166-tests/src/test/java/jsr166/CountedCompleterTest.java +++ b/jsr166-tests/src/test/java/jsr166/CountedCompleterTest.java @@ -31,7 +31,7 @@ public class CountedCompleterTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(CountedCompleterTest.class); // } // Runs with "mainPool" use > 1 thread. singletonPool tests use 1 @@ -53,7 +53,7 @@ private static ForkJoinPool asyncSingletonPool() { } private void testInvokeOnPool(ForkJoinPool pool, ForkJoinTask a) { - try { + try (PoolCleaner cleaner = cleaner(pool)) { assertFalse(a.isDone()); assertFalse(a.isCompletedNormally()); assertFalse(a.isCompletedAbnormally()); @@ -69,8 +69,6 @@ private void testInvokeOnPool(ForkJoinPool pool, ForkJoinTask a) { assertFalse(a.isCancelled()); assertNull(a.getException()); assertNull(a.getRawResult()); - } finally { - joinPool(pool); } } @@ -99,17 +97,17 @@ void checkCompletedNormally(CountedCompleter a) { { Thread.currentThread().interrupt(); - long t0 = System.nanoTime(); + long startTime = System.nanoTime(); assertNull(a.join()); - assertTrue(millisElapsedSince(t0) < SMALL_DELAY_MS); + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); Thread.interrupted(); } { Thread.currentThread().interrupt(); - long t0 = System.nanoTime(); + long startTime = System.nanoTime(); a.quietlyJoin(); // should be no-op - assertTrue(millisElapsedSince(t0) < SMALL_DELAY_MS); + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); Thread.interrupted(); } @@ -142,9 +140,9 @@ void checkCancelled(CountedCompleter a) { Thread.interrupted(); { - long t0 = System.nanoTime(); + long startTime = System.nanoTime(); a.quietlyJoin(); // should be no-op - assertTrue(millisElapsedSince(t0) < SMALL_DELAY_MS); + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); } try { @@ -180,9 +178,9 @@ void checkCompletedAbnormally(CountedCompleter a, Throwable t) { Thread.interrupted(); { - long t0 = System.nanoTime(); + long startTime = System.nanoTime(); a.quietlyJoin(); // should be no-op - assertTrue(millisElapsedSince(t0) < SMALL_DELAY_MS); + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); } try { @@ -284,6 +282,9 @@ void checkCompletedExceptionally(Throwable ex) { final class NoopCC extends CheckedCC { NoopCC() { super(); } NoopCC(CountedCompleter p) { super(p); } + NoopCC(CountedCompleter p, int initialPendingCount) { + super(p, initialPendingCount); + } protected void realCompute() {} } @@ -302,6 +303,7 @@ public void testComplete() { void testComplete(NoopCC cc, Object x, int pendingCount) { cc.setPendingCount(pendingCount); cc.checkCompletes(x); + assertEquals(pendingCount, cc.getPendingCount()); } /** @@ -315,14 +317,20 @@ public void testCompleteExceptionally() { } /** - * completeExceptionally(null) throws NullPointerException + * completeExceptionally(null) surprisingly has the same effect as + * completeExceptionally(new RuntimeException()) */ public void testCompleteExceptionally_null() { + NoopCC a = new NoopCC(); + a.completeExceptionally(null); try { - new NoopCC() - .checkCompletesExceptionally(null); + a.invoke(); shouldThrow(); - } catch (NullPointerException success) {} + } catch (RuntimeException success) { + assertSame(success.getClass(), RuntimeException.class); + assertNull(success.getCause()); + a.checkCompletedExceptionally(success); + } } /** @@ -331,10 +339,15 @@ public void testCompleteExceptionally_null() { public void testSetPendingCount() { NoopCC a = new NoopCC(); assertEquals(0, a.getPendingCount()); - a.setPendingCount(1); - assertEquals(1, a.getPendingCount()); - a.setPendingCount(27); - assertEquals(27, a.getPendingCount()); + int[] vals = { + -1, 0, 1, + Integer.MIN_VALUE, + Integer.MAX_VALUE, + }; + for (int val : vals) { + a.setPendingCount(val); + assertEquals(val, a.getPendingCount()); + } } /** @@ -347,21 +360,26 @@ public void testAddToPendingCount() { assertEquals(1, a.getPendingCount()); a.addToPendingCount(27); assertEquals(28, a.getPendingCount()); + a.addToPendingCount(-28); + assertEquals(0, a.getPendingCount()); } /** * decrementPendingCountUnlessZero decrements reported pending * count unless zero */ - public void testDecrementPendingCount() { - NoopCC a = new NoopCC(); - assertEquals(0, a.getPendingCount()); - a.addToPendingCount(1); + public void testDecrementPendingCountUnlessZero() { + NoopCC a = new NoopCC(null, 2); + assertEquals(2, a.getPendingCount()); + assertEquals(2, a.decrementPendingCountUnlessZero()); assertEquals(1, a.getPendingCount()); - a.decrementPendingCountUnlessZero(); + assertEquals(1, a.decrementPendingCountUnlessZero()); assertEquals(0, a.getPendingCount()); - a.decrementPendingCountUnlessZero(); + assertEquals(0, a.decrementPendingCountUnlessZero()); assertEquals(0, a.getPendingCount()); + a.setPendingCount(-1); + assertEquals(-1, a.decrementPendingCountUnlessZero()); + assertEquals(-2, a.getPendingCount()); } /** @@ -485,7 +503,7 @@ public void testNextComplete() { } /** - * quietlyCompleteRoot completes root task + * quietlyCompleteRoot completes root task and only root task */ public void testQuietlyCompleteRoot() { NoopCC a = new NoopCC(); diff --git a/jsr166-tests/src/test/java/jsr166/CyclicBarrierTest.java b/jsr166-tests/src/test/java/jsr166/CyclicBarrierTest.java index a9d8c5463..37adcb1f0 100644 --- a/jsr166-tests/src/test/java/jsr166/CyclicBarrierTest.java +++ b/jsr166-tests/src/test/java/jsr166/CyclicBarrierTest.java @@ -27,7 +27,7 @@ public class CyclicBarrierTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(CyclicBarrierTest.class); // } private volatile int countAction; diff --git a/jsr166-tests/src/test/java/jsr166/DelayQueueTest.java b/jsr166-tests/src/test/java/jsr166/DelayQueueTest.java index 7619b4894..e42ac2dfb 100644 --- a/jsr166-tests/src/test/java/jsr166/DelayQueueTest.java +++ b/jsr166-tests/src/test/java/jsr166/DelayQueueTest.java @@ -25,25 +25,26 @@ import junit.framework.Test; -// android-changed: Extend BlockingQueueTest directly. -public class DelayQueueTest extends BlockingQueueTest { +public class DelayQueueTest extends JSR166TestCase { // android-changed: Extend BlockingQueueTest directly instead of creating // an inner class and its associated suite. // // public static class Generic extends BlockingQueueTest { - // protected BlockingQueue emptyCollection() { + // protected BlockingQueue emptyCollection() { // return new DelayQueue(); // } // protected PDelay makeElement(int i) { // return new PDelay(i); // } // } + + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. // // public static void main(String[] args) { // main(suite(), args); // } - // // public static Test suite() { // return newTestSuite(DelayQueueTest.class, // new Generic().testSuite()); @@ -138,7 +139,7 @@ public String toString() { private DelayQueue populatedQueue(int n) { DelayQueue q = new DelayQueue(); assertTrue(q.isEmpty()); - for (int i = n-1; i >= 0; i -= 2) + for (int i = n - 1; i >= 0; i -= 2) assertTrue(q.offer(new PDelay(i))); for (int i = (n & 1); i < n; i += 2) assertTrue(q.offer(new PDelay(i))); @@ -170,8 +171,7 @@ public void testConstructor3() { */ public void testConstructor4() { try { - PDelay[] ints = new PDelay[SIZE]; - new DelayQueue(Arrays.asList(ints)); + new DelayQueue(Arrays.asList(new PDelay[SIZE])); shouldThrow(); } catch (NullPointerException success) {} } @@ -180,11 +180,11 @@ public void testConstructor4() { * Initializing from Collection with some null elements throws NPE */ public void testConstructor5() { + PDelay[] a = new PDelay[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + a[i] = new PDelay(i); try { - PDelay[] ints = new PDelay[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new PDelay(i); - new DelayQueue(Arrays.asList(ints)); + new DelayQueue(Arrays.asList(a)); shouldThrow(); } catch (NullPointerException success) {} } @@ -223,7 +223,7 @@ public void testRemainingCapacity() { BlockingQueue q = populatedQueue(SIZE); for (int i = 0; i < SIZE; ++i) { assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); assertTrue(q.remove() instanceof PDelay); } for (int i = 0; i < SIZE; ++i) { @@ -257,8 +257,8 @@ public void testAdd() { * addAll(this) throws IAE */ public void testAddAllSelf() { + DelayQueue q = populatedQueue(SIZE); try { - DelayQueue q = populatedQueue(SIZE); q.addAll(q); shouldThrow(); } catch (IllegalArgumentException success) {} @@ -269,12 +269,12 @@ public void testAddAllSelf() { * possibly adding some elements */ public void testAddAll3() { + DelayQueue q = new DelayQueue(); + PDelay[] a = new PDelay[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + a[i] = new PDelay(i); try { - DelayQueue q = new DelayQueue(); - PDelay[] ints = new PDelay[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new PDelay(i); - q.addAll(Arrays.asList(ints)); + q.addAll(Arrays.asList(a)); shouldThrow(); } catch (NullPointerException success) {} } @@ -285,7 +285,7 @@ public void testAddAll3() { public void testAddAll5() { PDelay[] empty = new PDelay[0]; PDelay[] ints = new PDelay[SIZE]; - for (int i = SIZE-1; i >= 0; --i) + for (int i = SIZE - 1; i >= 0; --i) ints[i] = new PDelay(i); DelayQueue q = new DelayQueue(); assertFalse(q.addAll(Arrays.asList(empty))); @@ -427,11 +427,13 @@ public void testTimedPoll() throws InterruptedException { */ public void testInterruptedTimedPoll() throws InterruptedException { final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + final DelayQueue q = populatedQueue(SIZE); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { - DelayQueue q = populatedQueue(SIZE); + long startTime = System.nanoTime(); for (int i = 0; i < SIZE; ++i) { - assertEquals(new PDelay(i), ((PDelay)q.poll(SHORT_DELAY_MS, MILLISECONDS))); + assertEquals(new PDelay(i), + ((PDelay)q.poll(LONG_DELAY_MS, MILLISECONDS))); } Thread.currentThread().interrupt(); @@ -447,12 +449,14 @@ public void realRun() throws InterruptedException { shouldThrow(); } catch (InterruptedException success) {} assertFalse(Thread.interrupted()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); }}); await(pleaseInterrupt); assertThreadStaysAlive(t); t.interrupt(); awaitTermination(t); + checkEmpty(q); } /** @@ -557,7 +561,7 @@ public void testRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.remove(); } } @@ -570,7 +574,7 @@ public void testRemoveAll() { DelayQueue q = populatedQueue(SIZE); DelayQueue p = populatedQueue(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { PDelay x = (PDelay)(p.remove()); assertFalse(q.contains(x)); @@ -668,22 +672,22 @@ public void testToString() { public void testPollInExecutor() { final DelayQueue q = new DelayQueue(); final CheckedBarrier threadsStarted = new CheckedBarrier(2); - ExecutorService executor = Executors.newFixedThreadPool(2); - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - assertNull(q.poll()); - threadsStarted.await(); - assertNotNull(q.poll(LONG_DELAY_MS, MILLISECONDS)); - checkEmpty(q); - }}); - - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - threadsStarted.await(); - q.put(new PDelay(1)); - }}); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertNull(q.poll()); + threadsStarted.await(); + assertNotNull(q.poll(LONG_DELAY_MS, MILLISECONDS)); + checkEmpty(q); + }}); - joinPool(executor); + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + q.put(new PDelay(1)); + }}); + } } /** @@ -768,7 +772,7 @@ public void testDrainToWithActivePut() throws InterruptedException { final DelayQueue q = populatedQueue(SIZE); Thread t = new Thread(new CheckedRunnable() { public void realRun() { - q.put(new PDelay(SIZE+1)); + q.put(new PDelay(SIZE + 1)); }}); t.start(); @@ -788,7 +792,7 @@ public void testDrainToN() { ArrayList l = new ArrayList(); q.drainTo(l, i); int k = (i < SIZE) ? i : SIZE; - assertEquals(SIZE-k, q.size()); + assertEquals(SIZE - k, q.size()); assertEquals(k, l.size()); } } diff --git a/jsr166-tests/src/test/java/jsr166/DoubleAccumulatorTest.java b/jsr166-tests/src/test/java/jsr166/DoubleAccumulatorTest.java new file mode 100644 index 000000000..e061f9a3f --- /dev/null +++ b/jsr166-tests/src/test/java/jsr166/DoubleAccumulatorTest.java @@ -0,0 +1,161 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package jsr166; + +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Phaser; +import java.util.concurrent.atomic.DoubleAccumulator; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class DoubleAccumulatorTest extends JSR166TestCase { + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. + // + // public static void main(String[] args) { + // main(suite(), args); + // } + // public static Test suite() { + // return new TestSuite(DoubleAccumulatorTest.class); + // } + + /** + * default constructed initializes to zero + */ + public void testConstructor() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + assertEquals(0.0, ai.get()); + } + + /** + * accumulate accumulates given value to current, and get returns current value + */ + public void testAccumulateAndGet() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + ai.accumulate(2.0); + assertEquals(2.0, ai.get()); + ai.accumulate(-4.0); + assertEquals(2.0, ai.get()); + ai.accumulate(4.0); + assertEquals(4.0, ai.get()); + } + + /** + * reset() causes subsequent get() to return zero + */ + public void testReset() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + ai.accumulate(2.0); + assertEquals(2.0, ai.get()); + ai.reset(); + assertEquals(0.0, ai.get()); + } + + /** + * getThenReset() returns current value; subsequent get() returns zero + */ + public void testGetThenReset() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + ai.accumulate(2.0); + assertEquals(2.0, ai.get()); + assertEquals(2.0, ai.getThenReset()); + assertEquals(0.0, ai.get()); + } + + /** + * toString returns current value. + */ + public void testToString() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + assertEquals("0.0", ai.toString()); + ai.accumulate(1.0); + assertEquals(Double.toString(1.0), ai.toString()); + } + + /** + * intValue returns current value. + */ + public void testIntValue() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + assertEquals(0, ai.intValue()); + ai.accumulate(1.0); + assertEquals(1, ai.intValue()); + } + + /** + * longValue returns current value. + */ + public void testLongValue() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + assertEquals(0, ai.longValue()); + ai.accumulate(1.0); + assertEquals(1, ai.longValue()); + } + + /** + * floatValue returns current value. + */ + public void testFloatValue() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + assertEquals(0.0f, ai.floatValue()); + ai.accumulate(1.0); + assertEquals(1.0f, ai.floatValue()); + } + + /** + * doubleValue returns current value. + */ + public void testDoubleValue() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + assertEquals(0.0, ai.doubleValue()); + ai.accumulate(1.0); + assertEquals(1.0, ai.doubleValue()); + } + + /** + * accumulates by multiple threads produce correct result + */ + public void testAccumulateAndGetMT() { + final int incs = 1000000; + final int nthreads = 4; + final ExecutorService pool = Executors.newCachedThreadPool(); + DoubleAccumulator a = new DoubleAccumulator(Double::max, 0.0); + Phaser phaser = new Phaser(nthreads + 1); + for (int i = 0; i < nthreads; ++i) + pool.execute(new AccTask(a, phaser, incs)); + phaser.arriveAndAwaitAdvance(); + phaser.arriveAndAwaitAdvance(); + double expected = incs - 1; + double result = a.get(); + assertEquals(expected, result); + pool.shutdown(); + } + + static final class AccTask implements Runnable { + final DoubleAccumulator acc; + final Phaser phaser; + final int incs; + volatile double result; + AccTask(DoubleAccumulator acc, Phaser phaser, int incs) { + this.acc = acc; + this.phaser = phaser; + this.incs = incs; + } + + public void run() { + phaser.arriveAndAwaitAdvance(); + DoubleAccumulator a = acc; + for (int i = 0; i < incs; ++i) + a.accumulate(i); + result = a.get(); + phaser.arrive(); + } + } + +} diff --git a/jsr166-tests/src/test/java/jsr166/DoubleAdderTest.java b/jsr166-tests/src/test/java/jsr166/DoubleAdderTest.java new file mode 100644 index 000000000..d02e2a112 --- /dev/null +++ b/jsr166-tests/src/test/java/jsr166/DoubleAdderTest.java @@ -0,0 +1,175 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package jsr166; + +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.DoubleAdder; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class DoubleAdderTest extends JSR166TestCase { + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. + // + // public static void main(String[] args) { + // main(suite(), args); + // } + // public static Test suite() { + // return new TestSuite(DoubleAdderTest.class); + // } + + /** + * default constructed initializes to zero + */ + public void testConstructor() { + DoubleAdder ai = new DoubleAdder(); + assertEquals(0.0, ai.sum()); + } + + /** + * add adds given value to current, and sum returns current value + */ + public void testAddAndSum() { + DoubleAdder ai = new DoubleAdder(); + ai.add(2.0); + assertEquals(2.0, ai.sum()); + ai.add(-4.0); + assertEquals(-2.0, ai.sum()); + } + + /** + * reset() causes subsequent sum() to return zero + */ + public void testReset() { + DoubleAdder ai = new DoubleAdder(); + ai.add(2.0); + assertEquals(2.0, ai.sum()); + ai.reset(); + assertEquals(0.0, ai.sum()); + } + + /** + * sumThenReset() returns sum; subsequent sum() returns zero + */ + public void testSumThenReset() { + DoubleAdder ai = new DoubleAdder(); + ai.add(2.0); + assertEquals(2.0, ai.sum()); + assertEquals(2.0, ai.sumThenReset()); + assertEquals(0.0, ai.sum()); + } + + /** + * a deserialized serialized adder holds same value + */ + public void testSerialization() throws Exception { + DoubleAdder x = new DoubleAdder(); + DoubleAdder y = serialClone(x); + assertNotSame(x, y); + x.add(-22.0); + DoubleAdder z = serialClone(x); + assertEquals(-22.0, x.sum()); + assertEquals(0.0, y.sum()); + assertEquals(-22.0, z.sum()); + } + + /** + * toString returns current value. + */ + public void testToString() { + DoubleAdder ai = new DoubleAdder(); + assertEquals(Double.toString(0.0), ai.toString()); + ai.add(1.0); + assertEquals(Double.toString(1.0), ai.toString()); + } + + /** + * intValue returns current value. + */ + public void testIntValue() { + DoubleAdder ai = new DoubleAdder(); + assertEquals(0, ai.intValue()); + ai.add(1.0); + assertEquals(1, ai.intValue()); + } + + /** + * longValue returns current value. + */ + public void testLongValue() { + DoubleAdder ai = new DoubleAdder(); + assertEquals(0, ai.longValue()); + ai.add(1.0); + assertEquals(1, ai.longValue()); + } + + /** + * floatValue returns current value. + */ + public void testFloatValue() { + DoubleAdder ai = new DoubleAdder(); + assertEquals(0.0f, ai.floatValue()); + ai.add(1.0); + assertEquals(1.0f, ai.floatValue()); + } + + /** + * doubleValue returns current value. + */ + public void testDoubleValue() { + DoubleAdder ai = new DoubleAdder(); + assertEquals(0.0, ai.doubleValue()); + ai.add(1.0); + assertEquals(1.0, ai.doubleValue()); + } + + /** + * adds by multiple threads produce correct sum + */ + public void testAddAndSumMT() throws Throwable { + final int incs = 1000000; + final int nthreads = 4; + final ExecutorService pool = Executors.newCachedThreadPool(); + DoubleAdder a = new DoubleAdder(); + CyclicBarrier barrier = new CyclicBarrier(nthreads + 1); + for (int i = 0; i < nthreads; ++i) + pool.execute(new AdderTask(a, barrier, incs)); + barrier.await(); + barrier.await(); + double total = (long)nthreads * incs; + double sum = a.sum(); + assertEquals(sum, total); + pool.shutdown(); + } + + static final class AdderTask implements Runnable { + final DoubleAdder adder; + final CyclicBarrier barrier; + final int incs; + volatile double result; + AdderTask(DoubleAdder adder, CyclicBarrier barrier, int incs) { + this.adder = adder; + this.barrier = barrier; + this.incs = incs; + } + + public void run() { + try { + barrier.await(); + DoubleAdder a = adder; + for (int i = 0; i < incs; ++i) + a.add(1.0); + result = a.sum(); + barrier.await(); + } catch (Throwable t) { throw new Error(t); } + } + } + +} diff --git a/jsr166-tests/src/test/java/jsr166/EntryTest.java b/jsr166-tests/src/test/java/jsr166/EntryTest.java index d141a84bf..72740e312 100644 --- a/jsr166-tests/src/test/java/jsr166/EntryTest.java +++ b/jsr166-tests/src/test/java/jsr166/EntryTest.java @@ -20,7 +20,7 @@ public class EntryTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(EntryTest.class); // } static final String k1 = "1"; diff --git a/jsr166-tests/src/test/java/jsr166/ExchangerTest.java b/jsr166-tests/src/test/java/jsr166/ExchangerTest.java index 172fccd42..b1119803b 100644 --- a/jsr166-tests/src/test/java/jsr166/ExchangerTest.java +++ b/jsr166-tests/src/test/java/jsr166/ExchangerTest.java @@ -26,7 +26,7 @@ public class ExchangerTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ExchangerTest.class); // } /** diff --git a/jsr166-tests/src/test/java/jsr166/ExecutorCompletionServiceTest.java b/jsr166-tests/src/test/java/jsr166/ExecutorCompletionServiceTest.java index e988cc6c7..0f58e786e 100644 --- a/jsr166-tests/src/test/java/jsr166/ExecutorCompletionServiceTest.java +++ b/jsr166-tests/src/test/java/jsr166/ExecutorCompletionServiceTest.java @@ -33,7 +33,7 @@ public class ExecutorCompletionServiceTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ExecutorCompletionServiceTest.class); // } /** @@ -61,15 +61,14 @@ public void testConstructorNPE2() { * Submitting a null callable throws NPE */ public void testSubmitNPE() { - ExecutorService e = Executors.newCachedThreadPool(); - ExecutorCompletionService ecs = new ExecutorCompletionService(e); - try { + final ExecutorService e = Executors.newCachedThreadPool(); + final ExecutorCompletionService ecs = new ExecutorCompletionService(e); + try (PoolCleaner cleaner = cleaner(e)) { Callable c = null; - ecs.submit(c); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try { + ecs.submit(c); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -77,15 +76,14 @@ public void testSubmitNPE() { * Submitting a null runnable throws NPE */ public void testSubmitNPE2() { - ExecutorService e = Executors.newCachedThreadPool(); - ExecutorCompletionService ecs = new ExecutorCompletionService(e); - try { + final ExecutorService e = Executors.newCachedThreadPool(); + final ExecutorCompletionService ecs = new ExecutorCompletionService(e); + try (PoolCleaner cleaner = cleaner(e)) { Runnable r = null; - ecs.submit(r, Boolean.TRUE); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try { + ecs.submit(r, Boolean.TRUE); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -93,15 +91,13 @@ public void testSubmitNPE2() { * A taken submitted task is completed */ public void testTake() throws InterruptedException { - ExecutorService e = Executors.newCachedThreadPool(); - ExecutorCompletionService ecs = new ExecutorCompletionService(e); - try { + final ExecutorService e = Executors.newCachedThreadPool(); + final ExecutorCompletionService ecs = new ExecutorCompletionService(e); + try (PoolCleaner cleaner = cleaner(e)) { Callable c = new StringTask(); ecs.submit(c); Future f = ecs.take(); assertTrue(f.isDone()); - } finally { - joinPool(e); } } @@ -109,15 +105,13 @@ public void testTake() throws InterruptedException { * Take returns the same future object returned by submit */ public void testTake2() throws InterruptedException { - ExecutorService e = Executors.newCachedThreadPool(); - ExecutorCompletionService ecs = new ExecutorCompletionService(e); - try { + final ExecutorService e = Executors.newCachedThreadPool(); + final ExecutorCompletionService ecs = new ExecutorCompletionService(e); + try (PoolCleaner cleaner = cleaner(e)) { Callable c = new StringTask(); Future f1 = ecs.submit(c); Future f2 = ecs.take(); assertSame(f1, f2); - } finally { - joinPool(e); } } @@ -125,9 +119,9 @@ public void testTake2() throws InterruptedException { * If poll returns non-null, the returned task is completed */ public void testPoll1() throws Exception { - ExecutorService e = Executors.newCachedThreadPool(); - ExecutorCompletionService ecs = new ExecutorCompletionService(e); - try { + final ExecutorService e = Executors.newCachedThreadPool(); + final ExecutorCompletionService ecs = new ExecutorCompletionService(e); + try (PoolCleaner cleaner = cleaner(e)) { assertNull(ecs.poll()); Callable c = new StringTask(); ecs.submit(c); @@ -141,8 +135,6 @@ public void testPoll1() throws Exception { } assertTrue(f.isDone()); assertSame(TEST_STRING, f.get()); - } finally { - joinPool(e); } } @@ -150,17 +142,15 @@ public void testPoll1() throws Exception { * If timed poll returns non-null, the returned task is completed */ public void testPoll2() throws InterruptedException { - ExecutorService e = Executors.newCachedThreadPool(); - ExecutorCompletionService ecs = new ExecutorCompletionService(e); - try { + final ExecutorService e = Executors.newCachedThreadPool(); + final ExecutorCompletionService ecs = new ExecutorCompletionService(e); + try (PoolCleaner cleaner = cleaner(e)) { assertNull(ecs.poll()); Callable c = new StringTask(); ecs.submit(c); Future f = ecs.poll(SHORT_DELAY_MS, MILLISECONDS); if (f != null) assertTrue(f.isDone()); - } finally { - joinPool(e); } } @@ -174,15 +164,16 @@ class MyCallableFuture extends FutureTask { MyCallableFuture(Callable c) { super(c); } protected void done() { done.set(true); } } - ExecutorService e = new ThreadPoolExecutor( - 1, 1, 30L, TimeUnit.SECONDS, - new ArrayBlockingQueue(1)) { - protected RunnableFuture newTaskFor(Callable c) { - return new MyCallableFuture(c); - }}; + final ExecutorService e = + new ThreadPoolExecutor(1, 1, + 30L, TimeUnit.SECONDS, + new ArrayBlockingQueue(1)) { + protected RunnableFuture newTaskFor(Callable c) { + return new MyCallableFuture(c); + }}; ExecutorCompletionService ecs = new ExecutorCompletionService(e); - try { + try (PoolCleaner cleaner = cleaner(e)) { assertNull(ecs.poll()); Callable c = new StringTask(); Future f1 = ecs.submit(c); @@ -191,8 +182,6 @@ protected RunnableFuture newTaskFor(Callable c) { Future f2 = ecs.take(); assertSame("submit and take must return same objects", f1, f2); assertTrue("completed task must have set done", done.get()); - } finally { - joinPool(e); } } @@ -206,15 +195,16 @@ class MyRunnableFuture extends FutureTask { MyRunnableFuture(Runnable t, V r) { super(t, r); } protected void done() { done.set(true); } } - ExecutorService e = new ThreadPoolExecutor( - 1, 1, 30L, TimeUnit.SECONDS, - new ArrayBlockingQueue(1)) { - protected RunnableFuture newTaskFor(Runnable t, T r) { - return new MyRunnableFuture(t, r); - }}; - ExecutorCompletionService ecs = + final ExecutorService e = + new ThreadPoolExecutor(1, 1, + 30L, TimeUnit.SECONDS, + new ArrayBlockingQueue(1)) { + protected RunnableFuture newTaskFor(Runnable t, T r) { + return new MyRunnableFuture(t, r); + }}; + final ExecutorCompletionService ecs = new ExecutorCompletionService(e); - try { + try (PoolCleaner cleaner = cleaner(e)) { assertNull(ecs.poll()); Runnable r = new NoOpRunnable(); Future f1 = ecs.submit(r, null); @@ -223,8 +213,6 @@ protected RunnableFuture newTaskFor(Runnable t, T r) { Future f2 = ecs.take(); assertSame("submit and take must return same objects", f1, f2); assertTrue("completed task must have set done", done.get()); - } finally { - joinPool(e); } } diff --git a/jsr166-tests/src/test/java/jsr166/ExecutorsTest.java b/jsr166-tests/src/test/java/jsr166/ExecutorsTest.java index ae475f1a7..d70ae6e39 100644 --- a/jsr166-tests/src/test/java/jsr166/ExecutorsTest.java +++ b/jsr166-tests/src/test/java/jsr166/ExecutorsTest.java @@ -36,29 +36,31 @@ public class ExecutorsTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ExecutorsTest.class); // } /** * A newCachedThreadPool can execute runnables */ public void testNewCachedThreadPool1() { - ExecutorService e = Executors.newCachedThreadPool(); - e.execute(new NoOpRunnable()); - e.execute(new NoOpRunnable()); - e.execute(new NoOpRunnable()); - joinPool(e); + final ExecutorService e = Executors.newCachedThreadPool(); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + } } /** * A newCachedThreadPool with given ThreadFactory can execute runnables */ public void testNewCachedThreadPool2() { - ExecutorService e = Executors.newCachedThreadPool(new SimpleThreadFactory()); - e.execute(new NoOpRunnable()); - e.execute(new NoOpRunnable()); - e.execute(new NoOpRunnable()); - joinPool(e); + final ExecutorService e = Executors.newCachedThreadPool(new SimpleThreadFactory()); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + } } /** @@ -75,22 +77,24 @@ public void testNewCachedThreadPool3() { * A new SingleThreadExecutor can execute runnables */ public void testNewSingleThreadExecutor1() { - ExecutorService e = Executors.newSingleThreadExecutor(); - e.execute(new NoOpRunnable()); - e.execute(new NoOpRunnable()); - e.execute(new NoOpRunnable()); - joinPool(e); + final ExecutorService e = Executors.newSingleThreadExecutor(); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + } } /** * A new SingleThreadExecutor with given ThreadFactory can execute runnables */ public void testNewSingleThreadExecutor2() { - ExecutorService e = Executors.newSingleThreadExecutor(new SimpleThreadFactory()); - e.execute(new NoOpRunnable()); - e.execute(new NoOpRunnable()); - e.execute(new NoOpRunnable()); - joinPool(e); + final ExecutorService e = Executors.newSingleThreadExecutor(new SimpleThreadFactory()); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + } } /** @@ -107,13 +111,12 @@ public void testNewSingleThreadExecutor3() { * A new SingleThreadExecutor cannot be casted to concrete implementation */ public void testCastNewSingleThreadExecutor() { - ExecutorService e = Executors.newSingleThreadExecutor(); - try { - ThreadPoolExecutor tpe = (ThreadPoolExecutor)e; - shouldThrow(); - } catch (ClassCastException success) { - } finally { - joinPool(e); + final ExecutorService e = Executors.newSingleThreadExecutor(); + try (PoolCleaner cleaner = cleaner(e)) { + try { + ThreadPoolExecutor tpe = (ThreadPoolExecutor)e; + shouldThrow(); + } catch (ClassCastException success) {} } } @@ -121,22 +124,24 @@ public void testCastNewSingleThreadExecutor() { * A new newFixedThreadPool can execute runnables */ public void testNewFixedThreadPool1() { - ExecutorService e = Executors.newFixedThreadPool(2); - e.execute(new NoOpRunnable()); - e.execute(new NoOpRunnable()); - e.execute(new NoOpRunnable()); - joinPool(e); + final ExecutorService e = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + } } /** * A new newFixedThreadPool with given ThreadFactory can execute runnables */ public void testNewFixedThreadPool2() { - ExecutorService e = Executors.newFixedThreadPool(2, new SimpleThreadFactory()); - e.execute(new NoOpRunnable()); - e.execute(new NoOpRunnable()); - e.execute(new NoOpRunnable()); - joinPool(e); + final ExecutorService e = Executors.newFixedThreadPool(2, new SimpleThreadFactory()); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + } } /** @@ -163,11 +168,12 @@ public void testNewFixedThreadPool4() { * An unconfigurable newFixedThreadPool can execute runnables */ public void testUnconfigurableExecutorService() { - ExecutorService e = Executors.unconfigurableExecutorService(Executors.newFixedThreadPool(2)); - e.execute(new NoOpRunnable()); - e.execute(new NoOpRunnable()); - e.execute(new NoOpRunnable()); - joinPool(e); + final ExecutorService e = Executors.unconfigurableExecutorService(Executors.newFixedThreadPool(2)); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + } } /** @@ -194,8 +200,8 @@ public void testUnconfigurableScheduledExecutorServiceNPE() { * a newSingleThreadScheduledExecutor successfully runs delayed task */ public void testNewSingleThreadScheduledExecutor() throws Exception { - ScheduledExecutorService p = Executors.newSingleThreadScheduledExecutor(); - try { + final ScheduledExecutorService p = Executors.newSingleThreadScheduledExecutor(); + try (PoolCleaner cleaner = cleaner(p)) { final CountDownLatch proceed = new CountDownLatch(1); final Runnable task = new CheckedRunnable() { public void realRun() { @@ -211,8 +217,6 @@ public void realRun() { assertTrue(f.isDone()); assertFalse(f.isCancelled()); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); - } finally { - joinPool(p); } } @@ -220,8 +224,8 @@ public void realRun() { * a newScheduledThreadPool successfully runs delayed task */ public void testNewScheduledThreadPool() throws Exception { - ScheduledExecutorService p = Executors.newScheduledThreadPool(2); - try { + final ScheduledExecutorService p = Executors.newScheduledThreadPool(2); + try (PoolCleaner cleaner = cleaner(p)) { final CountDownLatch proceed = new CountDownLatch(1); final Runnable task = new CheckedRunnable() { public void realRun() { @@ -237,8 +241,6 @@ public void realRun() { assertTrue(f.isDone()); assertFalse(f.isCancelled()); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); - } finally { - joinPool(p); } } @@ -246,10 +248,10 @@ public void realRun() { * an unconfigurable newScheduledThreadPool successfully runs delayed task */ public void testUnconfigurableScheduledExecutorService() throws Exception { - ScheduledExecutorService p = + final ScheduledExecutorService p = Executors.unconfigurableScheduledExecutorService (Executors.newScheduledThreadPool(2)); - try { + try (PoolCleaner cleaner = cleaner(p)) { final CountDownLatch proceed = new CountDownLatch(1); final Runnable task = new CheckedRunnable() { public void realRun() { @@ -265,8 +267,6 @@ public void realRun() { assertTrue(f.isDone()); assertFalse(f.isCancelled()); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); - } finally { - joinPool(p); } } @@ -327,16 +327,10 @@ public void realRun() { done.countDown(); }}; ExecutorService e = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory()); - - e.execute(r); - await(done); - - try { - e.shutdown(); - } catch (SecurityException ok) { + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(r); + await(done); } - - joinPool(e); } /** @@ -366,14 +360,14 @@ public void realRun() { String name = current.getName(); assertTrue(name.endsWith("thread-1")); assertSame(thisccl, current.getContextClassLoader()); - // assertEquals(thisacc, AccessController.getContext()); + //assertEquals(thisacc, AccessController.getContext()); done.countDown(); }}; ExecutorService e = Executors.newSingleThreadExecutor(Executors.privilegedThreadFactory()); - e.execute(r); - await(done); - e.shutdown(); - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(r); + await(done); + } }}; runWithPermissions(r, diff --git a/jsr166-tests/src/test/java/jsr166/ForkJoinPool8Test.java b/jsr166-tests/src/test/java/jsr166/ForkJoinPool8Test.java new file mode 100644 index 000000000..f9f9239ea --- /dev/null +++ b/jsr166-tests/src/test/java/jsr166/ForkJoinPool8Test.java @@ -0,0 +1,1592 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package jsr166; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.HashSet; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountedCompleter; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.RecursiveAction; +import java.util.concurrent.TimeoutException; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ForkJoinPool8Test extends JSR166TestCase { + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. + // + // public static void main(String[] args) { + // main(suite(), args); + // } + // public static Test suite() { + // return new TestSuite(ForkJoinPool8Test.class); + // } + + /** + * Common pool exists and has expected parallelism. + */ + public void testCommonPoolParallelism() { + assertEquals(ForkJoinPool.getCommonPoolParallelism(), + ForkJoinPool.commonPool().getParallelism()); + } + + /** + * Common pool cannot be shut down + */ + public void testCommonPoolShutDown() { + assertFalse(ForkJoinPool.commonPool().isShutdown()); + assertFalse(ForkJoinPool.commonPool().isTerminating()); + assertFalse(ForkJoinPool.commonPool().isTerminated()); + ForkJoinPool.commonPool().shutdown(); + assertFalse(ForkJoinPool.commonPool().isShutdown()); + assertFalse(ForkJoinPool.commonPool().isTerminating()); + assertFalse(ForkJoinPool.commonPool().isTerminated()); + ForkJoinPool.commonPool().shutdownNow(); + assertFalse(ForkJoinPool.commonPool().isShutdown()); + assertFalse(ForkJoinPool.commonPool().isTerminating()); + assertFalse(ForkJoinPool.commonPool().isTerminated()); + } + + /* + * All of the following test methods are adaptations of those for + * RecursiveAction and CountedCompleter, but with all actions + * executed in the common pool, generally implicitly via + * checkInvoke. + */ + + private void checkInvoke(ForkJoinTask a) { + checkNotDone(a); + assertNull(a.invoke()); + checkCompletedNormally(a); + } + + void checkNotDone(ForkJoinTask a) { + assertFalse(a.isDone()); + assertFalse(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + + if (! ForkJoinTask.inForkJoinPool()) { + Thread.currentThread().interrupt(); + try { + a.get(); + shouldThrow(); + } catch (InterruptedException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + Thread.currentThread().interrupt(); + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (InterruptedException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + try { + a.get(0L, SECONDS); + shouldThrow(); + } catch (TimeoutException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedNormally(ForkJoinTask a) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertTrue(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + assertNull(a.join()); + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + try { + assertNull(a.get()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + assertNull(a.get(5L, SECONDS)); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCancelled(ForkJoinTask a) { + assertTrue(a.isDone()); + assertTrue(a.isCancelled()); + assertFalse(a.isCompletedNormally()); + assertTrue(a.isCompletedAbnormally()); + assertTrue(a.getException() instanceof CancellationException); + assertNull(a.getRawResult()); + + try { + a.join(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedAbnormally(ForkJoinTask a, Throwable t) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertFalse(a.isCompletedNormally()); + assertTrue(a.isCompletedAbnormally()); + assertSame(t.getClass(), a.getException().getClass()); + assertNull(a.getRawResult()); + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + + try { + a.join(); + shouldThrow(); + } catch (Throwable expected) { + assertSame(expected.getClass(), t.getClass()); + } + + try { + a.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + public static final class FJException extends RuntimeException { + public FJException() { super(); } + public FJException(Throwable cause) { super(cause); } + } + + // A simple recursive action for testing + final class FibAction extends CheckedRecursiveAction { + final int number; + int result; + FibAction(int n) { number = n; } + protected void realCompute() { + int n = number; + if (n <= 1) + result = n; + else { + FibAction f1 = new FibAction(n - 1); + FibAction f2 = new FibAction(n - 2); + invokeAll(f1, f2); + result = f1.result + f2.result; + } + } + } + + // A recursive action failing in base case + static final class FailingFibAction extends RecursiveAction { + final int number; + int result; + FailingFibAction(int n) { number = n; } + public void compute() { + int n = number; + if (n <= 1) + throw new FJException(); + else { + FailingFibAction f1 = new FailingFibAction(n - 1); + FailingFibAction f2 = new FailingFibAction(n - 2); + invokeAll(f1, f2); + result = f1.result + f2.result; + } + } + } + + /** + * invoke returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks. getRawResult of a RecursiveAction returns null; + */ + public void testInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertNull(f.invoke()); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * quietlyInvoke task returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks + */ + public void testQuietlyInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + f.quietlyInvoke(); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * join of a forked task returns when task completes + */ + public void testForkJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertNull(f.join()); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * join/quietlyJoin of a forked task succeeds in the presence of interrupts + */ + public void testJoinIgnoresInterrupts() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + final Thread myself = Thread.currentThread(); + + // test join() + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + assertNull(f.join()); + Thread.interrupted(); + assertEquals(21, f.result); + checkCompletedNormally(f); + + f = new FibAction(8); + f.cancel(true); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) { + Thread.interrupted(); + checkCancelled(f); + } + + f = new FibAction(8); + f.completeExceptionally(new FJException()); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + Thread.interrupted(); + checkCompletedAbnormally(f, success); + } + + // test quietlyJoin() + f = new FibAction(8); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + f.quietlyJoin(); + Thread.interrupted(); + assertEquals(21, f.result); + checkCompletedNormally(f); + + f = new FibAction(8); + f.cancel(true); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + f.quietlyJoin(); + Thread.interrupted(); + checkCancelled(f); + + f = new FibAction(8); + f.completeExceptionally(new FJException()); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + f.quietlyJoin(); + Thread.interrupted(); + checkCompletedAbnormally(f, f.getException()); + }}; + checkInvoke(a); + a.reinitialize(); + checkInvoke(a); + } + + /** + * get of a forked task returns when task completes + */ + public void testForkGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertNull(f.get()); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * timed get of a forked task returns when task completes + */ + public void testForkTimedGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertNull(f.get(5L, SECONDS)); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * timed get with null time unit throws NPE + */ + public void testForkTimedGetNPE() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + try { + f.get(5L, null); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + checkInvoke(a); + } + + /** + * quietlyJoin of a forked task returns when task completes + */ + public void testForkQuietlyJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * invoke task throws exception when task completes abnormally + */ + public void testAbnormalInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + /** + * quietlyInvoke task returns when task completes abnormally + */ + public void testAbnormalQuietlyInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + f.quietlyInvoke(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + checkInvoke(a); + } + + /** + * join of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + /** + * get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingFibAction f = new FailingFibAction(8); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + checkInvoke(a); + } + + /** + * timed get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkTimedGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingFibAction f = new FailingFibAction(8); + assertSame(f, f.fork()); + try { + f.get(5L, SECONDS); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + checkInvoke(a); + } + + /** + * quietlyJoin of a forked task returns when task completes abnormally + */ + public void testAbnormalForkQuietlyJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + checkInvoke(a); + } + + /** + * invoke task throws exception when task cancelled + */ + public void testCancelledInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + try { + f.invoke(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * join of a forked task throws exception when task cancelled + */ + public void testCancelledForkJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * get of a forked task throws exception when task cancelled + */ + public void testCancelledForkGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * timed get of a forked task throws exception when task cancelled + */ + public void testCancelledForkTimedGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(5L, SECONDS); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * quietlyJoin of a forked task returns when task cancelled + */ + public void testCancelledForkQuietlyJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + f.quietlyJoin(); + checkCancelled(f); + }}; + checkInvoke(a); + } + + /** + * inForkJoinPool of non-FJ task returns false + */ + public void testInForkJoinPool2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertFalse(inForkJoinPool()); + }}; + assertNull(a.invoke()); + } + + /** + * A reinitialized normally completed task may be re-invoked + */ + public void testReinitialize() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + checkNotDone(f); + + for (int i = 0; i < 3; i++) { + assertNull(f.invoke()); + assertEquals(21, f.result); + checkCompletedNormally(f); + f.reinitialize(); + checkNotDone(f); + } + }}; + checkInvoke(a); + } + + /** + * A reinitialized abnormally completed task may be re-invoked + */ + public void testReinitializeAbnormal() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + checkNotDone(f); + + for (int i = 0; i < 3; i++) { + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + f.reinitialize(); + checkNotDone(f); + } + }}; + checkInvoke(a); + } + + /** + * invoke task throws exception after invoking completeExceptionally + */ + public void testCompleteExceptionally() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + f.completeExceptionally(new FJException()); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + /** + * invoke task suppresses execution invoking complete + */ + public void testComplete() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + f.complete(null); + assertNull(f.invoke()); + assertEquals(0, f.result); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * invokeAll(t1, t2) invokes all task arguments + */ + public void testInvokeAll2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FibAction g = new FibAction(9); + invokeAll(f, g); + checkCompletedNormally(f); + assertEquals(21, f.result); + checkCompletedNormally(g); + assertEquals(34, g.result); + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with 1 argument invokes task + */ + public void testInvokeAll1() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + invokeAll(f); + checkCompletedNormally(f); + assertEquals(21, f.result); + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with > 2 argument invokes tasks + */ + public void testInvokeAll3() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FibAction g = new FibAction(9); + FibAction h = new FibAction(7); + invokeAll(f, g, h); + assertTrue(f.isDone()); + assertTrue(g.isDone()); + assertTrue(h.isDone()); + checkCompletedNormally(f); + assertEquals(21, f.result); + checkCompletedNormally(g); + assertEquals(34, g.result); + checkCompletedNormally(g); + assertEquals(13, h.result); + }}; + checkInvoke(a); + } + + /** + * invokeAll(collection) invokes all tasks in the collection + */ + public void testInvokeAllCollection() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FibAction g = new FibAction(9); + FibAction h = new FibAction(7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + invokeAll(set); + assertTrue(f.isDone()); + assertTrue(g.isDone()); + assertTrue(h.isDone()); + checkCompletedNormally(f); + assertEquals(21, f.result); + checkCompletedNormally(g); + assertEquals(34, g.result); + checkCompletedNormally(g); + assertEquals(13, h.result); + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with any null task throws NPE + */ + public void testInvokeAllNPE() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FibAction g = new FibAction(9); + FibAction h = null; + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + checkInvoke(a); + } + + /** + * invokeAll(t1, t2) throw exception if any task does + */ + public void testAbnormalInvokeAll2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FailingFibAction g = new FailingFibAction(9); + try { + invokeAll(f, g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with 1 argument throws exception if task does + */ + public void testAbnormalInvokeAll1() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction g = new FailingFibAction(9); + try { + invokeAll(g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with > 2 argument throws exception if any task does + */ + public void testAbnormalInvokeAll3() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FailingFibAction g = new FailingFibAction(9); + FibAction h = new FibAction(7); + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + checkInvoke(a); + } + + /** + * invokeAll(collection) throws exception if any task does + */ + public void testAbnormalInvokeAllCollection() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + FibAction g = new FibAction(9); + FibAction h = new FibAction(7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + try { + invokeAll(set); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + // CountedCompleter versions + + abstract static class CCF extends CountedCompleter { + int number; + int rnumber; + + public CCF(CountedCompleter parent, int n) { + super(parent, 1); + this.number = n; + } + + public final void compute() { + CountedCompleter p; + CCF f = this; + int n = number; + while (n >= 2) { + new RCCF(f, n - 2).fork(); + f = new LCCF(f, --n); + } + f.number = n; + f.onCompletion(f); + if ((p = f.getCompleter()) != null) + p.tryComplete(); + else + f.quietlyComplete(); + } + } + + static final class LCCF extends CCF { + public LCCF(CountedCompleter parent, int n) { + super(parent, n); + } + public final void onCompletion(CountedCompleter caller) { + CCF p = (CCF)getCompleter(); + int n = number + rnumber; + if (p != null) + p.number = n; + else + number = n; + } + } + static final class RCCF extends CCF { + public RCCF(CountedCompleter parent, int n) { + super(parent, n); + } + public final void onCompletion(CountedCompleter caller) { + CCF p = (CCF)getCompleter(); + int n = number + rnumber; + if (p != null) + p.rnumber = n; + else + number = n; + } + } + + // Version of CCF with forced failure in left completions + abstract static class FailingCCF extends CountedCompleter { + int number; + int rnumber; + + public FailingCCF(CountedCompleter parent, int n) { + super(parent, 1); + this.number = n; + } + + public final void compute() { + CountedCompleter p; + FailingCCF f = this; + int n = number; + while (n >= 2) { + new RFCCF(f, n - 2).fork(); + f = new LFCCF(f, --n); + } + f.number = n; + f.onCompletion(f); + if ((p = f.getCompleter()) != null) + p.tryComplete(); + else + f.quietlyComplete(); + } + } + + static final class LFCCF extends FailingCCF { + public LFCCF(CountedCompleter parent, int n) { + super(parent, n); + } + public final void onCompletion(CountedCompleter caller) { + FailingCCF p = (FailingCCF)getCompleter(); + int n = number + rnumber; + if (p != null) + p.number = n; + else + number = n; + } + } + static final class RFCCF extends FailingCCF { + public RFCCF(CountedCompleter parent, int n) { + super(parent, n); + } + public final void onCompletion(CountedCompleter caller) { + completeExceptionally(new FJException()); + } + } + + /** + * invoke returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks; getRawResult returns null. + */ + public void testInvokeCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + assertNull(f.invoke()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * quietlyInvoke task returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks + */ + public void testQuietlyInvokeCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + f.quietlyInvoke(); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * join of a forked task returns when task completes + */ + public void testForkJoinCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + assertSame(f, f.fork()); + assertNull(f.join()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * get of a forked task returns when task completes + */ + public void testForkGetCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(null, 8); + assertSame(f, f.fork()); + assertNull(f.get()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * timed get of a forked task returns when task completes + */ + public void testForkTimedGetCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(null, 8); + assertSame(f, f.fork()); + assertNull(f.get(LONG_DELAY_MS, MILLISECONDS)); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * timed get with null time unit throws NPE + */ + public void testForkTimedGetNPECC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(null, 8); + assertSame(f, f.fork()); + try { + f.get(5L, null); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + checkInvoke(a); + } + + /** + * quietlyJoin of a forked task returns when task completes + */ + public void testForkQuietlyJoinCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * invoke task throws exception when task completes abnormally + */ + public void testAbnormalInvokeCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(null, 8); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + /** + * quietlyInvoke task returns when task completes abnormally + */ + public void testAbnormalQuietlyInvokeCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(null, 8); + f.quietlyInvoke(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + checkInvoke(a); + } + + /** + * join of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkJoinCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(null, 8); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + /** + * get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkGetCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingCCF f = new LFCCF(null, 8); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + checkInvoke(a); + } + + /** + * timed get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkTimedGetCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingCCF f = new LFCCF(null, 8); + assertSame(f, f.fork()); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + checkInvoke(a); + } + + /** + * quietlyJoin of a forked task returns when task completes abnormally + */ + public void testAbnormalForkQuietlyJoinCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(null, 8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + checkInvoke(a); + } + + /** + * invoke task throws exception when task cancelled + */ + public void testCancelledInvokeCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + assertTrue(f.cancel(true)); + try { + f.invoke(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * join of a forked task throws exception when task cancelled + */ + public void testCancelledForkJoinCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * get of a forked task throws exception when task cancelled + */ + public void testCancelledForkGetCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(null, 8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * timed get of a forked task throws exception when task cancelled + */ + public void testCancelledForkTimedGetCC() throws Exception { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(null, 8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * quietlyJoin of a forked task returns when task cancelled + */ + public void testCancelledForkQuietlyJoinCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + f.quietlyJoin(); + checkCancelled(f); + }}; + checkInvoke(a); + } + + /** + * getPool of non-FJ task returns null + */ + public void testGetPool2CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + assertNull(getPool()); + }}; + assertNull(a.invoke()); + } + + /** + * inForkJoinPool of non-FJ task returns false + */ + public void testInForkJoinPool2CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + assertFalse(inForkJoinPool()); + }}; + assertNull(a.invoke()); + } + + /** + * setRawResult(null) succeeds + */ + public void testSetRawResultCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + setRawResult(null); + assertNull(getRawResult()); + }}; + assertNull(a.invoke()); + } + + /** + * invoke task throws exception after invoking completeExceptionally + */ + public void testCompleteExceptionally2CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + f.completeExceptionally(new FJException()); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + /** + * invokeAll(t1, t2) invokes all task arguments + */ + public void testInvokeAll2CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + CCF g = new LCCF(null, 9); + invokeAll(f, g); + assertEquals(21, f.number); + assertEquals(34, g.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with 1 argument invokes task + */ + public void testInvokeAll1CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + invokeAll(f); + checkCompletedNormally(f); + assertEquals(21, f.number); + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with > 2 argument invokes tasks + */ + public void testInvokeAll3CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + CCF g = new LCCF(null, 9); + CCF h = new LCCF(null, 7); + invokeAll(f, g, h); + assertEquals(21, f.number); + assertEquals(34, g.number); + assertEquals(13, h.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + checkInvoke(a); + } + + /** + * invokeAll(collection) invokes all tasks in the collection + */ + public void testInvokeAllCollectionCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + CCF g = new LCCF(null, 9); + CCF h = new LCCF(null, 7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + invokeAll(set); + assertEquals(21, f.number); + assertEquals(34, g.number); + assertEquals(13, h.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with any null task throws NPE + */ + public void testInvokeAllNPECC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + CCF g = new LCCF(null, 9); + CCF h = null; + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + checkInvoke(a); + } + + /** + * invokeAll(t1, t2) throw exception if any task does + */ + public void testAbnormalInvokeAll2CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + FailingCCF g = new LFCCF(null, 9); + try { + invokeAll(f, g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with 1 argument throws exception if task does + */ + public void testAbnormalInvokeAll1CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF g = new LFCCF(null, 9); + try { + invokeAll(g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with > 2 argument throws exception if any task does + */ + public void testAbnormalInvokeAll3CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + FailingCCF g = new LFCCF(null, 9); + CCF h = new LCCF(null, 7); + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + checkInvoke(a); + } + + /** + * invokeAll(collection) throws exception if any task does + */ + public void testAbnormalInvokeAllCollectionCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(null, 8); + CCF g = new LCCF(null, 9); + CCF h = new LCCF(null, 7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + try { + invokeAll(set); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + /** + * awaitQuiescence by a worker is equivalent in effect to + * ForkJoinTask.helpQuiesce() + */ + public void testAwaitQuiescence1() throws Exception { + final ForkJoinPool p = new ForkJoinPool(); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + assertTrue(p.isQuiescent()); + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertSame(p, ForkJoinTask.getPool()); + boolean quiescent = p.awaitQuiescence(LONG_DELAY_MS, MILLISECONDS); + assertTrue(quiescent); + assertFalse(p.isQuiescent()); + while (!f.isDone()) { + assertFalse(p.getAsyncMode()); + assertFalse(p.isShutdown()); + assertFalse(p.isTerminating()); + assertFalse(p.isTerminated()); + Thread.yield(); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + assertFalse(p.isQuiescent()); + assertEquals(0, ForkJoinTask.getQueuedTaskCount()); + assertEquals(21, f.result); + }}; + p.execute(a); + while (!a.isDone() || !p.isQuiescent()) { + assertFalse(p.getAsyncMode()); + assertFalse(p.isShutdown()); + assertFalse(p.isTerminating()); + assertFalse(p.isTerminated()); + Thread.yield(); + } + assertEquals(0, p.getQueuedTaskCount()); + assertFalse(p.getAsyncMode()); + assertEquals(0, p.getQueuedSubmissionCount()); + assertFalse(p.hasQueuedSubmissions()); + while (p.getActiveThreadCount() != 0 + && millisElapsedSince(startTime) < LONG_DELAY_MS) + Thread.yield(); + assertFalse(p.isShutdown()); + assertFalse(p.isTerminating()); + assertFalse(p.isTerminated()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * awaitQuiescence returns when pool isQuiescent() or the indicated + * timeout elapsed + */ + public void testAwaitQuiescence2() throws Exception { + /** + * """It is possible to disable or limit the use of threads in the + * common pool by setting the parallelism property to zero. However + * doing so may cause unjoined tasks to never be executed.""" + */ + if ("0".equals(System.getProperty( + "java.util.concurrent.ForkJoinPool.common.parallelism"))) + return; + final ForkJoinPool p = new ForkJoinPool(); + try (PoolCleaner cleaner = cleaner(p)) { + assertTrue(p.isQuiescent()); + final long startTime = System.nanoTime(); + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + while (!f.isDone() + && millisElapsedSince(startTime) < LONG_DELAY_MS) { + assertFalse(p.getAsyncMode()); + assertFalse(p.isShutdown()); + assertFalse(p.isTerminating()); + assertFalse(p.isTerminated()); + Thread.yield(); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + assertEquals(0, ForkJoinTask.getQueuedTaskCount()); + assertEquals(21, f.result); + }}; + p.execute(a); + assertTrue(p.awaitQuiescence(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isQuiescent()); + assertTrue(a.isDone()); + assertEquals(0, p.getQueuedTaskCount()); + assertFalse(p.getAsyncMode()); + assertEquals(0, p.getQueuedSubmissionCount()); + assertFalse(p.hasQueuedSubmissions()); + while (p.getActiveThreadCount() != 0 + && millisElapsedSince(startTime) < LONG_DELAY_MS) + Thread.yield(); + assertFalse(p.isShutdown()); + assertFalse(p.isTerminating()); + assertFalse(p.isTerminated()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + +} diff --git a/jsr166-tests/src/test/java/jsr166/ForkJoinPoolTest.java b/jsr166-tests/src/test/java/jsr166/ForkJoinPoolTest.java index 09a35115b..e3bb42815 100644 --- a/jsr166-tests/src/test/java/jsr166/ForkJoinPoolTest.java +++ b/jsr166-tests/src/test/java/jsr166/ForkJoinPoolTest.java @@ -40,7 +40,7 @@ public class ForkJoinPoolTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ForkJoinPoolTest.class); // } /* @@ -68,10 +68,12 @@ public void uncaughtException(Thread t, Throwable e) { } } + static class MyError extends Error {} + // to test handlers static class FailingFJWSubclass extends ForkJoinWorkerThread { public FailingFJWSubclass(ForkJoinPool p) { super(p) ; } - protected void onStart() { super.onStart(); throw new Error(); } + protected void onStart() { super.onStart(); throw new MyError(); } } static class FailingThreadFactory @@ -166,7 +168,7 @@ protected Integer compute() { */ public void testDefaultInitialState() { ForkJoinPool p = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { assertSame(ForkJoinPool.defaultForkJoinWorkerThreadFactory, p.getFactory()); assertFalse(p.getAsyncMode()); @@ -178,8 +180,6 @@ public void testDefaultInitialState() { assertFalse(p.isShutdown()); assertFalse(p.isTerminating()); assertFalse(p.isTerminated()); - } finally { - joinPool(p); } } @@ -208,10 +208,8 @@ public void testConstructor2() { */ public void testGetParallelism() { ForkJoinPool p = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { assertEquals(1, p.getParallelism()); - } finally { - joinPool(p); } } @@ -219,14 +217,26 @@ public void testGetParallelism() { * getPoolSize returns number of started workers. */ public void testGetPoolSize() { - ForkJoinPool p = new ForkJoinPool(1); - try { + final CountDownLatch taskStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); + final ForkJoinPool p = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(p)) { assertEquals(0, p.getActiveThreadCount()); - Future future = p.submit(new StringTask()); + final Runnable task = new CheckedRunnable() { + public void realRun() throws InterruptedException { + taskStarted.countDown(); + assertEquals(1, p.getPoolSize()); + assertEquals(1, p.getActiveThreadCount()); + done.await(); + }}; + Future future = p.submit(task); + await(taskStarted); assertEquals(1, p.getPoolSize()); - } finally { - joinPool(p); + assertEquals(1, p.getActiveThreadCount()); + done.countDown(); } + assertEquals(0, p.getPoolSize()); + assertEquals(0, p.getActiveThreadCount()); } /** @@ -234,26 +244,28 @@ public void testGetPoolSize() { */ public void testAwaitTermination_timesOut() throws InterruptedException { ForkJoinPool p = new ForkJoinPool(1); - assertFalse(p.isTerminated()); - assertFalse(p.awaitTermination(Long.MIN_VALUE, NANOSECONDS)); - assertFalse(p.awaitTermination(Long.MIN_VALUE, MILLISECONDS)); - assertFalse(p.awaitTermination(-1L, NANOSECONDS)); - assertFalse(p.awaitTermination(-1L, MILLISECONDS)); - assertFalse(p.awaitTermination(0L, NANOSECONDS)); - assertFalse(p.awaitTermination(0L, MILLISECONDS)); - long timeoutNanos = 999999L; - long startTime = System.nanoTime(); - assertFalse(p.awaitTermination(timeoutNanos, NANOSECONDS)); - assertTrue(System.nanoTime() - startTime >= timeoutNanos); - assertFalse(p.isTerminated()); - startTime = System.nanoTime(); - long timeoutMillis = timeoutMillis(); - assertFalse(p.awaitTermination(timeoutMillis, MILLISECONDS)); - assertTrue(millisElapsedSince(startTime) >= timeoutMillis); - assertFalse(p.isTerminated()); - p.shutdown(); - assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); + try (PoolCleaner cleaner = cleaner(p)) { + assertFalse(p.isTerminated()); + assertFalse(p.awaitTermination(Long.MIN_VALUE, NANOSECONDS)); + assertFalse(p.awaitTermination(Long.MIN_VALUE, MILLISECONDS)); + assertFalse(p.awaitTermination(-1L, NANOSECONDS)); + assertFalse(p.awaitTermination(-1L, MILLISECONDS)); + assertFalse(p.awaitTermination(0L, NANOSECONDS)); + assertFalse(p.awaitTermination(0L, MILLISECONDS)); + long timeoutNanos = 999999L; + long startTime = System.nanoTime(); + assertFalse(p.awaitTermination(timeoutNanos, NANOSECONDS)); + assertTrue(System.nanoTime() - startTime >= timeoutNanos); + assertFalse(p.isTerminated()); + startTime = System.nanoTime(); + long timeoutMillis = timeoutMillis(); + assertFalse(p.awaitTermination(timeoutMillis, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + assertFalse(p.isTerminated()); + p.shutdown(); + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + } } /** @@ -264,23 +276,23 @@ public void testAwaitTermination_timesOut() throws InterruptedException { */ public void testSetUncaughtExceptionHandler() throws InterruptedException { final CountDownLatch uehInvoked = new CountDownLatch(1); - final Thread.UncaughtExceptionHandler eh = + final Thread.UncaughtExceptionHandler ueh = new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { + threadAssertTrue(e instanceof MyError); + threadAssertTrue(t instanceof FailingFJWSubclass); uehInvoked.countDown(); }}; ForkJoinPool p = new ForkJoinPool(1, new FailingThreadFactory(), - eh, false); - try { - assertSame(eh, p.getUncaughtExceptionHandler()); + ueh, false); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(ueh, p.getUncaughtExceptionHandler()); try { p.execute(new FibTask(8)); - assertTrue(uehInvoked.await(MEDIUM_DELAY_MS, MILLISECONDS)); - } catch (RejectedExecutionException ok) { + await(uehInvoked); + } finally { + p.shutdownNow(); // failure might have prevented processing task } - } finally { - p.shutdownNow(); // failure might have prevented processing task - joinPool(p); } } @@ -292,7 +304,7 @@ public void uncaughtException(Thread t, Throwable e) { */ public void testIsQuiescent() throws Exception { ForkJoinPool p = new ForkJoinPool(2); - try { + try (PoolCleaner cleaner = cleaner(p)) { assertTrue(p.isQuiescent()); long startTime = System.nanoTime(); FibTask f = new FibTask(20); @@ -311,17 +323,18 @@ public void testIsQuiescent() throws Exception { assertTrue(p.isQuiescent()); assertFalse(p.getAsyncMode()); - assertEquals(0, p.getActiveThreadCount()); assertEquals(0, p.getQueuedTaskCount()); assertEquals(0, p.getQueuedSubmissionCount()); assertFalse(p.hasQueuedSubmissions()); + while (p.getActiveThreadCount() != 0 + && millisElapsedSince(startTime) < LONG_DELAY_MS) + Thread.yield(); assertFalse(p.isShutdown()); assertFalse(p.isTerminating()); assertFalse(p.isTerminated()); assertTrue(f.isDone()); assertEquals(6765, (int) f.get()); - } finally { - joinPool(p); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } } @@ -330,11 +343,9 @@ public void testIsQuiescent() throws Exception { */ public void testSubmitForkJoinTask() throws Throwable { ForkJoinPool p = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { ForkJoinTask f = p.submit(new FibTask(8)); assertEquals(21, (int) f.get()); - } finally { - joinPool(p); } } @@ -343,15 +354,13 @@ public void testSubmitForkJoinTask() throws Throwable { */ public void testSubmitAfterShutdown() { ForkJoinPool p = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { p.shutdown(); assertTrue(p.isShutdown()); try { ForkJoinTask f = p.submit(new FibTask(8)); shouldThrow(); } catch (RejectedExecutionException success) {} - } finally { - joinPool(p); } } @@ -377,16 +386,14 @@ public void testBlockingForkJoinTask() throws Throwable { public void testPollSubmission() { final CountDownLatch done = new CountDownLatch(1); SubFJP p = new SubFJP(); - try { + try (PoolCleaner cleaner = cleaner(p)) { ForkJoinTask a = p.submit(awaiter(done)); ForkJoinTask b = p.submit(awaiter(done)); ForkJoinTask c = p.submit(awaiter(done)); ForkJoinTask r = p.pollSubmission(); assertTrue(r == a || r == b || r == c); assertFalse(r.isDone()); - } finally { done.countDown(); - joinPool(p); } } @@ -396,7 +403,7 @@ public void testPollSubmission() { public void testDrainTasksTo() { final CountDownLatch done = new CountDownLatch(1); SubFJP p = new SubFJP(); - try { + try (PoolCleaner cleaner = cleaner(p)) { ForkJoinTask a = p.submit(awaiter(done)); ForkJoinTask b = p.submit(awaiter(done)); ForkJoinTask c = p.submit(awaiter(done)); @@ -407,9 +414,7 @@ public void testDrainTasksTo() { assertTrue(r == a || r == b || r == c); assertFalse(r.isDone()); } - } finally { done.countDown(); - joinPool(p); } } @@ -420,7 +425,7 @@ public void testDrainTasksTo() { */ public void testExecuteRunnable() throws Throwable { ExecutorService e = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(e)) { final AtomicBoolean done = new AtomicBoolean(false); Future future = e.submit(new CheckedRunnable() { public void realRun() { @@ -431,8 +436,6 @@ public void realRun() { assertTrue(done.get()); assertTrue(future.isDone()); assertFalse(future.isCancelled()); - } finally { - joinPool(e); } } @@ -441,13 +444,11 @@ public void realRun() { */ public void testSubmitCallable() throws Throwable { ExecutorService e = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new StringTask()); assertSame(TEST_STRING, future.get()); assertTrue(future.isDone()); assertFalse(future.isCancelled()); - } finally { - joinPool(e); } } @@ -456,13 +457,11 @@ public void testSubmitCallable() throws Throwable { */ public void testSubmitRunnable() throws Throwable { ExecutorService e = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new NoOpRunnable()); assertNull(future.get()); assertTrue(future.isDone()); assertFalse(future.isCancelled()); - } finally { - joinPool(e); } } @@ -471,13 +470,11 @@ public void testSubmitRunnable() throws Throwable { */ public void testSubmitRunnable2() throws Throwable { ExecutorService e = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new NoOpRunnable(), TEST_STRING); assertSame(TEST_STRING, future.get()); assertTrue(future.isDone()); assertFalse(future.isCancelled()); - } finally { - joinPool(e); } } @@ -490,11 +487,9 @@ public void testSubmitPrivilegedAction() throws Exception { Runnable r = new CheckedRunnable() { public void realRun() throws Exception { ExecutorService e = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(callable); assertSame(TEST_STRING, future.get()); - } finally { - joinPool(e); } }}; @@ -511,11 +506,9 @@ public void testSubmitPrivilegedExceptionAction() throws Exception { Runnable r = new CheckedRunnable() { public void realRun() throws Exception { ExecutorService e = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(callable); assertSame(TEST_STRING, future.get()); - } finally { - joinPool(e); } }}; @@ -532,7 +525,7 @@ public void testSubmitFailedPrivilegedExceptionAction() throws Exception { Runnable r = new CheckedRunnable() { public void realRun() throws Exception { ExecutorService e = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(callable); try { future.get(); @@ -540,8 +533,6 @@ public void realRun() throws Exception { } catch (ExecutionException success) { assertTrue(success.getCause() instanceof IndexOutOfBoundsException); } - } finally { - joinPool(e); } }}; @@ -553,12 +544,11 @@ public void realRun() throws Exception { */ public void testExecuteNullRunnable() { ExecutorService e = new ForkJoinPool(1); - try { - Future future = e.submit((Runnable) null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + try { + Future future = e.submit((Runnable) null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -567,12 +557,11 @@ public void testExecuteNullRunnable() { */ public void testSubmitNullCallable() { ExecutorService e = new ForkJoinPool(1); - try { - Future future = e.submit((Callable) null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + try { + Future future = e.submit((Callable) null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -582,13 +571,13 @@ public void testSubmitNullCallable() { public void testInterruptedSubmit() throws InterruptedException { final CountDownLatch submitted = new CountDownLatch(1); final CountDownLatch quittingTime = new CountDownLatch(1); - final ExecutorService p = new ForkJoinPool(1); final Callable awaiter = new CheckedCallable() { public Void realCall() throws InterruptedException { - assertTrue(quittingTime.await(MEDIUM_DELAY_MS, MILLISECONDS)); + assertTrue(quittingTime.await(2*LONG_DELAY_MS, MILLISECONDS)); return null; }}; - try { + final ExecutorService p = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(p, quittingTime)) { Thread t = new Thread(new CheckedInterruptedRunnable() { public void realRun() throws Exception { Future future = p.submit(awaiter); @@ -596,12 +585,9 @@ public void realRun() throws Exception { future.get(); }}); t.start(); - assertTrue(submitted.await(MEDIUM_DELAY_MS, MILLISECONDS)); + await(submitted); t.interrupt(); - t.join(); - } finally { - quittingTime.countDown(); - joinPool(p); + awaitTermination(t); } } @@ -611,15 +597,15 @@ public void realRun() throws Exception { */ public void testSubmitEE() throws Throwable { ForkJoinPool p = new ForkJoinPool(1); - try { - p.submit(new Callable() { - public Object call() { throw new ArithmeticException(); }}) - .get(); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof ArithmeticException); - } finally { - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.submit(new Callable() { + public Object call() { throw new ArithmeticException(); }}) + .get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof ArithmeticException); + } } } @@ -628,12 +614,11 @@ public void testSubmitEE() throws Throwable { */ public void testInvokeAny1() throws Throwable { ExecutorService e = new ForkJoinPool(1); - try { - e.invokeAny(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -642,12 +627,11 @@ public void testInvokeAny1() throws Throwable { */ public void testInvokeAny2() throws Throwable { ExecutorService e = new ForkJoinPool(1); - try { - e.invokeAny(new ArrayList>()); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>()); + shouldThrow(); + } catch (IllegalArgumentException success) {} } } @@ -656,14 +640,13 @@ public void testInvokeAny2() throws Throwable { */ public void testInvokeAny3() throws Throwable { ExecutorService e = new ForkJoinPool(1); - List> l = new ArrayList>(); - l.add(null); - try { - e.invokeAny(l); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(null); + try { + e.invokeAny(l); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -673,16 +656,15 @@ public void testInvokeAny3() throws Throwable { public void testInvokeAny4() throws Throwable { CountDownLatch latch = new CountDownLatch(1); ExecutorService e = new ForkJoinPool(1); - List> l = new ArrayList>(); - l.add(latchAwaitingStringTask(latch)); - l.add(null); - try { - e.invokeAny(l); - shouldThrow(); - } catch (NullPointerException success) { - } finally { + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l); + shouldThrow(); + } catch (NullPointerException success) {} latch.countDown(); - joinPool(e); } } @@ -691,15 +673,15 @@ public void testInvokeAny4() throws Throwable { */ public void testInvokeAny5() throws Throwable { ExecutorService e = new ForkJoinPool(1); - List> l = new ArrayList>(); - l.add(new NPETask()); - try { - e.invokeAny(l); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -708,14 +690,12 @@ public void testInvokeAny5() throws Throwable { */ public void testInvokeAny6() throws Throwable { ExecutorService e = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); String result = e.invokeAny(l); assertSame(TEST_STRING, result); - } finally { - joinPool(e); } } @@ -724,12 +704,11 @@ public void testInvokeAny6() throws Throwable { */ public void testInvokeAll1() throws Throwable { ExecutorService e = new ForkJoinPool(1); - try { - e.invokeAll(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -738,12 +717,10 @@ public void testInvokeAll1() throws Throwable { */ public void testInvokeAll2() throws InterruptedException { ExecutorService e = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(e)) { List> r = e.invokeAll(new ArrayList>()); assertTrue(r.isEmpty()); - } finally { - joinPool(e); } } @@ -752,15 +729,14 @@ public void testInvokeAll2() throws InterruptedException { */ public void testInvokeAll3() throws InterruptedException { ExecutorService e = new ForkJoinPool(1); - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(null); - try { - e.invokeAll(l); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -770,17 +746,17 @@ public void testInvokeAll3() throws InterruptedException { */ public void testInvokeAll4() throws Throwable { ExecutorService e = new ForkJoinPool(1); - List> l = new ArrayList>(); - l.add(new NPETask()); - List> futures = e.invokeAll(l); - assertEquals(1, futures.size()); - try { - futures.get(0).get(); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = e.invokeAll(l); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -789,7 +765,7 @@ public void testInvokeAll4() throws Throwable { */ public void testInvokeAll5() throws Throwable { ExecutorService e = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); @@ -797,8 +773,6 @@ public void testInvokeAll5() throws Throwable { assertEquals(2, futures.size()); for (Future future : futures) assertSame(TEST_STRING, future.get()); - } finally { - joinPool(e); } } @@ -807,12 +781,11 @@ public void testInvokeAll5() throws Throwable { */ public void testTimedInvokeAny1() throws Throwable { ExecutorService e = new ForkJoinPool(1); - try { - e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -821,14 +794,13 @@ public void testTimedInvokeAny1() throws Throwable { */ public void testTimedInvokeAnyNullTimeUnit() throws Throwable { ExecutorService e = new ForkJoinPool(1); - List> l = new ArrayList>(); - l.add(new StringTask()); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -837,13 +809,12 @@ public void testTimedInvokeAnyNullTimeUnit() throws Throwable { */ public void testTimedInvokeAny2() throws Throwable { ExecutorService e = new ForkJoinPool(1); - try { - e.invokeAny(new ArrayList>(), - MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} } } @@ -853,16 +824,15 @@ public void testTimedInvokeAny2() throws Throwable { public void testTimedInvokeAny3() throws Throwable { CountDownLatch latch = new CountDownLatch(1); ExecutorService e = new ForkJoinPool(1); - List> l = new ArrayList>(); - l.add(latchAwaitingStringTask(latch)); - l.add(null); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} latch.countDown(); - joinPool(e); } } @@ -871,15 +841,17 @@ public void testTimedInvokeAny3() throws Throwable { */ public void testTimedInvokeAny4() throws Throwable { ExecutorService e = new ForkJoinPool(1); - List> l = new ArrayList>(); - l.add(new NPETask()); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } } @@ -888,14 +860,14 @@ public void testTimedInvokeAny4() throws Throwable { */ public void testTimedInvokeAny5() throws Throwable { ExecutorService e = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); - String result = e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + String result = e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); assertSame(TEST_STRING, result); - } finally { - joinPool(e); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } } @@ -904,12 +876,11 @@ public void testTimedInvokeAny5() throws Throwable { */ public void testTimedInvokeAll1() throws Throwable { ExecutorService e = new ForkJoinPool(1); - try { - e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -918,14 +889,13 @@ public void testTimedInvokeAll1() throws Throwable { */ public void testTimedInvokeAllNullTimeUnit() throws Throwable { ExecutorService e = new ForkJoinPool(1); - List> l = new ArrayList>(); - l.add(new StringTask()); - try { - e.invokeAll(l, MEDIUM_DELAY_MS, null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -934,13 +904,11 @@ public void testTimedInvokeAllNullTimeUnit() throws Throwable { */ public void testTimedInvokeAll2() throws InterruptedException { ExecutorService e = new ForkJoinPool(1); - try { + try (PoolCleaner cleaner = cleaner(e)) { List> r = e.invokeAll(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); assertTrue(r.isEmpty()); - } finally { - joinPool(e); } } @@ -949,15 +917,14 @@ public void testTimedInvokeAll2() throws InterruptedException { */ public void testTimedInvokeAll3() throws InterruptedException { ExecutorService e = new ForkJoinPool(1); - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(null); - try { - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -966,18 +933,18 @@ public void testTimedInvokeAll3() throws InterruptedException { */ public void testTimedInvokeAll4() throws Throwable { ExecutorService e = new ForkJoinPool(1); - List> l = new ArrayList>(); - l.add(new NPETask()); - List> futures - = e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); - assertEquals(1, futures.size()); - try { - futures.get(0).get(); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures + = e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -985,18 +952,16 @@ public void testTimedInvokeAll4() throws Throwable { * timed invokeAll(c) returns results of all completed tasks in c */ public void testTimedInvokeAll5() throws Throwable { - ExecutorService e = new ForkJoinPool(1); - try { + ForkJoinPool e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); List> futures - = e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + = e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); assertEquals(2, futures.size()); for (Future future : futures) assertSame(TEST_STRING, future.get()); - } finally { - joinPool(e); } } diff --git a/jsr166-tests/src/test/java/jsr166/ForkJoinTask8Test.java b/jsr166-tests/src/test/java/jsr166/ForkJoinTask8Test.java new file mode 100644 index 000000000..6c0334811 --- /dev/null +++ b/jsr166-tests/src/test/java/jsr166/ForkJoinTask8Test.java @@ -0,0 +1,1205 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package jsr166; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.RecursiveAction; +import java.util.concurrent.TimeoutException; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ForkJoinTask8Test extends JSR166TestCase { + + /* + * Testing notes: This differs from ForkJoinTaskTest mainly by + * defining a version of BinaryAsyncAction that uses JDK8 task + * tags for control state, thereby testing getForkJoinTaskTag, + * setForkJoinTaskTag, and compareAndSetForkJoinTaskTag across + * various contexts. Most of the test methods using it are + * otherwise identical, but omitting retest of those dealing with + * cancellation, which is not represented in this tag scheme. + */ + + static final short INITIAL_STATE = -1; + static final short COMPLETE_STATE = 0; + static final short EXCEPTION_STATE = 1; + + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. + // + // public static void main(String[] args) { + // main(suite(), args); + // } + // public static Test suite() { + // return new TestSuite(ForkJoinTask8Test.class); + // } + + // Runs with "mainPool" use > 1 thread. singletonPool tests use 1 + static final int mainPoolSize = + Math.max(2, Runtime.getRuntime().availableProcessors()); + + private static ForkJoinPool mainPool() { + return new ForkJoinPool(mainPoolSize); + } + + private static ForkJoinPool singletonPool() { + return new ForkJoinPool(1); + } + + private static ForkJoinPool asyncSingletonPool() { + return new ForkJoinPool(1, + ForkJoinPool.defaultForkJoinWorkerThreadFactory, + null, true); + } + + // Compute fib naively and efficiently + final int[] fib; + { + int[] fib = new int[10]; + fib[0] = 0; + fib[1] = 1; + for (int i = 2; i < fib.length; i++) + fib[i] = fib[i - 1] + fib[i - 2]; + this.fib = fib; + } + + private void testInvokeOnPool(ForkJoinPool pool, RecursiveAction a) { + try (PoolCleaner cleaner = cleaner(pool)) { + assertFalse(a.isDone()); + assertFalse(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + + assertNull(pool.invoke(a)); + + assertTrue(a.isDone()); + assertTrue(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + } + } + + void checkNotDone(ForkJoinTask a) { + assertFalse(a.isDone()); + assertFalse(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + if (a instanceof BinaryAsyncAction) + assertTrue(((BinaryAsyncAction)a).getForkJoinTaskTag() == INITIAL_STATE); + + try { + a.get(0L, SECONDS); + shouldThrow(); + } catch (TimeoutException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedNormally(ForkJoinTask a) { + checkCompletedNormally(a, null); + } + + void checkCompletedNormally(ForkJoinTask a, T expected) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertTrue(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertNull(a.getException()); + assertSame(expected, a.getRawResult()); + if (a instanceof BinaryAsyncAction) + assertTrue(((BinaryAsyncAction)a).getForkJoinTaskTag() == COMPLETE_STATE); + + { + Thread.currentThread().interrupt(); + long startTime = System.nanoTime(); + assertSame(expected, a.join()); + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); + Thread.interrupted(); + } + + { + Thread.currentThread().interrupt(); + long startTime = System.nanoTime(); + a.quietlyJoin(); // should be no-op + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); + Thread.interrupted(); + } + + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + try { + assertSame(expected, a.get()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + assertSame(expected, a.get(5L, SECONDS)); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedAbnormally(ForkJoinTask a, Throwable t) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertFalse(a.isCompletedNormally()); + assertTrue(a.isCompletedAbnormally()); + assertSame(t.getClass(), a.getException().getClass()); + assertNull(a.getRawResult()); + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + if (a instanceof BinaryAsyncAction) + assertTrue(((BinaryAsyncAction)a).getForkJoinTaskTag() != INITIAL_STATE); + + try { + Thread.currentThread().interrupt(); + a.join(); + shouldThrow(); + } catch (Throwable expected) { + assertSame(t.getClass(), expected.getClass()); + } + Thread.interrupted(); + + { + long startTime = System.nanoTime(); + a.quietlyJoin(); // should be no-op + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); + } + + try { + a.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + public static final class FJException extends RuntimeException { + FJException() { super(); } + } + + abstract static class BinaryAsyncAction extends ForkJoinTask { + + private volatile BinaryAsyncAction parent; + + private volatile BinaryAsyncAction sibling; + + protected BinaryAsyncAction() { + setForkJoinTaskTag(INITIAL_STATE); + } + + public final Void getRawResult() { return null; } + protected final void setRawResult(Void mustBeNull) { } + + public final void linkSubtasks(BinaryAsyncAction x, BinaryAsyncAction y) { + x.parent = y.parent = this; + x.sibling = y; + y.sibling = x; + } + + protected void onComplete(BinaryAsyncAction x, BinaryAsyncAction y) { + if (this.getForkJoinTaskTag() != COMPLETE_STATE || + x.getForkJoinTaskTag() != COMPLETE_STATE || + y.getForkJoinTaskTag() != COMPLETE_STATE) { + completeThisExceptionally(new FJException()); + } + } + + protected boolean onException() { + return true; + } + + public void linkAndForkSubtasks(BinaryAsyncAction x, BinaryAsyncAction y) { + linkSubtasks(x, y); + y.fork(); + x.fork(); + } + + private void completeThis() { + setForkJoinTaskTag(COMPLETE_STATE); + super.complete(null); + } + + private void completeThisExceptionally(Throwable ex) { + setForkJoinTaskTag(EXCEPTION_STATE); + super.completeExceptionally(ex); + } + + public boolean cancel(boolean mayInterruptIfRunning) { + if (super.cancel(mayInterruptIfRunning)) { + completeExceptionally(new FJException()); + return true; + } + return false; + } + + public final void complete() { + BinaryAsyncAction a = this; + for (;;) { + BinaryAsyncAction s = a.sibling; + BinaryAsyncAction p = a.parent; + a.sibling = null; + a.parent = null; + a.completeThis(); + if (p == null || + p.compareAndSetForkJoinTaskTag(INITIAL_STATE, COMPLETE_STATE)) + break; + try { + p.onComplete(a, s); + } catch (Throwable rex) { + p.completeExceptionally(rex); + return; + } + a = p; + } + } + + public final void completeExceptionally(Throwable ex) { + for (BinaryAsyncAction a = this;;) { + a.completeThisExceptionally(ex); + BinaryAsyncAction s = a.sibling; + if (s != null && !s.isDone()) + s.completeExceptionally(ex); + if ((a = a.parent) == null) + break; + } + } + + public final BinaryAsyncAction getParent() { + return parent; + } + + public BinaryAsyncAction getSibling() { + return sibling; + } + + public void reinitialize() { + parent = sibling = null; + super.reinitialize(); + } + + } + + final class AsyncFib extends BinaryAsyncAction { + int number; + int expectedResult; + public AsyncFib(int number) { + this.number = number; + this.expectedResult = fib[number]; + } + + public final boolean exec() { + try { + AsyncFib f = this; + int n = f.number; + while (n > 1) { + AsyncFib p = f; + AsyncFib r = new AsyncFib(n - 2); + f = new AsyncFib(--n); + p.linkSubtasks(r, f); + r.fork(); + } + f.complete(); + } + catch (Throwable ex) { + compareAndSetForkJoinTaskTag(INITIAL_STATE, EXCEPTION_STATE); + } + if (getForkJoinTaskTag() == EXCEPTION_STATE) + throw new FJException(); + return false; + } + + protected void onComplete(BinaryAsyncAction x, BinaryAsyncAction y) { + number = ((AsyncFib)x).number + ((AsyncFib)y).number; + super.onComplete(x, y); + } + + public void checkCompletedNormally() { + assertEquals(expectedResult, number); + ForkJoinTask8Test.this.checkCompletedNormally(this); + } + } + + static final class FailingAsyncFib extends BinaryAsyncAction { + int number; + public FailingAsyncFib(int n) { + this.number = n; + } + + public final boolean exec() { + try { + FailingAsyncFib f = this; + int n = f.number; + while (n > 1) { + FailingAsyncFib p = f; + FailingAsyncFib r = new FailingAsyncFib(n - 2); + f = new FailingAsyncFib(--n); + p.linkSubtasks(r, f); + r.fork(); + } + f.complete(); + } + catch (Throwable ex) { + compareAndSetForkJoinTaskTag(INITIAL_STATE, EXCEPTION_STATE); + } + if (getForkJoinTaskTag() == EXCEPTION_STATE) + throw new FJException(); + return false; + } + + protected void onComplete(BinaryAsyncAction x, BinaryAsyncAction y) { + completeExceptionally(new FJException()); + } + } + + /** + * invoke returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks; getRawResult returns null. + */ + public void testInvoke() { + testInvoke(mainPool()); + } + public void testInvoke_Singleton() { + testInvoke(singletonPool()); + } + public void testInvoke(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertNull(f.invoke()); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * quietlyInvoke task returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks + */ + public void testQuietlyInvoke() { + testQuietlyInvoke(mainPool()); + } + public void testQuietlyInvoke_Singleton() { + testQuietlyInvoke(singletonPool()); + } + public void testQuietlyInvoke(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + f.quietlyInvoke(); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * join of a forked task returns when task completes + */ + public void testForkJoin() { + testForkJoin(mainPool()); + } + public void testForkJoin_Singleton() { + testForkJoin(singletonPool()); + } + public void testForkJoin(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertNull(f.join()); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * get of a forked task returns when task completes + */ + public void testForkGet() { + testForkGet(mainPool()); + } + public void testForkGet_Singleton() { + testForkGet(singletonPool()); + } + public void testForkGet(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertNull(f.get()); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * timed get of a forked task returns when task completes + */ + public void testForkTimedGet() { + testForkTimedGet(mainPool()); + } + public void testForkTimedGet_Singleton() { + testForkTimedGet(singletonPool()); + } + public void testForkTimedGet(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertNull(f.get(LONG_DELAY_MS, MILLISECONDS)); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * timed get with null time unit throws NullPointerException + */ + public void testForkTimedGetNullTimeUnit() { + testForkTimedGetNullTimeUnit(mainPool()); + } + public void testForkTimedGetNullTimeUnit_Singleton() { + testForkTimedGet(singletonPool()); + } + public void testForkTimedGetNullTimeUnit(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + try { + f.get(5L, null); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + testInvokeOnPool(pool, a); + } + + /** + * quietlyJoin of a forked task returns when task completes + */ + public void testForkQuietlyJoin() { + testForkQuietlyJoin(mainPool()); + } + public void testForkQuietlyJoin_Singleton() { + testForkQuietlyJoin(singletonPool()); + } + public void testForkQuietlyJoin(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * helpQuiesce returns when tasks are complete. + * getQueuedTaskCount returns 0 when quiescent + */ + public void testForkHelpQuiesce() { + testForkHelpQuiesce(mainPool()); + } + public void testForkHelpQuiesce_Singleton() { + testForkHelpQuiesce(singletonPool()); + } + public void testForkHelpQuiesce(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + helpQuiesce(); + assertEquals(0, getQueuedTaskCount()); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * invoke task throws exception when task completes abnormally + */ + public void testAbnormalInvoke() { + testAbnormalInvoke(mainPool()); + } + public void testAbnormalInvoke_Singleton() { + testAbnormalInvoke(singletonPool()); + } + public void testAbnormalInvoke(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * quietlyInvoke task returns when task completes abnormally + */ + public void testAbnormalQuietlyInvoke() { + testAbnormalQuietlyInvoke(mainPool()); + } + public void testAbnormalQuietlyInvoke_Singleton() { + testAbnormalQuietlyInvoke(singletonPool()); + } + public void testAbnormalQuietlyInvoke(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + f.quietlyInvoke(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(pool, a); + } + + /** + * join of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkJoin() { + testAbnormalForkJoin(mainPool()); + } + public void testAbnormalForkJoin_Singleton() { + testAbnormalForkJoin(singletonPool()); + } + public void testAbnormalForkJoin(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkGet() { + testAbnormalForkGet(mainPool()); + } + public void testAbnormalForkGet_Singleton() { + testAbnormalForkJoin(singletonPool()); + } + public void testAbnormalForkGet(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * timed get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkTimedGet() { + testAbnormalForkTimedGet(mainPool()); + } + public void testAbnormalForkTimedGet_Singleton() { + testAbnormalForkTimedGet(singletonPool()); + } + public void testAbnormalForkTimedGet(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * quietlyJoin of a forked task returns when task completes abnormally + */ + public void testAbnormalForkQuietlyJoin() { + testAbnormalForkQuietlyJoin(mainPool()); + } + public void testAbnormalForkQuietlyJoin_Singleton() { + testAbnormalForkQuietlyJoin(singletonPool()); + } + public void testAbnormalForkQuietlyJoin(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(pool, a); + } + + /** + * getPool of executing task returns its pool + */ + public void testGetPool() { + testGetPool(mainPool()); + } + public void testGetPool_Singleton() { + testGetPool(singletonPool()); + } + public void testGetPool(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertSame(pool, getPool()); + }}; + testInvokeOnPool(pool, a); + } + + /** + * getPool of non-FJ task returns null + */ + public void testGetPool2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertNull(getPool()); + }}; + assertNull(a.invoke()); + } + + /** + * inForkJoinPool of executing task returns true + */ + public void testInForkJoinPool() { + testInForkJoinPool(mainPool()); + } + public void testInForkJoinPool_Singleton() { + testInForkJoinPool(singletonPool()); + } + public void testInForkJoinPool(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertTrue(inForkJoinPool()); + }}; + testInvokeOnPool(pool, a); + } + + /** + * inForkJoinPool of non-FJ task returns false + */ + public void testInForkJoinPool2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertFalse(inForkJoinPool()); + }}; + assertNull(a.invoke()); + } + + /** + * setRawResult(null) succeeds + */ + public void testSetRawResult() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + setRawResult(null); + assertNull(getRawResult()); + }}; + assertNull(a.invoke()); + } + + /** + * invoke task throws exception after invoking completeExceptionally + */ + public void testCompleteExceptionally() { + testCompleteExceptionally(mainPool()); + } + public void testCompleteExceptionally_Singleton() { + testCompleteExceptionally(singletonPool()); + } + public void testCompleteExceptionally(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + f.completeExceptionally(new FJException()); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(tasks) with 1 argument invokes task + */ + public void testInvokeAll1() { + testInvokeAll1(mainPool()); + } + public void testInvokeAll1_Singleton() { + testInvokeAll1(singletonPool()); + } + public void testInvokeAll1(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + invokeAll(f); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(t1, t2) invokes all task arguments + */ + public void testInvokeAll2() { + testInvokeAll2(mainPool()); + } + public void testInvokeAll2_Singleton() { + testInvokeAll2(singletonPool()); + } + public void testInvokeAll2(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib[] tasks = { + new AsyncFib(8), + new AsyncFib(9), + }; + invokeAll(tasks[0], tasks[1]); + for (AsyncFib task : tasks) assertTrue(task.isDone()); + for (AsyncFib task : tasks) task.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(tasks) with > 2 argument invokes tasks + */ + public void testInvokeAll3() { + testInvokeAll3(mainPool()); + } + public void testInvokeAll3_Singleton() { + testInvokeAll3(singletonPool()); + } + public void testInvokeAll3(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib[] tasks = { + new AsyncFib(8), + new AsyncFib(9), + new AsyncFib(7), + }; + invokeAll(tasks[0], tasks[1], tasks[2]); + for (AsyncFib task : tasks) assertTrue(task.isDone()); + for (AsyncFib task : tasks) task.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(collection) invokes all tasks in the collection + */ + public void testInvokeAllCollection() { + testInvokeAllCollection(mainPool()); + } + public void testInvokeAllCollection_Singleton() { + testInvokeAllCollection(singletonPool()); + } + public void testInvokeAllCollection(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib[] tasks = { + new AsyncFib(8), + new AsyncFib(9), + new AsyncFib(7), + }; + invokeAll(Arrays.asList(tasks)); + for (AsyncFib task : tasks) assertTrue(task.isDone()); + for (AsyncFib task : tasks) task.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(tasks) with any null task throws NullPointerException + */ + public void testInvokeAllNullTask() { + testInvokeAllNullTask(mainPool()); + } + public void testInvokeAllNullTask_Singleton() { + testInvokeAllNullTask(singletonPool()); + } + public void testInvokeAllNullTask(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib nul = null; + Runnable[] throwingActions = { + () -> invokeAll(nul), + () -> invokeAll(nul, nul), + () -> invokeAll(new AsyncFib(8), new AsyncFib(9), nul), + () -> invokeAll(new AsyncFib(8), nul, new AsyncFib(9)), + () -> invokeAll(nul, new AsyncFib(8), new AsyncFib(9)), + }; + assertThrows(NullPointerException.class, throwingActions); + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(tasks) with 1 argument throws exception if task does + */ + public void testAbnormalInvokeAll1() { + testAbnormalInvokeAll1(mainPool()); + } + public void testAbnormalInvokeAll1_Singleton() { + testAbnormalInvokeAll1(singletonPool()); + } + public void testAbnormalInvokeAll1(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib g = new FailingAsyncFib(9); + try { + invokeAll(g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(t1, t2) throw exception if any task does + */ + public void testAbnormalInvokeAll2() { + testAbnormalInvokeAll2(mainPool()); + } + public void testAbnormalInvokeAll2_Singleton() { + testAbnormalInvokeAll2(singletonPool()); + } + public void testAbnormalInvokeAll2(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + FailingAsyncFib g = new FailingAsyncFib(9); + ForkJoinTask[] tasks = { f, g }; + Collections.shuffle(Arrays.asList(tasks)); + try { + invokeAll(tasks[0], tasks[1]); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(tasks) with > 2 argument throws exception if any task does + */ + public void testAbnormalInvokeAll3() { + testAbnormalInvokeAll3(mainPool()); + } + public void testAbnormalInvokeAll3_Singleton() { + testAbnormalInvokeAll3(singletonPool()); + } + public void testAbnormalInvokeAll3(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + FailingAsyncFib g = new FailingAsyncFib(9); + AsyncFib h = new AsyncFib(7); + ForkJoinTask[] tasks = { f, g, h }; + Collections.shuffle(Arrays.asList(tasks)); + try { + invokeAll(tasks[0], tasks[1], tasks[2]); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(collection) throws exception if any task does + */ + public void testAbnormalInvokeAllCollection() { + testAbnormalInvokeAllCollection(mainPool()); + } + public void testAbnormalInvokeAllCollection_Singleton() { + testAbnormalInvokeAllCollection(singletonPool()); + } + public void testAbnormalInvokeAllCollection(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + AsyncFib g = new AsyncFib(9); + AsyncFib h = new AsyncFib(7); + ForkJoinTask[] tasks = { f, g, h }; + Collections.shuffle(Arrays.asList(tasks)); + try { + invokeAll(Arrays.asList(tasks)); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * tryUnfork returns true for most recent unexecuted task, + * and suppresses execution + */ + public void testTryUnfork() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertTrue(f.tryUnfork()); + helpQuiesce(); + checkNotDone(f); + g.checkCompletedNormally(); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * getSurplusQueuedTaskCount returns > 0 when + * there are more tasks than threads + */ + public void testGetSurplusQueuedTaskCount() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib h = new AsyncFib(7); + assertSame(h, h.fork()); + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertTrue(getSurplusQueuedTaskCount() > 0); + helpQuiesce(); + assertEquals(0, getSurplusQueuedTaskCount()); + f.checkCompletedNormally(); + g.checkCompletedNormally(); + h.checkCompletedNormally(); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * peekNextLocalTask returns most recent unexecuted task. + */ + public void testPeekNextLocalTask() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(f, peekNextLocalTask()); + assertNull(f.join()); + f.checkCompletedNormally(); + helpQuiesce(); + g.checkCompletedNormally(); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * pollNextLocalTask returns most recent unexecuted task without + * executing it + */ + public void testPollNextLocalTask() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(f, pollNextLocalTask()); + helpQuiesce(); + checkNotDone(f); + g.checkCompletedNormally(); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * pollTask returns an unexecuted task without executing it + */ + public void testPollTask() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(f, pollTask()); + helpQuiesce(); + checkNotDone(f); + g.checkCompletedNormally(); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * peekNextLocalTask returns least recent unexecuted task in async mode + */ + public void testPeekNextLocalTaskAsync() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(g, peekNextLocalTask()); + assertNull(f.join()); + helpQuiesce(); + f.checkCompletedNormally(); + g.checkCompletedNormally(); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + /** + * pollNextLocalTask returns least recent unexecuted task without + * executing it, in async mode + */ + public void testPollNextLocalTaskAsync() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(g, pollNextLocalTask()); + helpQuiesce(); + f.checkCompletedNormally(); + checkNotDone(g); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + /** + * pollTask returns an unexecuted task without executing it, in + * async mode + */ + public void testPollTaskAsync() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(g, pollTask()); + helpQuiesce(); + f.checkCompletedNormally(); + checkNotDone(g); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + /** + * ForkJoinTask.quietlyComplete returns when task completes + * normally without setting a value. The most recent value + * established by setRawResult(V) (or null by default) is returned + * from invoke. + */ + public void testQuietlyComplete() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + f.quietlyComplete(); + assertEquals(8, f.number); + assertTrue(f.isDone()); + assertFalse(f.isCancelled()); + assertTrue(f.isCompletedNormally()); + assertFalse(f.isCompletedAbnormally()); + assertNull(f.getException()); + }}; + testInvokeOnPool(mainPool(), a); + } + + // jdk9 + + /** + * pollSubmission returns unexecuted submitted task, if present + */ + public void testPollSubmission() { + final CountDownLatch done = new CountDownLatch(1); + final ForkJoinTask a = ForkJoinTask.adapt(awaiter(done)); + final ForkJoinTask b = ForkJoinTask.adapt(awaiter(done)); + final ForkJoinTask c = ForkJoinTask.adapt(awaiter(done)); + final ForkJoinPool p = singletonPool(); + try (PoolCleaner cleaner = cleaner(p, done)) { + Thread external = new Thread(new CheckedRunnable() { + public void realRun() { + p.execute(a); + p.execute(b); + p.execute(c); + }}); + RecursiveAction s = new CheckedRecursiveAction() { + protected void realCompute() { + external.start(); + try { + external.join(); + } catch (Exception ex) { + threadUnexpectedException(ex); + } + assertTrue(p.hasQueuedSubmissions()); + assertTrue(Thread.currentThread() instanceof ForkJoinWorkerThread); + ForkJoinTask r = ForkJoinTask.pollSubmission(); + assertTrue(r == a || r == b || r == c); + assertFalse(r.isDone()); + }}; + p.invoke(s); + } + } + +} diff --git a/jsr166-tests/src/test/java/jsr166/ForkJoinTaskTest.java b/jsr166-tests/src/test/java/jsr166/ForkJoinTaskTest.java index 3c1fcb7db..1616d4f86 100644 --- a/jsr166-tests/src/test/java/jsr166/ForkJoinTaskTest.java +++ b/jsr166-tests/src/test/java/jsr166/ForkJoinTaskTest.java @@ -9,7 +9,10 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; @@ -30,7 +33,7 @@ public class ForkJoinTaskTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ForkJoinTaskTest.class); // } // Runs with "mainPool" use > 1 thread. singletonPool tests use 1 @@ -52,7 +55,7 @@ private static ForkJoinPool asyncSingletonPool() { } private void testInvokeOnPool(ForkJoinPool pool, RecursiveAction a) { - try { + try (PoolCleaner cleaner = cleaner(pool)) { assertFalse(a.isDone()); assertFalse(a.isCompletedNormally()); assertFalse(a.isCompletedAbnormally()); @@ -68,8 +71,6 @@ private void testInvokeOnPool(ForkJoinPool pool, RecursiveAction a) { assertFalse(a.isCancelled()); assertNull(a.getException()); assertNull(a.getRawResult()); - } finally { - joinPool(pool); } } @@ -102,17 +103,17 @@ void checkCompletedNormally(ForkJoinTask a, T expected) { { Thread.currentThread().interrupt(); - long t0 = System.nanoTime(); + long startTime = System.nanoTime(); assertSame(expected, a.join()); - assertTrue(millisElapsedSince(t0) < SMALL_DELAY_MS); + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); Thread.interrupted(); } { Thread.currentThread().interrupt(); - long t0 = System.nanoTime(); + long startTime = System.nanoTime(); a.quietlyJoin(); // should be no-op - assertTrue(millisElapsedSince(t0) < SMALL_DELAY_MS); + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); Thread.interrupted(); } @@ -145,9 +146,9 @@ void checkCancelled(ForkJoinTask a) { Thread.interrupted(); { - long t0 = System.nanoTime(); + long startTime = System.nanoTime(); a.quietlyJoin(); // should be no-op - assertTrue(millisElapsedSince(t0) < SMALL_DELAY_MS); + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); } try { @@ -183,9 +184,9 @@ void checkCompletedAbnormally(ForkJoinTask a, Throwable t) { Thread.interrupted(); { - long t0 = System.nanoTime(); + long startTime = System.nanoTime(); a.quietlyJoin(); // should be no-op - assertTrue(millisElapsedSince(t0) < SMALL_DELAY_MS); + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); } try { @@ -222,9 +223,9 @@ abstract static class BinaryAsyncAction extends ForkJoinTask { AtomicIntegerFieldUpdater.newUpdater(BinaryAsyncAction.class, "controlState"); - private BinaryAsyncAction parent; + private volatile BinaryAsyncAction parent; - private BinaryAsyncAction sibling; + private volatile BinaryAsyncAction sibling; protected BinaryAsyncAction() { } @@ -259,6 +260,14 @@ private void completeThisExceptionally(Throwable ex) { super.completeExceptionally(ex); } + public boolean cancel(boolean mayInterruptIfRunning) { + if (super.cancel(mayInterruptIfRunning)) { + completeExceptionally(new FJException()); + return true; + } + return false; + } + public final void complete() { BinaryAsyncAction a = this; for (;;) { @@ -280,13 +289,12 @@ public final void complete() { } public final void completeExceptionally(Throwable ex) { - BinaryAsyncAction a = this; - while (!a.isCompletedAbnormally()) { + for (BinaryAsyncAction a = this;;) { a.completeThisExceptionally(ex); BinaryAsyncAction s = a.sibling; - if (s != null) - s.cancel(false); - if (!a.onException() || (a = a.parent) == null) + if (s != null && !s.isDone()) + s.completeExceptionally(ex); + if ((a = a.parent) == null) break; } } @@ -336,15 +344,12 @@ public AsyncFib(int n) { public final boolean exec() { AsyncFib f = this; int n = f.number; - if (n > 1) { - while (n > 1) { - AsyncFib p = f; - AsyncFib r = new AsyncFib(n - 2); - f = new AsyncFib(--n); - p.linkSubtasks(r, f); - r.fork(); - } - f.number = n; + while (n > 1) { + AsyncFib p = f; + AsyncFib r = new AsyncFib(n - 2); + f = new AsyncFib(--n); + p.linkSubtasks(r, f); + r.fork(); } f.complete(); return false; @@ -364,15 +369,12 @@ public FailingAsyncFib(int n) { public final boolean exec() { FailingAsyncFib f = this; int n = f.number; - if (n > 1) { - while (n > 1) { - FailingAsyncFib p = f; - FailingAsyncFib r = new FailingAsyncFib(n - 2); - f = new FailingAsyncFib(--n); - p.linkSubtasks(r, f); - r.fork(); - } - f.number = n; + while (n > 1) { + FailingAsyncFib p = f; + FailingAsyncFib r = new FailingAsyncFib(n - 2); + f = new FailingAsyncFib(--n); + p.linkSubtasks(r, f); + r.fork(); } f.complete(); return false; @@ -777,6 +779,27 @@ protected void realCompute() { testInvokeOnPool(mainPool(), a); } + /** + * completeExceptionally(null) surprisingly has the same effect as + * completeExceptionally(new RuntimeException()) + */ + public void testCompleteExceptionally_null() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + f.completeExceptionally(null); + try { + f.invoke(); + shouldThrow(); + } catch (RuntimeException success) { + assertSame(success.getClass(), RuntimeException.class); + assertNull(success.getCause()); + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + /** * invokeAll(t1, t2) invokes all task arguments */ @@ -877,8 +900,10 @@ public void testAbnormalInvokeAll2() { protected void realCompute() { AsyncFib f = new AsyncFib(8); FailingAsyncFib g = new FailingAsyncFib(9); + ForkJoinTask[] tasks = { f, g }; + Collections.shuffle(Arrays.asList(tasks)); try { - invokeAll(f, g); + invokeAll(tasks); shouldThrow(); } catch (FJException success) { checkCompletedAbnormally(g, success); @@ -913,8 +938,10 @@ protected void realCompute() { AsyncFib f = new AsyncFib(8); FailingAsyncFib g = new FailingAsyncFib(9); AsyncFib h = new AsyncFib(7); + ForkJoinTask[] tasks = { f, g, h }; + Collections.shuffle(Arrays.asList(tasks)); try { - invokeAll(f, g, h); + invokeAll(tasks); shouldThrow(); } catch (FJException success) { checkCompletedAbnormally(g, success); @@ -932,12 +959,11 @@ protected void realCompute() { FailingAsyncFib f = new FailingAsyncFib(8); AsyncFib g = new AsyncFib(9); AsyncFib h = new AsyncFib(7); - HashSet set = new HashSet(); - set.add(f); - set.add(g); - set.add(h); + ForkJoinTask[] tasks = { f, g, h }; + List taskList = Arrays.asList(tasks); + Collections.shuffle(taskList); try { - invokeAll(set); + invokeAll(taskList); shouldThrow(); } catch (FJException success) { checkCompletedAbnormally(f, success); @@ -1544,8 +1570,10 @@ public void testAbnormalInvokeAll2Singleton() { protected void realCompute() { AsyncFib f = new AsyncFib(8); FailingAsyncFib g = new FailingAsyncFib(9); + ForkJoinTask[] tasks = { f, g }; + Collections.shuffle(Arrays.asList(tasks)); try { - invokeAll(f, g); + invokeAll(tasks); shouldThrow(); } catch (FJException success) { checkCompletedAbnormally(g, success); @@ -1580,8 +1608,10 @@ protected void realCompute() { AsyncFib f = new AsyncFib(8); FailingAsyncFib g = new FailingAsyncFib(9); AsyncFib h = new AsyncFib(7); + ForkJoinTask[] tasks = { f, g, h }; + Collections.shuffle(Arrays.asList(tasks)); try { - invokeAll(f, g, h); + invokeAll(tasks); shouldThrow(); } catch (FJException success) { checkCompletedAbnormally(g, success); @@ -1599,12 +1629,11 @@ protected void realCompute() { FailingAsyncFib f = new FailingAsyncFib(8); AsyncFib g = new AsyncFib(9); AsyncFib h = new AsyncFib(7); - HashSet set = new HashSet(); - set.add(f); - set.add(g); - set.add(h); + ForkJoinTask[] tasks = { f, g, h }; + List taskList = Arrays.asList(tasks); + Collections.shuffle(taskList); try { - invokeAll(set); + invokeAll(taskList); shouldThrow(); } catch (FJException success) { checkCompletedAbnormally(f, success); diff --git a/jsr166-tests/src/test/java/jsr166/FutureTaskTest.java b/jsr166-tests/src/test/java/jsr166/FutureTaskTest.java index a5d8c466a..44d12b347 100644 --- a/jsr166-tests/src/test/java/jsr166/FutureTaskTest.java +++ b/jsr166-tests/src/test/java/jsr166/FutureTaskTest.java @@ -38,7 +38,7 @@ public class FutureTaskTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(FutureTaskTest.class); // } void checkIsDone(Future f) { @@ -272,8 +272,8 @@ public void testRunAndReset() { for (int i = 0; i < 3; i++) { assertTrue(task.runAndReset()); checkNotDone(task); - assertEquals(i+1, task.runCount()); - assertEquals(i+1, task.runAndResetCount()); + assertEquals(i + 1, task.runCount()); + assertEquals(i + 1, task.runAndResetCount()); assertEquals(0, task.setCount()); assertEquals(0, task.setExceptionCount()); } @@ -289,7 +289,7 @@ public void testRunAndResetAfterCancel() { for (int i = 0; i < 3; i++) { assertFalse(task.runAndReset()); assertEquals(0, task.runCount()); - assertEquals(i+1, task.runAndResetCount()); + assertEquals(i + 1, task.runAndResetCount()); assertEquals(0, task.setCount()); assertEquals(0, task.setExceptionCount()); } diff --git a/jsr166-tests/src/test/java/jsr166/JSR166TestCase.java b/jsr166-tests/src/test/java/jsr166/JSR166TestCase.java index 46be906c0..fc1632cb8 100644 --- a/jsr166-tests/src/test/java/jsr166/JSR166TestCase.java +++ b/jsr166-tests/src/test/java/jsr166/JSR166TestCase.java @@ -6,17 +6,21 @@ * Pat Fisher, Mike Judd. */ + package jsr166; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.NANOSECONDS; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; -import java.security.CodeSource; +import java.lang.reflect.Modifier; + import java.security.CodeSource; import java.security.Permission; import java.security.PermissionCollection; import java.security.Permissions; @@ -35,7 +39,10 @@ import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; import java.util.concurrent.RecursiveAction; import java.util.concurrent.RecursiveTask; @@ -44,12 +51,15 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; import java.util.regex.Pattern; import junit.framework.AssertionFailedError; import junit.framework.Test; import junit.framework.TestCase; +import junit.framework.TestResult; import junit.framework.TestSuite; /** @@ -62,18 +72,18 @@ * *
    * - *
  1. All assertions in code running in generated threads must use + *
  2. All assertions in code running in generated threads must use * the forms {@link #threadFail}, {@link #threadAssertTrue}, {@link * #threadAssertEquals}, or {@link #threadAssertNull}, (not * {@code fail}, {@code assertTrue}, etc.) It is OK (but not * particularly recommended) for other code to use these forms too. * Only the most typically used JUnit assertion methods are defined - * this way, but enough to live with.
  3. + * this way, but enough to live with. * - *
  4. If you override {@link #setUp} or {@link #tearDown}, make sure + *
  5. If you override {@link #setUp} or {@link #tearDown}, make sure * to invoke {@code super.setUp} and {@code super.tearDown} within * them. These methods are used to clear and check for thread - * assertion failures.
  6. + * assertion failures. * *
  7. All delays and timeouts must use one of the constants {@code * SHORT_DELAY_MS}, {@code SMALL_DELAY_MS}, {@code MEDIUM_DELAY_MS}, @@ -84,51 +94,480 @@ * is always discriminable as larger than SHORT and smaller than * MEDIUM. And so on. These constants are set to conservative values, * but even so, if there is ever any doubt, they can all be increased - * in one spot to rerun tests on slower platforms.
  8. + * in one spot to rerun tests on slower platforms. * - *
  9. All threads generated must be joined inside each test case + *
  10. All threads generated must be joined inside each test case * method (or {@code fail} to do so) before returning from the * method. The {@code joinPool} method can be used to do this when - * using Executors.
  11. + * using Executors. * *
* *

Other notes *

    * - *
  • Usually, there is one testcase method per JSR166 method + *
  • Usually, there is one testcase method per JSR166 method * covering "normal" operation, and then as many exception-testing * methods as there are exceptions the method can throw. Sometimes * there are multiple tests per JSR166 method when the different * "normal" behaviors differ significantly. And sometimes testcases - * cover multiple methods when they cannot be tested in - * isolation.
  • + * cover multiple methods when they cannot be tested in isolation. * - *
  • The documentation style for testcases is to provide as javadoc + *
  • The documentation style for testcases is to provide as javadoc * a simple sentence or two describing the property that the testcase * method purports to test. The javadocs do not say anything about how - * the property is tested. To find out, read the code.
  • + * the property is tested. To find out, read the code. * - *
  • These tests are "conformance tests", and do not attempt to + *
  • These tests are "conformance tests", and do not attempt to * test throughput, latency, scalability or other performance factors * (see the separate "jtreg" tests for a set intended to check these * for the most central aspects of functionality.) So, most tests use * the smallest sensible numbers of threads, collection sizes, etc - * needed to check basic conformance.
  • + * needed to check basic conformance. * *
  • The test classes currently do not declare inclusion in * any particular package to simplify things for people integrating - * them in TCK test suites.
  • + * them in TCK test suites. * - *
  • As a convenience, the {@code main} of this class (JSR166TestCase) - * runs all JSR166 unit tests.
  • + *
  • As a convenience, the {@code main} of this class (JSR166TestCase) + * runs all JSR166 unit tests. * *
*/ public class JSR166TestCase extends TestCase { - // Delays for timing-dependent tests, in milliseconds. + private static final boolean useSecurityManager = + Boolean.getBoolean("jsr166.useSecurityManager"); + + protected static final boolean expensiveTests = + Boolean.getBoolean("jsr166.expensiveTests"); + + /** + * If true, also run tests that are not part of the official tck + * because they test unspecified implementation details. + */ + protected static final boolean testImplementationDetails = + Boolean.getBoolean("jsr166.testImplementationDetails"); + + /** + * If true, report on stdout all "slow" tests, that is, ones that + * take more than profileThreshold milliseconds to execute. + */ + private static final boolean profileTests = + Boolean.getBoolean("jsr166.profileTests"); + + /** + * The number of milliseconds that tests are permitted for + * execution without being reported, when profileTests is set. + */ + private static final long profileThreshold = + Long.getLong("jsr166.profileThreshold", 100); + + /** + * The number of repetitions per test (for tickling rare bugs). + */ + private static final int runsPerTest = + Integer.getInteger("jsr166.runsPerTest", 1); + + /** + * The number of repetitions of the test suite (for finding leaks?). + */ + private static final int suiteRuns = + Integer.getInteger("jsr166.suiteRuns", 1); + + private static float systemPropertyValue(String name, float defaultValue) { + String floatString = System.getProperty(name); + if (floatString == null) + return defaultValue; + try { + return Float.parseFloat(floatString); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException( + String.format("Bad float value in system property %s=%s", + name, floatString)); + } + } + + /** + * The scaling factor to apply to standard delays used in tests. + */ + private static final float delayFactor = + systemPropertyValue("jsr166.delay.factor", 1.0f); + + /** + * The timeout factor as used in the jtreg test harness. + * See: http://openjdk.java.net/jtreg/tag-spec.html + */ + private static final float jtregTestTimeoutFactor + = systemPropertyValue("test.timeout.factor", 1.0f); + + public JSR166TestCase() { super(); } + public JSR166TestCase(String name) { super(name); } + + /** + * A filter for tests to run, matching strings of the form + * methodName(className), e.g. "testInvokeAll5(ForkJoinPoolTest)" + * Usefully combined with jsr166.runsPerTest. + */ + private static final Pattern methodFilter = methodFilter(); + + private static Pattern methodFilter() { + String regex = System.getProperty("jsr166.methodFilter"); + return (regex == null) ? null : Pattern.compile(regex); + } + + // Instrumentation to debug very rare, but very annoying hung test runs. + static volatile TestCase currentTestCase; + // static volatile int currentRun = 0; + static { + Runnable checkForWedgedTest = new Runnable() { public void run() { + // Avoid spurious reports with enormous runsPerTest. + // A single test case run should never take more than 1 second. + // But let's cap it at the high end too ... + final int timeoutMinutes = + Math.min(15, Math.max(runsPerTest / 60, 1)); + for (TestCase lastTestCase = currentTestCase;;) { + try { MINUTES.sleep(timeoutMinutes); } + catch (InterruptedException unexpected) { break; } + if (lastTestCase == currentTestCase) { + System.err.printf( + "Looks like we're stuck running test: %s%n", + lastTestCase); +// System.err.printf( +// "Looks like we're stuck running test: %s (%d/%d)%n", +// lastTestCase, currentRun, runsPerTest); +// System.err.println("availableProcessors=" + +// Runtime.getRuntime().availableProcessors()); +// System.err.printf("cpu model = %s%n", cpuModel()); + dumpTestThreads(); + // one stack dump is probably enough; more would be spam + break; + } + lastTestCase = currentTestCase; + }}}; + Thread thread = new Thread(checkForWedgedTest, "checkForWedgedTest"); + thread.setDaemon(true); + thread.start(); + } + +// public static String cpuModel() { +// try { +// Matcher matcher = Pattern.compile("model name\\s*: (.*)") +// .matcher(new String( +// Files.readAllBytes(Paths.get("/proc/cpuinfo")), "UTF-8")); +// matcher.find(); +// return matcher.group(1); +// } catch (Exception ex) { return null; } +// } + + public void runBare() throws Throwable { + currentTestCase = this; + if (methodFilter == null + || methodFilter.matcher(toString()).find()) + super.runBare(); + } - protected static final boolean expensiveTests = false; + protected void runTest() throws Throwable { + for (int i = 0; i < runsPerTest; i++) { + // currentRun = i; + if (profileTests) + runTestProfiled(); + else + super.runTest(); + } + } + + protected void runTestProfiled() throws Throwable { + for (int i = 0; i < 2; i++) { + long startTime = System.nanoTime(); + super.runTest(); + long elapsedMillis = millisElapsedSince(startTime); + if (elapsedMillis < profileThreshold) + break; + // Never report first run of any test; treat it as a + // warmup run, notably to trigger all needed classloading, + if (i > 0) + System.out.printf("%n%s: %d%n", toString(), elapsedMillis); + } + } + + /** + * Runs all JSR166 unit tests using junit.textui.TestRunner. + */ + // android-note: Removed because no junit.textui + // public static void main(String[] args) { + // main(suite(), args); + // } + + // static class PithyResultPrinter extends junit.textui.ResultPrinter { + // PithyResultPrinter(java.io.PrintStream writer) { super(writer); } + // long runTime; + // public void startTest(Test test) {} + // protected void printHeader(long runTime) { + // this.runTime = runTime; // defer printing for later + // } + // protected void printFooter(TestResult result) { + // if (result.wasSuccessful()) { + // getWriter().println("OK (" + result.runCount() + " tests)" + // + " Time: " + elapsedTimeAsString(runTime)); + // } else { + // getWriter().println("Time: " + elapsedTimeAsString(runTime)); + // super.printFooter(result); + // } + // } + // } + + /** + * Returns a TestRunner that doesn't bother with unnecessary + * fluff, like printing a "." for each test case. + */ + // static junit.textui.TestRunner newPithyTestRunner() { + // junit.textui.TestRunner runner = new junit.textui.TestRunner(); + // runner.setPrinter(new PithyResultPrinter(System.out)); + // return runner; + // } + + /** + * Runs all unit tests in the given test suite. + * Actual behavior influenced by jsr166.* system properties. + */ + // static void main(Test suite, String[] args) { + // if (useSecurityManager) { + // System.err.println("Setting a permissive security manager"); + // Policy.setPolicy(permissivePolicy()); + // System.setSecurityManager(new SecurityManager()); + // } + // for (int i = 0; i < suiteRuns; i++) { + // TestResult result = newPithyTestRunner().doRun(suite); + // if (!result.wasSuccessful()) + // System.exit(1); + // System.gc(); + // System.runFinalization(); + // } + // } + + public static TestSuite newTestSuite(Object... suiteOrClasses) { + TestSuite suite = new TestSuite(); + for (Object suiteOrClass : suiteOrClasses) { + if (suiteOrClass instanceof TestSuite) + suite.addTest((TestSuite) suiteOrClass); + else if (suiteOrClass instanceof Class) + suite.addTest(new TestSuite((Class) suiteOrClass)); + else + throw new ClassCastException("not a test suite or class"); + } + return suite; + } + + public static void addNamedTestClasses(TestSuite suite, + String... testClassNames) { + for (String testClassName : testClassNames) { + try { + Class testClass = Class.forName(testClassName); + Method m = testClass.getDeclaredMethod("suite", + new Class[0]); + suite.addTest(newTestSuite((Test)m.invoke(null))); + } catch (Exception e) { + throw new Error("Missing test class", e); + } + } + } + + public static final double JAVA_CLASS_VERSION; + public static final String JAVA_SPECIFICATION_VERSION; + static { + try { + JAVA_CLASS_VERSION = java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Double run() { + return Double.valueOf(System.getProperty("java.class.version"));}}); + JAVA_SPECIFICATION_VERSION = java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public String run() { + return System.getProperty("java.specification.version");}}); + } catch (Throwable t) { + throw new Error(t); + } + } + + public static boolean atLeastJava6() { return JAVA_CLASS_VERSION >= 50.0; } + public static boolean atLeastJava7() { return JAVA_CLASS_VERSION >= 51.0; } + public static boolean atLeastJava8() { return JAVA_CLASS_VERSION >= 52.0; } + public static boolean atLeastJava9() { + return JAVA_CLASS_VERSION >= 53.0 + // As of 2015-09, java9 still uses 52.0 class file version + || JAVA_SPECIFICATION_VERSION.matches("^(1\\.)?(9|[0-9][0-9])$"); + } + public static boolean atLeastJava10() { + return JAVA_CLASS_VERSION >= 54.0 + || JAVA_SPECIFICATION_VERSION.matches("^(1\\.)?[0-9][0-9]$"); + } + + /** + * Collects all JSR166 unit tests as one suite. + */ + // android-note: Removed because the CTS runner does a bad job of + // public static Test suite() { + // // Java7+ test classes + // TestSuite suite = newTestSuite( + // ForkJoinPoolTest.suite(), + // ForkJoinTaskTest.suite(), + // RecursiveActionTest.suite(), + // RecursiveTaskTest.suite(), + // LinkedTransferQueueTest.suite(), + // PhaserTest.suite(), + // ThreadLocalRandomTest.suite(), + // AbstractExecutorServiceTest.suite(), + // AbstractQueueTest.suite(), + // AbstractQueuedSynchronizerTest.suite(), + // AbstractQueuedLongSynchronizerTest.suite(), + // ArrayBlockingQueueTest.suite(), + // ArrayDequeTest.suite(), + // AtomicBooleanTest.suite(), + // AtomicIntegerArrayTest.suite(), + // AtomicIntegerFieldUpdaterTest.suite(), + // AtomicIntegerTest.suite(), + // AtomicLongArrayTest.suite(), + // AtomicLongFieldUpdaterTest.suite(), + // AtomicLongTest.suite(), + // AtomicMarkableReferenceTest.suite(), + // AtomicReferenceArrayTest.suite(), + // AtomicReferenceFieldUpdaterTest.suite(), + // AtomicReferenceTest.suite(), + // AtomicStampedReferenceTest.suite(), + // ConcurrentHashMapTest.suite(), + // ConcurrentLinkedDequeTest.suite(), + // ConcurrentLinkedQueueTest.suite(), + // ConcurrentSkipListMapTest.suite(), + // ConcurrentSkipListSubMapTest.suite(), + // ConcurrentSkipListSetTest.suite(), + // ConcurrentSkipListSubSetTest.suite(), + // CopyOnWriteArrayListTest.suite(), + // CopyOnWriteArraySetTest.suite(), + // CountDownLatchTest.suite(), + // CyclicBarrierTest.suite(), + // DelayQueueTest.suite(), + // EntryTest.suite(), + // ExchangerTest.suite(), + // ExecutorsTest.suite(), + // ExecutorCompletionServiceTest.suite(), + // FutureTaskTest.suite(), + // LinkedBlockingDequeTest.suite(), + // LinkedBlockingQueueTest.suite(), + // LinkedListTest.suite(), + // LockSupportTest.suite(), + // PriorityBlockingQueueTest.suite(), + // PriorityQueueTest.suite(), + // ReentrantLockTest.suite(), + // ReentrantReadWriteLockTest.suite(), + // ScheduledExecutorTest.suite(), + // ScheduledExecutorSubclassTest.suite(), + // SemaphoreTest.suite(), + // SynchronousQueueTest.suite(), + // SystemTest.suite(), + // ThreadLocalTest.suite(), + // ThreadPoolExecutorTest.suite(), + // ThreadPoolExecutorSubclassTest.suite(), + // ThreadTest.suite(), + // TimeUnitTest.suite(), + // TreeMapTest.suite(), + // TreeSetTest.suite(), + // TreeSubMapTest.suite(), + // TreeSubSetTest.suite()); + + // // Java8+ test classes + // if (atLeastJava8()) { + // String[] java8TestClassNames = { + // "Atomic8Test", + // "CompletableFutureTest", + // "ConcurrentHashMap8Test", + // "CountedCompleterTest", + // "DoubleAccumulatorTest", + // "DoubleAdderTest", + // "ForkJoinPool8Test", + // "ForkJoinTask8Test", + // "LongAccumulatorTest", + // "LongAdderTest", + // "SplittableRandomTest", + // "StampedLockTest", + // "SubmissionPublisherTest", + // "ThreadLocalRandom8Test", + // }; + // addNamedTestClasses(suite, java8TestClassNames); + // } + + // // Java9+ test classes + // if (atLeastJava9()) { + // String[] java9TestClassNames = { + // // Currently empty, but expecting varhandle tests + // }; + // addNamedTestClasses(suite, java9TestClassNames); + // } + + // return suite; + // } + + /** Returns list of junit-style test method names in given class. */ + public static ArrayList testMethodNames(Class testClass) { + Method[] methods = testClass.getDeclaredMethods(); + ArrayList names = new ArrayList(methods.length); + for (Method method : methods) { + if (method.getName().startsWith("test") + && Modifier.isPublic(method.getModifiers()) + // method.getParameterCount() requires jdk8+ + && method.getParameterTypes().length == 0) { + names.add(method.getName()); + } + } + return names; + } + + /** + * Returns junit-style testSuite for the given test class, but + * parameterized by passing extra data to each test. + */ + public static Test parameterizedTestSuite + (Class testClass, + Class dataClass, + ExtraData data) { + try { + TestSuite suite = new TestSuite(); + Constructor c = + testClass.getDeclaredConstructor(dataClass, String.class); + for (String methodName : testMethodNames(testClass)) + suite.addTest((Test) c.newInstance(data, methodName)); + return suite; + } catch (Exception e) { + throw new Error(e); + } + } + + /** + * Returns junit-style testSuite for the jdk8 extension of the + * given test class, but parameterized by passing extra data to + * each test. Uses reflection to allow compilation in jdk7. + */ + public static Test jdk8ParameterizedTestSuite + (Class testClass, + Class dataClass, + ExtraData data) { + if (atLeastJava8()) { + String name = testClass.getName(); + String name8 = name.replaceAll("Test$", "8Test"); + if (name.equals(name8)) throw new Error(name); + try { + return (Test) + Class.forName(name8) + .getMethod("testSuite", new Class[] { dataClass }) + .invoke(null, data); + } catch (Exception e) { + throw new Error(e); + } + } else { + return new TestSuite(); + } + } + + // Delays for timing-dependent tests, in milliseconds. public static long SHORT_DELAY_MS; public static long SMALL_DELAY_MS; @@ -136,11 +575,13 @@ public class JSR166TestCase extends TestCase { public static long LONG_DELAY_MS; /** - * Returns the shortest timed delay. This could - * be reimplemented to use for example a Property. + * Returns the shortest timed delay. This can be scaled up for + * slow machines using the jsr166.delay.factor system property, + * or via jtreg's -timeoutFactor: flag. + * http://openjdk.java.net/jtreg/command-help.html */ protected long getShortDelay() { - return 50; + return (long) (50 * delayFactor * jtregTestTimeoutFactor); } /** @@ -162,11 +603,12 @@ long timeoutMillis() { } /** - * Returns a new Date instance representing a time delayMillis - * milliseconds in the future. + * Returns a new Date instance representing a time at least + * delayMillis milliseconds in the future. */ Date delayedDate(long delayMillis) { - return new Date(System.currentTimeMillis() + delayMillis); + // Add 1 because currentTimeMillis is known to round into the past. + return new Date(System.currentTimeMillis() + delayMillis + 1); } /** @@ -182,6 +624,8 @@ Date delayedDate(long delayMillis) { * the same test have no effect. */ public void threadRecordFailure(Throwable t) { + System.err.println(t); + dumpTestThreads(); threadFailure.compareAndSet(null, t); } @@ -189,6 +633,13 @@ public void setUp() { setDelays(); } + void tearDownFail(String format, Object... args) { + String msg = toString() + ": " + String.format(format, args); + System.err.println(msg); + dumpTestThreads(); + throw new AssertionFailedError(msg); + } + /** * Extra checks that get done for all test cases. * @@ -216,16 +667,16 @@ else if (t instanceof Exception) } if (Thread.interrupted()) - throw new AssertionFailedError("interrupt status set in main thread"); + tearDownFail("interrupt status set in main thread"); checkForkJoinPoolThreadLeaks(); } /** - * Finds missing try { ... } finally { joinPool(e); } + * Finds missing PoolCleaners */ void checkForkJoinPoolThreadLeaks() throws InterruptedException { - Thread[] survivors = new Thread[5]; + Thread[] survivors = new Thread[7]; int count = Thread.enumerate(survivors); for (int i = 0; i < count; i++) { Thread thread = survivors[i]; @@ -233,13 +684,15 @@ void checkForkJoinPoolThreadLeaks() throws InterruptedException { if (name.startsWith("ForkJoinPool-")) { // give thread some time to terminate thread.join(LONG_DELAY_MS); - if (!thread.isAlive()) continue; - thread.stop(); - throw new AssertionFailedError - (String.format("Found leaked ForkJoinPool thread test=%s thread=%s%n", - toString(), name)); + if (thread.isAlive()) + tearDownFail("Found leaked ForkJoinPool thread thread=%s", + thread); } } + + if (!ForkJoinPool.commonPool() + .awaitQuiescence(LONG_DELAY_MS, MILLISECONDS)) + tearDownFail("ForkJoin common pool thread stuck"); } /** @@ -252,7 +705,7 @@ public void threadFail(String reason) { fail(reason); } catch (AssertionFailedError t) { threadRecordFailure(t); - fail(reason); + throw t; } } @@ -379,44 +832,148 @@ else if (t instanceof Error) /** * Delays, via Thread.sleep, for the given millisecond delay, but * if the sleep is shorter than specified, may re-sleep or yield - * until time elapses. + * until time elapses. Ensures that the given time, as measured + * by System.nanoTime(), has elapsed. */ static void delay(long millis) throws InterruptedException { - long startTime = System.nanoTime(); - long ns = millis * 1000 * 1000; - for (;;) { + long nanos = millis * (1000 * 1000); + final long wakeupTime = System.nanoTime() + nanos; + do { if (millis > 0L) Thread.sleep(millis); else // too short to sleep Thread.yield(); - long d = ns - (System.nanoTime() - startTime); - if (d > 0L) - millis = d / (1000 * 1000); - else - break; + nanos = wakeupTime - System.nanoTime(); + millis = nanos / (1000 * 1000); + } while (nanos >= 0L); + } + + /** + * Allows use of try-with-resources with per-test thread pools. + */ + class PoolCleaner implements AutoCloseable { + private final ExecutorService pool; + public PoolCleaner(ExecutorService pool) { this.pool = pool; } + public void close() { joinPool(pool); } + } + + /** + * An extension of PoolCleaner that has an action to release the pool. + */ + class PoolCleanerWithReleaser extends PoolCleaner { + private final Runnable releaser; + public PoolCleanerWithReleaser(ExecutorService pool, Runnable releaser) { + super(pool); + this.releaser = releaser; } + public void close() { + try { + releaser.run(); + } finally { + super.close(); + } + } + } + + PoolCleaner cleaner(ExecutorService pool) { + return new PoolCleaner(pool); + } + + PoolCleaner cleaner(ExecutorService pool, Runnable releaser) { + return new PoolCleanerWithReleaser(pool, releaser); + } + + PoolCleaner cleaner(ExecutorService pool, CountDownLatch latch) { + return new PoolCleanerWithReleaser(pool, releaser(latch)); + } + + Runnable releaser(final CountDownLatch latch) { + return new Runnable() { public void run() { + do { latch.countDown(); } + while (latch.getCount() > 0); + }}; + } + + PoolCleaner cleaner(ExecutorService pool, AtomicBoolean flag) { + return new PoolCleanerWithReleaser(pool, releaser(flag)); + } + + Runnable releaser(final AtomicBoolean flag) { + return new Runnable() { public void run() { flag.set(true); }}; } /** * Waits out termination of a thread pool or fails doing so. */ - void joinPool(ExecutorService exec) { + void joinPool(ExecutorService pool) { try { - exec.shutdown(); - if (!exec.awaitTermination(2 * LONG_DELAY_MS, MILLISECONDS)) - fail("ExecutorService " + exec + - " did not terminate in a timely manner"); + pool.shutdown(); + if (!pool.awaitTermination(2 * LONG_DELAY_MS, MILLISECONDS)) { + try { + threadFail("ExecutorService " + pool + + " did not terminate in a timely manner"); + } finally { + // last resort, for the benefit of subsequent tests + pool.shutdownNow(); + pool.awaitTermination(MEDIUM_DELAY_MS, MILLISECONDS); + } + } } catch (SecurityException ok) { // Allowed in case test doesn't have privs } catch (InterruptedException fail) { - fail("Unexpected InterruptedException"); + threadFail("Unexpected InterruptedException"); } } + /** Like Runnable, but with the freedom to throw anything */ + interface Action { public void run() throws Throwable; } + /** - * A debugging tool to print all stack traces, as jstack does. + * Runs all the given actions in parallel, failing if any fail. + * Useful for running multiple variants of tests that are + * necessarily individually slow because they must block. */ - static void printAllStackTraces() { + void testInParallel(Action ... actions) { + ExecutorService pool = Executors.newCachedThreadPool(); + try (PoolCleaner cleaner = cleaner(pool)) { + ArrayList> futures = new ArrayList<>(actions.length); + for (final Action action : actions) + futures.add(pool.submit(new CheckedRunnable() { + public void realRun() throws Throwable { action.run();}})); + for (Future future : futures) + try { + assertNull(future.get(LONG_DELAY_MS, MILLISECONDS)); + } catch (ExecutionException ex) { + threadUnexpectedException(ex.getCause()); + } catch (Exception ex) { + threadUnexpectedException(ex); + } + } + } + + /** + * A debugging tool to print stack traces of most threads, as jstack does. + * Uninteresting threads are filtered out. + */ + static void dumpTestThreads() { + // Android-change no ThreadMXBean + // ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + // System.err.println("------ stacktrace dump start ------"); + // for (ThreadInfo info : threadMXBean.dumpAllThreads(true, true)) { + // String name = info.getThreadName(); + // if ("Signal Dispatcher".equals(name)) + // continue; + // if ("Reference Handler".equals(name) + // && info.getLockName().startsWith("java.lang.ref.Reference$Lock")) + // continue; + // if ("Finalizer".equals(name) + // && info.getLockName().startsWith("java.lang.ref.ReferenceQueue$Lock")) + // continue; + // if ("checkForWedgedTest".equals(name)) + // continue; + // System.err.print(info); + // } + // System.err.println("------ stacktrace dump end ------"); } /** @@ -436,7 +993,7 @@ void assertThreadStaysAlive(Thread thread, long millis) { delay(millis); assertTrue(thread.isAlive()); } catch (InterruptedException fail) { - fail("Unexpected InterruptedException"); + threadFail("Unexpected InterruptedException"); } } @@ -458,7 +1015,7 @@ void assertThreadsStayAlive(long millis, Thread... threads) { for (Thread thread : threads) assertTrue(thread.isAlive()); } catch (InterruptedException fail) { - fail("Unexpected InterruptedException"); + threadFail("Unexpected InterruptedException"); } } @@ -532,6 +1089,12 @@ public void shouldThrow(String exceptionName) { * getPolicy/setPolicy. */ public void runWithPermissions(Runnable r, Permission... permissions) { + // Android-changed - no SecurityManager + // SecurityManager sm = System.getSecurityManager(); + // if (sm == null) { + // r.run(); + // } + // runWithSecurityManagerWithPermissions(r, permissions); r.run(); } @@ -544,6 +1107,30 @@ public void runWithPermissions(Runnable r, Permission... permissions) { */ public void runWithSecurityManagerWithPermissions(Runnable r, Permission... permissions) { + // Android-changed - no SecurityManager + // SecurityManager sm = System.getSecurityManager(); + // if (sm == null) { + // Policy savedPolicy = Policy.getPolicy(); + // try { + // Policy.setPolicy(permissivePolicy()); + // System.setSecurityManager(new SecurityManager()); + // runWithSecurityManagerWithPermissions(r, permissions); + // } finally { + // System.setSecurityManager(null); + // Policy.setPolicy(savedPolicy); + // } + // } else { + // Policy savedPolicy = Policy.getPolicy(); + // AdjustablePolicy policy = new AdjustablePolicy(permissions); + // Policy.setPolicy(policy); + + // try { + // r.run(); + // } finally { + // policy.addPermission(new SecurityPermission("setPolicy")); + // Policy.setPolicy(savedPolicy); + // } + // } r.run(); } @@ -648,19 +1235,6 @@ void waitForThreadToEnterWaitState(Thread thread) { waitForThreadToEnterWaitState(thread, LONG_DELAY_MS); } - void waitForThreadToEnterWaitStateNoTimeout(Thread thread) { - for (;;) { - Thread.State s = thread.getState(); - if (s == Thread.State.BLOCKED || - s == Thread.State.WAITING || - s == Thread.State.TIMED_WAITING) - return; - else if (s == Thread.State.TERMINATED) - fail("Unexpected thread termination"); - Thread.yield(); - } - } - /** * Returns the number of milliseconds since time given by * startNanoTime, which must have been previously returned from a @@ -723,7 +1297,7 @@ void awaitTermination(Thread t, long timeoutMillis) { } finally { if (t.getState() != Thread.State.TERMINATED) { t.interrupt(); - fail("Test timed out"); + threadFail("timed out waiting for thread to terminate"); } } } @@ -848,7 +1422,10 @@ public static class NoOpCallable implements Callable { public static final String TEST_STRING = "a test string"; public static class StringTask implements Callable { - public String call() { return TEST_STRING; } + final String value; + public StringTask() { this(TEST_STRING); } + public StringTask(String value) { this.value = value; } + public String call() { return value; } } public Callable latchAwaitingStringTask(final CountDownLatch latch) { @@ -861,24 +1438,50 @@ protected String realCall() { }}; } - public Runnable awaiter(final CountDownLatch latch) { + public Runnable countDowner(final CountDownLatch latch) { return new CheckedRunnable() { public void realRun() throws InterruptedException { - await(latch); + latch.countDown(); }}; } - public void await(CountDownLatch latch) { + class LatchAwaiter extends CheckedRunnable { + static final int NEW = 0; + static final int RUNNING = 1; + static final int DONE = 2; + final CountDownLatch latch; + int state = NEW; + LatchAwaiter(CountDownLatch latch) { this.latch = latch; } + public void realRun() throws InterruptedException { + state = 1; + await(latch); + state = 2; + } + } + + public LatchAwaiter awaiter(CountDownLatch latch) { + return new LatchAwaiter(latch); + } + + public void await(CountDownLatch latch, long timeoutMillis) { try { - assertTrue(latch.await(LONG_DELAY_MS, MILLISECONDS)); + if (!latch.await(timeoutMillis, MILLISECONDS)) + fail("timed out waiting for CountDownLatch for " + + (timeoutMillis/1000) + " sec"); } catch (Throwable fail) { threadUnexpectedException(fail); } } + public void await(CountDownLatch latch) { + await(latch, LONG_DELAY_MS); + } + public void await(Semaphore semaphore) { try { - assertTrue(semaphore.tryAcquire(LONG_DELAY_MS, MILLISECONDS)); + if (!semaphore.tryAcquire(LONG_DELAY_MS, MILLISECONDS)) + fail("timed out waiting for Semaphore for " + + (LONG_DELAY_MS/1000) + " sec"); } catch (Throwable fail) { threadUnexpectedException(fail); } diff --git a/jsr166-tests/src/test/java/jsr166/LinkedBlockingDequeTest.java b/jsr166-tests/src/test/java/jsr166/LinkedBlockingDequeTest.java index 62802bb7f..789373d3b 100644 --- a/jsr166-tests/src/test/java/jsr166/LinkedBlockingDequeTest.java +++ b/jsr166-tests/src/test/java/jsr166/LinkedBlockingDequeTest.java @@ -26,25 +26,24 @@ public class LinkedBlockingDequeTest extends JSR166TestCase { - // android-note: These tests have been moved into their own separate - // classes to work around CTS issues. - // - // public static class Unbounded extends BlockingQueueTest { - // protected BlockingQueue emptyCollection() { - // return new LinkedBlockingDeque(); - // } - // } - // - // public static class Bounded extends BlockingQueueTest { - // protected BlockingQueue emptyCollection() { - // return new LinkedBlockingDeque(SIZE); - // } - // } + public static class Unbounded extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new LinkedBlockingDeque(); + } + } + + public static class Bounded extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new LinkedBlockingDeque(SIZE); + } + } + + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. // // public static void main(String[] args) { // main(suite(), args); // } - // // public static Test suite() { // return newTestSuite(LinkedBlockingDequeTest.class, // new Unbounded().testSuite(), @@ -87,7 +86,7 @@ public void testEmpty() { public void testSize() { LinkedBlockingDeque q = populatedDeque(SIZE); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); q.removeFirst(); } for (int i = 0; i < SIZE; ++i) { @@ -152,7 +151,7 @@ public void testPollFirst() { */ public void testPollLast() { LinkedBlockingDeque q = populatedDeque(SIZE); - for (int i = SIZE-1; i >= 0; --i) { + for (int i = SIZE - 1; i >= 0; --i) { assertEquals(i, q.pollLast()); } assertNull(q.pollLast()); @@ -191,7 +190,7 @@ public void testPeek() { */ public void testPeekLast() { LinkedBlockingDeque q = populatedDeque(SIZE); - for (int i = SIZE-1; i >= 0; --i) { + for (int i = SIZE - 1; i >= 0; --i) { assertEquals(i, q.peekLast()); assertEquals(i, q.pollLast()); assertTrue(q.peekLast() == null || @@ -221,7 +220,7 @@ public void testFirstElement() { */ public void testLastElement() { LinkedBlockingDeque q = populatedDeque(SIZE); - for (int i = SIZE-1; i >= 0; --i) { + for (int i = SIZE - 1; i >= 0; --i) { assertEquals(i, q.getLast()); assertEquals(i, q.pollLast()); } @@ -286,7 +285,7 @@ public void testRemoveFirstOccurrence() { } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.removeFirstOccurrence(new Integer(i))); - assertFalse(q.removeFirstOccurrence(new Integer(i+1))); + assertFalse(q.removeFirstOccurrence(new Integer(i + 1))); } assertTrue(q.isEmpty()); } @@ -301,7 +300,7 @@ public void testRemoveLastOccurrence() { } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.removeLastOccurrence(new Integer(i))); - assertFalse(q.removeLastOccurrence(new Integer(i+1))); + assertFalse(q.removeLastOccurrence(new Integer(i + 1))); } assertTrue(q.isEmpty()); } @@ -372,7 +371,7 @@ public void testConstructor4() { */ public void testConstructor5() { Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) + for (int i = 0; i < SIZE - 1; ++i) ints[i] = i; Collection elements = Arrays.asList(ints); try { @@ -419,7 +418,7 @@ public void testRemainingCapacity() { assertEquals(i, q.remove()); } for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.remainingCapacity()); + assertEquals(SIZE - i, q.remainingCapacity()); assertEquals(SIZE, q.size() + q.remainingCapacity()); assertTrue(q.add(i)); } @@ -429,8 +428,8 @@ public void testRemainingCapacity() { * push(null) throws NPE */ public void testPushNull() { + LinkedBlockingDeque q = new LinkedBlockingDeque(1); try { - LinkedBlockingDeque q = new LinkedBlockingDeque(1); q.push(null); shouldThrow(); } catch (NullPointerException success) {} @@ -440,14 +439,14 @@ public void testPushNull() { * push succeeds if not full; throws ISE if full */ public void testPush() { + LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + Integer x = new Integer(i); + q.push(x); + assertEquals(x, q.peek()); + } + assertEquals(0, q.remainingCapacity()); try { - LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); - for (int i = 0; i < SIZE; ++i) { - Integer x = new Integer(i); - q.push(x); - assertEquals(x, q.peek()); - } - assertEquals(0, q.remainingCapacity()); q.push(new Integer(SIZE)); shouldThrow(); } catch (IllegalStateException success) {} @@ -518,7 +517,7 @@ public void testAddAllSelf() { public void testAddAll3() { LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) + for (int i = 0; i < SIZE - 1; ++i) ints[i] = new Integer(i); Collection elements = Arrays.asList(ints); try { @@ -756,25 +755,23 @@ public void testInterruptedTimedPoll() throws InterruptedException { final CountDownLatch aboutToWait = new CountDownLatch(1); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); for (int i = 0; i < SIZE; ++i) { - long t0 = System.nanoTime(); assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(millisElapsedSince(t0) < SMALL_DELAY_MS); } - long t0 = System.nanoTime(); aboutToWait.countDown(); try { - q.poll(MEDIUM_DELAY_MS, MILLISECONDS); + q.poll(LONG_DELAY_MS, MILLISECONDS); shouldThrow(); } catch (InterruptedException success) { - assertTrue(millisElapsedSince(t0) < MEDIUM_DELAY_MS); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } }}); aboutToWait.await(); - waitForThreadToEnterWaitState(t, SMALL_DELAY_MS); + waitForThreadToEnterWaitState(t, LONG_DELAY_MS); t.interrupt(); - awaitTermination(t, MEDIUM_DELAY_MS); + awaitTermination(t); checkEmpty(q); } @@ -1055,17 +1052,18 @@ public void testTimedPollFirst() throws InterruptedException { * returning timeout status */ public void testInterruptedTimedPollFirst() throws InterruptedException { + final LinkedBlockingDeque q = populatedDeque(SIZE); final CountDownLatch pleaseInterrupt = new CountDownLatch(1); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { - LinkedBlockingDeque q = populatedDeque(SIZE); + long startTime = System.nanoTime(); for (int i = 0; i < SIZE; ++i) { assertEquals(i, q.pollFirst(LONG_DELAY_MS, MILLISECONDS)); } Thread.currentThread().interrupt(); try { - q.pollFirst(SMALL_DELAY_MS, MILLISECONDS); + q.pollFirst(LONG_DELAY_MS, MILLISECONDS); shouldThrow(); } catch (InterruptedException success) {} assertFalse(Thread.interrupted()); @@ -1076,6 +1074,7 @@ public void realRun() throws InterruptedException { shouldThrow(); } catch (InterruptedException success) {} assertFalse(Thread.interrupted()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); }}); await(pleaseInterrupt); @@ -1251,7 +1250,7 @@ public void realRun() throws InterruptedException { public void testTakeLast() throws InterruptedException { LinkedBlockingDeque q = populatedDeque(SIZE); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i-1, q.takeLast()); + assertEquals(SIZE - i - 1, q.takeLast()); } } @@ -1264,7 +1263,7 @@ public void testBlockingTakeLast() throws InterruptedException { Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i-1, q.takeLast()); + assertEquals(SIZE - i - 1, q.takeLast()); } Thread.currentThread().interrupt(); @@ -1294,7 +1293,7 @@ public void realRun() throws InterruptedException { public void testTimedPollLast0() throws InterruptedException { LinkedBlockingDeque q = populatedDeque(SIZE); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i-1, q.pollLast(0, MILLISECONDS)); + assertEquals(SIZE - i - 1, q.pollLast(0, MILLISECONDS)); } assertNull(q.pollLast(0, MILLISECONDS)); } @@ -1306,7 +1305,7 @@ public void testTimedPollLast() throws InterruptedException { LinkedBlockingDeque q = populatedDeque(SIZE); for (int i = 0; i < SIZE; ++i) { long startTime = System.nanoTime(); - assertEquals(SIZE-i-1, q.pollLast(LONG_DELAY_MS, MILLISECONDS)); + assertEquals(SIZE - i - 1, q.pollLast(LONG_DELAY_MS, MILLISECONDS)); assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } long startTime = System.nanoTime(); @@ -1320,12 +1319,14 @@ public void testTimedPollLast() throws InterruptedException { * returning timeout status */ public void testInterruptedTimedPollLast() throws InterruptedException { + final LinkedBlockingDeque q = populatedDeque(SIZE); final CountDownLatch pleaseInterrupt = new CountDownLatch(1); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { - LinkedBlockingDeque q = populatedDeque(SIZE); + long startTime = System.nanoTime(); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i-1, q.pollLast(LONG_DELAY_MS, MILLISECONDS)); + assertEquals(SIZE - i - 1, + q.pollLast(LONG_DELAY_MS, MILLISECONDS)); } Thread.currentThread().interrupt(); @@ -1341,12 +1342,15 @@ public void realRun() throws InterruptedException { shouldThrow(); } catch (InterruptedException success) {} assertFalse(Thread.interrupted()); + + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); }}); await(pleaseInterrupt); assertThreadStaysAlive(t); t.interrupt(); awaitTermination(t); + checkEmpty(q); } /** @@ -1379,6 +1383,8 @@ public void realRun() throws InterruptedException { shouldThrow(); } catch (InterruptedException success) {} assertFalse(Thread.interrupted()); + + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); }}); barrier.await(); @@ -1463,7 +1469,7 @@ public void testRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.remove(); } } @@ -1476,7 +1482,7 @@ public void testRemoveAll() { LinkedBlockingDeque q = populatedDeque(SIZE); LinkedBlockingDeque p = populatedDeque(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { Integer x = (Integer)(p.remove()); assertFalse(q.contains(x)); @@ -1675,23 +1681,23 @@ public void testOfferInExecutor() { final LinkedBlockingDeque q = new LinkedBlockingDeque(2); q.add(one); q.add(two); - ExecutorService executor = Executors.newFixedThreadPool(2); final CheckedBarrier threadsStarted = new CheckedBarrier(2); - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - assertFalse(q.offer(three)); - threadsStarted.await(); - assertTrue(q.offer(three, LONG_DELAY_MS, MILLISECONDS)); - assertEquals(0, q.remainingCapacity()); - }}); - - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - threadsStarted.await(); - assertSame(one, q.take()); - }}); - - joinPool(executor); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(q.offer(three)); + threadsStarted.await(); + assertTrue(q.offer(three, LONG_DELAY_MS, MILLISECONDS)); + assertEquals(0, q.remainingCapacity()); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + assertSame(one, q.take()); + }}); + } } /** @@ -1700,22 +1706,22 @@ public void realRun() throws InterruptedException { public void testPollInExecutor() { final LinkedBlockingDeque q = new LinkedBlockingDeque(2); final CheckedBarrier threadsStarted = new CheckedBarrier(2); - ExecutorService executor = Executors.newFixedThreadPool(2); - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - assertNull(q.poll()); - threadsStarted.await(); - assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); - checkEmpty(q); - }}); - - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - threadsStarted.await(); - q.put(one); - }}); - - joinPool(executor); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertNull(q.poll()); + threadsStarted.await(); + assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); + checkEmpty(q); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + q.put(one); + }}); + } } /** @@ -1767,7 +1773,7 @@ public void testDrainToWithActivePut() throws InterruptedException { final LinkedBlockingDeque q = populatedDeque(SIZE); Thread t = new Thread(new CheckedRunnable() { public void realRun() throws InterruptedException { - q.put(new Integer(SIZE+1)); + q.put(new Integer(SIZE + 1)); }}); t.start(); @@ -1792,7 +1798,7 @@ public void testDrainToN() { q.drainTo(l, i); int k = (i < SIZE) ? i : SIZE; assertEquals(k, l.size()); - assertEquals(SIZE-k, q.size()); + assertEquals(SIZE - k, q.size()); for (int j = 0; j < k; ++j) assertEquals(l.get(j), new Integer(j)); do {} while (q.poll() != null); diff --git a/jsr166-tests/src/test/java/jsr166/LinkedBlockingQueueTest.java b/jsr166-tests/src/test/java/jsr166/LinkedBlockingQueueTest.java index bd37b2a4e..faf3f18e9 100644 --- a/jsr166-tests/src/test/java/jsr166/LinkedBlockingQueueTest.java +++ b/jsr166-tests/src/test/java/jsr166/LinkedBlockingQueueTest.java @@ -26,25 +26,27 @@ public class LinkedBlockingQueueTest extends JSR166TestCase { - // android-note: These tests have been moved into their own separate + // android-note: These tests have been moved into their own separate // classes to work around CTS issues. // // public static class Unbounded extends BlockingQueueTest { - // protected BlockingQueue emptyCollection() { - // return new LinkedBlockingQueue(); + // protected BlockingQueue emptyCollection() { + // return new LinkedBlockingQueue(); // } // } - // + // public static class Bounded extends BlockingQueueTest { - // protected BlockingQueue emptyCollection() { + // protected BlockingQueue emptyCollection() { // return new LinkedBlockingQueue(SIZE); // } // } + + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. // // public static void main(String[] args) { - // main(suite(), args); + // main(suite(), args); // } - // // public static Test suite() { // return newTestSuite(LinkedBlockingQueueTest.class, // new Unbounded().testSuite(), @@ -113,7 +115,7 @@ public void testConstructor4() { */ public void testConstructor5() { Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) + for (int i = 0; i < SIZE - 1; ++i) ints[i] = new Integer(i); Collection elements = Arrays.asList(ints); try { @@ -160,7 +162,7 @@ public void testRemainingCapacity() { assertEquals(i, q.remove()); } for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.remainingCapacity()); + assertEquals(SIZE - i, q.remainingCapacity()); assertEquals(SIZE, q.size() + q.remainingCapacity()); assertTrue(q.add(i)); } @@ -207,7 +209,7 @@ public void testAddAllSelf() { public void testAddAll3() { LinkedBlockingQueue q = new LinkedBlockingQueue(SIZE); Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) + for (int i = 0; i < SIZE - 1; ++i) ints[i] = new Integer(i); Collection elements = Arrays.asList(ints); try { @@ -445,25 +447,23 @@ public void testInterruptedTimedPoll() throws InterruptedException { final CountDownLatch aboutToWait = new CountDownLatch(1); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); for (int i = 0; i < SIZE; ++i) { - long t0 = System.nanoTime(); assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(millisElapsedSince(t0) < SMALL_DELAY_MS); } - long t0 = System.nanoTime(); aboutToWait.countDown(); try { - q.poll(MEDIUM_DELAY_MS, MILLISECONDS); + q.poll(LONG_DELAY_MS, MILLISECONDS); shouldThrow(); } catch (InterruptedException success) { - assertTrue(millisElapsedSince(t0) < MEDIUM_DELAY_MS); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } }}); - aboutToWait.await(); - waitForThreadToEnterWaitState(t, SMALL_DELAY_MS); + await(aboutToWait); + waitForThreadToEnterWaitState(t, LONG_DELAY_MS); t.interrupt(); - awaitTermination(t, MEDIUM_DELAY_MS); + awaitTermination(t); checkEmpty(q); } @@ -579,7 +579,7 @@ public void testRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.remove(); } } @@ -592,7 +592,7 @@ public void testRemoveAll() { LinkedBlockingQueue q = populatedQueue(SIZE); LinkedBlockingQueue p = populatedQueue(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { Integer x = (Integer)(p.remove()); assertFalse(q.contains(x)); @@ -727,23 +727,23 @@ public void testOfferInExecutor() { final LinkedBlockingQueue q = new LinkedBlockingQueue(2); q.add(one); q.add(two); - ExecutorService executor = Executors.newFixedThreadPool(2); final CheckedBarrier threadsStarted = new CheckedBarrier(2); - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - assertFalse(q.offer(three)); - threadsStarted.await(); - assertTrue(q.offer(three, LONG_DELAY_MS, MILLISECONDS)); - assertEquals(0, q.remainingCapacity()); - }}); - - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - threadsStarted.await(); - assertSame(one, q.take()); - }}); - - joinPool(executor); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(q.offer(three)); + threadsStarted.await(); + assertTrue(q.offer(three, LONG_DELAY_MS, MILLISECONDS)); + assertEquals(0, q.remainingCapacity()); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + assertSame(one, q.take()); + }}); + } } /** @@ -752,22 +752,22 @@ public void realRun() throws InterruptedException { public void testPollInExecutor() { final LinkedBlockingQueue q = new LinkedBlockingQueue(2); final CheckedBarrier threadsStarted = new CheckedBarrier(2); - ExecutorService executor = Executors.newFixedThreadPool(2); - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - assertNull(q.poll()); - threadsStarted.await(); - assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); - checkEmpty(q); - }}); - - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - threadsStarted.await(); - q.put(one); - }}); - - joinPool(executor); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertNull(q.poll()); + threadsStarted.await(); + assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); + checkEmpty(q); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + q.put(one); + }}); + } } /** @@ -819,7 +819,7 @@ public void testDrainToWithActivePut() throws InterruptedException { final LinkedBlockingQueue q = populatedQueue(SIZE); Thread t = new Thread(new CheckedRunnable() { public void realRun() throws InterruptedException { - q.put(new Integer(SIZE+1)); + q.put(new Integer(SIZE + 1)); }}); t.start(); @@ -844,7 +844,7 @@ public void testDrainToN() { q.drainTo(l, i); int k = (i < SIZE) ? i : SIZE; assertEquals(k, l.size()); - assertEquals(SIZE-k, q.size()); + assertEquals(SIZE - k, q.size()); for (int j = 0; j < k; ++j) assertEquals(l.get(j), new Integer(j)); do {} while (q.poll() != null); diff --git a/jsr166-tests/src/test/java/jsr166/LinkedListTest.java b/jsr166-tests/src/test/java/jsr166/LinkedListTest.java index 9d9481d83..9c971b4e5 100644 --- a/jsr166-tests/src/test/java/jsr166/LinkedListTest.java +++ b/jsr166-tests/src/test/java/jsr166/LinkedListTest.java @@ -25,7 +25,7 @@ public class LinkedListTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(LinkedListTest.class); // } /** @@ -91,7 +91,7 @@ public void testEmpty() { public void testSize() { LinkedList q = populatedQueue(SIZE); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); q.remove(); } for (int i = 0; i < SIZE; ++i) { @@ -106,6 +106,8 @@ public void testSize() { public void testOfferNull() { LinkedList q = new LinkedList(); q.offer(null); + assertNull(q.get(0)); + assertTrue(q.contains(null)); } /** @@ -132,8 +134,8 @@ public void testAdd() { * addAll(null) throws NPE */ public void testAddAll1() { + LinkedList q = new LinkedList(); try { - LinkedList q = new LinkedList(); q.addAll(null); shouldThrow(); } catch (NullPointerException success) {} @@ -245,14 +247,14 @@ public void testRemoveElement() { assertTrue(q.contains(i)); assertTrue(q.remove((Integer)i)); assertFalse(q.contains(i)); - assertTrue(q.contains(i-1)); + assertTrue(q.contains(i - 1)); } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.contains(i)); assertTrue(q.remove((Integer)i)); assertFalse(q.contains(i)); - assertFalse(q.remove((Integer)(i+1))); - assertFalse(q.contains(i+1)); + assertFalse(q.remove((Integer)(i + 1))); + assertFalse(q.contains(i + 1)); } assertTrue(q.isEmpty()); } @@ -311,7 +313,7 @@ public void testRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.remove(); } } @@ -324,7 +326,7 @@ public void testRemoveAll() { LinkedList q = populatedQueue(SIZE); LinkedList p = populatedQueue(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { Integer x = (Integer)(p.remove()); assertFalse(q.contains(x)); @@ -549,7 +551,7 @@ public void testOfferLast() { */ public void testPollLast() { LinkedList q = populatedQueue(SIZE); - for (int i = SIZE-1; i >= 0; --i) { + for (int i = SIZE - 1; i >= 0; --i) { assertEquals(i, q.pollLast()); } assertNull(q.pollLast()); @@ -574,7 +576,7 @@ public void testPeekFirst() { */ public void testPeekLast() { LinkedList q = populatedQueue(SIZE); - for (int i = SIZE-1; i >= 0; --i) { + for (int i = SIZE - 1; i >= 0; --i) { assertEquals(i, q.peekLast()); assertEquals(i, q.pollLast()); assertTrue(q.peekLast() == null || @@ -600,7 +602,7 @@ public void testFirstElement() { */ public void testLastElement() { LinkedList q = populatedQueue(SIZE); - for (int i = SIZE-1; i >= 0; --i) { + for (int i = SIZE - 1; i >= 0; --i) { assertEquals(i, q.getLast()); assertEquals(i, q.pollLast()); } @@ -621,7 +623,7 @@ public void testRemoveFirstOccurrence() { } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.removeFirstOccurrence(new Integer(i))); - assertFalse(q.removeFirstOccurrence(new Integer(i+1))); + assertFalse(q.removeFirstOccurrence(new Integer(i + 1))); } assertTrue(q.isEmpty()); } @@ -636,7 +638,7 @@ public void testRemoveLastOccurrence() { } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.removeLastOccurrence(new Integer(i))); - assertFalse(q.removeLastOccurrence(new Integer(i+1))); + assertFalse(q.removeLastOccurrence(new Integer(i + 1))); } assertTrue(q.isEmpty()); } diff --git a/jsr166-tests/src/test/java/jsr166/LinkedTransferQueueTest.java b/jsr166-tests/src/test/java/jsr166/LinkedTransferQueueTest.java index 8d3f276e0..c71259234 100644 --- a/jsr166-tests/src/test/java/jsr166/LinkedTransferQueueTest.java +++ b/jsr166-tests/src/test/java/jsr166/LinkedTransferQueueTest.java @@ -25,30 +25,33 @@ import junit.framework.Test; @SuppressWarnings({"unchecked", "rawtypes"}) -// android-changed: Extend BlockingQueueTest directly. -public class LinkedTransferQueueTest extends BlockingQueueTest { +public class LinkedTransferQueueTest extends JSR166TestCase { + static class Implementation implements CollectionImplementation { + public Class klazz() { return LinkedTransferQueue.class; } + public Collection emptyCollection() { return new LinkedTransferQueue(); } + public Object makeElement(int i) { return i; } + public boolean isConcurrent() { return true; } + public boolean permitsNulls() { return false; } + } - // android-changed: Extend BlockingQueueTest directly. - // - // public static class Generic extends BlockingQueueTest { - // protected BlockingQueue emptyCollection() { - // return new LinkedTransferQueue(); - // } - // } + public static class Generic extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new LinkedTransferQueue(); + } + } + + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. // // public static void main(String[] args) { // main(suite(), args); // } - // // public static Test suite() { // return newTestSuite(LinkedTransferQueueTest.class, - // new Generic().testSuite()); + // new Generic().testSuite(), + // CollectionTest.testSuite(new Implementation())); // } - protected BlockingQueue emptyCollection() { - return new LinkedTransferQueue(); - } - /** * Constructor builds new queue with size being zero and empty * being true @@ -87,7 +90,7 @@ public void testConstructor3() { */ public void testConstructor4() { Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) + for (int i = 0; i < SIZE - 1; ++i) ints[i] = i; Collection elements = Arrays.asList(ints); try { @@ -141,8 +144,8 @@ public void testRemainingCapacity() { * addAll(this) throws IllegalArgumentException */ public void testAddAllSelf() { + LinkedTransferQueue q = populatedQueue(SIZE); try { - LinkedTransferQueue q = populatedQueue(SIZE); q.addAll(q); shouldThrow(); } catch (IllegalArgumentException success) {} @@ -153,12 +156,11 @@ public void testAddAllSelf() { * NullPointerException after possibly adding some elements */ public void testAddAll3() { + LinkedTransferQueue q = new LinkedTransferQueue(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = i; try { - LinkedTransferQueue q = new LinkedTransferQueue(); - Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE - 1; ++i) { - ints[i] = i; - } q.addAll(Arrays.asList(ints)); shouldThrow(); } catch (NullPointerException success) {} @@ -265,12 +267,12 @@ public void testTimedPoll0() throws InterruptedException { */ public void testTimedPoll() throws InterruptedException { LinkedTransferQueue q = populatedQueue(SIZE); - for (int i = 0; i < SIZE; ++i) { - long startTime = System.nanoTime(); - assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); - } long startTime = System.nanoTime(); + for (int i = 0; i < SIZE; ++i) + assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + + startTime = System.nanoTime(); assertNull(q.poll(timeoutMillis(), MILLISECONDS)); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); checkEmpty(q); @@ -285,25 +287,21 @@ public void testInterruptedTimedPoll() throws InterruptedException { final CountDownLatch aboutToWait = new CountDownLatch(1); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { - for (int i = 0; i < SIZE; ++i) { - long t0 = System.nanoTime(); + long startTime = System.nanoTime(); + for (int i = 0; i < SIZE; ++i) assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(millisElapsedSince(t0) < SMALL_DELAY_MS); - } - long t0 = System.nanoTime(); aboutToWait.countDown(); try { - q.poll(MEDIUM_DELAY_MS, MILLISECONDS); + q.poll(LONG_DELAY_MS, MILLISECONDS); shouldThrow(); - } catch (InterruptedException success) { - assertTrue(millisElapsedSince(t0) < MEDIUM_DELAY_MS); - } + } catch (InterruptedException success) {} + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); }}); aboutToWait.await(); - waitForThreadToEnterWaitState(t, SMALL_DELAY_MS); + waitForThreadToEnterWaitState(t); t.interrupt(); - awaitTermination(t, MEDIUM_DELAY_MS); + awaitTermination(t); checkEmpty(q); } @@ -315,19 +313,18 @@ public void testTimedPollAfterInterrupt() throws InterruptedException { final BlockingQueue q = populatedQueue(SIZE); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); Thread.currentThread().interrupt(); - for (int i = 0; i < SIZE; ++i) { - long t0 = System.nanoTime(); + for (int i = 0; i < SIZE; ++i) assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(millisElapsedSince(t0) < SMALL_DELAY_MS); - } try { - q.poll(MEDIUM_DELAY_MS, MILLISECONDS); + q.poll(LONG_DELAY_MS, MILLISECONDS); shouldThrow(); } catch (InterruptedException success) {} + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); }}); - awaitTermination(t, MEDIUM_DELAY_MS); + awaitTermination(t); checkEmpty(q); } @@ -598,22 +595,24 @@ public void testToString() { public void testOfferInExecutor() { final LinkedTransferQueue q = new LinkedTransferQueue(); final CheckedBarrier threadsStarted = new CheckedBarrier(2); - ExecutorService executor = Executors.newFixedThreadPool(2); - - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - threadsStarted.await(); - assertTrue(q.offer(one, LONG_DELAY_MS, MILLISECONDS)); - }}); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - threadsStarted.await(); - assertSame(one, q.take()); - checkEmpty(q); - }}); + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + long startTime = System.nanoTime(); + assertTrue(q.offer(one, LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + }}); - joinPool(executor); + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + assertSame(one, q.take()); + checkEmpty(q); + }}); + } } /** @@ -622,23 +621,25 @@ public void realRun() throws InterruptedException { public void testPollInExecutor() { final LinkedTransferQueue q = new LinkedTransferQueue(); final CheckedBarrier threadsStarted = new CheckedBarrier(2); - ExecutorService executor = Executors.newFixedThreadPool(2); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - assertNull(q.poll()); - threadsStarted.await(); - assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); - checkEmpty(q); - }}); - - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - threadsStarted.await(); - q.put(one); - }}); + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertNull(q.poll()); + threadsStarted.await(); + long startTime = System.nanoTime(); + assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + checkEmpty(q); + }}); - joinPool(executor); + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + q.put(one); + }}); + } } /** @@ -699,7 +700,7 @@ public void realRun() { assertTrue(l.size() >= SIZE); for (int i = 0; i < SIZE; ++i) assertEquals(i, l.get(i)); - awaitTermination(t, MEDIUM_DELAY_MS); + awaitTermination(t); assertTrue(q.size() + l.size() >= SIZE); } @@ -736,14 +737,15 @@ public void testWaitingConsumer() throws InterruptedException { Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); + long startTime = System.nanoTime(); assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); assertEquals(0, q.getWaitingConsumerCount()); assertFalse(q.hasWaitingConsumer()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); }}); threadStarted.await(); - // waitForThreadToEnterWaitState(t, SMALL_DELAY_MS); - waitForThreadToEnterWaitStateNoTimeout(t); + waitForThreadToEnterWaitState(t); assertEquals(1, q.getWaitingConsumerCount()); assertTrue(q.hasWaitingConsumer()); @@ -751,7 +753,7 @@ public void realRun() throws InterruptedException { assertEquals(0, q.getWaitingConsumerCount()); assertFalse(q.hasWaitingConsumer()); - awaitTermination(t, MEDIUM_DELAY_MS); + awaitTermination(t); } /** @@ -782,12 +784,11 @@ public void realRun() throws InterruptedException { }}); threadStarted.await(); - // waitForThreadToEnterWaitState(t, SMALL_DELAY_MS); - waitForThreadToEnterWaitStateNoTimeout(t); + waitForThreadToEnterWaitState(t); assertEquals(1, q.size()); assertSame(five, q.poll()); checkEmpty(q); - awaitTermination(t, MEDIUM_DELAY_MS); + awaitTermination(t); } /** @@ -843,7 +844,7 @@ public void realRun() throws InterruptedException { assertEquals(1, q.size()); assertTrue(q.offer(three)); assertSame(four, q.poll()); - awaitTermination(t, MEDIUM_DELAY_MS); + awaitTermination(t); } /** @@ -866,15 +867,15 @@ public void realRun() throws InterruptedException { assertEquals(1, q.size()); assertSame(four, q.take()); checkEmpty(q); - awaitTermination(t, MEDIUM_DELAY_MS); + awaitTermination(t); } /** * tryTransfer(null) throws NullPointerException */ public void testTryTransfer1() { + final LinkedTransferQueue q = new LinkedTransferQueue(); try { - final LinkedTransferQueue q = new LinkedTransferQueue(); q.tryTransfer(null); shouldThrow(); } catch (NullPointerException success) {} @@ -908,9 +909,11 @@ public void realRun() { assertTrue(q.tryTransfer(hotPotato)); }}); - assertSame(hotPotato, q.poll(MEDIUM_DELAY_MS, MILLISECONDS)); + long startTime = System.nanoTime(); + assertSame(hotPotato, q.poll(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); checkEmpty(q); - awaitTermination(t, MEDIUM_DELAY_MS); + awaitTermination(t); } /** @@ -932,7 +935,7 @@ public void realRun() { assertSame(q.take(), hotPotato); checkEmpty(q); - awaitTermination(t, MEDIUM_DELAY_MS); + awaitTermination(t); } /** @@ -945,6 +948,7 @@ public void testTryTransfer5() throws InterruptedException { Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); Thread.currentThread().interrupt(); try { q.tryTransfer(new Object(), LONG_DELAY_MS, MILLISECONDS); @@ -958,6 +962,7 @@ public void realRun() throws InterruptedException { shouldThrow(); } catch (InterruptedException success) {} assertFalse(Thread.interrupted()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); }}); await(pleaseInterrupt); @@ -975,10 +980,10 @@ public void testTryTransfer6() throws InterruptedException { Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { - long t0 = System.nanoTime(); + long startTime = System.nanoTime(); assertFalse(q.tryTransfer(new Object(), timeoutMillis(), MILLISECONDS)); - assertTrue(millisElapsedSince(t0) >= timeoutMillis()); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); checkEmpty(q); }}); @@ -996,7 +1001,9 @@ public void testTryTransfer7() throws InterruptedException { Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { - assertTrue(q.tryTransfer(five, MEDIUM_DELAY_MS, MILLISECONDS)); + long startTime = System.nanoTime(); + assertTrue(q.tryTransfer(five, LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); checkEmpty(q); }}); @@ -1006,7 +1013,7 @@ public void realRun() throws InterruptedException { assertSame(four, q.poll()); assertSame(five, q.poll()); checkEmpty(q); - awaitTermination(t, MEDIUM_DELAY_MS); + awaitTermination(t); } /** @@ -1017,9 +1024,9 @@ public void testTryTransfer8() throws InterruptedException { final LinkedTransferQueue q = new LinkedTransferQueue(); assertTrue(q.offer(four)); assertEquals(1, q.size()); - long t0 = System.nanoTime(); + long startTime = System.nanoTime(); assertFalse(q.tryTransfer(five, timeoutMillis(), MILLISECONDS)); - assertTrue(millisElapsedSince(t0) >= timeoutMillis()); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); assertEquals(1, q.size()); assertSame(four, q.poll()); assertNull(q.poll()); diff --git a/jsr166-tests/src/test/java/jsr166/LockSupportTest.java b/jsr166-tests/src/test/java/jsr166/LockSupportTest.java index 8347b0810..b3a8ed837 100644 --- a/jsr166-tests/src/test/java/jsr166/LockSupportTest.java +++ b/jsr166-tests/src/test/java/jsr166/LockSupportTest.java @@ -26,9 +26,15 @@ public class LockSupportTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(LockSupportTest.class); // } + static { + // Reduce the risk of rare disastrous classloading in first call to + // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773 + Class ensureLoaded = LockSupport.class; + } + /** * Returns the blocker object used by tests in this file. * Any old object will do; we'll return a convenient one. diff --git a/jsr166-tests/src/test/java/jsr166/LongAccumulatorTest.java b/jsr166-tests/src/test/java/jsr166/LongAccumulatorTest.java new file mode 100644 index 000000000..5dd52e9c0 --- /dev/null +++ b/jsr166-tests/src/test/java/jsr166/LongAccumulatorTest.java @@ -0,0 +1,161 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package jsr166; + +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Phaser; +import java.util.concurrent.atomic.LongAccumulator; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class LongAccumulatorTest extends JSR166TestCase { + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. + // + // public static void main(String[] args) { + // main(suite(), args); + // } + // public static Test suite() { + // return new TestSuite(LongAccumulatorTest.class); + // } + + /** + * default constructed initializes to zero + */ + public void testConstructor() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + assertEquals(0, ai.get()); + } + + /** + * accumulate accumulates given value to current, and get returns current value + */ + public void testAccumulateAndGet() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + ai.accumulate(2); + assertEquals(2, ai.get()); + ai.accumulate(-4); + assertEquals(2, ai.get()); + ai.accumulate(4); + assertEquals(4, ai.get()); + } + + /** + * reset() causes subsequent get() to return zero + */ + public void testReset() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + ai.accumulate(2); + assertEquals(2, ai.get()); + ai.reset(); + assertEquals(0, ai.get()); + } + + /** + * getThenReset() returns current value; subsequent get() returns zero + */ + public void testGetThenReset() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + ai.accumulate(2); + assertEquals(2, ai.get()); + assertEquals(2, ai.getThenReset()); + assertEquals(0, ai.get()); + } + + /** + * toString returns current value. + */ + public void testToString() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + assertEquals("0", ai.toString()); + ai.accumulate(1); + assertEquals(Long.toString(1), ai.toString()); + } + + /** + * intValue returns current value. + */ + public void testIntValue() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + assertEquals(0, ai.intValue()); + ai.accumulate(1); + assertEquals(1, ai.intValue()); + } + + /** + * longValue returns current value. + */ + public void testLongValue() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + assertEquals(0, ai.longValue()); + ai.accumulate(1); + assertEquals(1, ai.longValue()); + } + + /** + * floatValue returns current value. + */ + public void testFloatValue() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + assertEquals(0.0f, ai.floatValue()); + ai.accumulate(1); + assertEquals(1.0f, ai.floatValue()); + } + + /** + * doubleValue returns current value. + */ + public void testDoubleValue() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + assertEquals(0.0, ai.doubleValue()); + ai.accumulate(1); + assertEquals(1.0, ai.doubleValue()); + } + + /** + * accumulates by multiple threads produce correct result + */ + public void testAccumulateAndGetMT() { + final int incs = 1000000; + final int nthreads = 4; + final ExecutorService pool = Executors.newCachedThreadPool(); + LongAccumulator a = new LongAccumulator(Long::max, 0L); + Phaser phaser = new Phaser(nthreads + 1); + for (int i = 0; i < nthreads; ++i) + pool.execute(new AccTask(a, phaser, incs)); + phaser.arriveAndAwaitAdvance(); + phaser.arriveAndAwaitAdvance(); + long expected = incs - 1; + long result = a.get(); + assertEquals(expected, result); + pool.shutdown(); + } + + static final class AccTask implements Runnable { + final LongAccumulator acc; + final Phaser phaser; + final int incs; + volatile long result; + AccTask(LongAccumulator acc, Phaser phaser, int incs) { + this.acc = acc; + this.phaser = phaser; + this.incs = incs; + } + + public void run() { + phaser.arriveAndAwaitAdvance(); + LongAccumulator a = acc; + for (int i = 0; i < incs; ++i) + a.accumulate(i); + result = a.get(); + phaser.arrive(); + } + } + +} diff --git a/jsr166-tests/src/test/java/jsr166/LongAdderTest.java b/jsr166-tests/src/test/java/jsr166/LongAdderTest.java new file mode 100644 index 000000000..800a9c8ba --- /dev/null +++ b/jsr166-tests/src/test/java/jsr166/LongAdderTest.java @@ -0,0 +1,198 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package jsr166; + +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.LongAdder; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class LongAdderTest extends JSR166TestCase { + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. + // + // public static void main(String[] args) { + // main(suite(), args); + // } + // public static Test suite() { + // return new TestSuite(LongAdderTest.class); + // } + + /** + * default constructed initializes to zero + */ + public void testConstructor() { + LongAdder ai = new LongAdder(); + assertEquals(0, ai.sum()); + } + + /** + * add adds given value to current, and sum returns current value + */ + public void testAddAndSum() { + LongAdder ai = new LongAdder(); + ai.add(2); + assertEquals(2, ai.sum()); + ai.add(-4); + assertEquals(-2, ai.sum()); + } + + /** + * decrement decrements and sum returns current value + */ + public void testDecrementAndsum() { + LongAdder ai = new LongAdder(); + ai.decrement(); + assertEquals(-1, ai.sum()); + ai.decrement(); + assertEquals(-2, ai.sum()); + } + + /** + * incrementAndGet increments and returns current value + */ + public void testIncrementAndsum() { + LongAdder ai = new LongAdder(); + ai.increment(); + assertEquals(1, ai.sum()); + ai.increment(); + assertEquals(2, ai.sum()); + } + + /** + * reset() causes subsequent sum() to return zero + */ + public void testReset() { + LongAdder ai = new LongAdder(); + ai.add(2); + assertEquals(2, ai.sum()); + ai.reset(); + assertEquals(0, ai.sum()); + } + + /** + * sumThenReset() returns sum; subsequent sum() returns zero + */ + public void testSumThenReset() { + LongAdder ai = new LongAdder(); + ai.add(2); + assertEquals(2, ai.sum()); + assertEquals(2, ai.sumThenReset()); + assertEquals(0, ai.sum()); + } + + /** + * a deserialized serialized adder holds same value + */ + public void testSerialization() throws Exception { + LongAdder x = new LongAdder(); + LongAdder y = serialClone(x); + assertNotSame(x, y); + x.add(-22); + LongAdder z = serialClone(x); + assertNotSame(y, z); + assertEquals(-22, x.sum()); + assertEquals(0, y.sum()); + assertEquals(-22, z.sum()); + } + + /** + * toString returns current value. + */ + public void testToString() { + LongAdder ai = new LongAdder(); + assertEquals("0", ai.toString()); + ai.increment(); + assertEquals(Long.toString(1), ai.toString()); + } + + /** + * intValue returns current value. + */ + public void testIntValue() { + LongAdder ai = new LongAdder(); + assertEquals(0, ai.intValue()); + ai.increment(); + assertEquals(1, ai.intValue()); + } + + /** + * longValue returns current value. + */ + public void testLongValue() { + LongAdder ai = new LongAdder(); + assertEquals(0, ai.longValue()); + ai.increment(); + assertEquals(1, ai.longValue()); + } + + /** + * floatValue returns current value. + */ + public void testFloatValue() { + LongAdder ai = new LongAdder(); + assertEquals(0.0f, ai.floatValue()); + ai.increment(); + assertEquals(1.0f, ai.floatValue()); + } + + /** + * doubleValue returns current value. + */ + public void testDoubleValue() { + LongAdder ai = new LongAdder(); + assertEquals(0.0, ai.doubleValue()); + ai.increment(); + assertEquals(1.0, ai.doubleValue()); + } + + /** + * adds by multiple threads produce correct sum + */ + public void testAddAndSumMT() throws Throwable { + final int incs = 1000000; + final int nthreads = 4; + final ExecutorService pool = Executors.newCachedThreadPool(); + LongAdder a = new LongAdder(); + CyclicBarrier barrier = new CyclicBarrier(nthreads + 1); + for (int i = 0; i < nthreads; ++i) + pool.execute(new AdderTask(a, barrier, incs)); + barrier.await(); + barrier.await(); + long total = (long)nthreads * incs; + long sum = a.sum(); + assertEquals(sum, total); + pool.shutdown(); + } + + static final class AdderTask implements Runnable { + final LongAdder adder; + final CyclicBarrier barrier; + final int incs; + volatile long result; + AdderTask(LongAdder adder, CyclicBarrier barrier, int incs) { + this.adder = adder; + this.barrier = barrier; + this.incs = incs; + } + + public void run() { + try { + barrier.await(); + LongAdder a = adder; + for (int i = 0; i < incs; ++i) + a.add(1L); + result = a.sum(); + barrier.await(); + } catch (Throwable t) { throw new Error(t); } + } + } + +} diff --git a/jsr166-tests/src/test/java/jsr166/PhaserTest.java b/jsr166-tests/src/test/java/jsr166/PhaserTest.java index 42d72f4e2..673e556a1 100644 --- a/jsr166-tests/src/test/java/jsr166/PhaserTest.java +++ b/jsr166-tests/src/test/java/jsr166/PhaserTest.java @@ -28,7 +28,7 @@ public class PhaserTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(PhaserTest.class); // } private static final int maxParties = 65535; @@ -342,8 +342,8 @@ public void testArrive3() { * registered or unarrived parties would become negative */ public void testArriveAndDeregister1() { + Phaser phaser = new Phaser(); try { - Phaser phaser = new Phaser(); phaser.arriveAndDeregister(); shouldThrow(); } catch (IllegalStateException success) {} @@ -629,11 +629,11 @@ public void testAwaitAdvance4() { threads.add(newStartedThread(new CheckedRunnable() { public void realRun() { for (int k = 0; k < 3; k++) { - assertEquals(2*k+1, phaser.arriveAndAwaitAdvance()); + assertEquals(2 * k + 1, phaser.arriveAndAwaitAdvance()); count.incrementAndGet(); - assertEquals(2*k+1, phaser.arrive()); - assertEquals(2*k+2, phaser.awaitAdvance(2*k+1)); - assertEquals(4*(k+1), count.get()); + assertEquals(2 * k + 1, phaser.arrive()); + assertEquals(2 * k + 2, phaser.awaitAdvance(2 * k + 1)); + assertEquals(4 * (k + 1), count.get()); }}})); for (Thread thread : threads) @@ -689,7 +689,7 @@ public void testAwaitAdvanceTieredPhaser() throws Exception { for (Phaser phaser : phasers) { assertEquals(-42, phaser.awaitAdvance(-42)); assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42)); - assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42, SMALL_DELAY_MS, MILLISECONDS)); + assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42, MEDIUM_DELAY_MS, MILLISECONDS)); } for (Phaser child : onePartyChildren) @@ -697,10 +697,10 @@ public void testAwaitAdvanceTieredPhaser() throws Exception { for (Phaser phaser : phasers) { assertEquals(-42, phaser.awaitAdvance(-42)); assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42)); - assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42, SMALL_DELAY_MS, MILLISECONDS)); + assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42, MEDIUM_DELAY_MS, MILLISECONDS)); assertEquals(1, phaser.awaitAdvance(0)); assertEquals(1, phaser.awaitAdvanceInterruptibly(0)); - assertEquals(1, phaser.awaitAdvanceInterruptibly(0, SMALL_DELAY_MS, MILLISECONDS)); + assertEquals(1, phaser.awaitAdvanceInterruptibly(0, MEDIUM_DELAY_MS, MILLISECONDS)); } for (Phaser child : onePartyChildren) @@ -708,13 +708,13 @@ public void testAwaitAdvanceTieredPhaser() throws Exception { for (Phaser phaser : phasers) { assertEquals(-42, phaser.awaitAdvance(-42)); assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42)); - assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42, SMALL_DELAY_MS, MILLISECONDS)); + assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42, MEDIUM_DELAY_MS, MILLISECONDS)); assertEquals(2, phaser.awaitAdvance(0)); assertEquals(2, phaser.awaitAdvanceInterruptibly(0)); - assertEquals(2, phaser.awaitAdvanceInterruptibly(0, SMALL_DELAY_MS, MILLISECONDS)); + assertEquals(2, phaser.awaitAdvanceInterruptibly(0, MEDIUM_DELAY_MS, MILLISECONDS)); assertEquals(2, phaser.awaitAdvance(1)); assertEquals(2, phaser.awaitAdvanceInterruptibly(1)); - assertEquals(2, phaser.awaitAdvanceInterruptibly(1, SMALL_DELAY_MS, MILLISECONDS)); + assertEquals(2, phaser.awaitAdvanceInterruptibly(1, MEDIUM_DELAY_MS, MILLISECONDS)); } } @@ -752,8 +752,8 @@ public void realRun() { * unarrived parties */ public void testArriveAndAwaitAdvance1() { + Phaser phaser = new Phaser(); try { - Phaser phaser = new Phaser(); phaser.arriveAndAwaitAdvance(); shouldThrow(); } catch (IllegalStateException success) {} diff --git a/jsr166-tests/src/test/java/jsr166/PriorityBlockingQueueTest.java b/jsr166-tests/src/test/java/jsr166/PriorityBlockingQueueTest.java index 64c3b3a31..41b06f5db 100644 --- a/jsr166-tests/src/test/java/jsr166/PriorityBlockingQueueTest.java +++ b/jsr166-tests/src/test/java/jsr166/PriorityBlockingQueueTest.java @@ -27,7 +27,7 @@ public class PriorityBlockingQueueTest extends JSR166TestCase { - // android-note: These tests have been moved into their own separate + // android-note: These tests have been moved into their own separate // classes to work around CTS issues. // // public static class Generic extends BlockingQueueTest { @@ -35,17 +35,19 @@ public class PriorityBlockingQueueTest extends JSR166TestCase { // return new PriorityBlockingQueue(); // } // } - // + // public static class InitialCapacity extends BlockingQueueTest { // protected BlockingQueue emptyCollection() { // return new PriorityBlockingQueue(SIZE); // } // } + + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. // // public static void main(String[] args) { // main(suite(), args); // } - // // public static Test suite() { // return newTestSuite(PriorityBlockingQueueTest.class, // new Generic().testSuite(), @@ -67,7 +69,7 @@ private PriorityBlockingQueue populatedQueue(int n) { PriorityBlockingQueue q = new PriorityBlockingQueue(n); assertTrue(q.isEmpty()); - for (int i = n-1; i >= 0; i -= 2) + for (int i = n - 1; i >= 0; i -= 2) assertTrue(q.offer(new Integer(i))); for (int i = (n & 1); i < n; i += 2) assertTrue(q.offer(new Integer(i))); @@ -121,7 +123,7 @@ public void testConstructor4() { */ public void testConstructor5() { Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) + for (int i = 0; i < SIZE - 1; ++i) ints[i] = i; Collection elements = Arrays.asList(ints); try { @@ -153,7 +155,7 @@ public void testConstructor7() { for (int i = 0; i < SIZE; ++i) ints[i] = new Integer(i); q.addAll(Arrays.asList(ints)); - for (int i = SIZE-1; i >= 0; --i) + for (int i = SIZE - 1; i >= 0; --i) assertEquals(ints[i], q.poll()); } @@ -225,8 +227,8 @@ public void testAdd() { * addAll(this) throws IAE */ public void testAddAllSelf() { + PriorityBlockingQueue q = populatedQueue(SIZE); try { - PriorityBlockingQueue q = populatedQueue(SIZE); q.addAll(q); shouldThrow(); } catch (IllegalArgumentException success) {} @@ -237,11 +239,11 @@ public void testAddAllSelf() { * possibly adding some elements */ public void testAddAll3() { + PriorityBlockingQueue q = new PriorityBlockingQueue(SIZE); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); try { - PriorityBlockingQueue q = new PriorityBlockingQueue(SIZE); - Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i); q.addAll(Arrays.asList(ints)); shouldThrow(); } catch (NullPointerException success) {} @@ -253,7 +255,7 @@ public void testAddAll3() { public void testAddAll5() { Integer[] empty = new Integer[0]; Integer[] ints = new Integer[SIZE]; - for (int i = SIZE-1; i >= 0; --i) + for (int i = SIZE - 1; i >= 0; --i) ints[i] = new Integer(i); PriorityBlockingQueue q = new PriorityBlockingQueue(SIZE); assertFalse(q.addAll(Arrays.asList(empty))); @@ -398,25 +400,23 @@ public void testInterruptedTimedPoll() throws InterruptedException { final CountDownLatch aboutToWait = new CountDownLatch(1); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); for (int i = 0; i < SIZE; ++i) { - long t0 = System.nanoTime(); assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(millisElapsedSince(t0) < SMALL_DELAY_MS); } - long t0 = System.nanoTime(); aboutToWait.countDown(); try { q.poll(LONG_DELAY_MS, MILLISECONDS); shouldThrow(); } catch (InterruptedException success) { - assertTrue(millisElapsedSince(t0) < MEDIUM_DELAY_MS); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } }}); aboutToWait.await(); - waitForThreadToEnterWaitState(t, SMALL_DELAY_MS); + waitForThreadToEnterWaitState(t, LONG_DELAY_MS); t.interrupt(); - awaitTermination(t, MEDIUM_DELAY_MS); + awaitTermination(t); } /** @@ -517,7 +517,7 @@ public void testRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.remove(); } } @@ -530,7 +530,7 @@ public void testRemoveAll() { PriorityBlockingQueue q = populatedQueue(SIZE); PriorityBlockingQueue p = populatedQueue(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { Integer x = (Integer)(p.remove()); assertFalse(q.contains(x)); @@ -629,22 +629,22 @@ public void testToString() { public void testPollInExecutor() { final PriorityBlockingQueue q = new PriorityBlockingQueue(2); final CheckedBarrier threadsStarted = new CheckedBarrier(2); - ExecutorService executor = Executors.newFixedThreadPool(2); - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - assertNull(q.poll()); - threadsStarted.await(); - assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); - checkEmpty(q); - }}); - - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - threadsStarted.await(); - q.put(one); - }}); - - joinPool(executor); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertNull(q.poll()); + threadsStarted.await(); + assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); + checkEmpty(q); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + q.put(one); + }}); + } } /** @@ -694,7 +694,7 @@ public void testDrainToWithActivePut() throws InterruptedException { final PriorityBlockingQueue q = populatedQueue(SIZE); Thread t = new Thread(new CheckedRunnable() { public void realRun() { - q.put(new Integer(SIZE+1)); + q.put(new Integer(SIZE + 1)); }}); t.start(); @@ -711,7 +711,7 @@ public void realRun() { * drainTo(c, n) empties first min(n, size) elements of queue into c */ public void testDrainToN() { - PriorityBlockingQueue q = new PriorityBlockingQueue(SIZE*2); + PriorityBlockingQueue q = new PriorityBlockingQueue(SIZE * 2); for (int i = 0; i < SIZE + 2; ++i) { for (int j = 0; j < SIZE; j++) assertTrue(q.offer(new Integer(j))); @@ -719,7 +719,7 @@ public void testDrainToN() { q.drainTo(l, i); int k = (i < SIZE) ? i : SIZE; assertEquals(k, l.size()); - assertEquals(SIZE-k, q.size()); + assertEquals(SIZE - k, q.size()); for (int j = 0; j < k; ++j) assertEquals(l.get(j), new Integer(j)); do {} while (q.poll() != null); diff --git a/jsr166-tests/src/test/java/jsr166/PriorityQueueTest.java b/jsr166-tests/src/test/java/jsr166/PriorityQueueTest.java index 88cdd37ba..f64ef68d8 100644 --- a/jsr166-tests/src/test/java/jsr166/PriorityQueueTest.java +++ b/jsr166-tests/src/test/java/jsr166/PriorityQueueTest.java @@ -27,7 +27,7 @@ public class PriorityQueueTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(PriorityQueueTest.class); // } static class MyReverseComparator implements Comparator { @@ -43,7 +43,7 @@ public int compare(Object x, Object y) { private PriorityQueue populatedQueue(int n) { PriorityQueue q = new PriorityQueue(n); assertTrue(q.isEmpty()); - for (int i = n-1; i >= 0; i -= 2) + for (int i = n - 1; i >= 0; i -= 2) assertTrue(q.offer(new Integer(i))); for (int i = (n & 1); i < n; i += 2) assertTrue(q.offer(new Integer(i))); @@ -84,8 +84,7 @@ public void testConstructor3() { */ public void testConstructor4() { try { - Integer[] ints = new Integer[SIZE]; - new PriorityQueue(Arrays.asList(ints)); + new PriorityQueue(Arrays.asList(new Integer[SIZE])); shouldThrow(); } catch (NullPointerException success) {} } @@ -94,10 +93,10 @@ public void testConstructor4() { * Initializing from Collection with some null elements throws NPE */ public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); try { - Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i); new PriorityQueue(Arrays.asList(ints)); shouldThrow(); } catch (NullPointerException success) {} @@ -126,7 +125,7 @@ public void testConstructor7() { for (int i = 0; i < SIZE; ++i) ints[i] = new Integer(i); q.addAll(Arrays.asList(ints)); - for (int i = SIZE-1; i >= 0; --i) + for (int i = SIZE - 1; i >= 0; --i) assertEquals(ints[i], q.poll()); } @@ -150,7 +149,7 @@ public void testEmpty() { public void testSize() { PriorityQueue q = populatedQueue(SIZE); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); q.remove(); } for (int i = 0; i < SIZE; ++i) { @@ -163,8 +162,8 @@ public void testSize() { * offer(null) throws NPE */ public void testOfferNull() { + PriorityQueue q = new PriorityQueue(1); try { - PriorityQueue q = new PriorityQueue(1); q.offer(null); shouldThrow(); } catch (NullPointerException success) {} @@ -174,8 +173,8 @@ public void testOfferNull() { * add(null) throws NPE */ public void testAddNull() { + PriorityQueue q = new PriorityQueue(1); try { - PriorityQueue q = new PriorityQueue(1); q.add(null); shouldThrow(); } catch (NullPointerException success) {} @@ -217,8 +216,8 @@ public void testAdd() { * addAll(null) throws NPE */ public void testAddAll1() { + PriorityQueue q = new PriorityQueue(1); try { - PriorityQueue q = new PriorityQueue(1); q.addAll(null); shouldThrow(); } catch (NullPointerException success) {} @@ -228,10 +227,9 @@ public void testAddAll1() { * addAll of a collection with null elements throws NPE */ public void testAddAll2() { + PriorityQueue q = new PriorityQueue(SIZE); try { - PriorityQueue q = new PriorityQueue(SIZE); - Integer[] ints = new Integer[SIZE]; - q.addAll(Arrays.asList(ints)); + q.addAll(Arrays.asList(new Integer[SIZE])); shouldThrow(); } catch (NullPointerException success) {} } @@ -241,11 +239,11 @@ public void testAddAll2() { * possibly adding some elements */ public void testAddAll3() { + PriorityQueue q = new PriorityQueue(SIZE); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); try { - PriorityQueue q = new PriorityQueue(SIZE); - Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i); q.addAll(Arrays.asList(ints)); shouldThrow(); } catch (NullPointerException success) {} @@ -258,7 +256,7 @@ public void testAddAll5() { Integer[] empty = new Integer[0]; Integer[] ints = new Integer[SIZE]; for (int i = 0; i < SIZE; ++i) - ints[i] = new Integer(SIZE-1-i); + ints[i] = new Integer(SIZE - 1 - i); PriorityQueue q = new PriorityQueue(SIZE); assertFalse(q.addAll(Arrays.asList(empty))); assertTrue(q.addAll(Arrays.asList(ints))); @@ -329,14 +327,14 @@ public void testRemoveElement() { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertTrue(q.contains(i-1)); + assertTrue(q.contains(i - 1)); } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertFalse(q.remove(i+1)); - assertFalse(q.contains(i+1)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); } assertTrue(q.isEmpty()); } @@ -395,7 +393,7 @@ public void testRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.remove(); } } @@ -408,7 +406,7 @@ public void testRemoveAll() { PriorityQueue q = populatedQueue(SIZE); PriorityQueue p = populatedQueue(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { Integer x = (Integer)(p.remove()); assertFalse(q.contains(x)); diff --git a/jsr166-tests/src/test/java/jsr166/RecursiveActionTest.java b/jsr166-tests/src/test/java/jsr166/RecursiveActionTest.java index 1c3bba841..c8e33beb2 100644 --- a/jsr166-tests/src/test/java/jsr166/RecursiveActionTest.java +++ b/jsr166-tests/src/test/java/jsr166/RecursiveActionTest.java @@ -32,7 +32,7 @@ public class RecursiveActionTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(RecursiveActionTest.class); // } private static ForkJoinPool mainPool() { @@ -50,14 +50,12 @@ private static ForkJoinPool asyncSingletonPool() { } private void testInvokeOnPool(ForkJoinPool pool, RecursiveAction a) { - try { + try (PoolCleaner cleaner = cleaner(pool)) { checkNotDone(a); assertNull(pool.invoke(a)); checkCompletedNormally(a); - } finally { - joinPool(pool); } } @@ -429,12 +427,12 @@ public void realRun() throws InterruptedException { t = newStartedThread(r); testInvokeOnPool(mainPool(), a); - awaitTermination(t, LONG_DELAY_MS); + awaitTermination(t); a.reinitialize(); t = newStartedThread(r); testInvokeOnPool(singletonPool(), a); - awaitTermination(t, LONG_DELAY_MS); + awaitTermination(t); } /** diff --git a/jsr166-tests/src/test/java/jsr166/RecursiveTaskTest.java b/jsr166-tests/src/test/java/jsr166/RecursiveTaskTest.java index 77833703d..2c07c2a2f 100644 --- a/jsr166-tests/src/test/java/jsr166/RecursiveTaskTest.java +++ b/jsr166-tests/src/test/java/jsr166/RecursiveTaskTest.java @@ -28,7 +28,7 @@ public class RecursiveTaskTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(RecursiveTaskTest.class); // } private static ForkJoinPool mainPool() { @@ -46,15 +46,13 @@ private static ForkJoinPool asyncSingletonPool() { } private T testInvokeOnPool(ForkJoinPool pool, RecursiveTask a) { - try { + try (PoolCleaner cleaner = cleaner(pool)) { checkNotDone(a); T result = pool.invoke(a); checkCompletedNormally(a, result); return result; - } finally { - joinPool(pool); } } @@ -335,6 +333,8 @@ public Integer realCompute() { FibTask f = new FibTask(8); assertSame(f, f.fork()); helpQuiesce(); + while (!f.isDone()) // wait out race + ; assertEquals(0, getQueuedTaskCount()); checkCompletedNormally(f, 21); return NoResult; diff --git a/jsr166-tests/src/test/java/jsr166/ReentrantLockTest.java b/jsr166-tests/src/test/java/jsr166/ReentrantLockTest.java index 17eaf7650..0024ff3bc 100644 --- a/jsr166-tests/src/test/java/jsr166/ReentrantLockTest.java +++ b/jsr166-tests/src/test/java/jsr166/ReentrantLockTest.java @@ -30,8 +30,9 @@ public class ReentrantLockTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ReentrantLockTest.class); // } + /** * A checked runnable calling lockInterruptibly */ @@ -150,7 +151,7 @@ void assertHasWaiters(PublicReentrantLock lock, Condition c, enum AwaitMethod { await, awaitTimed, awaitNanos, awaitUntil } /** - * Awaits condition using the specified AwaitMethod. + * Awaits condition "indefinitely" using the specified AwaitMethod. */ void await(Condition c, AwaitMethod awaitMethod) throws InterruptedException { @@ -163,9 +164,10 @@ void await(Condition c, AwaitMethod awaitMethod) assertTrue(c.await(timeoutMillis, MILLISECONDS)); break; case awaitNanos: - long nanosTimeout = MILLISECONDS.toNanos(timeoutMillis); - long nanosRemaining = c.awaitNanos(nanosTimeout); - assertTrue(nanosRemaining > 0); + long timeoutNanos = MILLISECONDS.toNanos(timeoutMillis); + long nanosRemaining = c.awaitNanos(timeoutNanos); + assertTrue(nanosRemaining > timeoutNanos / 2); + assertTrue(nanosRemaining <= timeoutNanos); break; case awaitUntil: assertTrue(c.awaitUntil(delayedDate(timeoutMillis))); @@ -428,7 +430,7 @@ public void testGetHoldCount(boolean fair) { } for (int i = SIZE; i > 0; i--) { lock.unlock(); - assertEquals(i-1, lock.getHoldCount()); + assertEquals(i - 1, lock.getHoldCount()); } } @@ -568,11 +570,11 @@ public void testAwaitUntil_Timeout(boolean fair) { final ReentrantLock lock = new ReentrantLock(fair); final Condition c = lock.newCondition(); lock.lock(); - long startTime = System.nanoTime(); - long timeoutMillis = 10; - java.util.Date d = new java.util.Date(); - assertFalse(c.awaitUntil(new java.util.Date(d.getTime() + timeoutMillis))); - assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + // We shouldn't assume that nanoTime and currentTimeMillis + // use the same time source, so don't use nanoTime here. + java.util.Date delayedDate = delayedDate(timeoutMillis()); + assertFalse(c.awaitUntil(delayedDate)); + assertTrue(new java.util.Date().getTime() >= delayedDate.getTime()); lock.unlock(); } catch (InterruptedException fail) { threadUnexpectedException(fail); } } diff --git a/jsr166-tests/src/test/java/jsr166/ReentrantReadWriteLockTest.java b/jsr166-tests/src/test/java/jsr166/ReentrantReadWriteLockTest.java index 7ef8ea34b..918f45d67 100644 --- a/jsr166-tests/src/test/java/jsr166/ReentrantReadWriteLockTest.java +++ b/jsr166-tests/src/test/java/jsr166/ReentrantReadWriteLockTest.java @@ -31,7 +31,7 @@ public class ReentrantReadWriteLockTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ReentrantReadWriteLockTest.class); // } /** @@ -160,24 +160,26 @@ void assertHasWaiters(PublicReentrantReadWriteLock lock, Condition c, enum AwaitMethod { await, awaitTimed, awaitNanos, awaitUntil } /** - * Awaits condition using the specified AwaitMethod. + * Awaits condition "indefinitely" using the specified AwaitMethod. */ void await(Condition c, AwaitMethod awaitMethod) throws InterruptedException { + long timeoutMillis = 2 * LONG_DELAY_MS; switch (awaitMethod) { case await: c.await(); break; case awaitTimed: - assertTrue(c.await(2 * LONG_DELAY_MS, MILLISECONDS)); + assertTrue(c.await(timeoutMillis, MILLISECONDS)); break; case awaitNanos: - long nanosRemaining = c.awaitNanos(MILLISECONDS.toNanos(2 * LONG_DELAY_MS)); - assertTrue(nanosRemaining > 0); + long timeoutNanos = MILLISECONDS.toNanos(timeoutMillis); + long nanosRemaining = c.awaitNanos(timeoutNanos); + assertTrue(nanosRemaining > timeoutNanos / 2); + assertTrue(nanosRemaining <= timeoutNanos); break; case awaitUntil: - java.util.Date d = new java.util.Date(); - assertTrue(c.awaitUntil(new java.util.Date(d.getTime() + 2 * LONG_DELAY_MS))); + assertTrue(c.awaitUntil(delayedDate(timeoutMillis))); break; default: throw new AssertionError(); @@ -241,7 +243,7 @@ public void testGetWriteHoldCount(boolean fair) { } for (int i = SIZE; i > 0; i--) { lock.writeLock().unlock(); - assertEquals(i-1,lock.getWriteHoldCount()); + assertEquals(i - 1,lock.getWriteHoldCount()); } } @@ -258,7 +260,7 @@ public void testGetHoldCount(boolean fair) { } for (int i = SIZE; i > 0; i--) { lock.writeLock().unlock(); - assertEquals(i-1,lock.writeLock().getHoldCount()); + assertEquals(i - 1,lock.writeLock().getHoldCount()); } } @@ -275,7 +277,7 @@ public void testGetReadHoldCount(boolean fair) { } for (int i = SIZE; i > 0; i--) { lock.readLock().unlock(); - assertEquals(i-1,lock.getReadHoldCount()); + assertEquals(i - 1,lock.getReadHoldCount()); } } @@ -972,11 +974,11 @@ public void testAwaitUntil_Timeout(boolean fair) { new ReentrantReadWriteLock(fair); final Condition c = lock.writeLock().newCondition(); lock.writeLock().lock(); - long startTime = System.nanoTime(); - long timeoutMillis = 10; - java.util.Date d = new java.util.Date(); - assertFalse(c.awaitUntil(new java.util.Date(d.getTime() + timeoutMillis))); - assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + // We shouldn't assume that nanoTime and currentTimeMillis + // use the same time source, so don't use nanoTime here. + java.util.Date delayedDate = delayedDate(timeoutMillis()); + assertFalse(c.awaitUntil(delayedDate)); + assertTrue(new java.util.Date().getTime() >= delayedDate.getTime()); lock.writeLock().unlock(); } catch (InterruptedException fail) { threadUnexpectedException(fail); } } diff --git a/jsr166-tests/src/test/java/jsr166/ScheduledExecutorSubclassTest.java b/jsr166-tests/src/test/java/jsr166/ScheduledExecutorSubclassTest.java index a93feea21..194dd58d0 100644 --- a/jsr166-tests/src/test/java/jsr166/ScheduledExecutorSubclassTest.java +++ b/jsr166-tests/src/test/java/jsr166/ScheduledExecutorSubclassTest.java @@ -7,11 +7,15 @@ package jsr166; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; @@ -27,7 +31,9 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import junit.framework.Test; import junit.framework.TestSuite; @@ -40,7 +46,7 @@ public class ScheduledExecutorSubclassTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ScheduledExecutorSubclassTest.class); // } static class CustomTask implements RunnableScheduledFuture { @@ -101,17 +107,13 @@ protected RunnableScheduledFuture decorateTask(Callable c, RunnableSch * execute successfully executes a runnable */ public void testExecute() throws InterruptedException { - CustomExecutor p = new CustomExecutor(1); - final CountDownLatch done = new CountDownLatch(1); - final Runnable task = new CheckedRunnable() { - public void realRun() { - done.countDown(); - }}; - try { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch done = new CountDownLatch(1); + final Runnable task = new CheckedRunnable() { + public void realRun() { done.countDown(); }}; p.execute(task); - assertTrue(done.await(SMALL_DELAY_MS, MILLISECONDS)); - } finally { - joinPool(p); + await(done); } } @@ -119,10 +121,10 @@ public void realRun() { * delayed schedule of callable successfully executes after delay */ public void testSchedule1() throws Exception { - CustomExecutor p = new CustomExecutor(1); - final long startTime = System.nanoTime(); final CountDownLatch done = new CountDownLatch(1); - try { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + final long startTime = System.nanoTime(); Callable task = new CheckedCallable() { public Boolean realCall() { done.countDown(); @@ -132,9 +134,6 @@ public Boolean realCall() { Future f = p.schedule(task, timeoutMillis(), MILLISECONDS); assertSame(Boolean.TRUE, f.get()); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); - assertTrue(done.await(0L, MILLISECONDS)); - } finally { - joinPool(p); } } @@ -142,10 +141,10 @@ public Boolean realCall() { * delayed schedule of runnable successfully executes after delay */ public void testSchedule3() throws Exception { - CustomExecutor p = new CustomExecutor(1); - final long startTime = System.nanoTime(); - final CountDownLatch done = new CountDownLatch(1); - try { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); Runnable task = new CheckedRunnable() { public void realRun() { done.countDown(); @@ -155,8 +154,6 @@ public void realRun() { await(done); assertNull(f.get(LONG_DELAY_MS, MILLISECONDS)); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); - } finally { - joinPool(p); } } @@ -164,10 +161,10 @@ public void realRun() { * scheduleAtFixedRate executes runnable after given initial delay */ public void testSchedule4() throws InterruptedException { - CustomExecutor p = new CustomExecutor(1); - final long startTime = System.nanoTime(); - final CountDownLatch done = new CountDownLatch(1); - try { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); Runnable task = new CheckedRunnable() { public void realRun() { done.countDown(); @@ -179,8 +176,6 @@ public void realRun() { await(done); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); f.cancel(true); - } finally { - joinPool(p); } } @@ -188,10 +183,10 @@ public void realRun() { * scheduleWithFixedDelay executes runnable after given initial delay */ public void testSchedule5() throws InterruptedException { - CustomExecutor p = new CustomExecutor(1); - final long startTime = System.nanoTime(); - final CountDownLatch done = new CountDownLatch(1); - try { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); Runnable task = new CheckedRunnable() { public void realRun() { done.countDown(); @@ -203,8 +198,6 @@ public void realRun() { await(done); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); f.cancel(true); - } finally { - joinPool(p); } } @@ -214,58 +207,77 @@ static class RunnableCounter implements Runnable { } /** - * scheduleAtFixedRate executes series of tasks at given rate + * scheduleAtFixedRate executes series of tasks at given rate. + * Eventually, it must hold that: + * cycles - 1 <= elapsedMillis/delay < cycles */ public void testFixedRateSequence() throws InterruptedException { - CustomExecutor p = new CustomExecutor(1); - try { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { for (int delay = 1; delay <= LONG_DELAY_MS; delay *= 3) { - long startTime = System.nanoTime(); - int cycles = 10; + final long startTime = System.nanoTime(); + final int cycles = 8; final CountDownLatch done = new CountDownLatch(cycles); - Runnable task = new CheckedRunnable() { + final Runnable task = new CheckedRunnable() { public void realRun() { done.countDown(); }}; - ScheduledFuture h = + final ScheduledFuture periodicTask = p.scheduleAtFixedRate(task, 0, delay, MILLISECONDS); - done.await(); - h.cancel(true); - double normalizedTime = - (double) millisElapsedSince(startTime) / delay; - if (normalizedTime >= cycles - 1 && - normalizedTime <= cycles) + final int totalDelayMillis = (cycles - 1) * delay; + await(done, totalDelayMillis + LONG_DELAY_MS); + periodicTask.cancel(true); + final long elapsedMillis = millisElapsedSince(startTime); + assertTrue(elapsedMillis >= totalDelayMillis); + if (elapsedMillis <= cycles * delay) return; + // else retry with longer delay } - throw new AssertionError("unexpected execution rate"); - } finally { - joinPool(p); + fail("unexpected execution rate"); } } /** - * scheduleWithFixedDelay executes series of tasks with given period + * scheduleWithFixedDelay executes series of tasks with given period. + * Eventually, it must hold that each task starts at least delay and at + * most 2 * delay after the termination of the previous task. */ public void testFixedDelaySequence() throws InterruptedException { - CustomExecutor p = new CustomExecutor(1); - try { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { for (int delay = 1; delay <= LONG_DELAY_MS; delay *= 3) { - long startTime = System.nanoTime(); - int cycles = 10; + final long startTime = System.nanoTime(); + final AtomicLong previous = new AtomicLong(startTime); + final AtomicBoolean tryLongerDelay = new AtomicBoolean(false); + final int cycles = 8; final CountDownLatch done = new CountDownLatch(cycles); - Runnable task = new CheckedRunnable() { - public void realRun() { done.countDown(); }}; - ScheduledFuture h = + final int d = delay; + final Runnable task = new CheckedRunnable() { + public void realRun() { + long now = System.nanoTime(); + long elapsedMillis + = NANOSECONDS.toMillis(now - previous.get()); + if (done.getCount() == cycles) { // first execution + if (elapsedMillis >= d) + tryLongerDelay.set(true); + } else { + assertTrue(elapsedMillis >= d); + if (elapsedMillis >= 2 * d) + tryLongerDelay.set(true); + } + previous.set(now); + done.countDown(); + }}; + final ScheduledFuture periodicTask = p.scheduleWithFixedDelay(task, 0, delay, MILLISECONDS); - done.await(); - h.cancel(true); - double normalizedTime = - (double) millisElapsedSince(startTime) / delay; - if (normalizedTime >= cycles - 1 && - normalizedTime <= cycles) + final int totalDelayMillis = (cycles - 1) * delay; + await(done, totalDelayMillis + cycles * LONG_DELAY_MS); + periodicTask.cancel(true); + final long elapsedMillis = millisElapsedSince(startTime); + assertTrue(elapsedMillis >= totalDelayMillis); + if (!tryLongerDelay.get()) return; + // else retry with longer delay } - throw new AssertionError("unexpected execution rate"); - } finally { - joinPool(p); + fail("unexpected execution rate"); } } @@ -273,106 +285,107 @@ public void testFixedDelaySequence() throws InterruptedException { * execute(null) throws NPE */ public void testExecuteNull() throws InterruptedException { - CustomExecutor se = new CustomExecutor(1); - try { - se.execute(null); - shouldThrow(); - } catch (NullPointerException success) {} - joinPool(se); + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.execute(null); + shouldThrow(); + } catch (NullPointerException success) {} + } } /** * schedule(null) throws NPE */ public void testScheduleNull() throws InterruptedException { - CustomExecutor se = new CustomExecutor(1); - try { - TrackedCallable callable = null; - Future f = se.schedule(callable, SHORT_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) {} - joinPool(se); + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + TrackedCallable callable = null; + Future f = p.schedule(callable, SHORT_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } } /** * execute throws RejectedExecutionException if shutdown */ public void testSchedule1_RejectedExecutionException() { - CustomExecutor se = new CustomExecutor(1); - try { - se.shutdown(); - se.schedule(new NoOpRunnable(), - MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (RejectedExecutionException success) { - } catch (SecurityException ok) { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.schedule(new NoOpRunnable(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} } - - joinPool(se); } /** * schedule throws RejectedExecutionException if shutdown */ public void testSchedule2_RejectedExecutionException() { - CustomExecutor se = new CustomExecutor(1); - try { - se.shutdown(); - se.schedule(new NoOpCallable(), - MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (RejectedExecutionException success) { - } catch (SecurityException ok) { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.schedule(new NoOpCallable(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} } - joinPool(se); } /** * schedule callable throws RejectedExecutionException if shutdown */ public void testSchedule3_RejectedExecutionException() { - CustomExecutor se = new CustomExecutor(1); - try { - se.shutdown(); - se.schedule(new NoOpCallable(), - MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (RejectedExecutionException success) { - } catch (SecurityException ok) { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.schedule(new NoOpCallable(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} } - joinPool(se); } /** * scheduleAtFixedRate throws RejectedExecutionException if shutdown */ public void testScheduleAtFixedRate1_RejectedExecutionException() { - CustomExecutor se = new CustomExecutor(1); - try { - se.shutdown(); - se.scheduleAtFixedRate(new NoOpRunnable(), - MEDIUM_DELAY_MS, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (RejectedExecutionException success) { - } catch (SecurityException ok) { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.scheduleAtFixedRate(new NoOpRunnable(), + MEDIUM_DELAY_MS, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} } - joinPool(se); } /** * scheduleWithFixedDelay throws RejectedExecutionException if shutdown */ public void testScheduleWithFixedDelay1_RejectedExecutionException() { - CustomExecutor se = new CustomExecutor(1); - try { - se.shutdown(); - se.scheduleWithFixedDelay(new NoOpRunnable(), - MEDIUM_DELAY_MS, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (RejectedExecutionException success) { - } catch (SecurityException ok) { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.scheduleWithFixedDelay(new NoOpRunnable(), + MEDIUM_DELAY_MS, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} } - joinPool(se); } /** @@ -380,22 +393,19 @@ public void testScheduleWithFixedDelay1_RejectedExecutionException() { * thread becomes active */ public void testGetActiveCount() throws InterruptedException { - final ThreadPoolExecutor p = new CustomExecutor(2); - final CountDownLatch threadStarted = new CountDownLatch(1); final CountDownLatch done = new CountDownLatch(1); - try { + final ThreadPoolExecutor p = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); assertEquals(0, p.getActiveCount()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); assertEquals(1, p.getActiveCount()); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertEquals(1, p.getActiveCount()); - } finally { - done.countDown(); - joinPool(p); } } @@ -405,10 +415,10 @@ public void realRun() throws InterruptedException { */ public void testGetCompletedTaskCount() throws InterruptedException { final ThreadPoolExecutor p = new CustomExecutor(2); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch threadProceed = new CountDownLatch(1); - final CountDownLatch threadDone = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch threadProceed = new CountDownLatch(1); + final CountDownLatch threadDone = new CountDownLatch(1); assertEquals(0, p.getCompletedTaskCount()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { @@ -427,8 +437,6 @@ public void realRun() throws InterruptedException { fail("timed out"); Thread.yield(); } - } finally { - joinPool(p); } } @@ -436,9 +444,10 @@ public void realRun() throws InterruptedException { * getCorePoolSize returns size given in constructor if not otherwise set */ public void testGetCorePoolSize() { - CustomExecutor p = new CustomExecutor(1); - assertEquals(1, p.getCorePoolSize()); - joinPool(p); + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(1, p.getCorePoolSize()); + } } /** @@ -447,25 +456,22 @@ public void testGetCorePoolSize() { */ public void testGetLargestPoolSize() throws InterruptedException { final int THREADS = 3; - final ThreadPoolExecutor p = new CustomExecutor(THREADS); - final CountDownLatch threadsStarted = new CountDownLatch(THREADS); final CountDownLatch done = new CountDownLatch(1); - try { + final ThreadPoolExecutor p = new CustomExecutor(THREADS); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadsStarted = new CountDownLatch(THREADS); assertEquals(0, p.getLargestPoolSize()); for (int i = 0; i < THREADS; i++) p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadsStarted.countDown(); - done.await(); + await(done); assertEquals(THREADS, p.getLargestPoolSize()); }}); - assertTrue(threadsStarted.await(SMALL_DELAY_MS, MILLISECONDS)); - assertEquals(THREADS, p.getLargestPoolSize()); - } finally { - done.countDown(); - joinPool(p); + await(threadsStarted); assertEquals(THREADS, p.getLargestPoolSize()); } + assertEquals(THREADS, p.getLargestPoolSize()); } /** @@ -473,22 +479,19 @@ public void realRun() throws InterruptedException { * become active */ public void testGetPoolSize() throws InterruptedException { - final ThreadPoolExecutor p = new CustomExecutor(1); - final CountDownLatch threadStarted = new CountDownLatch(1); final CountDownLatch done = new CountDownLatch(1); - try { + final ThreadPoolExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); assertEquals(0, p.getPoolSize()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); assertEquals(1, p.getPoolSize()); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertEquals(1, p.getPoolSize()); - } finally { - done.countDown(); - joinPool(p); } } @@ -497,58 +500,70 @@ public void realRun() throws InterruptedException { * submitted */ public void testGetTaskCount() throws InterruptedException { - final ThreadPoolExecutor p = new CustomExecutor(1); - final CountDownLatch threadStarted = new CountDownLatch(1); + final int TASKS = 3; final CountDownLatch done = new CountDownLatch(1); - final int TASKS = 5; - try { + final ThreadPoolExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); assertEquals(0, p.getTaskCount()); - for (int i = 0; i < TASKS; i++) + assertEquals(0, p.getCompletedTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + await(done); + }}); + await(threadStarted); + assertEquals(1, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); + for (int i = 0; i < TASKS; i++) { + assertEquals(1 + i, p.getTaskCount()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); - done.await(); + assertEquals(1 + TASKS, p.getTaskCount()); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); - assertEquals(TASKS, p.getTaskCount()); - } finally { - done.countDown(); - joinPool(p); + } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(1 + TASKS, p.getCompletedTaskCount()); } /** * getThreadFactory returns factory in constructor if not set */ public void testGetThreadFactory() { - ThreadFactory tf = new SimpleThreadFactory(); - CustomExecutor p = new CustomExecutor(1, tf); - assertSame(tf, p.getThreadFactory()); - joinPool(p); + final ThreadFactory threadFactory = new SimpleThreadFactory(); + final CustomExecutor p = new CustomExecutor(1, threadFactory); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(threadFactory, p.getThreadFactory()); + } } /** * setThreadFactory sets the thread factory returned by getThreadFactory */ public void testSetThreadFactory() { - ThreadFactory tf = new SimpleThreadFactory(); - CustomExecutor p = new CustomExecutor(1); - p.setThreadFactory(tf); - assertSame(tf, p.getThreadFactory()); - joinPool(p); + final ThreadFactory threadFactory = new SimpleThreadFactory(); + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + p.setThreadFactory(threadFactory); + assertSame(threadFactory, p.getThreadFactory()); + } } /** * setThreadFactory(null) throws NPE */ public void testSetThreadFactoryNull() { - CustomExecutor p = new CustomExecutor(1); - try { - p.setThreadFactory(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(p); + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setThreadFactory(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -556,91 +571,84 @@ public void testSetThreadFactoryNull() { * isShutdown is false before shutdown, true after */ public void testIsShutdown() { - CustomExecutor p = new CustomExecutor(1); - try { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { assertFalse(p.isShutdown()); - } - finally { try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.isShutdown()); } - assertTrue(p.isShutdown()); } /** * isTerminated is false before termination, true after */ public void testIsTerminated() throws InterruptedException { - final ThreadPoolExecutor p = new CustomExecutor(1); - final CountDownLatch threadStarted = new CountDownLatch(1); final CountDownLatch done = new CountDownLatch(1); - assertFalse(p.isTerminated()); - try { + final ThreadPoolExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { assertFalse(p.isTerminated()); threadStarted.countDown(); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); + assertFalse(p.isTerminated()); assertFalse(p.isTerminating()); done.countDown(); - } finally { try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); } - assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); } /** * isTerminating is not true when running or when terminated */ public void testIsTerminating() throws InterruptedException { - final ThreadPoolExecutor p = new CustomExecutor(1); - final CountDownLatch threadStarted = new CountDownLatch(1); final CountDownLatch done = new CountDownLatch(1); - try { + final ThreadPoolExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); assertFalse(p.isTerminating()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { assertFalse(p.isTerminating()); threadStarted.countDown(); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertFalse(p.isTerminating()); done.countDown(); - } finally { try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertFalse(p.isTerminating()); } - assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); - assertFalse(p.isTerminating()); } /** * getQueue returns the work queue, which contains queued tasks */ public void testGetQueue() throws InterruptedException { - ScheduledThreadPoolExecutor p = new CustomExecutor(1); - final CountDownLatch threadStarted = new CountDownLatch(1); final CountDownLatch done = new CountDownLatch(1); - try { + final ScheduledThreadPoolExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); ScheduledFuture[] tasks = new ScheduledFuture[5]; for (int i = 0; i < tasks.length; i++) { Runnable r = new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); - done.await(); + await(done); }}; tasks[i] = p.schedule(r, 1, MILLISECONDS); } - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); BlockingQueue q = p.getQueue(); assertTrue(q.contains(tasks[tasks.length - 1])); assertFalse(q.contains(tasks[0])); - } finally { - done.countDown(); - joinPool(p); } } @@ -648,20 +656,20 @@ public void realRun() throws InterruptedException { * remove(task) removes queued task, and fails to remove active task */ public void testRemove() throws InterruptedException { - final ScheduledThreadPoolExecutor p = new CustomExecutor(1); - ScheduledFuture[] tasks = new ScheduledFuture[5]; - final CountDownLatch threadStarted = new CountDownLatch(1); final CountDownLatch done = new CountDownLatch(1); - try { + final ScheduledThreadPoolExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + ScheduledFuture[] tasks = new ScheduledFuture[5]; + final CountDownLatch threadStarted = new CountDownLatch(1); for (int i = 0; i < tasks.length; i++) { Runnable r = new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); - done.await(); + await(done); }}; tasks[i] = p.schedule(r, 1, MILLISECONDS); } - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); BlockingQueue q = p.getQueue(); assertFalse(p.remove((Runnable)tasks[0])); assertTrue(q.contains((Runnable)tasks[4])); @@ -672,9 +680,6 @@ public void realRun() throws InterruptedException { assertTrue(q.contains((Runnable)tasks[3])); assertTrue(p.remove((Runnable)tasks[3])); assertFalse(q.contains((Runnable)tasks[3])); - } finally { - done.countDown(); - joinPool(p); } } @@ -682,12 +687,15 @@ public void realRun() throws InterruptedException { * purge removes cancelled tasks from the queue */ public void testPurge() throws InterruptedException { - CustomExecutor p = new CustomExecutor(1); - ScheduledFuture[] tasks = new ScheduledFuture[5]; - for (int i = 0; i < tasks.length; i++) - tasks[i] = p.schedule(new SmallPossiblyInterruptedRunnable(), - LONG_DELAY_MS, MILLISECONDS); - try { + final ScheduledFuture[] tasks = new ScheduledFuture[5]; + final Runnable releaser = new Runnable() { public void run() { + for (ScheduledFuture task : tasks) + if (task != null) task.cancel(true); }}; + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p, releaser)) { + for (int i = 0; i < tasks.length; i++) + tasks[i] = p.schedule(new SmallPossiblyInterruptedRunnable(), + LONG_DELAY_MS, MILLISECONDS); int max = tasks.length; if (tasks[4].cancel(true)) --max; if (tasks[3].cancel(true)) --max; @@ -699,159 +707,185 @@ public void testPurge() throws InterruptedException { long count = p.getTaskCount(); if (count == max) return; - } while (millisElapsedSince(startTime) < MEDIUM_DELAY_MS); + } while (millisElapsedSince(startTime) < LONG_DELAY_MS); fail("Purge failed to remove cancelled tasks"); - } finally { - for (ScheduledFuture task : tasks) - task.cancel(true); - joinPool(p); } } /** - * shutdownNow returns a list containing tasks that were not run + * shutdownNow returns a list containing tasks that were not run, + * and those tasks are drained from the queue */ - public void testShutdownNow() { - CustomExecutor p = new CustomExecutor(1); - for (int i = 0; i < 5; i++) - p.schedule(new SmallPossiblyInterruptedRunnable(), - LONG_DELAY_MS, MILLISECONDS); + public void testShutdownNow() throws InterruptedException { + final int poolSize = 2; + final int count = 5; + final AtomicInteger ran = new AtomicInteger(0); + final CustomExecutor p = new CustomExecutor(poolSize); + final CountDownLatch threadsStarted = new CountDownLatch(poolSize); + Runnable waiter = new CheckedRunnable() { public void realRun() { + threadsStarted.countDown(); + try { + MILLISECONDS.sleep(2 * LONG_DELAY_MS); + } catch (InterruptedException success) {} + ran.getAndIncrement(); + }}; + for (int i = 0; i < count; i++) + p.execute(waiter); + await(threadsStarted); + assertEquals(poolSize, p.getActiveCount()); + assertEquals(0, p.getCompletedTaskCount()); + final List queuedTasks; try { - List l = p.shutdownNow(); - assertTrue(p.isShutdown()); - assertEquals(5, l.size()); + queuedTasks = p.shutdownNow(); } catch (SecurityException ok) { - // Allowed in case test doesn't have privs - } finally { - joinPool(p); + return; // Allowed in case test doesn't have privs } + assertTrue(p.isShutdown()); + assertTrue(p.getQueue().isEmpty()); + assertEquals(count - poolSize, queuedTasks.size()); + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertEquals(poolSize, ran.get()); + assertEquals(poolSize, p.getCompletedTaskCount()); } /** - * In default setting, shutdown cancels periodic but not delayed - * tasks at shutdown + * shutdownNow returns a list containing tasks that were not run, + * and those tasks are drained from the queue */ - public void testShutdown1() throws InterruptedException { - CustomExecutor p = new CustomExecutor(1); - assertTrue(p.getExecuteExistingDelayedTasksAfterShutdownPolicy()); - assertFalse(p.getContinueExistingPeriodicTasksAfterShutdownPolicy()); - - ScheduledFuture[] tasks = new ScheduledFuture[5]; - for (int i = 0; i < tasks.length; i++) - tasks[i] = p.schedule(new NoOpRunnable(), - SHORT_DELAY_MS, MILLISECONDS); - try { p.shutdown(); } catch (SecurityException ok) { return; } - BlockingQueue q = p.getQueue(); - for (ScheduledFuture task : tasks) { - assertFalse(task.isDone()); - assertFalse(task.isCancelled()); - assertTrue(q.contains(task)); + public void testShutdownNow_delayedTasks() throws InterruptedException { + final CustomExecutor p = new CustomExecutor(1); + List tasks = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + Runnable r = new NoOpRunnable(); + tasks.add(p.schedule(r, 9, SECONDS)); + tasks.add(p.scheduleAtFixedRate(r, 9, 9, SECONDS)); + tasks.add(p.scheduleWithFixedDelay(r, 9, 9, SECONDS)); + } + if (testImplementationDetails) + assertEquals(new HashSet(tasks), new HashSet(p.getQueue())); + final List queuedTasks; + try { + queuedTasks = p.shutdownNow(); + } catch (SecurityException ok) { + return; // Allowed in case test doesn't have privs } assertTrue(p.isShutdown()); - assertTrue(p.awaitTermination(SMALL_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); + assertTrue(p.getQueue().isEmpty()); + if (testImplementationDetails) + assertEquals(new HashSet(tasks), new HashSet(queuedTasks)); + assertEquals(tasks.size(), queuedTasks.size()); for (ScheduledFuture task : tasks) { - assertTrue(task.isDone()); + assertFalse(((CustomTask)task).ran); + assertFalse(task.isDone()); assertFalse(task.isCancelled()); } - } - - /** - * If setExecuteExistingDelayedTasksAfterShutdownPolicy is false, - * delayed tasks are cancelled at shutdown - */ - public void testShutdown2() throws InterruptedException { - CustomExecutor p = new CustomExecutor(1); - p.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); - assertFalse(p.getExecuteExistingDelayedTasksAfterShutdownPolicy()); - assertFalse(p.getContinueExistingPeriodicTasksAfterShutdownPolicy()); - ScheduledFuture[] tasks = new ScheduledFuture[5]; - for (int i = 0; i < tasks.length; i++) - tasks[i] = p.schedule(new NoOpRunnable(), - SHORT_DELAY_MS, MILLISECONDS); - BlockingQueue q = p.getQueue(); - assertEquals(tasks.length, q.size()); - try { p.shutdown(); } catch (SecurityException ok) { return; } - assertTrue(p.isShutdown()); - assertTrue(q.isEmpty()); - assertTrue(p.awaitTermination(SMALL_DELAY_MS, MILLISECONDS)); + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); assertTrue(p.isTerminated()); - for (ScheduledFuture task : tasks) { - assertTrue(task.isDone()); - assertTrue(task.isCancelled()); - } } /** - * If setContinueExistingPeriodicTasksAfterShutdownPolicy is set false, - * periodic tasks are cancelled at shutdown + * By default, periodic tasks are cancelled at shutdown. + * By default, delayed tasks keep running after shutdown. + * Check that changing the default values work: + * - setExecuteExistingDelayedTasksAfterShutdownPolicy + * - setContinueExistingPeriodicTasksAfterShutdownPolicy */ - public void testShutdown3() throws InterruptedException { - CustomExecutor p = new CustomExecutor(1); - assertTrue(p.getExecuteExistingDelayedTasksAfterShutdownPolicy()); - assertFalse(p.getContinueExistingPeriodicTasksAfterShutdownPolicy()); - p.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); - assertTrue(p.getExecuteExistingDelayedTasksAfterShutdownPolicy()); - assertFalse(p.getContinueExistingPeriodicTasksAfterShutdownPolicy()); - long initialDelay = LONG_DELAY_MS; - ScheduledFuture task = - p.scheduleAtFixedRate(new NoOpRunnable(), initialDelay, - 5, MILLISECONDS); + public void testShutdown_cancellation() throws Exception { + Boolean[] allBooleans = { null, Boolean.FALSE, Boolean.TRUE }; + for (Boolean policy : allBooleans) + { + final int poolSize = 2; + final CustomExecutor p = new CustomExecutor(poolSize); + final boolean effectiveDelayedPolicy = (policy != Boolean.FALSE); + final boolean effectivePeriodicPolicy = (policy == Boolean.TRUE); + final boolean effectiveRemovePolicy = (policy == Boolean.TRUE); + if (policy != null) { + p.setExecuteExistingDelayedTasksAfterShutdownPolicy(policy); + p.setContinueExistingPeriodicTasksAfterShutdownPolicy(policy); + p.setRemoveOnCancelPolicy(policy); + } + assertEquals(effectiveDelayedPolicy, + p.getExecuteExistingDelayedTasksAfterShutdownPolicy()); + assertEquals(effectivePeriodicPolicy, + p.getContinueExistingPeriodicTasksAfterShutdownPolicy()); + assertEquals(effectiveRemovePolicy, + p.getRemoveOnCancelPolicy()); + // Strategy: Wedge the pool with poolSize "blocker" threads + final AtomicInteger ran = new AtomicInteger(0); + final CountDownLatch poolBlocked = new CountDownLatch(poolSize); + final CountDownLatch unblock = new CountDownLatch(1); + final CountDownLatch periodicLatch1 = new CountDownLatch(2); + final CountDownLatch periodicLatch2 = new CountDownLatch(2); + Runnable task = new CheckedRunnable() { public void realRun() + throws InterruptedException { + poolBlocked.countDown(); + assertTrue(unblock.await(LONG_DELAY_MS, MILLISECONDS)); + ran.getAndIncrement(); + }}; + List> blockers = new ArrayList<>(); + List> periodics = new ArrayList<>(); + List> delayeds = new ArrayList<>(); + for (int i = 0; i < poolSize; i++) + blockers.add(p.submit(task)); + assertTrue(poolBlocked.await(LONG_DELAY_MS, MILLISECONDS)); + + periodics.add(p.scheduleAtFixedRate(countDowner(periodicLatch1), + 1, 1, MILLISECONDS)); + periodics.add(p.scheduleWithFixedDelay(countDowner(periodicLatch2), + 1, 1, MILLISECONDS)); + delayeds.add(p.schedule(task, 1, MILLISECONDS)); + + assertTrue(p.getQueue().containsAll(periodics)); + assertTrue(p.getQueue().containsAll(delayeds)); try { p.shutdown(); } catch (SecurityException ok) { return; } assertTrue(p.isShutdown()); - assertTrue(p.getQueue().isEmpty()); - assertTrue(task.isDone()); - assertTrue(task.isCancelled()); - joinPool(p); - } - - /** - * if setContinueExistingPeriodicTasksAfterShutdownPolicy is true, - * periodic tasks are not cancelled at shutdown - */ - public void testShutdown4() throws InterruptedException { - CustomExecutor p = new CustomExecutor(1); - final CountDownLatch counter = new CountDownLatch(2); - try { - p.setContinueExistingPeriodicTasksAfterShutdownPolicy(true); - assertTrue(p.getExecuteExistingDelayedTasksAfterShutdownPolicy()); - assertTrue(p.getContinueExistingPeriodicTasksAfterShutdownPolicy()); - final Runnable r = new CheckedRunnable() { - public void realRun() { - counter.countDown(); - }}; - ScheduledFuture task = - p.scheduleAtFixedRate(r, 1, 1, MILLISECONDS); - assertFalse(task.isDone()); - assertFalse(task.isCancelled()); - try { p.shutdown(); } catch (SecurityException ok) { return; } - assertFalse(task.isCancelled()); - assertFalse(p.isTerminated()); - assertTrue(p.isShutdown()); - assertTrue(counter.await(SMALL_DELAY_MS, MILLISECONDS)); - assertFalse(task.isCancelled()); - assertTrue(task.cancel(false)); - assertTrue(task.isDone()); - assertTrue(task.isCancelled()); - assertTrue(p.awaitTermination(SMALL_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); + assertFalse(p.isTerminated()); + for (Future periodic : periodics) { + assertTrue(effectivePeriodicPolicy ^ periodic.isCancelled()); + assertTrue(effectivePeriodicPolicy ^ periodic.isDone()); + } + for (Future delayed : delayeds) { + assertTrue(effectiveDelayedPolicy ^ delayed.isCancelled()); + assertTrue(effectiveDelayedPolicy ^ delayed.isDone()); + } + if (testImplementationDetails) { + assertEquals(effectivePeriodicPolicy, + p.getQueue().containsAll(periodics)); + assertEquals(effectiveDelayedPolicy, + p.getQueue().containsAll(delayeds)); + } + // Release all pool threads + unblock.countDown(); + + for (Future delayed : delayeds) { + if (effectiveDelayedPolicy) { + assertNull(delayed.get()); + } } - finally { - joinPool(p); + if (effectivePeriodicPolicy) { + assertTrue(periodicLatch1.await(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(periodicLatch2.await(LONG_DELAY_MS, MILLISECONDS)); + for (Future periodic : periodics) { + assertTrue(periodic.cancel(false)); + assertTrue(periodic.isCancelled()); + assertTrue(periodic.isDone()); + } } - } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertEquals(2 + (effectiveDelayedPolicy ? 1 : 0), ran.get()); + }} /** * completed submit of callable returns result */ public void testSubmitCallable() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new StringTask()); String result = future.get(); assertSame(TEST_STRING, result); - } finally { - joinPool(e); } } @@ -859,13 +893,11 @@ public void testSubmitCallable() throws Exception { * completed submit of runnable returns successfully */ public void testSubmitRunnable() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new NoOpRunnable()); future.get(); assertTrue(future.isDone()); - } finally { - joinPool(e); } } @@ -873,13 +905,11 @@ public void testSubmitRunnable() throws Exception { * completed submit of (runnable, result) returns result */ public void testSubmitRunnable2() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new NoOpRunnable(), TEST_STRING); String result = future.get(); assertSame(TEST_STRING, result); - } finally { - joinPool(e); } } @@ -887,13 +917,12 @@ public void testSubmitRunnable2() throws Exception { * invokeAny(null) throws NPE */ public void testInvokeAny1() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { - e.invokeAny(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -901,13 +930,12 @@ public void testInvokeAny1() throws Exception { * invokeAny(empty collection) throws IAE */ public void testInvokeAny2() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { - e.invokeAny(new ArrayList>()); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - joinPool(e); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>()); + shouldThrow(); + } catch (IllegalArgumentException success) {} } } @@ -915,18 +943,17 @@ public void testInvokeAny2() throws Exception { * invokeAny(c) throws NPE if c has null elements */ public void testInvokeAny3() throws Exception { - CountDownLatch latch = new CountDownLatch(1); - ExecutorService e = new CustomExecutor(2); - List> l = new ArrayList>(); - l.add(latchAwaitingStringTask(latch)); - l.add(null); - try { - e.invokeAny(l); - shouldThrow(); - } catch (NullPointerException success) { - } finally { + final CountDownLatch latch = new CountDownLatch(1); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l); + shouldThrow(); + } catch (NullPointerException success) {} latch.countDown(); - joinPool(e); } } @@ -934,16 +961,16 @@ public void testInvokeAny3() throws Exception { * invokeAny(c) throws ExecutionException if no task completes */ public void testInvokeAny4() throws Exception { - ExecutorService e = new CustomExecutor(2); - List> l = new ArrayList>(); - l.add(new NPETask()); - try { - e.invokeAny(l); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -951,15 +978,13 @@ public void testInvokeAny4() throws Exception { * invokeAny(c) returns result of some task */ public void testInvokeAny5() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); String result = e.invokeAny(l); assertSame(TEST_STRING, result); - } finally { - joinPool(e); } } @@ -967,13 +992,12 @@ public void testInvokeAny5() throws Exception { * invokeAll(null) throws NPE */ public void testInvokeAll1() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { - e.invokeAll(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -981,12 +1005,10 @@ public void testInvokeAll1() throws Exception { * invokeAll(empty collection) returns empty collection */ public void testInvokeAll2() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { List> r = e.invokeAll(new ArrayList>()); assertTrue(r.isEmpty()); - } finally { - joinPool(e); } } @@ -994,16 +1016,15 @@ public void testInvokeAll2() throws Exception { * invokeAll(c) throws NPE if c has null elements */ public void testInvokeAll3() throws Exception { - ExecutorService e = new CustomExecutor(2); - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(null); - try { - e.invokeAll(l); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1011,18 +1032,18 @@ public void testInvokeAll3() throws Exception { * get of invokeAll(c) throws exception on failed task */ public void testInvokeAll4() throws Exception { - ExecutorService e = new CustomExecutor(2); - List> l = new ArrayList>(); - l.add(new NPETask()); - List> futures = e.invokeAll(l); - assertEquals(1, futures.size()); - try { - futures.get(0).get(); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = e.invokeAll(l); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -1030,8 +1051,8 @@ public void testInvokeAll4() throws Exception { * invokeAll(c) returns results of all completed tasks */ public void testInvokeAll5() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); @@ -1039,8 +1060,6 @@ public void testInvokeAll5() throws Exception { assertEquals(2, futures.size()); for (Future future : futures) assertSame(TEST_STRING, future.get()); - } finally { - joinPool(e); } } @@ -1048,13 +1067,12 @@ public void testInvokeAll5() throws Exception { * timed invokeAny(null) throws NPE */ public void testTimedInvokeAny1() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { - e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1062,15 +1080,14 @@ public void testTimedInvokeAny1() throws Exception { * timed invokeAny(,,null) throws NPE */ public void testTimedInvokeAnyNullTimeUnit() throws Exception { - ExecutorService e = new CustomExecutor(2); - List> l = new ArrayList>(); - l.add(new StringTask()); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1078,13 +1095,12 @@ public void testTimedInvokeAnyNullTimeUnit() throws Exception { * timed invokeAny(empty collection) throws IAE */ public void testTimedInvokeAny2() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { - e.invokeAny(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - joinPool(e); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} } } @@ -1093,17 +1109,16 @@ public void testTimedInvokeAny2() throws Exception { */ public void testTimedInvokeAny3() throws Exception { CountDownLatch latch = new CountDownLatch(1); - ExecutorService e = new CustomExecutor(2); - List> l = new ArrayList>(); - l.add(latchAwaitingStringTask(latch)); - l.add(null); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} latch.countDown(); - joinPool(e); } } @@ -1111,16 +1126,18 @@ public void testTimedInvokeAny3() throws Exception { * timed invokeAny(c) throws ExecutionException if no task completes */ public void testTimedInvokeAny4() throws Exception { - ExecutorService e = new CustomExecutor(2); - List> l = new ArrayList>(); - l.add(new NPETask()); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } } @@ -1128,15 +1145,15 @@ public void testTimedInvokeAny4() throws Exception { * timed invokeAny(c) returns result of some task */ public void testTimedInvokeAny5() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); - String result = e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + String result = e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); assertSame(TEST_STRING, result); - } finally { - joinPool(e); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } } @@ -1144,13 +1161,12 @@ public void testTimedInvokeAny5() throws Exception { * timed invokeAll(null) throws NPE */ public void testTimedInvokeAll1() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { - e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1158,15 +1174,14 @@ public void testTimedInvokeAll1() throws Exception { * timed invokeAll(,,null) throws NPE */ public void testTimedInvokeAllNullTimeUnit() throws Exception { - ExecutorService e = new CustomExecutor(2); - List> l = new ArrayList>(); - l.add(new StringTask()); - try { - e.invokeAll(l, MEDIUM_DELAY_MS, null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1174,12 +1189,10 @@ public void testTimedInvokeAllNullTimeUnit() throws Exception { * timed invokeAll(empty collection) returns empty collection */ public void testTimedInvokeAll2() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { List> r = e.invokeAll(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); assertTrue(r.isEmpty()); - } finally { - joinPool(e); } } @@ -1187,16 +1200,15 @@ public void testTimedInvokeAll2() throws Exception { * timed invokeAll(c) throws NPE if c has null elements */ public void testTimedInvokeAll3() throws Exception { - ExecutorService e = new CustomExecutor(2); - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(null); - try { - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1204,19 +1216,19 @@ public void testTimedInvokeAll3() throws Exception { * get of element of invokeAll(c) throws exception on failed task */ public void testTimedInvokeAll4() throws Exception { - ExecutorService e = new CustomExecutor(2); - List> l = new ArrayList>(); - l.add(new NPETask()); - List> futures = - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); - assertEquals(1, futures.size()); - try { - futures.get(0).get(); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = + e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -1224,18 +1236,16 @@ public void testTimedInvokeAll4() throws Exception { * timed invokeAll(c) returns results of all completed tasks */ public void testTimedInvokeAll5() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); List> futures = - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); assertEquals(2, futures.size()); for (Future future : futures) assertSame(TEST_STRING, future.get()); - } finally { - joinPool(e); } } @@ -1243,21 +1253,37 @@ public void testTimedInvokeAll5() throws Exception { * timed invokeAll(c) cancels tasks not completed by timeout */ public void testTimedInvokeAll6() throws Exception { - ExecutorService e = new CustomExecutor(2); - try { - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(Executors.callable(new MediumPossiblyInterruptedRunnable(), TEST_STRING)); - l.add(new StringTask()); - List> futures = - e.invokeAll(l, SHORT_DELAY_MS, MILLISECONDS); - assertEquals(l.size(), futures.size()); - for (Future future : futures) - assertTrue(future.isDone()); - assertFalse(futures.get(0).isCancelled()); - assertTrue(futures.get(1).isCancelled()); - } finally { - joinPool(e); + for (long timeout = timeoutMillis();;) { + final CountDownLatch done = new CountDownLatch(1); + final Callable waiter = new CheckedCallable() { + public String realCall() { + try { done.await(LONG_DELAY_MS, MILLISECONDS); } + catch (InterruptedException ok) {} + return "1"; }}; + final ExecutorService p = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(p, done)) { + List> tasks = new ArrayList<>(); + tasks.add(new StringTask("0")); + tasks.add(waiter); + tasks.add(new StringTask("2")); + long startTime = System.nanoTime(); + List> futures = + p.invokeAll(tasks, timeout, MILLISECONDS); + assertEquals(tasks.size(), futures.size()); + assertTrue(millisElapsedSince(startTime) >= timeout); + for (Future future : futures) + assertTrue(future.isDone()); + assertTrue(futures.get(1).isCancelled()); + try { + assertEquals("0", futures.get(0).get()); + assertEquals("2", futures.get(2).get()); + break; + } catch (CancellationException retryWithLongerTimeout) { + timeout *= 2; + if (timeout >= LONG_DELAY_MS / 2) + fail("expected exactly one task to be cancelled"); + } + } } } diff --git a/jsr166-tests/src/test/java/jsr166/ScheduledExecutorTest.java b/jsr166-tests/src/test/java/jsr166/ScheduledExecutorTest.java index a2e83d0a5..81f73700a 100644 --- a/jsr166-tests/src/test/java/jsr166/ScheduledExecutorTest.java +++ b/jsr166-tests/src/test/java/jsr166/ScheduledExecutorTest.java @@ -9,11 +9,15 @@ package jsr166; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; @@ -24,7 +28,9 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import junit.framework.Test; import junit.framework.TestSuite; @@ -37,24 +43,20 @@ public class ScheduledExecutorTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ScheduledExecutorTest.class); // } /** * execute successfully executes a runnable */ public void testExecute() throws InterruptedException { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - final CountDownLatch done = new CountDownLatch(1); - final Runnable task = new CheckedRunnable() { - public void realRun() { - done.countDown(); - }}; - try { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch done = new CountDownLatch(1); + final Runnable task = new CheckedRunnable() { + public void realRun() { done.countDown(); }}; p.execute(task); - assertTrue(done.await(SMALL_DELAY_MS, MILLISECONDS)); - } finally { - joinPool(p); + assertTrue(done.await(LONG_DELAY_MS, MILLISECONDS)); } } @@ -62,10 +64,10 @@ public void realRun() { * delayed schedule of callable successfully executes after delay */ public void testSchedule1() throws Exception { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - final long startTime = System.nanoTime(); - final CountDownLatch done = new CountDownLatch(1); - try { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); Callable task = new CheckedCallable() { public Boolean realCall() { done.countDown(); @@ -76,8 +78,6 @@ public Boolean realCall() { assertSame(Boolean.TRUE, f.get()); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); assertTrue(done.await(0L, MILLISECONDS)); - } finally { - joinPool(p); } } @@ -85,10 +85,10 @@ public Boolean realCall() { * delayed schedule of runnable successfully executes after delay */ public void testSchedule3() throws Exception { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - final long startTime = System.nanoTime(); - final CountDownLatch done = new CountDownLatch(1); - try { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); Runnable task = new CheckedRunnable() { public void realRun() { done.countDown(); @@ -98,8 +98,6 @@ public void realRun() { await(done); assertNull(f.get(LONG_DELAY_MS, MILLISECONDS)); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); - } finally { - joinPool(p); } } @@ -107,10 +105,10 @@ public void realRun() { * scheduleAtFixedRate executes runnable after given initial delay */ public void testSchedule4() throws Exception { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - final long startTime = System.nanoTime(); - final CountDownLatch done = new CountDownLatch(1); - try { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); Runnable task = new CheckedRunnable() { public void realRun() { done.countDown(); @@ -122,8 +120,6 @@ public void realRun() { await(done); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); f.cancel(true); - } finally { - joinPool(p); } } @@ -131,10 +127,10 @@ public void realRun() { * scheduleWithFixedDelay executes runnable after given initial delay */ public void testSchedule5() throws Exception { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - final long startTime = System.nanoTime(); - final CountDownLatch done = new CountDownLatch(1); - try { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); Runnable task = new CheckedRunnable() { public void realRun() { done.countDown(); @@ -146,8 +142,6 @@ public void realRun() { await(done); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); f.cancel(true); - } finally { - joinPool(p); } } @@ -157,58 +151,77 @@ static class RunnableCounter implements Runnable { } /** - * scheduleAtFixedRate executes series of tasks at given rate + * scheduleAtFixedRate executes series of tasks at given rate. + * Eventually, it must hold that: + * cycles - 1 <= elapsedMillis/delay < cycles */ public void testFixedRateSequence() throws InterruptedException { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - try { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { for (int delay = 1; delay <= LONG_DELAY_MS; delay *= 3) { - long startTime = System.nanoTime(); - int cycles = 10; + final long startTime = System.nanoTime(); + final int cycles = 8; final CountDownLatch done = new CountDownLatch(cycles); - Runnable task = new CheckedRunnable() { + final Runnable task = new CheckedRunnable() { public void realRun() { done.countDown(); }}; - ScheduledFuture h = + final ScheduledFuture periodicTask = p.scheduleAtFixedRate(task, 0, delay, MILLISECONDS); - done.await(); - h.cancel(true); - double normalizedTime = - (double) millisElapsedSince(startTime) / delay; - if (normalizedTime >= cycles - 1 && - normalizedTime <= cycles) + final int totalDelayMillis = (cycles - 1) * delay; + await(done, totalDelayMillis + LONG_DELAY_MS); + periodicTask.cancel(true); + final long elapsedMillis = millisElapsedSince(startTime); + assertTrue(elapsedMillis >= totalDelayMillis); + if (elapsedMillis <= cycles * delay) return; + // else retry with longer delay } - throw new AssertionError("unexpected execution rate"); - } finally { - joinPool(p); + fail("unexpected execution rate"); } } /** - * scheduleWithFixedDelay executes series of tasks with given period + * scheduleWithFixedDelay executes series of tasks with given period. + * Eventually, it must hold that each task starts at least delay and at + * most 2 * delay after the termination of the previous task. */ public void testFixedDelaySequence() throws InterruptedException { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - try { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { for (int delay = 1; delay <= LONG_DELAY_MS; delay *= 3) { - long startTime = System.nanoTime(); - int cycles = 10; + final long startTime = System.nanoTime(); + final AtomicLong previous = new AtomicLong(startTime); + final AtomicBoolean tryLongerDelay = new AtomicBoolean(false); + final int cycles = 8; final CountDownLatch done = new CountDownLatch(cycles); - Runnable task = new CheckedRunnable() { - public void realRun() { done.countDown(); }}; - ScheduledFuture h = + final int d = delay; + final Runnable task = new CheckedRunnable() { + public void realRun() { + long now = System.nanoTime(); + long elapsedMillis + = NANOSECONDS.toMillis(now - previous.get()); + if (done.getCount() == cycles) { // first execution + if (elapsedMillis >= d) + tryLongerDelay.set(true); + } else { + assertTrue(elapsedMillis >= d); + if (elapsedMillis >= 2 * d) + tryLongerDelay.set(true); + } + previous.set(now); + done.countDown(); + }}; + final ScheduledFuture periodicTask = p.scheduleWithFixedDelay(task, 0, delay, MILLISECONDS); - done.await(); - h.cancel(true); - double normalizedTime = - (double) millisElapsedSince(startTime) / delay; - if (normalizedTime >= cycles - 1 && - normalizedTime <= cycles) + final int totalDelayMillis = (cycles - 1) * delay; + await(done, totalDelayMillis + cycles * LONG_DELAY_MS); + periodicTask.cancel(true); + final long elapsedMillis = millisElapsedSince(startTime); + assertTrue(elapsedMillis >= totalDelayMillis); + if (!tryLongerDelay.get()) return; + // else retry with longer delay } - throw new AssertionError("unexpected execution rate"); - } finally { - joinPool(p); + fail("unexpected execution rate"); } } @@ -216,108 +229,107 @@ public void testFixedDelaySequence() throws InterruptedException { * execute(null) throws NPE */ public void testExecuteNull() throws InterruptedException { - ScheduledThreadPoolExecutor se = null; - try { - se = new ScheduledThreadPoolExecutor(1); - se.execute(null); - shouldThrow(); - } catch (NullPointerException success) {} - - joinPool(se); + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.execute(null); + shouldThrow(); + } catch (NullPointerException success) {} + } } /** * schedule(null) throws NPE */ public void testScheduleNull() throws InterruptedException { - ScheduledThreadPoolExecutor se = new ScheduledThreadPoolExecutor(1); - try { - TrackedCallable callable = null; - Future f = se.schedule(callable, SHORT_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) {} - joinPool(se); + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + TrackedCallable callable = null; + Future f = p.schedule(callable, SHORT_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } } /** * execute throws RejectedExecutionException if shutdown */ public void testSchedule1_RejectedExecutionException() throws InterruptedException { - ScheduledThreadPoolExecutor se = new ScheduledThreadPoolExecutor(1); - try { - se.shutdown(); - se.schedule(new NoOpRunnable(), - MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (RejectedExecutionException success) { - } catch (SecurityException ok) { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.schedule(new NoOpRunnable(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} } - - joinPool(se); } /** * schedule throws RejectedExecutionException if shutdown */ public void testSchedule2_RejectedExecutionException() throws InterruptedException { - ScheduledThreadPoolExecutor se = new ScheduledThreadPoolExecutor(1); - try { - se.shutdown(); - se.schedule(new NoOpCallable(), - MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (RejectedExecutionException success) { - } catch (SecurityException ok) { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.schedule(new NoOpCallable(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} } - joinPool(se); } /** * schedule callable throws RejectedExecutionException if shutdown */ public void testSchedule3_RejectedExecutionException() throws InterruptedException { - ScheduledThreadPoolExecutor se = new ScheduledThreadPoolExecutor(1); - try { - se.shutdown(); - se.schedule(new NoOpCallable(), - MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (RejectedExecutionException success) { - } catch (SecurityException ok) { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.schedule(new NoOpCallable(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} } - joinPool(se); } /** * scheduleAtFixedRate throws RejectedExecutionException if shutdown */ public void testScheduleAtFixedRate1_RejectedExecutionException() throws InterruptedException { - ScheduledThreadPoolExecutor se = new ScheduledThreadPoolExecutor(1); - try { - se.shutdown(); - se.scheduleAtFixedRate(new NoOpRunnable(), - MEDIUM_DELAY_MS, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (RejectedExecutionException success) { - } catch (SecurityException ok) { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.scheduleAtFixedRate(new NoOpRunnable(), + MEDIUM_DELAY_MS, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} } - joinPool(se); } /** * scheduleWithFixedDelay throws RejectedExecutionException if shutdown */ public void testScheduleWithFixedDelay1_RejectedExecutionException() throws InterruptedException { - ScheduledThreadPoolExecutor se = new ScheduledThreadPoolExecutor(1); - try { - se.shutdown(); - se.scheduleWithFixedDelay(new NoOpRunnable(), - MEDIUM_DELAY_MS, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (RejectedExecutionException success) { - } catch (SecurityException ok) { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.scheduleWithFixedDelay(new NoOpRunnable(), + MEDIUM_DELAY_MS, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} } - joinPool(se); } /** @@ -325,22 +337,19 @@ public void testScheduleWithFixedDelay1_RejectedExecutionException() throws Inte * thread becomes active */ public void testGetActiveCount() throws InterruptedException { - final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(2); - final CountDownLatch threadStarted = new CountDownLatch(1); final CountDownLatch done = new CountDownLatch(1); - try { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); assertEquals(0, p.getActiveCount()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); assertEquals(1, p.getActiveCount()); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertEquals(1, p.getActiveCount()); - } finally { - done.countDown(); - joinPool(p); } } @@ -350,10 +359,10 @@ public void realRun() throws InterruptedException { */ public void testGetCompletedTaskCount() throws InterruptedException { final ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(2); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch threadProceed = new CountDownLatch(1); - final CountDownLatch threadDone = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch threadProceed = new CountDownLatch(1); + final CountDownLatch threadDone = new CountDownLatch(1); assertEquals(0, p.getCompletedTaskCount()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { @@ -372,8 +381,6 @@ public void realRun() throws InterruptedException { fail("timed out"); Thread.yield(); } - } finally { - joinPool(p); } } @@ -382,8 +389,9 @@ public void realRun() throws InterruptedException { */ public void testGetCorePoolSize() throws InterruptedException { ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - assertEquals(1, p.getCorePoolSize()); - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(1, p.getCorePoolSize()); + } } /** @@ -395,22 +403,19 @@ public void testGetLargestPoolSize() throws InterruptedException { final ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(THREADS); final CountDownLatch threadsStarted = new CountDownLatch(THREADS); final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { assertEquals(0, p.getLargestPoolSize()); for (int i = 0; i < THREADS; i++) p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadsStarted.countDown(); - done.await(); + await(done); assertEquals(THREADS, p.getLargestPoolSize()); }}); - assertTrue(threadsStarted.await(SMALL_DELAY_MS, MILLISECONDS)); - assertEquals(THREADS, p.getLargestPoolSize()); - } finally { - done.countDown(); - joinPool(p); + await(threadsStarted); assertEquals(THREADS, p.getLargestPoolSize()); } + assertEquals(THREADS, p.getLargestPoolSize()); } /** @@ -421,19 +426,16 @@ public void testGetPoolSize() throws InterruptedException { final ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); final CountDownLatch threadStarted = new CountDownLatch(1); final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { assertEquals(0, p.getPoolSize()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); assertEquals(1, p.getPoolSize()); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertEquals(1, p.getPoolSize()); - } finally { - done.countDown(); - joinPool(p); } } @@ -442,58 +444,71 @@ public void realRun() throws InterruptedException { * submitted */ public void testGetTaskCount() throws InterruptedException { - final ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - final CountDownLatch threadStarted = new CountDownLatch(1); + final int TASKS = 3; final CountDownLatch done = new CountDownLatch(1); - final int TASKS = 5; - try { + final ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); assertEquals(0, p.getTaskCount()); - for (int i = 0; i < TASKS; i++) + assertEquals(0, p.getCompletedTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + await(done); + }}); + await(threadStarted); + assertEquals(1, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); + for (int i = 0; i < TASKS; i++) { + assertEquals(1 + i, p.getTaskCount()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); - done.await(); + assertEquals(1 + TASKS, p.getTaskCount()); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); - assertEquals(TASKS, p.getTaskCount()); - } finally { - done.countDown(); - joinPool(p); + } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(1 + TASKS, p.getCompletedTaskCount()); } /** * getThreadFactory returns factory in constructor if not set */ public void testGetThreadFactory() throws InterruptedException { - ThreadFactory tf = new SimpleThreadFactory(); - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1, tf); - assertSame(tf, p.getThreadFactory()); - joinPool(p); + final ThreadFactory threadFactory = new SimpleThreadFactory(); + final ScheduledThreadPoolExecutor p = + new ScheduledThreadPoolExecutor(1, threadFactory); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(threadFactory, p.getThreadFactory()); + } } /** * setThreadFactory sets the thread factory returned by getThreadFactory */ public void testSetThreadFactory() throws InterruptedException { - ThreadFactory tf = new SimpleThreadFactory(); - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - p.setThreadFactory(tf); - assertSame(tf, p.getThreadFactory()); - joinPool(p); + ThreadFactory threadFactory = new SimpleThreadFactory(); + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + p.setThreadFactory(threadFactory); + assertSame(threadFactory, p.getThreadFactory()); + } } /** * setThreadFactory(null) throws NPE */ public void testSetThreadFactoryNull() throws InterruptedException { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - try { - p.setThreadFactory(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(p); + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setThreadFactory(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -502,7 +517,7 @@ public void testSetThreadFactoryNull() throws InterruptedException { */ public void testIsShutdown() { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); try { assertFalse(p.isShutdown()); } @@ -517,24 +532,23 @@ public void testIsShutdown() { */ public void testIsTerminated() throws InterruptedException { final ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - assertFalse(p.isTerminated()); - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); + assertFalse(p.isTerminated()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { assertFalse(p.isTerminated()); threadStarted.countDown(); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertFalse(p.isTerminating()); done.countDown(); - } finally { try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); } - assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); } /** @@ -544,49 +558,45 @@ public void testIsTerminating() throws InterruptedException { final ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); final CountDownLatch threadStarted = new CountDownLatch(1); final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { assertFalse(p.isTerminating()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { assertFalse(p.isTerminating()); threadStarted.countDown(); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertFalse(p.isTerminating()); done.countDown(); - } finally { try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertFalse(p.isTerminating()); } - assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); - assertFalse(p.isTerminating()); } /** * getQueue returns the work queue, which contains queued tasks */ public void testGetQueue() throws InterruptedException { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - final CountDownLatch threadStarted = new CountDownLatch(1); final CountDownLatch done = new CountDownLatch(1); - try { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); ScheduledFuture[] tasks = new ScheduledFuture[5]; for (int i = 0; i < tasks.length; i++) { Runnable r = new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); - done.await(); + await(done); }}; tasks[i] = p.schedule(r, 1, MILLISECONDS); } - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); BlockingQueue q = p.getQueue(); assertTrue(q.contains(tasks[tasks.length - 1])); assertFalse(q.contains(tasks[0])); - } finally { - done.countDown(); - joinPool(p); } } @@ -594,20 +604,20 @@ public void realRun() throws InterruptedException { * remove(task) removes queued task, and fails to remove active task */ public void testRemove() throws InterruptedException { - final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - ScheduledFuture[] tasks = new ScheduledFuture[5]; - final CountDownLatch threadStarted = new CountDownLatch(1); final CountDownLatch done = new CountDownLatch(1); - try { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + ScheduledFuture[] tasks = new ScheduledFuture[5]; + final CountDownLatch threadStarted = new CountDownLatch(1); for (int i = 0; i < tasks.length; i++) { Runnable r = new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); - done.await(); + await(done); }}; tasks[i] = p.schedule(r, 1, MILLISECONDS); } - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); BlockingQueue q = p.getQueue(); assertFalse(p.remove((Runnable)tasks[0])); assertTrue(q.contains((Runnable)tasks[4])); @@ -618,9 +628,6 @@ public void realRun() throws InterruptedException { assertTrue(q.contains((Runnable)tasks[3])); assertTrue(p.remove((Runnable)tasks[3])); assertFalse(q.contains((Runnable)tasks[3])); - } finally { - done.countDown(); - joinPool(p); } } @@ -628,12 +635,15 @@ public void realRun() throws InterruptedException { * purge eventually removes cancelled tasks from the queue */ public void testPurge() throws InterruptedException { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - ScheduledFuture[] tasks = new ScheduledFuture[5]; - for (int i = 0; i < tasks.length; i++) - tasks[i] = p.schedule(new SmallPossiblyInterruptedRunnable(), - LONG_DELAY_MS, MILLISECONDS); - try { + final ScheduledFuture[] tasks = new ScheduledFuture[5]; + final Runnable releaser = new Runnable() { public void run() { + for (ScheduledFuture task : tasks) + if (task != null) task.cancel(true); }}; + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p, releaser)) { + for (int i = 0; i < tasks.length; i++) + tasks[i] = p.schedule(new SmallPossiblyInterruptedRunnable(), + LONG_DELAY_MS, MILLISECONDS); int max = tasks.length; if (tasks[4].cancel(true)) --max; if (tasks[3].cancel(true)) --max; @@ -645,159 +655,186 @@ public void testPurge() throws InterruptedException { long count = p.getTaskCount(); if (count == max) return; - } while (millisElapsedSince(startTime) < MEDIUM_DELAY_MS); + } while (millisElapsedSince(startTime) < LONG_DELAY_MS); fail("Purge failed to remove cancelled tasks"); - } finally { - for (ScheduledFuture task : tasks) - task.cancel(true); - joinPool(p); } } /** - * shutdownNow returns a list containing tasks that were not run + * shutdownNow returns a list containing tasks that were not run, + * and those tasks are drained from the queue */ - public void testShutdownNow() { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - for (int i = 0; i < 5; i++) - p.schedule(new SmallPossiblyInterruptedRunnable(), - LONG_DELAY_MS, MILLISECONDS); + public void testShutdownNow() throws InterruptedException { + final int poolSize = 2; + final int count = 5; + final AtomicInteger ran = new AtomicInteger(0); + final ScheduledThreadPoolExecutor p = + new ScheduledThreadPoolExecutor(poolSize); + final CountDownLatch threadsStarted = new CountDownLatch(poolSize); + Runnable waiter = new CheckedRunnable() { public void realRun() { + threadsStarted.countDown(); + try { + MILLISECONDS.sleep(2 * LONG_DELAY_MS); + } catch (InterruptedException success) {} + ran.getAndIncrement(); + }}; + for (int i = 0; i < count; i++) + p.execute(waiter); + await(threadsStarted); + assertEquals(poolSize, p.getActiveCount()); + assertEquals(0, p.getCompletedTaskCount()); + final List queuedTasks; try { - List l = p.shutdownNow(); - assertTrue(p.isShutdown()); - assertEquals(5, l.size()); + queuedTasks = p.shutdownNow(); } catch (SecurityException ok) { - // Allowed in case test doesn't have privs - } finally { - joinPool(p); + return; // Allowed in case test doesn't have privs } + assertTrue(p.isShutdown()); + assertTrue(p.getQueue().isEmpty()); + assertEquals(count - poolSize, queuedTasks.size()); + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertEquals(poolSize, ran.get()); + assertEquals(poolSize, p.getCompletedTaskCount()); } /** - * In default setting, shutdown cancels periodic but not delayed - * tasks at shutdown + * shutdownNow returns a list containing tasks that were not run, + * and those tasks are drained from the queue */ - public void testShutdown1() throws InterruptedException { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - assertTrue(p.getExecuteExistingDelayedTasksAfterShutdownPolicy()); - assertFalse(p.getContinueExistingPeriodicTasksAfterShutdownPolicy()); - - ScheduledFuture[] tasks = new ScheduledFuture[5]; - for (int i = 0; i < tasks.length; i++) - tasks[i] = p.schedule(new NoOpRunnable(), - SHORT_DELAY_MS, MILLISECONDS); - try { p.shutdown(); } catch (SecurityException ok) { return; } - BlockingQueue q = p.getQueue(); - for (ScheduledFuture task : tasks) { - assertFalse(task.isDone()); - assertFalse(task.isCancelled()); - assertTrue(q.contains(task)); + public void testShutdownNow_delayedTasks() throws InterruptedException { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + List tasks = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + Runnable r = new NoOpRunnable(); + tasks.add(p.schedule(r, 9, SECONDS)); + tasks.add(p.scheduleAtFixedRate(r, 9, 9, SECONDS)); + tasks.add(p.scheduleWithFixedDelay(r, 9, 9, SECONDS)); + } + if (testImplementationDetails) + assertEquals(new HashSet(tasks), new HashSet(p.getQueue())); + final List queuedTasks; + try { + queuedTasks = p.shutdownNow(); + } catch (SecurityException ok) { + return; // Allowed in case test doesn't have privs } assertTrue(p.isShutdown()); - assertTrue(p.awaitTermination(SMALL_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); + assertTrue(p.getQueue().isEmpty()); + if (testImplementationDetails) + assertEquals(new HashSet(tasks), new HashSet(queuedTasks)); + assertEquals(tasks.size(), queuedTasks.size()); for (ScheduledFuture task : tasks) { - assertTrue(task.isDone()); + assertFalse(task.isDone()); assertFalse(task.isCancelled()); } - } - - /** - * If setExecuteExistingDelayedTasksAfterShutdownPolicy is false, - * delayed tasks are cancelled at shutdown - */ - public void testShutdown2() throws InterruptedException { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - p.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); - assertFalse(p.getExecuteExistingDelayedTasksAfterShutdownPolicy()); - assertFalse(p.getContinueExistingPeriodicTasksAfterShutdownPolicy()); - ScheduledFuture[] tasks = new ScheduledFuture[5]; - for (int i = 0; i < tasks.length; i++) - tasks[i] = p.schedule(new NoOpRunnable(), - SHORT_DELAY_MS, MILLISECONDS); - BlockingQueue q = p.getQueue(); - assertEquals(tasks.length, q.size()); - try { p.shutdown(); } catch (SecurityException ok) { return; } - assertTrue(p.isShutdown()); - assertTrue(q.isEmpty()); - assertTrue(p.awaitTermination(SMALL_DELAY_MS, MILLISECONDS)); + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); assertTrue(p.isTerminated()); - for (ScheduledFuture task : tasks) { - assertTrue(task.isDone()); - assertTrue(task.isCancelled()); - } } /** - * If setContinueExistingPeriodicTasksAfterShutdownPolicy is set false, - * periodic tasks are cancelled at shutdown + * By default, periodic tasks are cancelled at shutdown. + * By default, delayed tasks keep running after shutdown. + * Check that changing the default values work: + * - setExecuteExistingDelayedTasksAfterShutdownPolicy + * - setContinueExistingPeriodicTasksAfterShutdownPolicy */ - public void testShutdown3() throws InterruptedException { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - assertTrue(p.getExecuteExistingDelayedTasksAfterShutdownPolicy()); - assertFalse(p.getContinueExistingPeriodicTasksAfterShutdownPolicy()); - p.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); - assertTrue(p.getExecuteExistingDelayedTasksAfterShutdownPolicy()); - assertFalse(p.getContinueExistingPeriodicTasksAfterShutdownPolicy()); - long initialDelay = LONG_DELAY_MS; - ScheduledFuture task = - p.scheduleAtFixedRate(new NoOpRunnable(), initialDelay, - 5, MILLISECONDS); + public void testShutdown_cancellation() throws Exception { + Boolean[] allBooleans = { null, Boolean.FALSE, Boolean.TRUE }; + for (Boolean policy : allBooleans) + { + final int poolSize = 2; + final ScheduledThreadPoolExecutor p + = new ScheduledThreadPoolExecutor(poolSize); + final boolean effectiveDelayedPolicy = (policy != Boolean.FALSE); + final boolean effectivePeriodicPolicy = (policy == Boolean.TRUE); + final boolean effectiveRemovePolicy = (policy == Boolean.TRUE); + if (policy != null) { + p.setExecuteExistingDelayedTasksAfterShutdownPolicy(policy); + p.setContinueExistingPeriodicTasksAfterShutdownPolicy(policy); + p.setRemoveOnCancelPolicy(policy); + } + assertEquals(effectiveDelayedPolicy, + p.getExecuteExistingDelayedTasksAfterShutdownPolicy()); + assertEquals(effectivePeriodicPolicy, + p.getContinueExistingPeriodicTasksAfterShutdownPolicy()); + assertEquals(effectiveRemovePolicy, + p.getRemoveOnCancelPolicy()); + // Strategy: Wedge the pool with poolSize "blocker" threads + final AtomicInteger ran = new AtomicInteger(0); + final CountDownLatch poolBlocked = new CountDownLatch(poolSize); + final CountDownLatch unblock = new CountDownLatch(1); + final CountDownLatch periodicLatch1 = new CountDownLatch(2); + final CountDownLatch periodicLatch2 = new CountDownLatch(2); + Runnable task = new CheckedRunnable() { public void realRun() + throws InterruptedException { + poolBlocked.countDown(); + assertTrue(unblock.await(LONG_DELAY_MS, MILLISECONDS)); + ran.getAndIncrement(); + }}; + List> blockers = new ArrayList<>(); + List> periodics = new ArrayList<>(); + List> delayeds = new ArrayList<>(); + for (int i = 0; i < poolSize; i++) + blockers.add(p.submit(task)); + assertTrue(poolBlocked.await(LONG_DELAY_MS, MILLISECONDS)); + + periodics.add(p.scheduleAtFixedRate(countDowner(periodicLatch1), + 1, 1, MILLISECONDS)); + periodics.add(p.scheduleWithFixedDelay(countDowner(periodicLatch2), + 1, 1, MILLISECONDS)); + delayeds.add(p.schedule(task, 1, MILLISECONDS)); + + assertTrue(p.getQueue().containsAll(periodics)); + assertTrue(p.getQueue().containsAll(delayeds)); try { p.shutdown(); } catch (SecurityException ok) { return; } assertTrue(p.isShutdown()); - assertTrue(p.getQueue().isEmpty()); - assertTrue(task.isDone()); - assertTrue(task.isCancelled()); - joinPool(p); - } + assertFalse(p.isTerminated()); + for (Future periodic : periodics) { + assertTrue(effectivePeriodicPolicy ^ periodic.isCancelled()); + assertTrue(effectivePeriodicPolicy ^ periodic.isDone()); + } + for (Future delayed : delayeds) { + assertTrue(effectiveDelayedPolicy ^ delayed.isCancelled()); + assertTrue(effectiveDelayedPolicy ^ delayed.isDone()); + } + if (testImplementationDetails) { + assertEquals(effectivePeriodicPolicy, + p.getQueue().containsAll(periodics)); + assertEquals(effectiveDelayedPolicy, + p.getQueue().containsAll(delayeds)); + } + // Release all pool threads + unblock.countDown(); - /** - * if setContinueExistingPeriodicTasksAfterShutdownPolicy is true, - * periodic tasks are not cancelled at shutdown - */ - public void testShutdown4() throws InterruptedException { - ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); - final CountDownLatch counter = new CountDownLatch(2); - try { - p.setContinueExistingPeriodicTasksAfterShutdownPolicy(true); - assertTrue(p.getExecuteExistingDelayedTasksAfterShutdownPolicy()); - assertTrue(p.getContinueExistingPeriodicTasksAfterShutdownPolicy()); - final Runnable r = new CheckedRunnable() { - public void realRun() { - counter.countDown(); - }}; - ScheduledFuture task = - p.scheduleAtFixedRate(r, 1, 1, MILLISECONDS); - assertFalse(task.isDone()); - assertFalse(task.isCancelled()); - try { p.shutdown(); } catch (SecurityException ok) { return; } - assertFalse(task.isCancelled()); - assertFalse(p.isTerminated()); - assertTrue(p.isShutdown()); - assertTrue(counter.await(SMALL_DELAY_MS, MILLISECONDS)); - assertFalse(task.isCancelled()); - assertTrue(task.cancel(false)); - assertTrue(task.isDone()); - assertTrue(task.isCancelled()); - assertTrue(p.awaitTermination(SMALL_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); + for (Future delayed : delayeds) { + if (effectiveDelayedPolicy) { + assertNull(delayed.get()); + } } - finally { - joinPool(p); + if (effectivePeriodicPolicy) { + assertTrue(periodicLatch1.await(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(periodicLatch2.await(LONG_DELAY_MS, MILLISECONDS)); + for (Future periodic : periodics) { + assertTrue(periodic.cancel(false)); + assertTrue(periodic.isCancelled()); + assertTrue(periodic.isDone()); + } } - } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertEquals(2 + (effectiveDelayedPolicy ? 1 : 0), ran.get()); + }} /** * completed submit of callable returns result */ public void testSubmitCallable() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new StringTask()); String result = future.get(); assertSame(TEST_STRING, result); - } finally { - joinPool(e); } } @@ -805,13 +842,11 @@ public void testSubmitCallable() throws Exception { * completed submit of runnable returns successfully */ public void testSubmitRunnable() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new NoOpRunnable()); future.get(); assertTrue(future.isDone()); - } finally { - joinPool(e); } } @@ -819,13 +854,11 @@ public void testSubmitRunnable() throws Exception { * completed submit of (runnable, result) returns result */ public void testSubmitRunnable2() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new NoOpRunnable(), TEST_STRING); String result = future.get(); assertSame(TEST_STRING, result); - } finally { - joinPool(e); } } @@ -833,13 +866,12 @@ public void testSubmitRunnable2() throws Exception { * invokeAny(null) throws NPE */ public void testInvokeAny1() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { - e.invokeAny(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -847,13 +879,12 @@ public void testInvokeAny1() throws Exception { * invokeAny(empty collection) throws IAE */ public void testInvokeAny2() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { - e.invokeAny(new ArrayList>()); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - joinPool(e); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>()); + shouldThrow(); + } catch (IllegalArgumentException success) {} } } @@ -862,17 +893,16 @@ public void testInvokeAny2() throws Exception { */ public void testInvokeAny3() throws Exception { CountDownLatch latch = new CountDownLatch(1); - ExecutorService e = new ScheduledThreadPoolExecutor(2); - List> l = new ArrayList>(); - l.add(latchAwaitingStringTask(latch)); - l.add(null); - try { - e.invokeAny(l); - shouldThrow(); - } catch (NullPointerException success) { - } finally { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l); + shouldThrow(); + } catch (NullPointerException success) {} latch.countDown(); - joinPool(e); } } @@ -880,16 +910,16 @@ public void testInvokeAny3() throws Exception { * invokeAny(c) throws ExecutionException if no task completes */ public void testInvokeAny4() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - List> l = new ArrayList>(); - l.add(new NPETask()); - try { - e.invokeAny(l); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -897,15 +927,13 @@ public void testInvokeAny4() throws Exception { * invokeAny(c) returns result of some task */ public void testInvokeAny5() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); String result = e.invokeAny(l); assertSame(TEST_STRING, result); - } finally { - joinPool(e); } } @@ -913,13 +941,12 @@ public void testInvokeAny5() throws Exception { * invokeAll(null) throws NPE */ public void testInvokeAll1() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { - e.invokeAll(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -927,12 +954,10 @@ public void testInvokeAll1() throws Exception { * invokeAll(empty collection) returns empty collection */ public void testInvokeAll2() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { List> r = e.invokeAll(new ArrayList>()); assertTrue(r.isEmpty()); - } finally { - joinPool(e); } } @@ -940,16 +965,15 @@ public void testInvokeAll2() throws Exception { * invokeAll(c) throws NPE if c has null elements */ public void testInvokeAll3() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(null); - try { - e.invokeAll(l); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -957,18 +981,18 @@ public void testInvokeAll3() throws Exception { * get of invokeAll(c) throws exception on failed task */ public void testInvokeAll4() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - List> l = new ArrayList>(); - l.add(new NPETask()); - List> futures = e.invokeAll(l); - assertEquals(1, futures.size()); - try { - futures.get(0).get(); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = e.invokeAll(l); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -976,8 +1000,8 @@ public void testInvokeAll4() throws Exception { * invokeAll(c) returns results of all completed tasks */ public void testInvokeAll5() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); @@ -985,8 +1009,6 @@ public void testInvokeAll5() throws Exception { assertEquals(2, futures.size()); for (Future future : futures) assertSame(TEST_STRING, future.get()); - } finally { - joinPool(e); } } @@ -994,13 +1016,12 @@ public void testInvokeAll5() throws Exception { * timed invokeAny(null) throws NPE */ public void testTimedInvokeAny1() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { - e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1008,15 +1029,14 @@ public void testTimedInvokeAny1() throws Exception { * timed invokeAny(,,null) throws NPE */ public void testTimedInvokeAnyNullTimeUnit() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - List> l = new ArrayList>(); - l.add(new StringTask()); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1024,13 +1044,12 @@ public void testTimedInvokeAnyNullTimeUnit() throws Exception { * timed invokeAny(empty collection) throws IAE */ public void testTimedInvokeAny2() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { - e.invokeAny(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - joinPool(e); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} } } @@ -1039,17 +1058,16 @@ public void testTimedInvokeAny2() throws Exception { */ public void testTimedInvokeAny3() throws Exception { CountDownLatch latch = new CountDownLatch(1); - ExecutorService e = new ScheduledThreadPoolExecutor(2); - List> l = new ArrayList>(); - l.add(latchAwaitingStringTask(latch)); - l.add(null); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} latch.countDown(); - joinPool(e); } } @@ -1057,16 +1075,18 @@ public void testTimedInvokeAny3() throws Exception { * timed invokeAny(c) throws ExecutionException if no task completes */ public void testTimedInvokeAny4() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - List> l = new ArrayList>(); - l.add(new NPETask()); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } } @@ -1074,15 +1094,15 @@ public void testTimedInvokeAny4() throws Exception { * timed invokeAny(c) returns result of some task */ public void testTimedInvokeAny5() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); - String result = e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + String result = e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); assertSame(TEST_STRING, result); - } finally { - joinPool(e); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } } @@ -1090,13 +1110,12 @@ public void testTimedInvokeAny5() throws Exception { * timed invokeAll(null) throws NPE */ public void testTimedInvokeAll1() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { - e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1104,15 +1123,14 @@ public void testTimedInvokeAll1() throws Exception { * timed invokeAll(,,null) throws NPE */ public void testTimedInvokeAllNullTimeUnit() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - List> l = new ArrayList>(); - l.add(new StringTask()); - try { - e.invokeAll(l, MEDIUM_DELAY_MS, null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1120,12 +1138,11 @@ public void testTimedInvokeAllNullTimeUnit() throws Exception { * timed invokeAll(empty collection) returns empty collection */ public void testTimedInvokeAll2() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { - List> r = e.invokeAll(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> r = e.invokeAll(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); assertTrue(r.isEmpty()); - } finally { - joinPool(e); } } @@ -1133,16 +1150,15 @@ public void testTimedInvokeAll2() throws Exception { * timed invokeAll(c) throws NPE if c has null elements */ public void testTimedInvokeAll3() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(null); - try { - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1150,19 +1166,19 @@ public void testTimedInvokeAll3() throws Exception { * get of element of invokeAll(c) throws exception on failed task */ public void testTimedInvokeAll4() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - List> l = new ArrayList>(); - l.add(new NPETask()); - List> futures = - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); - assertEquals(1, futures.size()); - try { - futures.get(0).get(); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -1170,18 +1186,16 @@ public void testTimedInvokeAll4() throws Exception { * timed invokeAll(c) returns results of all completed tasks */ public void testTimedInvokeAll5() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); List> futures = - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); assertEquals(2, futures.size()); for (Future future : futures) assertSame(TEST_STRING, future.get()); - } finally { - joinPool(e); } } @@ -1189,21 +1203,60 @@ public void testTimedInvokeAll5() throws Exception { * timed invokeAll(c) cancels tasks not completed by timeout */ public void testTimedInvokeAll6() throws Exception { - ExecutorService e = new ScheduledThreadPoolExecutor(2); - try { - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(Executors.callable(new MediumPossiblyInterruptedRunnable(), TEST_STRING)); - l.add(new StringTask()); - List> futures = - e.invokeAll(l, SHORT_DELAY_MS, MILLISECONDS); - assertEquals(l.size(), futures.size()); - for (Future future : futures) - assertTrue(future.isDone()); - assertFalse(futures.get(0).isCancelled()); - assertTrue(futures.get(1).isCancelled()); - } finally { - joinPool(e); + for (long timeout = timeoutMillis();;) { + final CountDownLatch done = new CountDownLatch(1); + final Callable waiter = new CheckedCallable() { + public String realCall() { + try { done.await(LONG_DELAY_MS, MILLISECONDS); } + catch (InterruptedException ok) {} + return "1"; }}; + final ExecutorService p = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(p, done)) { + List> tasks = new ArrayList<>(); + tasks.add(new StringTask("0")); + tasks.add(waiter); + tasks.add(new StringTask("2")); + long startTime = System.nanoTime(); + List> futures = + p.invokeAll(tasks, timeout, MILLISECONDS); + assertEquals(tasks.size(), futures.size()); + assertTrue(millisElapsedSince(startTime) >= timeout); + for (Future future : futures) + assertTrue(future.isDone()); + assertTrue(futures.get(1).isCancelled()); + try { + assertEquals("0", futures.get(0).get()); + assertEquals("2", futures.get(2).get()); + break; + } catch (CancellationException retryWithLongerTimeout) { + timeout *= 2; + if (timeout >= LONG_DELAY_MS / 2) + fail("expected exactly one task to be cancelled"); + } + } + } + } + + /** + * A fixed delay task with overflowing period should not prevent a + * one-shot task from executing. + * https://bugs.openjdk.java.net/browse/JDK-8051859 + */ + public void testScheduleWithFixedDelay_overflow() throws Exception { + final CountDownLatch delayedDone = new CountDownLatch(1); + final CountDownLatch immediateDone = new CountDownLatch(1); + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final Runnable immediate = new Runnable() { public void run() { + immediateDone.countDown(); + }}; + final Runnable delayed = new Runnable() { public void run() { + delayedDone.countDown(); + p.submit(immediate); + }}; + p.scheduleWithFixedDelay(delayed, 0L, Long.MAX_VALUE, SECONDS); + await(delayedDone); + await(immediateDone); } } diff --git a/jsr166-tests/src/test/java/jsr166/SemaphoreTest.java b/jsr166-tests/src/test/java/jsr166/SemaphoreTest.java index db4f4b424..09c82c897 100644 --- a/jsr166-tests/src/test/java/jsr166/SemaphoreTest.java +++ b/jsr166-tests/src/test/java/jsr166/SemaphoreTest.java @@ -26,8 +26,9 @@ public class SemaphoreTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(SemaphoreTest.class); // } + /** * Subclass to expose protected methods */ @@ -471,11 +472,16 @@ public void testSerialization(boolean fair) { clone.release(); assertEquals(2, s.availablePermits()); assertEquals(1, clone.availablePermits()); + assertFalse(s.hasQueuedThreads()); + assertFalse(clone.hasQueuedThreads()); + } catch (InterruptedException e) { threadUnexpectedException(e); } - s = new Semaphore(0, fair); + { + PublicSemaphore s = new PublicSemaphore(0, fair); Thread t = newStartedThread(new InterruptibleLockRunnable(s)); - waitForQueuedThreads(s); - clone = serialClone(s); + // waitForQueuedThreads(s); // suffers from "flicker", so ... + waitForQueuedThread(s, t); // ... we use this instead + PublicSemaphore clone = serialClone(s); assertEquals(fair, s.isFair()); assertEquals(fair, clone.isFair()); assertEquals(0, s.availablePermits()); @@ -486,7 +492,7 @@ public void testSerialization(boolean fair) { awaitTermination(t); assertFalse(s.hasQueuedThreads()); assertFalse(clone.hasQueuedThreads()); - } catch (InterruptedException e) { threadUnexpectedException(e); } + } } /** @@ -594,7 +600,7 @@ public void realRun() throws InterruptedException { s.acquire(3); }}); - waitForQueuedThreads(s); + waitForQueuedThread(s, t1); Thread t2 = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { diff --git a/jsr166-tests/src/test/java/jsr166/StampedLockTest.java b/jsr166-tests/src/test/java/jsr166/StampedLockTest.java new file mode 100644 index 000000000..d347c7d67 --- /dev/null +++ b/jsr166-tests/src/test/java/jsr166/StampedLockTest.java @@ -0,0 +1,884 @@ +/* + * Written by Doug Lea and Martin Buchholz + * with assistance from members of JCP JSR-166 Expert Group and + * released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package jsr166; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.StampedLock; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class StampedLockTest extends JSR166TestCase { + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. + // + // public static void main(String[] args) { + // main(suite(), args); + // } + // public static Test suite() { + // return new TestSuite(StampedLockTest.class); + // } + + /** + * A runnable calling writeLockInterruptibly + */ + class InterruptibleLockRunnable extends CheckedRunnable { + final StampedLock lock; + InterruptibleLockRunnable(StampedLock l) { lock = l; } + public void realRun() throws InterruptedException { + lock.writeLockInterruptibly(); + } + } + + /** + * A runnable calling writeLockInterruptibly that expects to be + * interrupted + */ + class InterruptedLockRunnable extends CheckedInterruptedRunnable { + final StampedLock lock; + InterruptedLockRunnable(StampedLock l) { lock = l; } + public void realRun() throws InterruptedException { + lock.writeLockInterruptibly(); + } + } + + /** + * Releases write lock, checking isWriteLocked before and after + */ + void releaseWriteLock(StampedLock lock, long s) { + assertTrue(lock.isWriteLocked()); + lock.unlockWrite(s); + assertFalse(lock.isWriteLocked()); + } + + /** + * Constructed StampedLock is in unlocked state + */ + public void testConstructor() { + StampedLock lock; + lock = new StampedLock(); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + } + + /** + * write-locking and read-locking an unlocked lock succeed + */ + public void testLock() { + StampedLock lock = new StampedLock(); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + long s = lock.writeLock(); + assertTrue(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + lock.unlockWrite(s); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + long rs = lock.readLock(); + assertFalse(lock.isWriteLocked()); + assertTrue(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 1); + lock.unlockRead(rs); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + } + + /** + * unlock releases either a read or write lock + */ + public void testUnlock() { + StampedLock lock = new StampedLock(); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + long s = lock.writeLock(); + assertTrue(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + lock.unlock(s); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + long rs = lock.readLock(); + assertFalse(lock.isWriteLocked()); + assertTrue(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 1); + lock.unlock(rs); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + } + + /** + * tryUnlockRead/Write succeeds if locked in associated mode else + * returns false + */ + public void testTryUnlock() { + StampedLock lock = new StampedLock(); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + long s = lock.writeLock(); + assertTrue(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + assertFalse(lock.tryUnlockRead()); + assertTrue(lock.tryUnlockWrite()); + assertFalse(lock.tryUnlockWrite()); + assertFalse(lock.tryUnlockRead()); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + long rs = lock.readLock(); + assertFalse(lock.isWriteLocked()); + assertTrue(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 1); + assertFalse(lock.tryUnlockWrite()); + assertTrue(lock.tryUnlockRead()); + assertFalse(lock.tryUnlockRead()); + assertFalse(lock.tryUnlockWrite()); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + } + + /** + * write-unlocking an unlocked lock throws IllegalMonitorStateException + */ + public void testWriteUnlock_IMSE() { + StampedLock lock = new StampedLock(); + try { + lock.unlockWrite(0L); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * write-unlocking an unlocked lock throws IllegalMonitorStateException + */ + public void testWriteUnlock_IMSE2() { + StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + lock.unlockWrite(s); + try { + lock.unlockWrite(s); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * write-unlocking after readlock throws IllegalMonitorStateException + */ + public void testWriteUnlock_IMSE3() { + StampedLock lock = new StampedLock(); + long s = lock.readLock(); + try { + lock.unlockWrite(s); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * read-unlocking an unlocked lock throws IllegalMonitorStateException + */ + public void testReadUnlock_IMSE() { + StampedLock lock = new StampedLock(); + long s = lock.readLock(); + lock.unlockRead(s); + try { + lock.unlockRead(s); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * read-unlocking an unlocked lock throws IllegalMonitorStateException + */ + public void testReadUnlock_IMSE2() { + StampedLock lock = new StampedLock(); + try { + lock.unlockRead(0L); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * read-unlocking after writeLock throws IllegalMonitorStateException + */ + public void testReadUnlock_IMSE3() { + StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + try { + lock.unlockRead(s); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * validate(0) fails + */ + public void testValidate0() { + StampedLock lock = new StampedLock(); + assertFalse(lock.validate(0L)); + } + + /** + * A stamp obtained from a successful lock operation validates + */ + public void testValidate() throws InterruptedException { + StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + assertTrue(lock.validate(s)); + lock.unlockWrite(s); + s = lock.readLock(); + assertTrue(lock.validate(s)); + lock.unlockRead(s); + assertTrue((s = lock.tryWriteLock()) != 0L); + assertTrue(lock.validate(s)); + lock.unlockWrite(s); + assertTrue((s = lock.tryReadLock()) != 0L); + assertTrue(lock.validate(s)); + lock.unlockRead(s); + assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); + assertTrue(lock.validate(s)); + lock.unlockWrite(s); + assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); + assertTrue(lock.validate(s)); + lock.unlockRead(s); + assertTrue((s = lock.tryOptimisticRead()) != 0L); + } + + /** + * A stamp obtained from an unsuccessful lock operation does not validate + */ + public void testValidate2() throws InterruptedException { + StampedLock lock = new StampedLock(); + long s; + assertTrue((s = lock.writeLock()) != 0L); + assertTrue(lock.validate(s)); + assertFalse(lock.validate(lock.tryWriteLock())); + assertFalse(lock.validate(lock.tryWriteLock(10L, MILLISECONDS))); + assertFalse(lock.validate(lock.tryReadLock())); + assertFalse(lock.validate(lock.tryReadLock(10L, MILLISECONDS))); + assertFalse(lock.validate(lock.tryOptimisticRead())); + lock.unlockWrite(s); + } + + /** + * writeLockInterruptibly is interruptible + */ + public void testWriteLockInterruptibly_Interruptible() + throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + running.countDown(); + lock.writeLockInterruptibly(); + }}); + + running.await(); + waitForThreadToEnterWaitState(t, 100); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * timed tryWriteLock is interruptible + */ + public void testWriteTryLock_Interruptible() throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + running.countDown(); + lock.tryWriteLock(2 * LONG_DELAY_MS, MILLISECONDS); + }}); + + running.await(); + waitForThreadToEnterWaitState(t, 100); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * readLockInterruptibly is interruptible + */ + public void testReadLockInterruptibly_Interruptible() + throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + running.countDown(); + lock.readLockInterruptibly(); + }}); + + running.await(); + waitForThreadToEnterWaitState(t, 100); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * timed tryReadLock is interruptible + */ + public void testReadTryLock_Interruptible() throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + running.countDown(); + lock.tryReadLock(2 * LONG_DELAY_MS, MILLISECONDS); + }}); + + running.await(); + waitForThreadToEnterWaitState(t, 100); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * tryWriteLock on an unlocked lock succeeds + */ + public void testWriteTryLock() { + final StampedLock lock = new StampedLock(); + long s = lock.tryWriteLock(); + assertTrue(s != 0L); + assertTrue(lock.isWriteLocked()); + long s2 = lock.tryWriteLock(); + assertEquals(s2, 0L); + releaseWriteLock(lock, s); + } + + /** + * tryWriteLock fails if locked + */ + public void testWriteTryLockWhenLocked() { + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + long ws = lock.tryWriteLock(); + assertTrue(ws == 0L); + }}); + + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * tryReadLock fails if write-locked + */ + public void testReadTryLockWhenLocked() { + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + long rs = lock.tryReadLock(); + assertEquals(rs, 0L); + }}); + + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * Multiple threads can hold a read lock when not write-locked + */ + public void testMultipleReadLocks() { + final StampedLock lock = new StampedLock(); + final long s = lock.readLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long s2 = lock.tryReadLock(); + assertTrue(s2 != 0L); + lock.unlockRead(s2); + long s3 = lock.tryReadLock(LONG_DELAY_MS, MILLISECONDS); + assertTrue(s3 != 0L); + lock.unlockRead(s3); + long s4 = lock.readLock(); + lock.unlockRead(s4); + }}); + + awaitTermination(t); + lock.unlockRead(s); + } + + /** + * A writelock succeeds only after a reading thread unlocks + */ + public void testWriteAfterReadLock() throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long rs = lock.readLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + running.countDown(); + long s = lock.writeLock(); + lock.unlockWrite(s); + }}); + + running.await(); + waitForThreadToEnterWaitState(t, 100); + assertFalse(lock.isWriteLocked()); + lock.unlockRead(rs); + awaitTermination(t); + assertFalse(lock.isWriteLocked()); + } + + /** + * A writelock succeeds only after reading threads unlock + */ + public void testWriteAfterMultipleReadLocks() { + final StampedLock lock = new StampedLock(); + long s = lock.readLock(); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() { + long rs = lock.readLock(); + lock.unlockRead(rs); + }}); + + awaitTermination(t1); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() { + long ws = lock.writeLock(); + lock.unlockWrite(ws); + }}); + + assertFalse(lock.isWriteLocked()); + lock.unlockRead(s); + awaitTermination(t2); + assertFalse(lock.isWriteLocked()); + } + + /** + * Readlocks succeed only after a writing thread unlocks + */ + public void testReadAfterWriteLock() { + final StampedLock lock = new StampedLock(); + final long s = lock.writeLock(); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() { + long rs = lock.readLock(); + lock.unlockRead(rs); + }}); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() { + long rs = lock.readLock(); + lock.unlockRead(rs); + }}); + + releaseWriteLock(lock, s); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * tryReadLock succeeds if readlocked but not writelocked + */ + public void testTryLockWhenReadLocked() { + final StampedLock lock = new StampedLock(); + long s = lock.readLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + long rs = lock.tryReadLock(); + threadAssertTrue(rs != 0L); + lock.unlockRead(rs); + }}); + + awaitTermination(t); + lock.unlockRead(s); + } + + /** + * tryWriteLock fails when readlocked + */ + public void testWriteTryLockWhenReadLocked() { + final StampedLock lock = new StampedLock(); + long s = lock.readLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + long ws = lock.tryWriteLock(); + threadAssertEquals(ws, 0L); + }}); + + awaitTermination(t); + lock.unlockRead(s); + } + + /** + * timed tryWriteLock times out if locked + */ + public void testWriteTryLock_Timeout() { + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + long timeoutMillis = 10; + long ws = lock.tryWriteLock(timeoutMillis, MILLISECONDS); + assertEquals(ws, 0L); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + }}); + + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * timed tryReadLock times out if write-locked + */ + public void testReadTryLock_Timeout() { + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + long timeoutMillis = 10; + long rs = lock.tryReadLock(timeoutMillis, MILLISECONDS); + assertEquals(rs, 0L); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + }}); + + awaitTermination(t); + assertTrue(lock.isWriteLocked()); + lock.unlockWrite(s); + } + + /** + * writeLockInterruptibly succeeds if unlocked, else is interruptible + */ + public void testWriteLockInterruptibly() throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long s = lock.writeLockInterruptibly(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + running.countDown(); + lock.writeLockInterruptibly(); + }}); + + running.await(); + waitForThreadToEnterWaitState(t, 100); + t.interrupt(); + assertTrue(lock.isWriteLocked()); + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * readLockInterruptibly succeeds if lock free else is interruptible + */ + public void testReadLockInterruptibly() throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long s; + s = lock.readLockInterruptibly(); + lock.unlockRead(s); + s = lock.writeLockInterruptibly(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + running.countDown(); + lock.readLockInterruptibly(); + }}); + + running.await(); + waitForThreadToEnterWaitState(t, 100); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * A serialized lock deserializes as unlocked + */ + public void testSerialization() { + StampedLock lock = new StampedLock(); + lock.writeLock(); + StampedLock clone = serialClone(lock); + assertTrue(lock.isWriteLocked()); + assertFalse(clone.isWriteLocked()); + long s = clone.writeLock(); + assertTrue(clone.isWriteLocked()); + clone.unlockWrite(s); + assertFalse(clone.isWriteLocked()); + } + + /** + * toString indicates current lock state + */ + public void testToString() { + StampedLock lock = new StampedLock(); + assertTrue(lock.toString().contains("Unlocked")); + long s = lock.writeLock(); + assertTrue(lock.toString().contains("Write-locked")); + lock.unlockWrite(s); + s = lock.readLock(); + assertTrue(lock.toString().contains("Read-locks")); + } + + /** + * tryOptimisticRead succeeds and validates if unlocked, fails if locked + */ + public void testValidateOptimistic() throws InterruptedException { + StampedLock lock = new StampedLock(); + long s, p; + assertTrue((p = lock.tryOptimisticRead()) != 0L); + assertTrue(lock.validate(p)); + assertTrue((s = lock.writeLock()) != 0L); + assertFalse((p = lock.tryOptimisticRead()) != 0L); + assertTrue(lock.validate(s)); + lock.unlockWrite(s); + assertTrue((p = lock.tryOptimisticRead()) != 0L); + assertTrue(lock.validate(p)); + assertTrue((s = lock.readLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryOptimisticRead()) != 0L); + assertTrue(lock.validate(p)); + lock.unlockRead(s); + assertTrue((s = lock.tryWriteLock()) != 0L); + assertTrue(lock.validate(s)); + assertFalse((p = lock.tryOptimisticRead()) != 0L); + lock.unlockWrite(s); + assertTrue((s = lock.tryReadLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryOptimisticRead()) != 0L); + lock.unlockRead(s); + assertTrue(lock.validate(p)); + assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); + assertFalse((p = lock.tryOptimisticRead()) != 0L); + assertTrue(lock.validate(s)); + lock.unlockWrite(s); + assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryOptimisticRead()) != 0L); + lock.unlockRead(s); + assertTrue((p = lock.tryOptimisticRead()) != 0L); + } + + /** + * tryOptimisticRead stamp does not validate if a write lock intervenes + */ + public void testValidateOptimisticWriteLocked() { + StampedLock lock = new StampedLock(); + long s, p; + assertTrue((p = lock.tryOptimisticRead()) != 0L); + assertTrue((s = lock.writeLock()) != 0L); + assertFalse(lock.validate(p)); + assertFalse((p = lock.tryOptimisticRead()) != 0L); + assertTrue(lock.validate(s)); + lock.unlockWrite(s); + } + + /** + * tryOptimisticRead stamp does not validate if a write lock + * intervenes in another thread + */ + public void testValidateOptimisticWriteLocked2() + throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long s, p; + assertTrue((p = lock.tryOptimisticRead()) != 0L); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + lock.writeLockInterruptibly(); + running.countDown(); + lock.writeLockInterruptibly(); + }}); + + running.await(); + assertFalse(lock.validate(p)); + assertFalse((p = lock.tryOptimisticRead()) != 0L); + t.interrupt(); + awaitTermination(t); + } + + /** + * tryConvertToOptimisticRead succeeds and validates if successfully locked, + */ + public void testTryConvertToOptimisticRead() throws InterruptedException { + StampedLock lock = new StampedLock(); + long s, p; + s = 0L; + assertFalse((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue((s = lock.tryOptimisticRead()) != 0L); + assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue((s = lock.writeLock()) != 0L); + assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue(lock.validate(p)); + assertTrue((s = lock.readLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue(lock.validate(p)); + assertTrue((s = lock.tryWriteLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue(lock.validate(p)); + assertTrue((s = lock.tryReadLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue(lock.validate(p)); + assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); + assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue(lock.validate(p)); + assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue(lock.validate(p)); + } + + /** + * tryConvertToReadLock succeeds and validates if successfully locked + * or lock free; + */ + public void testTryConvertToReadLock() throws InterruptedException { + StampedLock lock = new StampedLock(); + long s, p; + s = 0L; + assertFalse((p = lock.tryConvertToReadLock(s)) != 0L); + assertTrue((s = lock.tryOptimisticRead()) != 0L); + assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); + lock.unlockRead(p); + assertTrue((s = lock.writeLock()) != 0L); + assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockRead(p); + assertTrue((s = lock.readLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockRead(p); + assertTrue((s = lock.tryWriteLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockRead(p); + assertTrue((s = lock.tryReadLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockRead(p); + assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); + assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockRead(p); + assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockRead(p); + } + + /** + * tryConvertToWriteLock succeeds and validates if successfully locked + * or lock free; + */ + public void testTryConvertToWriteLock() throws InterruptedException { + StampedLock lock = new StampedLock(); + long s, p; + s = 0L; + assertFalse((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue((s = lock.tryOptimisticRead()) != 0L); + assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + lock.unlockWrite(p); + assertTrue((s = lock.writeLock()) != 0L); + assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockWrite(p); + assertTrue((s = lock.readLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockWrite(p); + assertTrue((s = lock.tryWriteLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockWrite(p); + assertTrue((s = lock.tryReadLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockWrite(p); + assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); + assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockWrite(p); + assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockWrite(p); + } + + /** + * asWriteLock can be locked and unlocked + */ + public void testAsWriteLock() { + StampedLock sl = new StampedLock(); + Lock lock = sl.asWriteLock(); + lock.lock(); + assertFalse(lock.tryLock()); + lock.unlock(); + assertTrue(lock.tryLock()); + } + + /** + * asReadLock can be locked and unlocked + */ + public void testAsReadLock() { + StampedLock sl = new StampedLock(); + Lock lock = sl.asReadLock(); + lock.lock(); + lock.unlock(); + assertTrue(lock.tryLock()); + } + + /** + * asReadWriteLock.writeLock can be locked and unlocked + */ + public void testAsReadWriteLockWriteLock() { + StampedLock sl = new StampedLock(); + Lock lock = sl.asReadWriteLock().writeLock(); + lock.lock(); + assertFalse(lock.tryLock()); + lock.unlock(); + assertTrue(lock.tryLock()); + } + + /** + * asReadWriteLock.readLock can be locked and unlocked + */ + public void testAsReadWriteLockReadLock() { + StampedLock sl = new StampedLock(); + Lock lock = sl.asReadWriteLock().readLock(); + lock.lock(); + lock.unlock(); + assertTrue(lock.tryLock()); + } + +} diff --git a/jsr166-tests/src/test/java/jsr166/SynchronousQueueTest.java b/jsr166-tests/src/test/java/jsr166/SynchronousQueueTest.java index 605a95595..9d3f21258 100644 --- a/jsr166-tests/src/test/java/jsr166/SynchronousQueueTest.java +++ b/jsr166-tests/src/test/java/jsr166/SynchronousQueueTest.java @@ -25,7 +25,7 @@ public class SynchronousQueueTest extends JSR166TestCase { - // android-note: These tests have been moved into their own separate + // android-note: These tests have been moved into their own separate // classes to work around CTS issues. // // public static class Fair extends BlockingQueueTest { @@ -33,17 +33,19 @@ public class SynchronousQueueTest extends JSR166TestCase { // return new SynchronousQueue(true); // } // } - // + // public static class NonFair extends BlockingQueueTest { // protected BlockingQueue emptyCollection() { // return new SynchronousQueue(false); // } // } + + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. // // public static void main(String[] args) { // main(suite(), args); // } - // // public static Test suite() { // return newTestSuite(SynchronousQueueTest.class, // new Fair().testSuite(), @@ -262,7 +264,6 @@ public void realRun() throws InterruptedException { pleaseOffer.countDown(); startTime = System.nanoTime(); assertSame(zero, q.poll(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(millisElapsedSince(startTime) < MEDIUM_DELAY_MS); Thread.currentThread().interrupt(); try { @@ -277,13 +278,15 @@ public void realRun() throws InterruptedException { shouldThrow(); } catch (InterruptedException success) {} assertFalse(Thread.interrupted()); + + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); }}); await(pleaseOffer); long startTime = System.nanoTime(); try { assertTrue(q.offer(zero, LONG_DELAY_MS, MILLISECONDS)); } catch (InterruptedException e) { threadUnexpectedException(e); } - assertTrue(millisElapsedSince(startTime) < MEDIUM_DELAY_MS); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); await(pleaseInterrupt); assertThreadStaysAlive(t); @@ -474,24 +477,24 @@ public void testToString(boolean fair) { public void testOfferInExecutor_fair() { testOfferInExecutor(true); } public void testOfferInExecutor(boolean fair) { final SynchronousQueue q = new SynchronousQueue(fair); - ExecutorService executor = Executors.newFixedThreadPool(2); final CheckedBarrier threadsStarted = new CheckedBarrier(2); - - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - assertFalse(q.offer(one)); - threadsStarted.await(); - assertTrue(q.offer(one, LONG_DELAY_MS, MILLISECONDS)); - assertEquals(0, q.remainingCapacity()); - }}); - - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - threadsStarted.await(); - assertSame(one, q.take()); - }}); - - joinPool(executor); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(q.offer(one)); + threadsStarted.await(); + assertTrue(q.offer(one, LONG_DELAY_MS, MILLISECONDS)); + assertEquals(0, q.remainingCapacity()); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + assertSame(one, q.take()); + }}); + } } /** @@ -502,22 +505,22 @@ public void realRun() throws InterruptedException { public void testPollInExecutor(boolean fair) { final SynchronousQueue q = new SynchronousQueue(fair); final CheckedBarrier threadsStarted = new CheckedBarrier(2); - ExecutorService executor = Executors.newFixedThreadPool(2); - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - assertNull(q.poll()); - threadsStarted.await(); - assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(q.isEmpty()); - }}); - - executor.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { - threadsStarted.await(); - q.put(one); - }}); - - joinPool(executor); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertNull(q.poll()); + threadsStarted.await(); + assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(q.isEmpty()); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + q.put(one); + }}); + } } /** @@ -595,10 +598,12 @@ public void realRun() throws InterruptedException { }}); ArrayList l = new ArrayList(); - delay(SHORT_DELAY_MS); - q.drainTo(l, 1); + int drained; + while ((drained = q.drainTo(l, 1)) == 0) Thread.yield(); + assertEquals(1, drained); assertEquals(1, l.size()); - q.drainTo(l, 1); + while ((drained = q.drainTo(l, 1)) == 0) Thread.yield(); + assertEquals(1, drained); assertEquals(2, l.size()); assertTrue(l.contains(one)); assertTrue(l.contains(two)); diff --git a/jsr166-tests/src/test/java/jsr166/SystemTest.java b/jsr166-tests/src/test/java/jsr166/SystemTest.java index 69183746a..412ce17b8 100644 --- a/jsr166-tests/src/test/java/jsr166/SystemTest.java +++ b/jsr166-tests/src/test/java/jsr166/SystemTest.java @@ -19,7 +19,7 @@ public class SystemTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(SystemTest.class); // } /** diff --git a/jsr166-tests/src/test/java/jsr166/ThreadLocalRandom8Test.java b/jsr166-tests/src/test/java/jsr166/ThreadLocalRandom8Test.java new file mode 100644 index 000000000..614af8372 --- /dev/null +++ b/jsr166-tests/src/test/java/jsr166/ThreadLocalRandom8Test.java @@ -0,0 +1,241 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package jsr166; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.LongAdder; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ThreadLocalRandom8Test extends JSR166TestCase { + + // android-note: Removed because the CTS runner does a bad job of + // retrying tests that have suite() declarations. + // + // public static void main(String[] args) { + // main(suite(), args); + // } + // public static Test suite() { + // return new TestSuite(ThreadLocalRandom8Test.class); + // } + + // max sampled int bound + static final int MAX_INT_BOUND = (1 << 26); + + // max sampled long bound + static final long MAX_LONG_BOUND = (1L << 42); + + // Number of replications for other checks + static final int REPS = + Integer.getInteger("ThreadLocalRandom8Test.reps", 4); + + /** + * Invoking sized ints, long, doubles, with negative sizes throws + * IllegalArgumentException + */ + // TODO(streams): + // public void testBadStreamSize() { + // ThreadLocalRandom r = ThreadLocalRandom.current(); + // Runnable[] throwingActions = { + // () -> r.ints(-1L), + // () -> r.ints(-1L, 2, 3), + // () -> r.longs(-1L), + // () -> r.longs(-1L, -1L, 1L), + // () -> r.doubles(-1L), + // () -> r.doubles(-1L, .5, .6), + // }; + // assertThrows(IllegalArgumentException.class, throwingActions); + // } + + // /** + // * Invoking bounded ints, long, doubles, with illegal bounds throws + // * IllegalArgumentException + // */ + // public void testBadStreamBounds() { + // ThreadLocalRandom r = ThreadLocalRandom.current(); + // Runnable[] throwingActions = { + // () -> r.ints(2, 1), + // () -> r.ints(10, 42, 42), + // () -> r.longs(-1L, -1L), + // () -> r.longs(10, 1L, -2L), + // () -> r.doubles(0.0, 0.0), + // () -> r.doubles(10, .5, .4), + // }; + // assertThrows(IllegalArgumentException.class, throwingActions); + // } + + // /** + // * A parallel sized stream of ints generates the given number of values + // */ + // public void testIntsCount() { + // LongAdder counter = new LongAdder(); + // ThreadLocalRandom r = ThreadLocalRandom.current(); + // long size = 0; + // for (int reps = 0; reps < REPS; ++reps) { + // counter.reset(); + // r.ints(size).parallel().forEach(x -> counter.increment()); + // assertEquals(size, counter.sum()); + // size += 524959; + // } + // } + + // /** + // * A parallel sized stream of longs generates the given number of values + // */ + // public void testLongsCount() { + // LongAdder counter = new LongAdder(); + // ThreadLocalRandom r = ThreadLocalRandom.current(); + // long size = 0; + // for (int reps = 0; reps < REPS; ++reps) { + // counter.reset(); + // r.longs(size).parallel().forEach(x -> counter.increment()); + // assertEquals(size, counter.sum()); + // size += 524959; + // } + // } + + // /** + // * A parallel sized stream of doubles generates the given number of values + // */ + // public void testDoublesCount() { + // LongAdder counter = new LongAdder(); + // ThreadLocalRandom r = ThreadLocalRandom.current(); + // long size = 0; + // for (int reps = 0; reps < REPS; ++reps) { + // counter.reset(); + // r.doubles(size).parallel().forEach(x -> counter.increment()); + // assertEquals(size, counter.sum()); + // size += 524959; + // } + // } + + // /** + // * Each of a parallel sized stream of bounded ints is within bounds + // */ + // public void testBoundedInts() { + // AtomicInteger fails = new AtomicInteger(0); + // ThreadLocalRandom r = ThreadLocalRandom.current(); + // long size = 12345L; + // for (int least = -15485867; least < MAX_INT_BOUND; least += 524959) { + // for (int bound = least + 2; bound > least && bound < MAX_INT_BOUND; bound += 67867967) { + // final int lo = least, hi = bound; + // r.ints(size, lo, hi).parallel().forEach( + // x -> { + // if (x < lo || x >= hi) + // fails.getAndIncrement(); }); + // } + // } + // assertEquals(0, fails.get()); + // } + + // /** + // * Each of a parallel sized stream of bounded longs is within bounds + // */ + // public void testBoundedLongs() { + // AtomicInteger fails = new AtomicInteger(0); + // ThreadLocalRandom r = ThreadLocalRandom.current(); + // long size = 123L; + // for (long least = -86028121; least < MAX_LONG_BOUND; least += 1982451653L) { + // for (long bound = least + 2; bound > least && bound < MAX_LONG_BOUND; bound += Math.abs(bound * 7919)) { + // final long lo = least, hi = bound; + // r.longs(size, lo, hi).parallel().forEach( + // x -> { + // if (x < lo || x >= hi) + // fails.getAndIncrement(); }); + // } + // } + // assertEquals(0, fails.get()); + // } + + // /** + // * Each of a parallel sized stream of bounded doubles is within bounds + // */ + // public void testBoundedDoubles() { + // AtomicInteger fails = new AtomicInteger(0); + // ThreadLocalRandom r = ThreadLocalRandom.current(); + // long size = 456; + // for (double least = 0.00011; least < 1.0e20; least *= 9) { + // for (double bound = least * 1.0011; bound < 1.0e20; bound *= 17) { + // final double lo = least, hi = bound; + // r.doubles(size, lo, hi).parallel().forEach( + // x -> { + // if (x < lo || x >= hi) + // fails.getAndIncrement(); }); + // } + // } + // assertEquals(0, fails.get()); + // } + + // /** + // * A parallel unsized stream of ints generates at least 100 values + // */ + // public void testUnsizedIntsCount() { + // LongAdder counter = new LongAdder(); + // ThreadLocalRandom r = ThreadLocalRandom.current(); + // long size = 100; + // r.ints().limit(size).parallel().forEach(x -> counter.increment()); + // assertEquals(size, counter.sum()); + // } + + // /** + // * A parallel unsized stream of longs generates at least 100 values + // */ + // public void testUnsizedLongsCount() { + // LongAdder counter = new LongAdder(); + // ThreadLocalRandom r = ThreadLocalRandom.current(); + // long size = 100; + // r.longs().limit(size).parallel().forEach(x -> counter.increment()); + // assertEquals(size, counter.sum()); + // } + + // /** + // * A parallel unsized stream of doubles generates at least 100 values + // */ + // public void testUnsizedDoublesCount() { + // LongAdder counter = new LongAdder(); + // ThreadLocalRandom r = ThreadLocalRandom.current(); + // long size = 100; + // r.doubles().limit(size).parallel().forEach(x -> counter.increment()); + // assertEquals(size, counter.sum()); + // } + + // /** + // * A sequential unsized stream of ints generates at least 100 values + // */ + // public void testUnsizedIntsCountSeq() { + // LongAdder counter = new LongAdder(); + // ThreadLocalRandom r = ThreadLocalRandom.current(); + // long size = 100; + // r.ints().limit(size).forEach(x -> counter.increment()); + // assertEquals(size, counter.sum()); + // } + + // /** + // * A sequential unsized stream of longs generates at least 100 values + // */ + // public void testUnsizedLongsCountSeq() { + // LongAdder counter = new LongAdder(); + // ThreadLocalRandom r = ThreadLocalRandom.current(); + // long size = 100; + // r.longs().limit(size).forEach(x -> counter.increment()); + // assertEquals(size, counter.sum()); + // } + + // /** + // * A sequential unsized stream of doubles generates at least 100 values + // */ + // public void testUnsizedDoublesCountSeq() { + // LongAdder counter = new LongAdder(); + // ThreadLocalRandom r = ThreadLocalRandom.current(); + // long size = 100; + // r.doubles().limit(size).forEach(x -> counter.increment()); + // assertEquals(size, counter.sum()); + // } + +} diff --git a/jsr166-tests/src/test/java/jsr166/ThreadLocalRandomTest.java b/jsr166-tests/src/test/java/jsr166/ThreadLocalRandomTest.java index 4ae141dcc..5d9f8943e 100644 --- a/jsr166-tests/src/test/java/jsr166/ThreadLocalRandomTest.java +++ b/jsr166-tests/src/test/java/jsr166/ThreadLocalRandomTest.java @@ -22,7 +22,7 @@ public class ThreadLocalRandomTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ThreadLocalRandomTest.class); // } /* diff --git a/jsr166-tests/src/test/java/jsr166/ThreadLocalTest.java b/jsr166-tests/src/test/java/jsr166/ThreadLocalTest.java index 7f5f0724d..8bfcf7076 100644 --- a/jsr166-tests/src/test/java/jsr166/ThreadLocalTest.java +++ b/jsr166-tests/src/test/java/jsr166/ThreadLocalTest.java @@ -19,7 +19,7 @@ public class ThreadLocalTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ThreadLocalTest.class); // } static ThreadLocal tl = new ThreadLocal() { diff --git a/jsr166-tests/src/test/java/jsr166/ThreadPoolExecutorSubclassTest.java b/jsr166-tests/src/test/java/jsr166/ThreadPoolExecutorSubclassTest.java index 5f38d39c4..a502392dc 100644 --- a/jsr166-tests/src/test/java/jsr166/ThreadPoolExecutorSubclassTest.java +++ b/jsr166-tests/src/test/java/jsr166/ThreadPoolExecutorSubclassTest.java @@ -9,12 +9,14 @@ package jsr166; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; @@ -30,6 +32,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; @@ -44,7 +47,7 @@ public class ThreadPoolExecutorSubclassTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ThreadPoolExecutorSubclassTest.class); // } static class CustomTask implements RunnableFuture { @@ -103,11 +106,13 @@ public void run() { } lock.lock(); try { - result = v; - exception = e; - done = true; - thread = null; - cond.signalAll(); + if (!done) { + result = v; + exception = e; + done = true; + thread = null; + cond.signalAll(); + } } finally { lock.unlock(); } } @@ -116,6 +121,8 @@ public V get() throws InterruptedException, ExecutionException { try { while (!done) cond.await(); + if (cancelled) + throw new CancellationException(); if (exception != null) throw new ExecutionException(exception); return result; @@ -127,12 +134,13 @@ public V get(long timeout, TimeUnit unit) long nanos = unit.toNanos(timeout); lock.lock(); try { - for (;;) { - if (done) break; - if (nanos < 0) + while (!done) { + if (nanos <= 0L) throw new TimeoutException(); nanos = cond.awaitNanos(nanos); } + if (cancelled) + throw new CancellationException(); if (exception != null) throw new ExecutionException(exception); return result; @@ -229,18 +237,14 @@ public Thread newThread(Runnable r) { public void testExecute() throws InterruptedException { final ThreadPoolExecutor p = new CustomTPE(1, 1, - LONG_DELAY_MS, MILLISECONDS, + 2 * LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch done = new CountDownLatch(1); - final Runnable task = new CheckedRunnable() { - public void realRun() { - done.countDown(); - }}; - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch done = new CountDownLatch(1); + final Runnable task = new CheckedRunnable() { + public void realRun() { done.countDown(); }}; p.execute(task); - assertTrue(done.await(SMALL_DELAY_MS, MILLISECONDS)); - } finally { - joinPool(p); + assertTrue(done.await(LONG_DELAY_MS, MILLISECONDS)); } } @@ -249,25 +253,22 @@ public void realRun() { * thread becomes active */ public void testGetActiveCount() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); final ThreadPoolExecutor p = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); assertEquals(0, p.getActiveCount()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); assertEquals(1, p.getActiveCount()); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertEquals(1, p.getActiveCount()); - } finally { - done.countDown(); - joinPool(p); } } @@ -275,28 +276,48 @@ public void realRun() throws InterruptedException { * prestartCoreThread starts a thread if under corePoolSize, else doesn't */ public void testPrestartCoreThread() { - ThreadPoolExecutor p = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - assertEquals(0, p.getPoolSize()); - assertTrue(p.prestartCoreThread()); - assertEquals(1, p.getPoolSize()); - assertTrue(p.prestartCoreThread()); - assertEquals(2, p.getPoolSize()); - assertFalse(p.prestartCoreThread()); - assertEquals(2, p.getPoolSize()); - joinPool(p); + final ThreadPoolExecutor p = + new CustomTPE(2, 6, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(0, p.getPoolSize()); + assertTrue(p.prestartCoreThread()); + assertEquals(1, p.getPoolSize()); + assertTrue(p.prestartCoreThread()); + assertEquals(2, p.getPoolSize()); + assertFalse(p.prestartCoreThread()); + assertEquals(2, p.getPoolSize()); + p.setCorePoolSize(4); + assertTrue(p.prestartCoreThread()); + assertEquals(3, p.getPoolSize()); + assertTrue(p.prestartCoreThread()); + assertEquals(4, p.getPoolSize()); + assertFalse(p.prestartCoreThread()); + assertEquals(4, p.getPoolSize()); + } } /** * prestartAllCoreThreads starts all corePoolSize threads */ public void testPrestartAllCoreThreads() { - ThreadPoolExecutor p = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - assertEquals(0, p.getPoolSize()); - p.prestartAllCoreThreads(); - assertEquals(2, p.getPoolSize()); - p.prestartAllCoreThreads(); - assertEquals(2, p.getPoolSize()); - joinPool(p); + final ThreadPoolExecutor p = + new CustomTPE(2, 6, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(0, p.getPoolSize()); + p.prestartAllCoreThreads(); + assertEquals(2, p.getPoolSize()); + p.prestartAllCoreThreads(); + assertEquals(2, p.getPoolSize()); + p.setCorePoolSize(4); + p.prestartAllCoreThreads(); + assertEquals(4, p.getPoolSize()); + p.prestartAllCoreThreads(); + assertEquals(4, p.getPoolSize()); + } } /** @@ -308,10 +329,10 @@ public void testGetCompletedTaskCount() throws InterruptedException { new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch threadProceed = new CountDownLatch(1); - final CountDownLatch threadDone = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch threadProceed = new CountDownLatch(1); + final CountDownLatch threadDone = new CountDownLatch(1); assertEquals(0, p.getCompletedTaskCount()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { @@ -330,8 +351,6 @@ public void realRun() throws InterruptedException { fail("timed out"); Thread.yield(); } - } finally { - joinPool(p); } } @@ -339,52 +358,72 @@ public void realRun() throws InterruptedException { * getCorePoolSize returns size given in constructor if not otherwise set */ public void testGetCorePoolSize() { - ThreadPoolExecutor p = new CustomTPE(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - assertEquals(1, p.getCorePoolSize()); - joinPool(p); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(1, p.getCorePoolSize()); + } } /** * getKeepAliveTime returns value given in constructor if not otherwise set */ public void testGetKeepAliveTime() { - ThreadPoolExecutor p = new CustomTPE(2, 2, 1000, MILLISECONDS, new ArrayBlockingQueue(10)); - assertEquals(1, p.getKeepAliveTime(TimeUnit.SECONDS)); - joinPool(p); + final ThreadPoolExecutor p = + new CustomTPE(2, 2, + 1000, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(1, p.getKeepAliveTime(SECONDS)); + } } /** * getThreadFactory returns factory in constructor if not set */ public void testGetThreadFactory() { - ThreadFactory tf = new SimpleThreadFactory(); - ThreadPoolExecutor p = new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10), tf, new NoOpREHandler()); - assertSame(tf, p.getThreadFactory()); - joinPool(p); + final ThreadFactory threadFactory = new SimpleThreadFactory(); + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10), + threadFactory, + new NoOpREHandler()); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(threadFactory, p.getThreadFactory()); + } } /** * setThreadFactory sets the thread factory returned by getThreadFactory */ public void testSetThreadFactory() { - ThreadPoolExecutor p = new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - ThreadFactory tf = new SimpleThreadFactory(); - p.setThreadFactory(tf); - assertSame(tf, p.getThreadFactory()); - joinPool(p); + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + ThreadFactory threadFactory = new SimpleThreadFactory(); + p.setThreadFactory(threadFactory); + assertSame(threadFactory, p.getThreadFactory()); + } } /** * setThreadFactory(null) throws NPE */ public void testSetThreadFactoryNull() { - ThreadPoolExecutor p = new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - p.setThreadFactory(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(p); + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setThreadFactory(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -392,10 +431,15 @@ public void testSetThreadFactoryNull() { * getRejectedExecutionHandler returns handler in constructor if not set */ public void testGetRejectedExecutionHandler() { - RejectedExecutionHandler h = new NoOpREHandler(); - ThreadPoolExecutor p = new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10), h); - assertSame(h, p.getRejectedExecutionHandler()); - joinPool(p); + final RejectedExecutionHandler handler = new NoOpREHandler(); + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10), + handler); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(handler, p.getRejectedExecutionHandler()); + } } /** @@ -403,24 +447,30 @@ public void testGetRejectedExecutionHandler() { * getRejectedExecutionHandler */ public void testSetRejectedExecutionHandler() { - ThreadPoolExecutor p = new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - RejectedExecutionHandler h = new NoOpREHandler(); - p.setRejectedExecutionHandler(h); - assertSame(h, p.getRejectedExecutionHandler()); - joinPool(p); + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + RejectedExecutionHandler handler = new NoOpREHandler(); + p.setRejectedExecutionHandler(handler); + assertSame(handler, p.getRejectedExecutionHandler()); + } } /** * setRejectedExecutionHandler(null) throws NPE */ public void testSetRejectedExecutionHandlerNull() { - ThreadPoolExecutor p = new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - p.setRejectedExecutionHandler(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(p); + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setRejectedExecutionHandler(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -430,28 +480,25 @@ public void testSetRejectedExecutionHandlerNull() { */ public void testGetLargestPoolSize() throws InterruptedException { final int THREADS = 3; + final CountDownLatch done = new CountDownLatch(1); final ThreadPoolExecutor p = new CustomTPE(THREADS, THREADS, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadsStarted = new CountDownLatch(THREADS); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { assertEquals(0, p.getLargestPoolSize()); + final CountDownLatch threadsStarted = new CountDownLatch(THREADS); for (int i = 0; i < THREADS; i++) p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadsStarted.countDown(); - done.await(); + await(done); assertEquals(THREADS, p.getLargestPoolSize()); }}); - assertTrue(threadsStarted.await(SMALL_DELAY_MS, MILLISECONDS)); - assertEquals(THREADS, p.getLargestPoolSize()); - } finally { - done.countDown(); - joinPool(p); + await(threadsStarted); assertEquals(THREADS, p.getLargestPoolSize()); } + assertEquals(THREADS, p.getLargestPoolSize()); } /** @@ -459,9 +506,17 @@ public void realRun() throws InterruptedException { * otherwise set */ public void testGetMaximumPoolSize() { - ThreadPoolExecutor p = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - assertEquals(2, p.getMaximumPoolSize()); - joinPool(p); + final ThreadPoolExecutor p = + new CustomTPE(2, 3, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(3, p.getMaximumPoolSize()); + p.setMaximumPoolSize(5); + assertEquals(5, p.getMaximumPoolSize()); + p.setMaximumPoolSize(4); + assertEquals(4, p.getMaximumPoolSize()); + } } /** @@ -469,25 +524,22 @@ public void testGetMaximumPoolSize() { * become active */ public void testGetPoolSize() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); final ThreadPoolExecutor p = new CustomTPE(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { assertEquals(0, p.getPoolSize()); + final CountDownLatch threadStarted = new CountDownLatch(1); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); assertEquals(1, p.getPoolSize()); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertEquals(1, p.getPoolSize()); - } finally { - done.countDown(); - joinPool(p); } } @@ -495,38 +547,53 @@ public void realRun() throws InterruptedException { * getTaskCount increases, but doesn't overestimate, when tasks submitted */ public void testGetTaskCount() throws InterruptedException { + final int TASKS = 3; + final CountDownLatch done = new CountDownLatch(1); final ThreadPoolExecutor p = new CustomTPE(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); assertEquals(0, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); - assertEquals(1, p.getTaskCount()); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertEquals(1, p.getTaskCount()); - } finally { - done.countDown(); - joinPool(p); + assertEquals(0, p.getCompletedTaskCount()); + for (int i = 0; i < TASKS; i++) { + assertEquals(1 + i, p.getTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(1 + TASKS, p.getTaskCount()); + await(done); + }}); + } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(1 + TASKS, p.getCompletedTaskCount()); } /** * isShutdown is false before shutdown, true after */ public void testIsShutdown() { - - ThreadPoolExecutor p = new CustomTPE(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - assertFalse(p.isShutdown()); - try { p.shutdown(); } catch (SecurityException ok) { return; } - assertTrue(p.isShutdown()); - joinPool(p); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertFalse(p.isShutdown()); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.isShutdown()); + } } /** @@ -537,25 +604,24 @@ public void testIsTerminated() throws InterruptedException { new CustomTPE(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); assertFalse(p.isTerminating()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { assertFalse(p.isTerminating()); threadStarted.countDown(); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertFalse(p.isTerminating()); done.countDown(); - } finally { try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertFalse(p.isTerminating()); } - assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); - assertFalse(p.isTerminating()); } /** @@ -566,59 +632,55 @@ public void testIsTerminating() throws InterruptedException { new CustomTPE(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); assertFalse(p.isTerminating()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { assertFalse(p.isTerminating()); threadStarted.countDown(); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertFalse(p.isTerminating()); done.countDown(); - } finally { try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertFalse(p.isTerminating()); } - assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); - assertFalse(p.isTerminating()); } /** * getQueue returns the work queue, which contains queued tasks */ public void testGetQueue() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); final BlockingQueue q = new ArrayBlockingQueue(10); final ThreadPoolExecutor p = new CustomTPE(1, 1, LONG_DELAY_MS, MILLISECONDS, q); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); FutureTask[] tasks = new FutureTask[5]; for (int i = 0; i < tasks.length; i++) { Callable task = new CheckedCallable() { public Boolean realCall() throws InterruptedException { threadStarted.countDown(); assertSame(q, p.getQueue()); - done.await(); + await(done); return Boolean.TRUE; }}; tasks[i] = new FutureTask(task); p.execute(tasks[i]); } - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertSame(q, p.getQueue()); assertFalse(q.contains(tasks[0])); assertTrue(q.contains(tasks[tasks.length - 1])); assertEquals(tasks.length - 1, q.size()); - } finally { - done.countDown(); - joinPool(p); } } @@ -626,24 +688,24 @@ public Boolean realCall() throws InterruptedException { * remove(task) removes queued task, and fails to remove active task */ public void testRemove() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); BlockingQueue q = new ArrayBlockingQueue(10); final ThreadPoolExecutor p = new CustomTPE(1, 1, LONG_DELAY_MS, MILLISECONDS, q); - Runnable[] tasks = new Runnable[6]; - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { + Runnable[] tasks = new Runnable[6]; + final CountDownLatch threadStarted = new CountDownLatch(1); for (int i = 0; i < tasks.length; i++) { tasks[i] = new CheckedRunnable() { - public void realRun() throws InterruptedException { - threadStarted.countDown(); - done.await(); - }}; + public void realRun() throws InterruptedException { + threadStarted.countDown(); + await(done); + }}; p.execute(tasks[i]); } - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertFalse(p.remove(tasks[0])); assertTrue(q.contains(tasks[4])); assertTrue(q.contains(tasks[3])); @@ -653,9 +715,6 @@ public void realRun() throws InterruptedException { assertTrue(q.contains(tasks[3])); assertTrue(p.remove(tasks[3])); assertFalse(q.contains(tasks[3])); - } finally { - done.countDown(); - joinPool(p); } } @@ -670,19 +729,19 @@ public void testPurge() throws InterruptedException { new CustomTPE(1, 1, LONG_DELAY_MS, MILLISECONDS, q); - FutureTask[] tasks = new FutureTask[5]; - try { + try (PoolCleaner cleaner = cleaner(p, done)) { + FutureTask[] tasks = new FutureTask[5]; for (int i = 0; i < tasks.length; i++) { Callable task = new CheckedCallable() { public Boolean realCall() throws InterruptedException { threadStarted.countDown(); - done.await(); + await(done); return Boolean.TRUE; }}; tasks[i] = new FutureTask(task); p.execute(tasks[i]); } - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertEquals(tasks.length, p.getTaskCount()); assertEquals(tasks.length - 1, q.size()); assertEquals(1L, p.getActiveCount()); @@ -695,29 +754,47 @@ public Boolean realCall() throws InterruptedException { p.purge(); // Nothing to do assertEquals(tasks.length - 3, q.size()); assertEquals(tasks.length - 2, p.getTaskCount()); - } finally { - done.countDown(); - joinPool(p); } } /** - * shutdownNow returns a list containing tasks that were not run + * shutdownNow returns a list containing tasks that were not run, + * and those tasks are drained from the queue */ - public void testShutdownNow() { - ThreadPoolExecutor p = new CustomTPE(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List l; - try { - for (int i = 0; i < 5; i++) - p.execute(new MediumPossiblyInterruptedRunnable()); - } - finally { + public void testShutdownNow() throws InterruptedException { + final int poolSize = 2; + final int count = 5; + final AtomicInteger ran = new AtomicInteger(0); + final ThreadPoolExecutor p = + new CustomTPE(poolSize, poolSize, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + final CountDownLatch threadsStarted = new CountDownLatch(poolSize); + Runnable waiter = new CheckedRunnable() { public void realRun() { + threadsStarted.countDown(); try { - l = p.shutdownNow(); - } catch (SecurityException ok) { return; } + MILLISECONDS.sleep(2 * LONG_DELAY_MS); + } catch (InterruptedException success) {} + ran.getAndIncrement(); + }}; + for (int i = 0; i < count; i++) + p.execute(waiter); + await(threadsStarted); + assertEquals(poolSize, p.getActiveCount()); + assertEquals(0, p.getCompletedTaskCount()); + final List queuedTasks; + try { + queuedTasks = p.shutdownNow(); + } catch (SecurityException ok) { + return; // Allowed in case test doesn't have privs } assertTrue(p.isShutdown()); - assertTrue(l.size() <= 4); + assertTrue(p.getQueue().isEmpty()); + assertEquals(count - poolSize, queuedTasks.size()); + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertEquals(poolSize, ran.get()); + assertEquals(poolSize, p.getCompletedTaskCount()); } // Exception Tests @@ -727,7 +804,8 @@ public void testShutdownNow() { */ public void testConstructor1() { try { - new CustomTPE(-1,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); + new CustomTPE(-1, 1, 1L, SECONDS, + new ArrayBlockingQueue(10)); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -737,7 +815,8 @@ public void testConstructor1() { */ public void testConstructor2() { try { - new CustomTPE(1,-1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); + new CustomTPE(1, -1, 1L, SECONDS, + new ArrayBlockingQueue(10)); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -747,7 +826,8 @@ public void testConstructor2() { */ public void testConstructor3() { try { - new CustomTPE(1,0,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); + new CustomTPE(1, 0, 1L, SECONDS, + new ArrayBlockingQueue(10)); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -757,7 +837,8 @@ public void testConstructor3() { */ public void testConstructor4() { try { - new CustomTPE(1,2,-1L,MILLISECONDS, new ArrayBlockingQueue(10)); + new CustomTPE(1, 2, -1L, SECONDS, + new ArrayBlockingQueue(10)); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -767,7 +848,8 @@ public void testConstructor4() { */ public void testConstructor5() { try { - new CustomTPE(2,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); + new CustomTPE(2, 1, 1L, SECONDS, + new ArrayBlockingQueue(10)); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -777,7 +859,7 @@ public void testConstructor5() { */ public void testConstructorNullPointerException() { try { - new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS,null); + new CustomTPE(1, 2, 1L, SECONDS, null); shouldThrow(); } catch (NullPointerException success) {} } @@ -787,7 +869,9 @@ public void testConstructorNullPointerException() { */ public void testConstructor6() { try { - new CustomTPE(-1,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10),new SimpleThreadFactory()); + new CustomTPE(-1, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -797,7 +881,9 @@ public void testConstructor6() { */ public void testConstructor7() { try { - new CustomTPE(1,-1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10),new SimpleThreadFactory()); + new CustomTPE(1,-1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -807,7 +893,9 @@ public void testConstructor7() { */ public void testConstructor8() { try { - new CustomTPE(1,0,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10),new SimpleThreadFactory()); + new CustomTPE(1, 0, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -817,7 +905,9 @@ public void testConstructor8() { */ public void testConstructor9() { try { - new CustomTPE(1,2,-1L,MILLISECONDS, new ArrayBlockingQueue(10),new SimpleThreadFactory()); + new CustomTPE(1, 2, -1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -827,7 +917,9 @@ public void testConstructor9() { */ public void testConstructor10() { try { - new CustomTPE(2,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10),new SimpleThreadFactory()); + new CustomTPE(2, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -837,7 +929,7 @@ public void testConstructor10() { */ public void testConstructorNullPointerException2() { try { - new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS,null,new SimpleThreadFactory()); + new CustomTPE(1, 2, 1L, SECONDS, null, new SimpleThreadFactory()); shouldThrow(); } catch (NullPointerException success) {} } @@ -847,8 +939,9 @@ public void testConstructorNullPointerException2() { */ public void testConstructorNullPointerException3() { try { - ThreadFactory f = null; - new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue(10),f); + new CustomTPE(1, 2, 1L, SECONDS, + new ArrayBlockingQueue(10), + (ThreadFactory) null); shouldThrow(); } catch (NullPointerException success) {} } @@ -858,7 +951,9 @@ public void testConstructorNullPointerException3() { */ public void testConstructor11() { try { - new CustomTPE(-1,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10),new NoOpREHandler()); + new CustomTPE(-1, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -868,7 +963,9 @@ public void testConstructor11() { */ public void testConstructor12() { try { - new CustomTPE(1,-1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10),new NoOpREHandler()); + new CustomTPE(1, -1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -878,7 +975,9 @@ public void testConstructor12() { */ public void testConstructor13() { try { - new CustomTPE(1,0,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10),new NoOpREHandler()); + new CustomTPE(1, 0, 1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -888,7 +987,9 @@ public void testConstructor13() { */ public void testConstructor14() { try { - new CustomTPE(1,2,-1L,MILLISECONDS, new ArrayBlockingQueue(10),new NoOpREHandler()); + new CustomTPE(1, 2, -1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -898,7 +999,9 @@ public void testConstructor14() { */ public void testConstructor15() { try { - new CustomTPE(2,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10),new NoOpREHandler()); + new CustomTPE(2, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -908,7 +1011,9 @@ public void testConstructor15() { */ public void testConstructorNullPointerException4() { try { - new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS,null,new NoOpREHandler()); + new CustomTPE(1, 2, 1L, SECONDS, + null, + new NoOpREHandler()); shouldThrow(); } catch (NullPointerException success) {} } @@ -918,8 +1023,9 @@ public void testConstructorNullPointerException4() { */ public void testConstructorNullPointerException5() { try { - RejectedExecutionHandler r = null; - new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue(10),r); + new CustomTPE(1, 2, 1L, SECONDS, + new ArrayBlockingQueue(10), + (RejectedExecutionHandler) null); shouldThrow(); } catch (NullPointerException success) {} } @@ -929,7 +1035,10 @@ public void testConstructorNullPointerException5() { */ public void testConstructor16() { try { - new CustomTPE(-1,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10),new SimpleThreadFactory(),new NoOpREHandler()); + new CustomTPE(-1, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -939,7 +1048,10 @@ public void testConstructor16() { */ public void testConstructor17() { try { - new CustomTPE(1,-1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10),new SimpleThreadFactory(),new NoOpREHandler()); + new CustomTPE(1, -1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -949,7 +1061,10 @@ public void testConstructor17() { */ public void testConstructor18() { try { - new CustomTPE(1,0,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10),new SimpleThreadFactory(),new NoOpREHandler()); + new CustomTPE(1, 0, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -959,7 +1074,10 @@ public void testConstructor18() { */ public void testConstructor19() { try { - new CustomTPE(1,2,-1L,MILLISECONDS, new ArrayBlockingQueue(10),new SimpleThreadFactory(),new NoOpREHandler()); + new CustomTPE(1, 2, -1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -969,7 +1087,10 @@ public void testConstructor19() { */ public void testConstructor20() { try { - new CustomTPE(2,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10),new SimpleThreadFactory(),new NoOpREHandler()); + new CustomTPE(2, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); shouldThrow(); } catch (IllegalArgumentException success) {} } @@ -979,7 +1100,10 @@ public void testConstructor20() { */ public void testConstructorNullPointerException6() { try { - new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS,null,new SimpleThreadFactory(),new NoOpREHandler()); + new CustomTPE(1, 2, 1L, SECONDS, + null, + new SimpleThreadFactory(), + new NoOpREHandler()); shouldThrow(); } catch (NullPointerException success) {} } @@ -989,8 +1113,10 @@ public void testConstructorNullPointerException6() { */ public void testConstructorNullPointerException7() { try { - RejectedExecutionHandler r = null; - new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue(10),new SimpleThreadFactory(),r); + new CustomTPE(1, 2, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + (RejectedExecutionHandler) null); shouldThrow(); } catch (NullPointerException success) {} } @@ -1000,8 +1126,7 @@ public void testConstructorNullPointerException7() { */ public void testConstructorNullPointerException8() { try { - new CustomTPE(1, 2, - LONG_DELAY_MS, MILLISECONDS, + new CustomTPE(1, 2, 1L, SECONDS, new ArrayBlockingQueue(10), (ThreadFactory) null, new NoOpREHandler()); @@ -1013,15 +1138,15 @@ public void testConstructorNullPointerException8() { * execute throws RejectedExecutionException if saturated. */ public void testSaturatedExecute() { - ThreadPoolExecutor p = + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = new CustomTPE(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(1)); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { Runnable task = new CheckedRunnable() { public void realRun() throws InterruptedException { - done.await(); + await(done); }}; for (int i = 0; i < 2; ++i) p.execute(task); @@ -1032,9 +1157,6 @@ public void realRun() throws InterruptedException { } catch (RejectedExecutionException success) {} assertTrue(p.getTaskCount() <= 2); } - } finally { - done.countDown(); - joinPool(p); } } @@ -1042,24 +1164,26 @@ public void realRun() throws InterruptedException { * executor using CallerRunsPolicy runs task if saturated. */ public void testSaturatedExecute2() { - RejectedExecutionHandler h = new CustomTPE.CallerRunsPolicy(); - ThreadPoolExecutor p = new CustomTPE(1, 1, - LONG_DELAY_MS, MILLISECONDS, - new ArrayBlockingQueue(1), - h); - try { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new CustomTPE.CallerRunsPolicy()); + try (PoolCleaner cleaner = cleaner(p, done)) { + Runnable blocker = new CheckedRunnable() { + public void realRun() throws InterruptedException { + await(done); + }}; + p.execute(blocker); TrackedNoOpRunnable[] tasks = new TrackedNoOpRunnable[5]; - for (int i = 0; i < tasks.length; ++i) + for (int i = 0; i < tasks.length; i++) tasks[i] = new TrackedNoOpRunnable(); - TrackedLongRunnable mr = new TrackedLongRunnable(); - p.execute(mr); - for (int i = 0; i < tasks.length; ++i) + for (int i = 0; i < tasks.length; i++) p.execute(tasks[i]); - for (int i = 1; i < tasks.length; ++i) + for (int i = 1; i < tasks.length; i++) assertTrue(tasks[i].done); - try { p.shutdownNow(); } catch (SecurityException ok) { return; } - } finally { - joinPool(p); + assertFalse(tasks[0].done); // waiting in queue } } @@ -1067,77 +1191,88 @@ public void testSaturatedExecute2() { * executor using DiscardPolicy drops task if saturated. */ public void testSaturatedExecute3() { - RejectedExecutionHandler h = new CustomTPE.DiscardPolicy(); - ThreadPoolExecutor p = + final TrackedNoOpRunnable[] tasks = new TrackedNoOpRunnable[5]; + for (int i = 0; i < tasks.length; ++i) + tasks[i] = new TrackedNoOpRunnable(); + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = new CustomTPE(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(1), - h); - try { - TrackedNoOpRunnable[] tasks = new TrackedNoOpRunnable[5]; - for (int i = 0; i < tasks.length; ++i) - tasks[i] = new TrackedNoOpRunnable(); - p.execute(new TrackedLongRunnable()); + new CustomTPE.DiscardPolicy()); + try (PoolCleaner cleaner = cleaner(p, done)) { + p.execute(awaiter(done)); + for (TrackedNoOpRunnable task : tasks) p.execute(task); - for (TrackedNoOpRunnable task : tasks) - assertFalse(task.done); - try { p.shutdownNow(); } catch (SecurityException ok) { return; } - } finally { - joinPool(p); + for (int i = 1; i < tasks.length; i++) + assertFalse(tasks[i].done); } + for (int i = 1; i < tasks.length; i++) + assertFalse(tasks[i].done); + assertTrue(tasks[0].done); // was waiting in queue } /** * executor using DiscardOldestPolicy drops oldest task if saturated. */ public void testSaturatedExecute4() { - RejectedExecutionHandler h = new CustomTPE.DiscardOldestPolicy(); - ThreadPoolExecutor p = new CustomTPE(1,1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(1), h); - try { - p.execute(new TrackedLongRunnable()); - TrackedLongRunnable r2 = new TrackedLongRunnable(); + final CountDownLatch done = new CountDownLatch(1); + LatchAwaiter r1 = awaiter(done); + LatchAwaiter r2 = awaiter(done); + LatchAwaiter r3 = awaiter(done); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new CustomTPE.DiscardOldestPolicy()); + try (PoolCleaner cleaner = cleaner(p, done)) { + assertEquals(LatchAwaiter.NEW, r1.state); + assertEquals(LatchAwaiter.NEW, r2.state); + assertEquals(LatchAwaiter.NEW, r3.state); + p.execute(r1); p.execute(r2); assertTrue(p.getQueue().contains(r2)); - TrackedNoOpRunnable r3 = new TrackedNoOpRunnable(); p.execute(r3); assertFalse(p.getQueue().contains(r2)); assertTrue(p.getQueue().contains(r3)); - try { p.shutdownNow(); } catch (SecurityException ok) { return; } - } finally { - joinPool(p); } + assertEquals(LatchAwaiter.DONE, r1.state); + assertEquals(LatchAwaiter.NEW, r2.state); + assertEquals(LatchAwaiter.DONE, r3.state); } /** * execute throws RejectedExecutionException if shutdown */ public void testRejectedExecutionExceptionOnShutdown() { - ThreadPoolExecutor p = - new CustomTPE(1,1,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue(1)); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1)); try { p.shutdown(); } catch (SecurityException ok) { return; } - try { - p.execute(new NoOpRunnable()); - shouldThrow(); - } catch (RejectedExecutionException success) {} - - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.execute(new NoOpRunnable()); + shouldThrow(); + } catch (RejectedExecutionException success) {} + } } /** * execute using CallerRunsPolicy drops task on shutdown */ public void testCallerRunsOnShutdown() { - RejectedExecutionHandler h = new CustomTPE.CallerRunsPolicy(); - ThreadPoolExecutor p = new CustomTPE(1,1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(1), h); - + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new CustomTPE.CallerRunsPolicy()); try { p.shutdown(); } catch (SecurityException ok) { return; } - try { + try (PoolCleaner cleaner = cleaner(p)) { TrackedNoOpRunnable r = new TrackedNoOpRunnable(); p.execute(r); assertFalse(r.done); - } finally { - joinPool(p); } } @@ -1145,16 +1280,16 @@ public void testCallerRunsOnShutdown() { * execute using DiscardPolicy drops task on shutdown */ public void testDiscardOnShutdown() { - RejectedExecutionHandler h = new CustomTPE.DiscardPolicy(); - ThreadPoolExecutor p = new CustomTPE(1,1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(1), h); - + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new CustomTPE.DiscardPolicy()); try { p.shutdown(); } catch (SecurityException ok) { return; } - try { + try (PoolCleaner cleaner = cleaner(p)) { TrackedNoOpRunnable r = new TrackedNoOpRunnable(); p.execute(r); assertFalse(r.done); - } finally { - joinPool(p); } } @@ -1162,16 +1297,17 @@ public void testDiscardOnShutdown() { * execute using DiscardOldestPolicy drops task on shutdown */ public void testDiscardOldestOnShutdown() { - RejectedExecutionHandler h = new CustomTPE.DiscardOldestPolicy(); - ThreadPoolExecutor p = new CustomTPE(1,1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(1), h); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new CustomTPE.DiscardOldestPolicy()); try { p.shutdown(); } catch (SecurityException ok) { return; } - try { + try (PoolCleaner cleaner = cleaner(p)) { TrackedNoOpRunnable r = new TrackedNoOpRunnable(); p.execute(r); assertFalse(r.done); - } finally { - joinPool(p); } } @@ -1179,30 +1315,32 @@ public void testDiscardOldestOnShutdown() { * execute(null) throws NPE */ public void testExecuteNull() { - ThreadPoolExecutor p = null; - try { - p = new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue(10)); - p.execute(null); - shouldThrow(); - } catch (NullPointerException success) {} - - joinPool(p); + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + 1L, SECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.execute(null); + shouldThrow(); + } catch (NullPointerException success) {} + } } /** * setCorePoolSize of negative value throws IllegalArgumentException */ public void testCorePoolSizeIllegalArgumentException() { - ThreadPoolExecutor p = - new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue(10)); - try { - p.setCorePoolSize(-1); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - try { p.shutdown(); } catch (SecurityException ok) { return; } + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setCorePoolSize(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} } - joinPool(p); } /** @@ -1210,16 +1348,16 @@ public void testCorePoolSizeIllegalArgumentException() { * if given a value less the core pool size */ public void testMaximumPoolSizeIllegalArgumentException() { - ThreadPoolExecutor p = - new CustomTPE(2,3,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue(10)); - try { - p.setMaximumPoolSize(1); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - try { p.shutdown(); } catch (SecurityException ok) { return; } + final ThreadPoolExecutor p = + new CustomTPE(2, 3, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setMaximumPoolSize(1); + shouldThrow(); + } catch (IllegalArgumentException success) {} } - joinPool(p); } /** @@ -1227,16 +1365,16 @@ public void testMaximumPoolSizeIllegalArgumentException() { * if given a negative value */ public void testMaximumPoolSizeIllegalArgumentException2() { - ThreadPoolExecutor p = - new CustomTPE(2,3,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue(10)); - try { - p.setMaximumPoolSize(-1); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - try { p.shutdown(); } catch (SecurityException ok) { return; } + final ThreadPoolExecutor p = + new CustomTPE(2, 3, + LONG_DELAY_MS, + MILLISECONDS,new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setMaximumPoolSize(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} } - joinPool(p); } /** @@ -1244,17 +1382,16 @@ public void testMaximumPoolSizeIllegalArgumentException2() { * when given a negative value */ public void testKeepAliveTimeIllegalArgumentException() { - ThreadPoolExecutor p = - new CustomTPE(2,3,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue(10)); - - try { - p.setKeepAliveTime(-1,MILLISECONDS); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - try { p.shutdown(); } catch (SecurityException ok) { return; } + final ThreadPoolExecutor p = + new CustomTPE(2, 3, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setKeepAliveTime(-1, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} } - joinPool(p); } /** @@ -1262,9 +1399,11 @@ public void testKeepAliveTimeIllegalArgumentException() { */ public void testTerminated() { CustomTPE p = new CustomTPE(); - try { p.shutdown(); } catch (SecurityException ok) { return; } - assertTrue(p.terminatedCalled()); - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.terminatedCalled()); + assertTrue(p.isShutdown()); + } } /** @@ -1272,7 +1411,7 @@ public void testTerminated() { */ public void testBeforeAfter() throws InterruptedException { CustomTPE p = new CustomTPE(); - try { + try (PoolCleaner cleaner = cleaner(p)) { final CountDownLatch done = new CountDownLatch(1); p.execute(new CheckedRunnable() { public void realRun() { @@ -1282,9 +1421,6 @@ public void realRun() { assertEquals(0, done.getCount()); assertTrue(p.afterCalled()); assertTrue(p.beforeCalled()); - try { p.shutdown(); } catch (SecurityException ok) { return; } - } finally { - joinPool(p); } } @@ -1292,13 +1428,14 @@ public void realRun() { * completed submit of callable returns result */ public void testSubmitCallable() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new StringTask()); String result = future.get(); assertSame(TEST_STRING, result); - } finally { - joinPool(e); } } @@ -1306,13 +1443,14 @@ public void testSubmitCallable() throws Exception { * completed submit of runnable returns successfully */ public void testSubmitRunnable() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new NoOpRunnable()); future.get(); assertTrue(future.isDone()); - } finally { - joinPool(e); } } @@ -1320,13 +1458,14 @@ public void testSubmitRunnable() throws Exception { * completed submit of (runnable, result) returns result */ public void testSubmitRunnable2() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new NoOpRunnable(), TEST_STRING); String result = future.get(); assertSame(TEST_STRING, result); - } finally { - joinPool(e); } } @@ -1334,13 +1473,15 @@ public void testSubmitRunnable2() throws Exception { * invokeAny(null) throws NPE */ public void testInvokeAny1() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - e.invokeAny(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1348,13 +1489,15 @@ public void testInvokeAny1() throws Exception { * invokeAny(empty collection) throws IAE */ public void testInvokeAny2() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - e.invokeAny(new ArrayList>()); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - joinPool(e); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>()); + shouldThrow(); + } catch (IllegalArgumentException success) {} } } @@ -1363,17 +1506,19 @@ public void testInvokeAny2() throws Exception { */ public void testInvokeAny3() throws Exception { CountDownLatch latch = new CountDownLatch(1); - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(latchAwaitingStringTask(latch)); - l.add(null); - try { - e.invokeAny(l); - shouldThrow(); - } catch (NullPointerException success) { - } finally { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l); + shouldThrow(); + } catch (NullPointerException success) {} latch.countDown(); - joinPool(e); } } @@ -1381,16 +1526,19 @@ public void testInvokeAny3() throws Exception { * invokeAny(c) throws ExecutionException if no task completes */ public void testInvokeAny4() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new NPETask()); - try { - e.invokeAny(l); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -1398,15 +1546,16 @@ public void testInvokeAny4() throws Exception { * invokeAny(c) returns result of some task */ public void testInvokeAny5() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); String result = e.invokeAny(l); assertSame(TEST_STRING, result); - } finally { - joinPool(e); } } @@ -1414,13 +1563,15 @@ public void testInvokeAny5() throws Exception { * invokeAll(null) throws NPE */ public void testInvokeAll1() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - e.invokeAll(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1428,12 +1579,13 @@ public void testInvokeAll1() throws Exception { * invokeAll(empty collection) returns empty collection */ public void testInvokeAll2() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { List> r = e.invokeAll(new ArrayList>()); assertTrue(r.isEmpty()); - } finally { - joinPool(e); } } @@ -1441,16 +1593,18 @@ public void testInvokeAll2() throws Exception { * invokeAll(c) throws NPE if c has null elements */ public void testInvokeAll3() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(null); - try { - e.invokeAll(l); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1458,18 +1612,21 @@ public void testInvokeAll3() throws Exception { * get of element of invokeAll(c) throws exception on failed task */ public void testInvokeAll4() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new NPETask()); - List> futures = e.invokeAll(l); - assertEquals(1, futures.size()); - try { - futures.get(0).get(); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = e.invokeAll(l); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -1477,8 +1634,11 @@ public void testInvokeAll4() throws Exception { * invokeAll(c) returns results of all completed tasks */ public void testInvokeAll5() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); @@ -1486,8 +1646,6 @@ public void testInvokeAll5() throws Exception { assertEquals(2, futures.size()); for (Future future : futures) assertSame(TEST_STRING, future.get()); - } finally { - joinPool(e); } } @@ -1495,13 +1653,15 @@ public void testInvokeAll5() throws Exception { * timed invokeAny(null) throws NPE */ public void testTimedInvokeAny1() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1509,15 +1669,17 @@ public void testTimedInvokeAny1() throws Exception { * timed invokeAny(,,null) throws NPE */ public void testTimedInvokeAnyNullTimeUnit() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new StringTask()); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1525,13 +1687,16 @@ public void testTimedInvokeAnyNullTimeUnit() throws Exception { * timed invokeAny(empty collection) throws IAE */ public void testTimedInvokeAny2() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - e.invokeAny(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - joinPool(e); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} } } @@ -1540,17 +1705,19 @@ public void testTimedInvokeAny2() throws Exception { */ public void testTimedInvokeAny3() throws Exception { CountDownLatch latch = new CountDownLatch(1); - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(latchAwaitingStringTask(latch)); - l.add(null); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} latch.countDown(); - joinPool(e); } } @@ -1558,16 +1725,21 @@ public void testTimedInvokeAny3() throws Exception { * timed invokeAny(c) throws ExecutionException if no task completes */ public void testTimedInvokeAny4() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new NPETask()); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } } @@ -1575,15 +1747,18 @@ public void testTimedInvokeAny4() throws Exception { * timed invokeAny(c) returns result of some task */ public void testTimedInvokeAny5() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); - String result = e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + String result = e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); assertSame(TEST_STRING, result); - } finally { - joinPool(e); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } } @@ -1591,13 +1766,15 @@ public void testTimedInvokeAny5() throws Exception { * timed invokeAll(null) throws NPE */ public void testTimedInvokeAll1() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1605,15 +1782,17 @@ public void testTimedInvokeAll1() throws Exception { * timed invokeAll(,,null) throws NPE */ public void testTimedInvokeAllNullTimeUnit() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new StringTask()); - try { - e.invokeAll(l, MEDIUM_DELAY_MS, null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1621,12 +1800,14 @@ public void testTimedInvokeAllNullTimeUnit() throws Exception { * timed invokeAll(empty collection) returns empty collection */ public void testTimedInvokeAll2() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - List> r = e.invokeAll(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> r = e.invokeAll(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); assertTrue(r.isEmpty()); - } finally { - joinPool(e); } } @@ -1634,16 +1815,18 @@ public void testTimedInvokeAll2() throws Exception { * timed invokeAll(c) throws NPE if c has null elements */ public void testTimedInvokeAll3() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(null); - try { - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1651,19 +1834,22 @@ public void testTimedInvokeAll3() throws Exception { * get of element of invokeAll(c) throws exception on failed task */ public void testTimedInvokeAll4() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new NPETask()); - List> futures = - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); - assertEquals(1, futures.size()); - try { - futures.get(0).get(); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -1671,18 +1857,19 @@ public void testTimedInvokeAll4() throws Exception { * timed invokeAll(c) returns results of all completed tasks */ public void testTimedInvokeAll5() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); List> futures = - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); assertEquals(2, futures.size()); for (Future future : futures) assertSame(TEST_STRING, future.get()); - } finally { - joinPool(e); } } @@ -1690,21 +1877,40 @@ public void testTimedInvokeAll5() throws Exception { * timed invokeAll(c) cancels tasks not completed by timeout */ public void testTimedInvokeAll6() throws Exception { - ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(Executors.callable(new MediumPossiblyInterruptedRunnable(), TEST_STRING)); - l.add(new StringTask()); - List> futures = - e.invokeAll(l, SHORT_DELAY_MS, MILLISECONDS); - assertEquals(l.size(), futures.size()); - for (Future future : futures) - assertTrue(future.isDone()); - assertFalse(futures.get(0).isCancelled()); - assertTrue(futures.get(1).isCancelled()); - } finally { - joinPool(e); + for (long timeout = timeoutMillis();;) { + final CountDownLatch done = new CountDownLatch(1); + final Callable waiter = new CheckedCallable() { + public String realCall() { + try { done.await(LONG_DELAY_MS, MILLISECONDS); } + catch (InterruptedException ok) {} + return "1"; }}; + final ExecutorService p = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p, done)) { + List> tasks = new ArrayList<>(); + tasks.add(new StringTask("0")); + tasks.add(waiter); + tasks.add(new StringTask("2")); + long startTime = System.nanoTime(); + List> futures = + p.invokeAll(tasks, timeout, MILLISECONDS); + assertEquals(tasks.size(), futures.size()); + assertTrue(millisElapsedSince(startTime) >= timeout); + for (Future future : futures) + assertTrue(future.isDone()); + assertTrue(futures.get(1).isCancelled()); + try { + assertEquals("0", futures.get(0).get()); + assertEquals("2", futures.get(2).get()); + break; + } catch (CancellationException retryWithLongerTimeout) { + timeout *= 2; + if (timeout >= LONG_DELAY_MS / 2) + fail("expected exactly one task to be cancelled"); + } + } } } @@ -1718,7 +1924,7 @@ public void testFailingThreadFactory() throws InterruptedException { LONG_DELAY_MS, MILLISECONDS, new LinkedBlockingQueue(), new FailingThreadFactory()); - try { + try (PoolCleaner cleaner = cleaner(e)) { final int TASKS = 100; final CountDownLatch done = new CountDownLatch(TASKS); for (int k = 0; k < TASKS; ++k) @@ -1727,8 +1933,6 @@ public void realRun() { done.countDown(); }}); assertTrue(done.await(LONG_DELAY_MS, MILLISECONDS)); - } finally { - joinPool(e); } } @@ -1736,38 +1940,40 @@ public void realRun() { * allowsCoreThreadTimeOut is by default false. */ public void testAllowsCoreThreadTimeOut() { - ThreadPoolExecutor p = new CustomTPE(2, 2, 1000, MILLISECONDS, new ArrayBlockingQueue(10)); - assertFalse(p.allowsCoreThreadTimeOut()); - joinPool(p); + final ThreadPoolExecutor p = + new CustomTPE(2, 2, + 1000, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertFalse(p.allowsCoreThreadTimeOut()); + } } /** * allowCoreThreadTimeOut(true) causes idle threads to time out */ public void testAllowCoreThreadTimeOut_true() throws Exception { - long coreThreadTimeOut = SHORT_DELAY_MS; + long keepAliveTime = timeoutMillis(); final ThreadPoolExecutor p = new CustomTPE(2, 10, - coreThreadTimeOut, MILLISECONDS, + keepAliveTime, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); p.allowCoreThreadTimeOut(true); p.execute(new CheckedRunnable() { - public void realRun() throws InterruptedException { + public void realRun() { threadStarted.countDown(); assertEquals(1, p.getPoolSize()); }}); await(threadStarted); - delay(coreThreadTimeOut); + delay(keepAliveTime); long startTime = System.nanoTime(); while (p.getPoolSize() > 0 && millisElapsedSince(startTime) < LONG_DELAY_MS) Thread.yield(); assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); assertEquals(0, p.getPoolSize()); - } finally { - joinPool(p); } } @@ -1775,23 +1981,59 @@ && millisElapsedSince(startTime) < LONG_DELAY_MS) * allowCoreThreadTimeOut(false) causes idle threads not to time out */ public void testAllowCoreThreadTimeOut_false() throws Exception { - long coreThreadTimeOut = SHORT_DELAY_MS; + long keepAliveTime = timeoutMillis(); final ThreadPoolExecutor p = new CustomTPE(2, 10, - coreThreadTimeOut, MILLISECONDS, + keepAliveTime, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); p.allowCoreThreadTimeOut(false); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); assertTrue(p.getPoolSize() >= 1); }}); - delay(2 * coreThreadTimeOut); + delay(2 * keepAliveTime); assertTrue(p.getPoolSize() >= 1); - } finally { - joinPool(p); + } + } + + /** + * get(cancelled task) throws CancellationException + * (in part, a test of CustomTPE itself) + */ + public void testGet_cancelled() throws Exception { + final CountDownLatch done = new CountDownLatch(1); + final ExecutorService e = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new LinkedBlockingQueue()); + try (PoolCleaner cleaner = cleaner(e, done)) { + final CountDownLatch blockerStarted = new CountDownLatch(1); + final List> futures = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + Runnable r = new CheckedRunnable() { public void realRun() + throws Throwable { + blockerStarted.countDown(); + assertTrue(done.await(2 * LONG_DELAY_MS, MILLISECONDS)); + }}; + futures.add(e.submit(r)); + } + await(blockerStarted); + for (Future future : futures) future.cancel(false); + for (Future future : futures) { + try { + future.get(); + shouldThrow(); + } catch (CancellationException success) {} + try { + future.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (CancellationException success) {} + assertTrue(future.isCancelled()); + assertTrue(future.isDone()); + } } } diff --git a/jsr166-tests/src/test/java/jsr166/ThreadPoolExecutorTest.java b/jsr166-tests/src/test/java/jsr166/ThreadPoolExecutorTest.java index 52a700276..7fe26f438 100644 --- a/jsr166-tests/src/test/java/jsr166/ThreadPoolExecutorTest.java +++ b/jsr166-tests/src/test/java/jsr166/ThreadPoolExecutorTest.java @@ -10,12 +10,14 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; @@ -29,6 +31,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import junit.framework.Test; import junit.framework.TestSuite; @@ -41,7 +44,7 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ThreadPoolExecutorTest.class); // } static class ExtendedTPE extends ThreadPoolExecutor { @@ -89,16 +92,12 @@ public void testExecute() throws InterruptedException { new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch done = new CountDownLatch(1); - final Runnable task = new CheckedRunnable() { - public void realRun() { - done.countDown(); - }}; - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch done = new CountDownLatch(1); + final Runnable task = new CheckedRunnable() { + public void realRun() { done.countDown(); }}; p.execute(task); - assertTrue(done.await(SMALL_DELAY_MS, MILLISECONDS)); - } finally { - joinPool(p); + assertTrue(done.await(LONG_DELAY_MS, MILLISECONDS)); } } @@ -107,25 +106,22 @@ public void realRun() { * thread becomes active */ public void testGetActiveCount() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); final ThreadPoolExecutor p = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); assertEquals(0, p.getActiveCount()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); assertEquals(1, p.getActiveCount()); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertEquals(1, p.getActiveCount()); - } finally { - done.countDown(); - joinPool(p); } } @@ -134,17 +130,25 @@ public void realRun() throws InterruptedException { */ public void testPrestartCoreThread() { final ThreadPoolExecutor p = - new ThreadPoolExecutor(2, 2, + new ThreadPoolExecutor(2, 6, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - assertEquals(0, p.getPoolSize()); - assertTrue(p.prestartCoreThread()); - assertEquals(1, p.getPoolSize()); - assertTrue(p.prestartCoreThread()); - assertEquals(2, p.getPoolSize()); - assertFalse(p.prestartCoreThread()); - assertEquals(2, p.getPoolSize()); - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(0, p.getPoolSize()); + assertTrue(p.prestartCoreThread()); + assertEquals(1, p.getPoolSize()); + assertTrue(p.prestartCoreThread()); + assertEquals(2, p.getPoolSize()); + assertFalse(p.prestartCoreThread()); + assertEquals(2, p.getPoolSize()); + p.setCorePoolSize(4); + assertTrue(p.prestartCoreThread()); + assertEquals(3, p.getPoolSize()); + assertTrue(p.prestartCoreThread()); + assertEquals(4, p.getPoolSize()); + assertFalse(p.prestartCoreThread()); + assertEquals(4, p.getPoolSize()); + } } /** @@ -152,15 +156,21 @@ public void testPrestartCoreThread() { */ public void testPrestartAllCoreThreads() { final ThreadPoolExecutor p = - new ThreadPoolExecutor(2, 2, + new ThreadPoolExecutor(2, 6, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - assertEquals(0, p.getPoolSize()); - p.prestartAllCoreThreads(); - assertEquals(2, p.getPoolSize()); - p.prestartAllCoreThreads(); - assertEquals(2, p.getPoolSize()); - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(0, p.getPoolSize()); + p.prestartAllCoreThreads(); + assertEquals(2, p.getPoolSize()); + p.prestartAllCoreThreads(); + assertEquals(2, p.getPoolSize()); + p.setCorePoolSize(4); + p.prestartAllCoreThreads(); + assertEquals(4, p.getPoolSize()); + p.prestartAllCoreThreads(); + assertEquals(4, p.getPoolSize()); + } } /** @@ -172,10 +182,10 @@ public void testGetCompletedTaskCount() throws InterruptedException { new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch threadProceed = new CountDownLatch(1); - final CountDownLatch threadDone = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch threadProceed = new CountDownLatch(1); + final CountDownLatch threadDone = new CountDownLatch(1); assertEquals(0, p.getCompletedTaskCount()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { @@ -194,8 +204,6 @@ public void realRun() throws InterruptedException { fail("timed out"); Thread.yield(); } - } finally { - joinPool(p); } } @@ -207,8 +215,9 @@ public void testGetCorePoolSize() { new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - assertEquals(1, p.getCorePoolSize()); - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(1, p.getCorePoolSize()); + } } /** @@ -219,23 +228,25 @@ public void testGetKeepAliveTime() { new ThreadPoolExecutor(2, 2, 1000, MILLISECONDS, new ArrayBlockingQueue(10)); - assertEquals(1, p.getKeepAliveTime(TimeUnit.SECONDS)); - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(1, p.getKeepAliveTime(SECONDS)); + } } /** * getThreadFactory returns factory in constructor if not set */ public void testGetThreadFactory() { - ThreadFactory tf = new SimpleThreadFactory(); + ThreadFactory threadFactory = new SimpleThreadFactory(); final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10), - tf, + threadFactory, new NoOpREHandler()); - assertSame(tf, p.getThreadFactory()); - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(threadFactory, p.getThreadFactory()); + } } /** @@ -246,10 +257,11 @@ public void testSetThreadFactory() { new ThreadPoolExecutor(1, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - ThreadFactory tf = new SimpleThreadFactory(); - p.setThreadFactory(tf); - assertSame(tf, p.getThreadFactory()); - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + ThreadFactory threadFactory = new SimpleThreadFactory(); + p.setThreadFactory(threadFactory); + assertSame(threadFactory, p.getThreadFactory()); + } } /** @@ -260,12 +272,11 @@ public void testSetThreadFactoryNull() { new ThreadPoolExecutor(1, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - p.setThreadFactory(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setThreadFactory(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -273,14 +284,15 @@ public void testSetThreadFactoryNull() { * getRejectedExecutionHandler returns handler in constructor if not set */ public void testGetRejectedExecutionHandler() { - final RejectedExecutionHandler h = new NoOpREHandler(); + final RejectedExecutionHandler handler = new NoOpREHandler(); final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10), - h); - assertSame(h, p.getRejectedExecutionHandler()); - joinPool(p); + handler); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(handler, p.getRejectedExecutionHandler()); + } } /** @@ -292,10 +304,11 @@ public void testSetRejectedExecutionHandler() { new ThreadPoolExecutor(1, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - RejectedExecutionHandler h = new NoOpREHandler(); - p.setRejectedExecutionHandler(h); - assertSame(h, p.getRejectedExecutionHandler()); - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + RejectedExecutionHandler handler = new NoOpREHandler(); + p.setRejectedExecutionHandler(handler); + assertSame(handler, p.getRejectedExecutionHandler()); + } } /** @@ -306,12 +319,11 @@ public void testSetRejectedExecutionHandlerNull() { new ThreadPoolExecutor(1, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - p.setRejectedExecutionHandler(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setRejectedExecutionHandler(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -321,28 +333,25 @@ public void testSetRejectedExecutionHandlerNull() { */ public void testGetLargestPoolSize() throws InterruptedException { final int THREADS = 3; + final CountDownLatch done = new CountDownLatch(1); final ThreadPoolExecutor p = new ThreadPoolExecutor(THREADS, THREADS, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadsStarted = new CountDownLatch(THREADS); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { assertEquals(0, p.getLargestPoolSize()); + final CountDownLatch threadsStarted = new CountDownLatch(THREADS); for (int i = 0; i < THREADS; i++) p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadsStarted.countDown(); - done.await(); + await(done); assertEquals(THREADS, p.getLargestPoolSize()); }}); - assertTrue(threadsStarted.await(SMALL_DELAY_MS, MILLISECONDS)); - assertEquals(THREADS, p.getLargestPoolSize()); - } finally { - done.countDown(); - joinPool(p); + await(threadsStarted); assertEquals(THREADS, p.getLargestPoolSize()); } + assertEquals(THREADS, p.getLargestPoolSize()); } /** @@ -354,8 +363,13 @@ public void testGetMaximumPoolSize() { new ThreadPoolExecutor(2, 3, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - assertEquals(3, p.getMaximumPoolSize()); - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(3, p.getMaximumPoolSize()); + p.setMaximumPoolSize(5); + assertEquals(5, p.getMaximumPoolSize()); + p.setMaximumPoolSize(4); + assertEquals(4, p.getMaximumPoolSize()); + } } /** @@ -363,25 +377,22 @@ public void testGetMaximumPoolSize() { * become active */ public void testGetPoolSize() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { assertEquals(0, p.getPoolSize()); + final CountDownLatch threadStarted = new CountDownLatch(1); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); assertEquals(1, p.getPoolSize()); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertEquals(1, p.getPoolSize()); - } finally { - done.countDown(); - joinPool(p); } } @@ -389,26 +400,38 @@ public void realRun() throws InterruptedException { * getTaskCount increases, but doesn't overestimate, when tasks submitted */ public void testGetTaskCount() throws InterruptedException { + final int TASKS = 3; + final CountDownLatch done = new CountDownLatch(1); final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); assertEquals(0, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); - assertEquals(1, p.getTaskCount()); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertEquals(1, p.getTaskCount()); - } finally { - done.countDown(); - joinPool(p); + assertEquals(0, p.getCompletedTaskCount()); + for (int i = 0; i < TASKS; i++) { + assertEquals(1 + i, p.getTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(1 + TASKS, p.getTaskCount()); + await(done); + }}); + } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(1 + TASKS, p.getCompletedTaskCount()); } /** @@ -419,10 +442,11 @@ public void testIsShutdown() { new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - assertFalse(p.isShutdown()); - try { p.shutdown(); } catch (SecurityException ok) { return; } - assertTrue(p.isShutdown()); - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + assertFalse(p.isShutdown()); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.isShutdown()); + } } /** @@ -433,26 +457,28 @@ public void testAwaitTermination_timesOut() throws InterruptedException { new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - assertFalse(p.isTerminated()); - assertFalse(p.awaitTermination(Long.MIN_VALUE, NANOSECONDS)); - assertFalse(p.awaitTermination(Long.MIN_VALUE, MILLISECONDS)); - assertFalse(p.awaitTermination(-1L, NANOSECONDS)); - assertFalse(p.awaitTermination(-1L, MILLISECONDS)); - assertFalse(p.awaitTermination(0L, NANOSECONDS)); - assertFalse(p.awaitTermination(0L, MILLISECONDS)); - long timeoutNanos = 999999L; - long startTime = System.nanoTime(); - assertFalse(p.awaitTermination(timeoutNanos, NANOSECONDS)); - assertTrue(System.nanoTime() - startTime >= timeoutNanos); - assertFalse(p.isTerminated()); - startTime = System.nanoTime(); - long timeoutMillis = timeoutMillis(); - assertFalse(p.awaitTermination(timeoutMillis, MILLISECONDS)); - assertTrue(millisElapsedSince(startTime) >= timeoutMillis); - assertFalse(p.isTerminated()); - p.shutdown(); - assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); + try (PoolCleaner cleaner = cleaner(p)) { + assertFalse(p.isTerminated()); + assertFalse(p.awaitTermination(Long.MIN_VALUE, NANOSECONDS)); + assertFalse(p.awaitTermination(Long.MIN_VALUE, MILLISECONDS)); + assertFalse(p.awaitTermination(-1L, NANOSECONDS)); + assertFalse(p.awaitTermination(-1L, MILLISECONDS)); + assertFalse(p.awaitTermination(0L, NANOSECONDS)); + assertFalse(p.awaitTermination(0L, MILLISECONDS)); + long timeoutNanos = 999999L; + long startTime = System.nanoTime(); + assertFalse(p.awaitTermination(timeoutNanos, NANOSECONDS)); + assertTrue(System.nanoTime() - startTime >= timeoutNanos); + assertFalse(p.isTerminated()); + startTime = System.nanoTime(); + long timeoutMillis = timeoutMillis(); + assertFalse(p.awaitTermination(timeoutMillis, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + assertFalse(p.isTerminated()); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + } } /** @@ -463,24 +489,24 @@ public void testIsTerminated() throws InterruptedException { new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - assertFalse(p.isTerminated()); - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); + assertFalse(p.isTerminating()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { - assertFalse(p.isTerminated()); + assertFalse(p.isTerminating()); threadStarted.countDown(); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertFalse(p.isTerminating()); done.countDown(); - } finally { try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertFalse(p.isTerminating()); } - assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); } /** @@ -491,59 +517,55 @@ public void testIsTerminating() throws InterruptedException { new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); assertFalse(p.isTerminating()); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { assertFalse(p.isTerminating()); threadStarted.countDown(); - done.await(); + await(done); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertFalse(p.isTerminating()); done.countDown(); - } finally { try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertFalse(p.isTerminating()); } - assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); - assertFalse(p.isTerminating()); } /** * getQueue returns the work queue, which contains queued tasks */ public void testGetQueue() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); final BlockingQueue q = new ArrayBlockingQueue(10); final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, q); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); FutureTask[] tasks = new FutureTask[5]; for (int i = 0; i < tasks.length; i++) { Callable task = new CheckedCallable() { public Boolean realCall() throws InterruptedException { threadStarted.countDown(); assertSame(q, p.getQueue()); - done.await(); + await(done); return Boolean.TRUE; }}; tasks[i] = new FutureTask(task); p.execute(tasks[i]); } - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertSame(q, p.getQueue()); assertFalse(q.contains(tasks[0])); assertTrue(q.contains(tasks[tasks.length - 1])); assertEquals(tasks.length - 1, q.size()); - } finally { - done.countDown(); - joinPool(p); } } @@ -551,24 +573,24 @@ public Boolean realCall() throws InterruptedException { * remove(task) removes queued task, and fails to remove active task */ public void testRemove() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); BlockingQueue q = new ArrayBlockingQueue(10); final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, q); - Runnable[] tasks = new Runnable[5]; - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { + Runnable[] tasks = new Runnable[6]; + final CountDownLatch threadStarted = new CountDownLatch(1); for (int i = 0; i < tasks.length; i++) { tasks[i] = new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); - done.await(); + await(done); }}; p.execute(tasks[i]); } - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertFalse(p.remove(tasks[0])); assertTrue(q.contains(tasks[4])); assertTrue(q.contains(tasks[3])); @@ -578,9 +600,6 @@ public void realRun() throws InterruptedException { assertTrue(q.contains(tasks[3])); assertTrue(p.remove(tasks[3])); assertFalse(q.contains(tasks[3])); - } finally { - done.countDown(); - joinPool(p); } } @@ -595,19 +614,19 @@ public void testPurge() throws InterruptedException { new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, q); - FutureTask[] tasks = new FutureTask[5]; - try { + try (PoolCleaner cleaner = cleaner(p, done)) { + FutureTask[] tasks = new FutureTask[5]; for (int i = 0; i < tasks.length; i++) { Callable task = new CheckedCallable() { public Boolean realCall() throws InterruptedException { threadStarted.countDown(); - done.await(); + await(done); return Boolean.TRUE; }}; tasks[i] = new FutureTask(task); p.execute(tasks[i]); } - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); assertEquals(tasks.length, p.getTaskCount()); assertEquals(tasks.length - 1, q.size()); assertEquals(1L, p.getActiveCount()); @@ -620,32 +639,47 @@ public Boolean realCall() throws InterruptedException { p.purge(); // Nothing to do assertEquals(tasks.length - 3, q.size()); assertEquals(tasks.length - 2, p.getTaskCount()); - } finally { - done.countDown(); - joinPool(p); } } /** - * shutdownNow returns a list containing tasks that were not run + * shutdownNow returns a list containing tasks that were not run, + * and those tasks are drained from the queue */ - public void testShutdownNow() { + public void testShutdownNow() throws InterruptedException { + final int poolSize = 2; + final int count = 5; + final AtomicInteger ran = new AtomicInteger(0); final ThreadPoolExecutor p = - new ThreadPoolExecutor(1, 1, + new ThreadPoolExecutor(poolSize, poolSize, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List l; - try { - for (int i = 0; i < 5; i++) - p.execute(new MediumPossiblyInterruptedRunnable()); - } - finally { + final CountDownLatch threadsStarted = new CountDownLatch(poolSize); + Runnable waiter = new CheckedRunnable() { public void realRun() { + threadsStarted.countDown(); try { - l = p.shutdownNow(); - } catch (SecurityException ok) { return; } + MILLISECONDS.sleep(2 * LONG_DELAY_MS); + } catch (InterruptedException success) {} + ran.getAndIncrement(); + }}; + for (int i = 0; i < count; i++) + p.execute(waiter); + await(threadsStarted); + assertEquals(poolSize, p.getActiveCount()); + assertEquals(0, p.getCompletedTaskCount()); + final List queuedTasks; + try { + queuedTasks = p.shutdownNow(); + } catch (SecurityException ok) { + return; // Allowed in case test doesn't have privs } assertTrue(p.isShutdown()); - assertTrue(l.size() <= 4); + assertTrue(p.getQueue().isEmpty()); + assertEquals(count - poolSize, queuedTasks.size()); + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertEquals(poolSize, ran.get()); + assertEquals(poolSize, p.getCompletedTaskCount()); } // Exception Tests @@ -655,8 +689,7 @@ public void testShutdownNow() { */ public void testConstructor1() { try { - new ThreadPoolExecutor(-1, 1, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(-1, 1, 1L, SECONDS, new ArrayBlockingQueue(10)); shouldThrow(); } catch (IllegalArgumentException success) {} @@ -667,8 +700,7 @@ public void testConstructor1() { */ public void testConstructor2() { try { - new ThreadPoolExecutor(1, -1, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, -1, 1L, SECONDS, new ArrayBlockingQueue(10)); shouldThrow(); } catch (IllegalArgumentException success) {} @@ -679,8 +711,7 @@ public void testConstructor2() { */ public void testConstructor3() { try { - new ThreadPoolExecutor(1, 0, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, 0, 1L, SECONDS, new ArrayBlockingQueue(10)); shouldThrow(); } catch (IllegalArgumentException success) {} @@ -691,8 +722,7 @@ public void testConstructor3() { */ public void testConstructor4() { try { - new ThreadPoolExecutor(1, 2, - -1L, MILLISECONDS, + new ThreadPoolExecutor(1, 2, -1L, SECONDS, new ArrayBlockingQueue(10)); shouldThrow(); } catch (IllegalArgumentException success) {} @@ -703,8 +733,7 @@ public void testConstructor4() { */ public void testConstructor5() { try { - new ThreadPoolExecutor(2, 1, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(2, 1, 1L, SECONDS, new ArrayBlockingQueue(10)); shouldThrow(); } catch (IllegalArgumentException success) {} @@ -715,8 +744,7 @@ public void testConstructor5() { */ public void testConstructorNullPointerException() { try { - new ThreadPoolExecutor(1, 2, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, 2, 1L, SECONDS, (BlockingQueue) null); shouldThrow(); } catch (NullPointerException success) {} @@ -727,8 +755,7 @@ public void testConstructorNullPointerException() { */ public void testConstructor6() { try { - new ThreadPoolExecutor(-1, 1, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(-1, 1, 1L, SECONDS, new ArrayBlockingQueue(10), new SimpleThreadFactory()); shouldThrow(); @@ -740,8 +767,7 @@ public void testConstructor6() { */ public void testConstructor7() { try { - new ThreadPoolExecutor(1, -1, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, -1, 1L, SECONDS, new ArrayBlockingQueue(10), new SimpleThreadFactory()); shouldThrow(); @@ -753,8 +779,7 @@ public void testConstructor7() { */ public void testConstructor8() { try { - new ThreadPoolExecutor(1, 0, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, 0, 1L, SECONDS, new ArrayBlockingQueue(10), new SimpleThreadFactory()); shouldThrow(); @@ -766,8 +791,7 @@ public void testConstructor8() { */ public void testConstructor9() { try { - new ThreadPoolExecutor(1, 2, - -1L, MILLISECONDS, + new ThreadPoolExecutor(1, 2, -1L, SECONDS, new ArrayBlockingQueue(10), new SimpleThreadFactory()); shouldThrow(); @@ -779,8 +803,7 @@ public void testConstructor9() { */ public void testConstructor10() { try { - new ThreadPoolExecutor(2, 1, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(2, 1, 1L, SECONDS, new ArrayBlockingQueue(10), new SimpleThreadFactory()); shouldThrow(); @@ -792,8 +815,7 @@ public void testConstructor10() { */ public void testConstructorNullPointerException2() { try { - new ThreadPoolExecutor(1, 2, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, 2, 1L, SECONDS, (BlockingQueue) null, new SimpleThreadFactory()); shouldThrow(); @@ -805,8 +827,7 @@ public void testConstructorNullPointerException2() { */ public void testConstructorNullPointerException3() { try { - new ThreadPoolExecutor(1, 2, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, 2, 1L, SECONDS, new ArrayBlockingQueue(10), (ThreadFactory) null); shouldThrow(); @@ -818,8 +839,7 @@ public void testConstructorNullPointerException3() { */ public void testConstructor11() { try { - new ThreadPoolExecutor(-1, 1, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(-1, 1, 1L, SECONDS, new ArrayBlockingQueue(10), new NoOpREHandler()); shouldThrow(); @@ -831,8 +851,7 @@ public void testConstructor11() { */ public void testConstructor12() { try { - new ThreadPoolExecutor(1, -1, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, -1, 1L, SECONDS, new ArrayBlockingQueue(10), new NoOpREHandler()); shouldThrow(); @@ -844,8 +863,7 @@ public void testConstructor12() { */ public void testConstructor13() { try { - new ThreadPoolExecutor(1, 0, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, 0, 1L, SECONDS, new ArrayBlockingQueue(10), new NoOpREHandler()); shouldThrow(); @@ -857,8 +875,7 @@ public void testConstructor13() { */ public void testConstructor14() { try { - new ThreadPoolExecutor(1, 2, - -1L, MILLISECONDS, + new ThreadPoolExecutor(1, 2, -1L, SECONDS, new ArrayBlockingQueue(10), new NoOpREHandler()); shouldThrow(); @@ -870,8 +887,7 @@ public void testConstructor14() { */ public void testConstructor15() { try { - new ThreadPoolExecutor(2, 1, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(2, 1, 1L, SECONDS, new ArrayBlockingQueue(10), new NoOpREHandler()); shouldThrow(); @@ -883,8 +899,7 @@ public void testConstructor15() { */ public void testConstructorNullPointerException4() { try { - new ThreadPoolExecutor(1, 2, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, 2, 1L, SECONDS, (BlockingQueue) null, new NoOpREHandler()); shouldThrow(); @@ -896,8 +911,7 @@ public void testConstructorNullPointerException4() { */ public void testConstructorNullPointerException5() { try { - new ThreadPoolExecutor(1, 2, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, 2, 1L, SECONDS, new ArrayBlockingQueue(10), (RejectedExecutionHandler) null); shouldThrow(); @@ -909,8 +923,7 @@ public void testConstructorNullPointerException5() { */ public void testConstructor16() { try { - new ThreadPoolExecutor(-1, 1, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(-1, 1, 1L, SECONDS, new ArrayBlockingQueue(10), new SimpleThreadFactory(), new NoOpREHandler()); @@ -923,8 +936,7 @@ public void testConstructor16() { */ public void testConstructor17() { try { - new ThreadPoolExecutor(1, -1, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, -1, 1L, SECONDS, new ArrayBlockingQueue(10), new SimpleThreadFactory(), new NoOpREHandler()); @@ -937,8 +949,7 @@ public void testConstructor17() { */ public void testConstructor18() { try { - new ThreadPoolExecutor(1, 0, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, 0, 1L, SECONDS, new ArrayBlockingQueue(10), new SimpleThreadFactory(), new NoOpREHandler()); @@ -951,8 +962,7 @@ public void testConstructor18() { */ public void testConstructor19() { try { - new ThreadPoolExecutor(1, 2, - -1L, MILLISECONDS, + new ThreadPoolExecutor(1, 2, -1L, SECONDS, new ArrayBlockingQueue(10), new SimpleThreadFactory(), new NoOpREHandler()); @@ -965,8 +975,7 @@ public void testConstructor19() { */ public void testConstructor20() { try { - new ThreadPoolExecutor(2, 1, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(2, 1, 1L, SECONDS, new ArrayBlockingQueue(10), new SimpleThreadFactory(), new NoOpREHandler()); @@ -979,8 +988,7 @@ public void testConstructor20() { */ public void testConstructorNullPointerException6() { try { - new ThreadPoolExecutor(1, 2, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, 2, 1L, SECONDS, (BlockingQueue) null, new SimpleThreadFactory(), new NoOpREHandler()); @@ -993,8 +1001,7 @@ public void testConstructorNullPointerException6() { */ public void testConstructorNullPointerException7() { try { - new ThreadPoolExecutor(1, 2, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, 2, 1L, SECONDS, new ArrayBlockingQueue(10), new SimpleThreadFactory(), (RejectedExecutionHandler) null); @@ -1007,8 +1014,7 @@ public void testConstructorNullPointerException7() { */ public void testConstructorNullPointerException8() { try { - new ThreadPoolExecutor(1, 2, - LONG_DELAY_MS, MILLISECONDS, + new ThreadPoolExecutor(1, 2, 1L, SECONDS, new ArrayBlockingQueue(10), (ThreadFactory) null, new NoOpREHandler()); @@ -1020,31 +1026,28 @@ public void testConstructorNullPointerException8() { * get of submitted callable throws InterruptedException if interrupted */ public void testInterruptedSubmit() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, - 60, TimeUnit.SECONDS, + 60, SECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); Thread t = newStartedThread(new CheckedInterruptedRunnable() { public void realRun() throws Exception { Callable task = new CheckedCallable() { public Boolean realCall() throws InterruptedException { threadStarted.countDown(); - done.await(); + await(done); return Boolean.TRUE; }}; p.submit(task).get(); }}); - assertTrue(threadStarted.await(SMALL_DELAY_MS, MILLISECONDS)); + await(threadStarted); t.interrupt(); - awaitTermination(t, MEDIUM_DELAY_MS); - } finally { - done.countDown(); - joinPool(p); + awaitTermination(t); } } @@ -1052,15 +1055,15 @@ public Boolean realCall() throws InterruptedException { * execute throws RejectedExecutionException if saturated. */ public void testSaturatedExecute() { - ThreadPoolExecutor p = + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(1)); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { Runnable task = new CheckedRunnable() { public void realRun() throws InterruptedException { - done.await(); + await(done); }}; for (int i = 0; i < 2; ++i) p.execute(task); @@ -1071,9 +1074,6 @@ public void realRun() throws InterruptedException { } catch (RejectedExecutionException success) {} assertTrue(p.getTaskCount() <= 2); } - } finally { - done.countDown(); - joinPool(p); } } @@ -1081,15 +1081,15 @@ public void realRun() throws InterruptedException { * submit(runnable) throws RejectedExecutionException if saturated. */ public void testSaturatedSubmitRunnable() { - ThreadPoolExecutor p = + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(1)); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { Runnable task = new CheckedRunnable() { public void realRun() throws InterruptedException { - done.await(); + await(done); }}; for (int i = 0; i < 2; ++i) p.submit(task); @@ -1100,9 +1100,6 @@ public void realRun() throws InterruptedException { } catch (RejectedExecutionException success) {} assertTrue(p.getTaskCount() <= 2); } - } finally { - done.countDown(); - joinPool(p); } } @@ -1110,15 +1107,15 @@ public void realRun() throws InterruptedException { * submit(callable) throws RejectedExecutionException if saturated. */ public void testSaturatedSubmitCallable() { - ThreadPoolExecutor p = + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(1)); - final CountDownLatch done = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p, done)) { Runnable task = new CheckedRunnable() { public void realRun() throws InterruptedException { - done.await(); + await(done); }}; for (int i = 0; i < 2; ++i) p.submit(Executors.callable(task)); @@ -1129,9 +1126,6 @@ public void realRun() throws InterruptedException { } catch (RejectedExecutionException success) {} assertTrue(p.getTaskCount() <= 2); } - } finally { - done.countDown(); - joinPool(p); } } @@ -1139,26 +1133,28 @@ public void realRun() throws InterruptedException { * executor using CallerRunsPolicy runs task if saturated. */ public void testSaturatedExecute2() { - RejectedExecutionHandler h = new ThreadPoolExecutor.CallerRunsPolicy(); final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(1), - h); - try { + new ThreadPoolExecutor.CallerRunsPolicy()); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch done = new CountDownLatch(1); + Runnable blocker = new CheckedRunnable() { + public void realRun() throws InterruptedException { + await(done); + }}; + p.execute(blocker); TrackedNoOpRunnable[] tasks = new TrackedNoOpRunnable[5]; - for (int i = 0; i < tasks.length; ++i) + for (int i = 0; i < tasks.length; i++) tasks[i] = new TrackedNoOpRunnable(); - TrackedLongRunnable mr = new TrackedLongRunnable(); - p.execute(mr); - for (int i = 0; i < tasks.length; ++i) + for (int i = 0; i < tasks.length; i++) p.execute(tasks[i]); - for (int i = 1; i < tasks.length; ++i) + for (int i = 1; i < tasks.length; i++) assertTrue(tasks[i].done); - try { p.shutdownNow(); } catch (SecurityException ok) { return; } - } finally { - joinPool(p); + assertFalse(tasks[0].done); // waiting in queue + done.countDown(); } } @@ -1166,67 +1162,72 @@ public void testSaturatedExecute2() { * executor using DiscardPolicy drops task if saturated. */ public void testSaturatedExecute3() { - RejectedExecutionHandler h = new ThreadPoolExecutor.DiscardPolicy(); + final CountDownLatch done = new CountDownLatch(1); + final TrackedNoOpRunnable[] tasks = new TrackedNoOpRunnable[5]; + for (int i = 0; i < tasks.length; ++i) + tasks[i] = new TrackedNoOpRunnable(); final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, - LONG_DELAY_MS, MILLISECONDS, - new ArrayBlockingQueue(1), - h); - try { - TrackedNoOpRunnable[] tasks = new TrackedNoOpRunnable[5]; - for (int i = 0; i < tasks.length; ++i) - tasks[i] = new TrackedNoOpRunnable(); - p.execute(new TrackedLongRunnable()); + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new ThreadPoolExecutor.DiscardPolicy()); + try (PoolCleaner cleaner = cleaner(p, done)) { + p.execute(awaiter(done)); + for (TrackedNoOpRunnable task : tasks) p.execute(task); - for (TrackedNoOpRunnable task : tasks) - assertFalse(task.done); - try { p.shutdownNow(); } catch (SecurityException ok) { return; } - } finally { - joinPool(p); + for (int i = 1; i < tasks.length; i++) + assertFalse(tasks[i].done); } + for (int i = 1; i < tasks.length; i++) + assertFalse(tasks[i].done); + assertTrue(tasks[0].done); // was waiting in queue } /** * executor using DiscardOldestPolicy drops oldest task if saturated. */ public void testSaturatedExecute4() { - RejectedExecutionHandler h = new ThreadPoolExecutor.DiscardOldestPolicy(); + final CountDownLatch done = new CountDownLatch(1); + LatchAwaiter r1 = awaiter(done); + LatchAwaiter r2 = awaiter(done); + LatchAwaiter r3 = awaiter(done); final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(1), - h); - try { - p.execute(new TrackedLongRunnable()); - TrackedLongRunnable r2 = new TrackedLongRunnable(); + new ThreadPoolExecutor.DiscardOldestPolicy()); + try (PoolCleaner cleaner = cleaner(p, done)) { + assertEquals(LatchAwaiter.NEW, r1.state); + assertEquals(LatchAwaiter.NEW, r2.state); + assertEquals(LatchAwaiter.NEW, r3.state); + p.execute(r1); p.execute(r2); assertTrue(p.getQueue().contains(r2)); - TrackedNoOpRunnable r3 = new TrackedNoOpRunnable(); p.execute(r3); assertFalse(p.getQueue().contains(r2)); assertTrue(p.getQueue().contains(r3)); - try { p.shutdownNow(); } catch (SecurityException ok) { return; } - } finally { - joinPool(p); } + assertEquals(LatchAwaiter.DONE, r1.state); + assertEquals(LatchAwaiter.NEW, r2.state); + assertEquals(LatchAwaiter.DONE, r3.state); } /** * execute throws RejectedExecutionException if shutdown */ public void testRejectedExecutionExceptionOnShutdown() { - ThreadPoolExecutor p = + final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(1)); try { p.shutdown(); } catch (SecurityException ok) { return; } - try { - p.execute(new NoOpRunnable()); - shouldThrow(); - } catch (RejectedExecutionException success) {} - - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.execute(new NoOpRunnable()); + shouldThrow(); + } catch (RejectedExecutionException success) {} + } } /** @@ -1240,12 +1241,10 @@ public void testCallerRunsOnShutdown() { new ArrayBlockingQueue(1), h); try { p.shutdown(); } catch (SecurityException ok) { return; } - try { + try (PoolCleaner cleaner = cleaner(p)) { TrackedNoOpRunnable r = new TrackedNoOpRunnable(); p.execute(r); assertFalse(r.done); - } finally { - joinPool(p); } } @@ -1253,20 +1252,17 @@ public void testCallerRunsOnShutdown() { * execute using DiscardPolicy drops task on shutdown */ public void testDiscardOnShutdown() { - RejectedExecutionHandler h = new ThreadPoolExecutor.DiscardPolicy(); - ThreadPoolExecutor p = + final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(1), - h); + new ThreadPoolExecutor.DiscardPolicy()); try { p.shutdown(); } catch (SecurityException ok) { return; } - try { + try (PoolCleaner cleaner = cleaner(p)) { TrackedNoOpRunnable r = new TrackedNoOpRunnable(); p.execute(r); assertFalse(r.done); - } finally { - joinPool(p); } } @@ -1274,20 +1270,17 @@ public void testDiscardOnShutdown() { * execute using DiscardOldestPolicy drops task on shutdown */ public void testDiscardOldestOnShutdown() { - RejectedExecutionHandler h = new ThreadPoolExecutor.DiscardOldestPolicy(); - ThreadPoolExecutor p = + final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 1, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(1), - h); + new ThreadPoolExecutor.DiscardOldestPolicy()); try { p.shutdown(); } catch (SecurityException ok) { return; } - try { + try (PoolCleaner cleaner = cleaner(p)) { TrackedNoOpRunnable r = new TrackedNoOpRunnable(); p.execute(r); assertFalse(r.done); - } finally { - joinPool(p); } } @@ -1295,34 +1288,32 @@ public void testDiscardOldestOnShutdown() { * execute(null) throws NPE */ public void testExecuteNull() { - ThreadPoolExecutor p = + final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 2, - LONG_DELAY_MS, MILLISECONDS, + 1L, SECONDS, new ArrayBlockingQueue(10)); - try { - p.execute(null); - shouldThrow(); - } catch (NullPointerException success) {} - - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.execute(null); + shouldThrow(); + } catch (NullPointerException success) {} + } } /** * setCorePoolSize of negative value throws IllegalArgumentException */ public void testCorePoolSizeIllegalArgumentException() { - ThreadPoolExecutor p = + final ThreadPoolExecutor p = new ThreadPoolExecutor(1, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - p.setCorePoolSize(-1); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - try { p.shutdown(); } catch (SecurityException ok) { return; } + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setCorePoolSize(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} } - joinPool(p); } /** @@ -1330,18 +1321,16 @@ public void testCorePoolSizeIllegalArgumentException() { * given a value less the core pool size */ public void testMaximumPoolSizeIllegalArgumentException() { - ThreadPoolExecutor p = + final ThreadPoolExecutor p = new ThreadPoolExecutor(2, 3, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - p.setMaximumPoolSize(1); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - try { p.shutdown(); } catch (SecurityException ok) { return; } + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setMaximumPoolSize(1); + shouldThrow(); + } catch (IllegalArgumentException success) {} } - joinPool(p); } /** @@ -1349,18 +1338,45 @@ public void testMaximumPoolSizeIllegalArgumentException() { * if given a negative value */ public void testMaximumPoolSizeIllegalArgumentException2() { - ThreadPoolExecutor p = + final ThreadPoolExecutor p = new ThreadPoolExecutor(2, 3, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - p.setMaximumPoolSize(-1); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - try { p.shutdown(); } catch (SecurityException ok) { return; } + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setMaximumPoolSize(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * Configuration changes that allow core pool size greater than + * max pool size result in IllegalArgumentException. + */ + public void testPoolSizeInvariants() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + for (int s = 1; s < 5; s++) { + p.setMaximumPoolSize(s); + p.setCorePoolSize(s); + try { + p.setMaximumPoolSize(s - 1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + assertEquals(s, p.getCorePoolSize()); + assertEquals(s, p.getMaximumPoolSize()); + try { + p.setCorePoolSize(s + 1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + assertEquals(s, p.getCorePoolSize()); + assertEquals(s, p.getMaximumPoolSize()); + } } - joinPool(p); } /** @@ -1368,18 +1384,16 @@ public void testMaximumPoolSizeIllegalArgumentException2() { * when given a negative value */ public void testKeepAliveTimeIllegalArgumentException() { - ThreadPoolExecutor p = + final ThreadPoolExecutor p = new ThreadPoolExecutor(2, 3, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - p.setKeepAliveTime(-1,MILLISECONDS); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - try { p.shutdown(); } catch (SecurityException ok) { return; } + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setKeepAliveTime(-1, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} } - joinPool(p); } /** @@ -1387,9 +1401,11 @@ public void testKeepAliveTimeIllegalArgumentException() { */ public void testTerminated() { ExtendedTPE p = new ExtendedTPE(); - try { p.shutdown(); } catch (SecurityException ok) { return; } - assertTrue(p.terminatedCalled()); - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.terminatedCalled()); + assertTrue(p.isShutdown()); + } } /** @@ -1397,7 +1413,7 @@ public void testTerminated() { */ public void testBeforeAfter() throws InterruptedException { ExtendedTPE p = new ExtendedTPE(); - try { + try (PoolCleaner cleaner = cleaner(p)) { final CountDownLatch done = new CountDownLatch(1); p.execute(new CheckedRunnable() { public void realRun() { @@ -1407,9 +1423,6 @@ public void realRun() { assertEquals(0, done.getCount()); assertTrue(p.afterCalled()); assertTrue(p.beforeCalled()); - try { p.shutdown(); } catch (SecurityException ok) { return; } - } finally { - joinPool(p); } } @@ -1417,16 +1430,14 @@ public void realRun() { * completed submit of callable returns result */ public void testSubmitCallable() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new StringTask()); String result = future.get(); assertSame(TEST_STRING, result); - } finally { - joinPool(e); } } @@ -1434,16 +1445,14 @@ public void testSubmitCallable() throws Exception { * completed submit of runnable returns successfully */ public void testSubmitRunnable() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new NoOpRunnable()); future.get(); assertTrue(future.isDone()); - } finally { - joinPool(e); } } @@ -1451,16 +1460,14 @@ public void testSubmitRunnable() throws Exception { * completed submit of (runnable, result) returns result */ public void testSubmitRunnable2() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + try (PoolCleaner cleaner = cleaner(e)) { Future future = e.submit(new NoOpRunnable(), TEST_STRING); String result = future.get(); assertSame(TEST_STRING, result); - } finally { - joinPool(e); } } @@ -1468,16 +1475,15 @@ public void testSubmitRunnable2() throws Exception { * invokeAny(null) throws NPE */ public void testInvokeAny1() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - e.invokeAny(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1485,16 +1491,15 @@ public void testInvokeAny1() throws Exception { * invokeAny(empty collection) throws IAE */ public void testInvokeAny2() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - e.invokeAny(new ArrayList>()); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>()); + shouldThrow(); + } catch (IllegalArgumentException success) {} } } @@ -1507,16 +1512,15 @@ public void testInvokeAny3() throws Exception { new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(latchAwaitingStringTask(latch)); - l.add(null); - try { - e.invokeAny(l); - shouldThrow(); - } catch (NullPointerException success) { - } finally { + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l); + shouldThrow(); + } catch (NullPointerException success) {} latch.countDown(); - joinPool(e); } } @@ -1524,19 +1528,19 @@ public void testInvokeAny3() throws Exception { * invokeAny(c) throws ExecutionException if no task completes */ public void testInvokeAny4() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new NPETask()); - try { - e.invokeAny(l); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -1544,18 +1548,16 @@ public void testInvokeAny4() throws Exception { * invokeAny(c) returns result of some task */ public void testInvokeAny5() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); String result = e.invokeAny(l); assertSame(TEST_STRING, result); - } finally { - joinPool(e); } } @@ -1563,16 +1565,15 @@ public void testInvokeAny5() throws Exception { * invokeAll(null) throws NPE */ public void testInvokeAll1() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - e.invokeAll(null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1580,15 +1581,13 @@ public void testInvokeAll1() throws Exception { * invokeAll(empty collection) returns empty collection */ public void testInvokeAll2() throws InterruptedException { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + try (PoolCleaner cleaner = cleaner(e)) { List> r = e.invokeAll(new ArrayList>()); assertTrue(r.isEmpty()); - } finally { - joinPool(e); } } @@ -1596,19 +1595,18 @@ public void testInvokeAll2() throws InterruptedException { * invokeAll(c) throws NPE if c has null elements */ public void testInvokeAll3() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(null); - try { - e.invokeAll(l); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1616,11 +1614,11 @@ public void testInvokeAll3() throws Exception { * get of element of invokeAll(c) throws exception on failed task */ public void testInvokeAll4() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new NPETask()); List> futures = e.invokeAll(l); @@ -1631,8 +1629,6 @@ public void testInvokeAll4() throws Exception { } catch (ExecutionException success) { assertTrue(success.getCause() instanceof NullPointerException); } - } finally { - joinPool(e); } } @@ -1640,11 +1636,11 @@ public void testInvokeAll4() throws Exception { * invokeAll(c) returns results of all completed tasks */ public void testInvokeAll5() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); @@ -1652,8 +1648,6 @@ public void testInvokeAll5() throws Exception { assertEquals(2, futures.size()); for (Future future : futures) assertSame(TEST_STRING, future.get()); - } finally { - joinPool(e); } } @@ -1661,16 +1655,15 @@ public void testInvokeAll5() throws Exception { * timed invokeAny(null) throws NPE */ public void testTimedInvokeAny1() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1678,18 +1671,17 @@ public void testTimedInvokeAny1() throws Exception { * timed invokeAny(,,null) throws NPE */ public void testTimedInvokeAnyNullTimeUnit() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new StringTask()); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1697,16 +1689,16 @@ public void testTimedInvokeAnyNullTimeUnit() throws Exception { * timed invokeAny(empty collection) throws IAE */ public void testTimedInvokeAny2() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - e.invokeAny(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (IllegalArgumentException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} } } @@ -1719,16 +1711,15 @@ public void testTimedInvokeAny3() throws Exception { new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(latchAwaitingStringTask(latch)); - l.add(null); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} latch.countDown(); - joinPool(e); } } @@ -1736,19 +1727,21 @@ public void testTimedInvokeAny3() throws Exception { * timed invokeAny(c) throws ExecutionException if no task completes */ public void testTimedInvokeAny4() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new NPETask()); - try { - e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } } @@ -1756,18 +1749,18 @@ public void testTimedInvokeAny4() throws Exception { * timed invokeAny(c) returns result of some task */ public void testTimedInvokeAny5() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); - String result = e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + String result = e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); assertSame(TEST_STRING, result); - } finally { - joinPool(e); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); } } @@ -1775,16 +1768,15 @@ public void testTimedInvokeAny5() throws Exception { * timed invokeAll(null) throws NPE */ public void testTimedInvokeAll1() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1792,18 +1784,17 @@ public void testTimedInvokeAll1() throws Exception { * timed invokeAll(,,null) throws NPE */ public void testTimedInvokeAllNullTimeUnit() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new StringTask()); - try { - e.invokeAll(l, MEDIUM_DELAY_MS, null); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1811,15 +1802,14 @@ public void testTimedInvokeAllNullTimeUnit() throws Exception { * timed invokeAll(empty collection) returns empty collection */ public void testTimedInvokeAll2() throws InterruptedException { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { - List> r = e.invokeAll(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); + try (PoolCleaner cleaner = cleaner(e)) { + List> r = e.invokeAll(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); assertTrue(r.isEmpty()); - } finally { - joinPool(e); } } @@ -1827,19 +1817,18 @@ public void testTimedInvokeAll2() throws InterruptedException { * timed invokeAll(c) throws NPE if c has null elements */ public void testTimedInvokeAll3() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(null); - try { - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); - shouldThrow(); - } catch (NullPointerException success) { - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} } } @@ -1847,22 +1836,22 @@ public void testTimedInvokeAll3() throws Exception { * get of element of invokeAll(c) throws exception on failed task */ public void testTimedInvokeAll4() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - List> l = new ArrayList>(); - l.add(new NPETask()); - List> futures = - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); - assertEquals(1, futures.size()); - try { - futures.get(0).get(); - shouldThrow(); - } catch (ExecutionException success) { - assertTrue(success.getCause() instanceof NullPointerException); - } finally { - joinPool(e); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } } } @@ -1870,21 +1859,19 @@ public void testTimedInvokeAll4() throws Exception { * timed invokeAll(c) returns results of all completed tasks */ public void testTimedInvokeAll5() throws Exception { - ExecutorService e = + final ExecutorService e = new ThreadPoolExecutor(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue(10)); - try { + try (PoolCleaner cleaner = cleaner(e)) { List> l = new ArrayList>(); l.add(new StringTask()); l.add(new StringTask()); List> futures = - e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); assertEquals(2, futures.size()); for (Future future : futures) assertSame(TEST_STRING, future.get()); - } finally { - joinPool(e); } } @@ -1892,24 +1879,40 @@ public void testTimedInvokeAll5() throws Exception { * timed invokeAll(c) cancels tasks not completed by timeout */ public void testTimedInvokeAll6() throws Exception { - ExecutorService e = - new ThreadPoolExecutor(2, 2, - LONG_DELAY_MS, MILLISECONDS, - new ArrayBlockingQueue(10)); - try { - List> l = new ArrayList>(); - l.add(new StringTask()); - l.add(Executors.callable(new MediumPossiblyInterruptedRunnable(), TEST_STRING)); - l.add(new StringTask()); - List> futures = - e.invokeAll(l, SHORT_DELAY_MS, MILLISECONDS); - assertEquals(l.size(), futures.size()); - for (Future future : futures) - assertTrue(future.isDone()); - assertFalse(futures.get(0).isCancelled()); - assertTrue(futures.get(1).isCancelled()); - } finally { - joinPool(e); + for (long timeout = timeoutMillis();;) { + final CountDownLatch done = new CountDownLatch(1); + final Callable waiter = new CheckedCallable() { + public String realCall() { + try { done.await(LONG_DELAY_MS, MILLISECONDS); } + catch (InterruptedException ok) {} + return "1"; }}; + final ExecutorService p = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p, done)) { + List> tasks = new ArrayList<>(); + tasks.add(new StringTask("0")); + tasks.add(waiter); + tasks.add(new StringTask("2")); + long startTime = System.nanoTime(); + List> futures = + p.invokeAll(tasks, timeout, MILLISECONDS); + assertEquals(tasks.size(), futures.size()); + assertTrue(millisElapsedSince(startTime) >= timeout); + for (Future future : futures) + assertTrue(future.isDone()); + assertTrue(futures.get(1).isCancelled()); + try { + assertEquals("0", futures.get(0).get()); + assertEquals("2", futures.get(2).get()); + break; + } catch (CancellationException retryWithLongerTimeout) { + timeout *= 2; + if (timeout >= LONG_DELAY_MS / 2) + fail("expected exactly one task to be cancelled"); + } + } } } @@ -1923,7 +1926,7 @@ public void testFailingThreadFactory() throws InterruptedException { LONG_DELAY_MS, MILLISECONDS, new LinkedBlockingQueue(), new FailingThreadFactory()); - try { + try (PoolCleaner cleaner = cleaner(e)) { final int TASKS = 100; final CountDownLatch done = new CountDownLatch(TASKS); for (int k = 0; k < TASKS; ++k) @@ -1932,8 +1935,6 @@ public void realRun() { done.countDown(); }}); assertTrue(done.await(LONG_DELAY_MS, MILLISECONDS)); - } finally { - joinPool(e); } } @@ -1945,21 +1946,22 @@ public void testAllowsCoreThreadTimeOut() { new ThreadPoolExecutor(2, 2, 1000, MILLISECONDS, new ArrayBlockingQueue(10)); - assertFalse(p.allowsCoreThreadTimeOut()); - joinPool(p); + try (PoolCleaner cleaner = cleaner(p)) { + assertFalse(p.allowsCoreThreadTimeOut()); + } } /** * allowCoreThreadTimeOut(true) causes idle threads to time out */ public void testAllowCoreThreadTimeOut_true() throws Exception { - long coreThreadTimeOut = SHORT_DELAY_MS; + long keepAliveTime = timeoutMillis(); final ThreadPoolExecutor p = new ThreadPoolExecutor(2, 10, - coreThreadTimeOut, MILLISECONDS, + keepAliveTime, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); p.allowCoreThreadTimeOut(true); p.execute(new CheckedRunnable() { public void realRun() { @@ -1967,15 +1969,13 @@ public void realRun() { assertEquals(1, p.getPoolSize()); }}); await(threadStarted); - delay(coreThreadTimeOut); + delay(keepAliveTime); long startTime = System.nanoTime(); while (p.getPoolSize() > 0 && millisElapsedSince(startTime) < LONG_DELAY_MS) Thread.yield(); assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); assertEquals(0, p.getPoolSize()); - } finally { - joinPool(p); } } @@ -1983,23 +1983,21 @@ && millisElapsedSince(startTime) < LONG_DELAY_MS) * allowCoreThreadTimeOut(false) causes idle threads not to time out */ public void testAllowCoreThreadTimeOut_false() throws Exception { - long coreThreadTimeOut = SHORT_DELAY_MS; + long keepAliveTime = timeoutMillis(); final ThreadPoolExecutor p = new ThreadPoolExecutor(2, 10, - coreThreadTimeOut, MILLISECONDS, + keepAliveTime, MILLISECONDS, new ArrayBlockingQueue(10)); - final CountDownLatch threadStarted = new CountDownLatch(1); - try { + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); p.allowCoreThreadTimeOut(false); p.execute(new CheckedRunnable() { public void realRun() throws InterruptedException { threadStarted.countDown(); assertTrue(p.getPoolSize() >= 1); }}); - delay(2 * coreThreadTimeOut); + delay(2 * keepAliveTime); assertTrue(p.getPoolSize() >= 1); - } finally { - joinPool(p); } } @@ -2015,9 +2013,10 @@ public void run() { done.countDown(); }}; final ThreadPoolExecutor p = - new ThreadPoolExecutor(1, 30, 60, TimeUnit.SECONDS, + new ThreadPoolExecutor(1, 30, + 60, SECONDS, new ArrayBlockingQueue(30)); - try { + try (PoolCleaner cleaner = cleaner(p)) { for (int i = 0; i < nTasks; ++i) { for (;;) { try { @@ -2029,8 +2028,43 @@ public void run() { } // enough time to run all tasks assertTrue(done.await(nTasks * SHORT_DELAY_MS, MILLISECONDS)); - } finally { - joinPool(p); + } + } + + /** + * get(cancelled task) throws CancellationException + */ + public void testGet_cancelled() throws Exception { + final CountDownLatch done = new CountDownLatch(1); + final ExecutorService e = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new LinkedBlockingQueue()); + try (PoolCleaner cleaner = cleaner(e, done)) { + final CountDownLatch blockerStarted = new CountDownLatch(1); + final List> futures = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + Runnable r = new CheckedRunnable() { public void realRun() + throws Throwable { + blockerStarted.countDown(); + assertTrue(done.await(2 * LONG_DELAY_MS, MILLISECONDS)); + }}; + futures.add(e.submit(r)); + } + await(blockerStarted); + for (Future future : futures) future.cancel(false); + for (Future future : futures) { + try { + future.get(); + shouldThrow(); + } catch (CancellationException success) {} + try { + future.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (CancellationException success) {} + assertTrue(future.isCancelled()); + assertTrue(future.isDone()); + } } } diff --git a/jsr166-tests/src/test/java/jsr166/ThreadTest.java b/jsr166-tests/src/test/java/jsr166/ThreadTest.java index 27f22cafa..e69b42203 100644 --- a/jsr166-tests/src/test/java/jsr166/ThreadTest.java +++ b/jsr166-tests/src/test/java/jsr166/ThreadTest.java @@ -19,7 +19,7 @@ public class ThreadTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(ThreadTest.class); // } static class MyHandler implements Thread.UncaughtExceptionHandler { @@ -57,8 +57,7 @@ public void testGetAndSetDefaultUncaughtExceptionHandler() { // default uncaught exception handler installed by the framework. // // assertEquals(null, Thread.getDefaultUncaughtExceptionHandler()); - - // failure due to securityException is OK. + // failure due to SecurityException is OK. // Would be nice to explicitly test both ways, but cannot yet. Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); diff --git a/jsr166-tests/src/test/java/jsr166/TimeUnitTest.java b/jsr166-tests/src/test/java/jsr166/TimeUnitTest.java index 2c9529b43..b21fa7d1a 100644 --- a/jsr166-tests/src/test/java/jsr166/TimeUnitTest.java +++ b/jsr166-tests/src/test/java/jsr166/TimeUnitTest.java @@ -30,7 +30,7 @@ public class TimeUnitTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(TimeUnitTest.class); // } // (loops to 88888 check increments at all time divisions.) @@ -433,8 +433,8 @@ public void realRun() throws InterruptedException { * a deserialized serialized unit is the same instance */ public void testSerialization() throws Exception { - TimeUnit x = MILLISECONDS; - assertSame(x, serialClone(x)); + for (TimeUnit x : TimeUnit.values()) + assertSame(x, serialClone(x)); } } diff --git a/jsr166-tests/src/test/java/jsr166/TreeMapTest.java b/jsr166-tests/src/test/java/jsr166/TreeMapTest.java index afc73dea2..e4456090b 100644 --- a/jsr166-tests/src/test/java/jsr166/TreeMapTest.java +++ b/jsr166-tests/src/test/java/jsr166/TreeMapTest.java @@ -29,7 +29,7 @@ public class TreeMapTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(TreeMapTest.class); // } /** diff --git a/jsr166-tests/src/test/java/jsr166/TreeSetTest.java b/jsr166-tests/src/test/java/jsr166/TreeSetTest.java index a93563743..c3093f6c7 100644 --- a/jsr166-tests/src/test/java/jsr166/TreeSetTest.java +++ b/jsr166-tests/src/test/java/jsr166/TreeSetTest.java @@ -29,7 +29,7 @@ public class TreeSetTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(TreeSetTest.class); // } static class MyReverseComparator implements Comparator { @@ -50,7 +50,7 @@ public int compare(Object x, Object y) { private TreeSet populatedSet(int n) { TreeSet q = new TreeSet(); assertTrue(q.isEmpty()); - for (int i = n-1; i >= 0; i -= 2) + for (int i = n - 1; i >= 0; i -= 2) assertTrue(q.add(new Integer(i))); for (int i = (n & 1); i < n; i += 2) assertTrue(q.add(new Integer(i))); @@ -96,8 +96,7 @@ public void testConstructor3() { */ public void testConstructor4() { try { - Integer[] ints = new Integer[SIZE]; - new TreeSet(Arrays.asList(ints)); + new TreeSet(Arrays.asList(new Integer[SIZE])); shouldThrow(); } catch (NullPointerException success) {} } @@ -106,10 +105,10 @@ public void testConstructor4() { * Initializing from Collection with some null elements throws NPE */ public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); try { - Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i); new TreeSet(Arrays.asList(ints)); shouldThrow(); } catch (NullPointerException success) {} @@ -138,7 +137,7 @@ public void testConstructor7() { for (int i = 0; i < SIZE; ++i) ints[i] = new Integer(i); q.addAll(Arrays.asList(ints)); - for (int i = SIZE-1; i >= 0; --i) + for (int i = SIZE - 1; i >= 0; --i) assertEquals(ints[i], q.pollFirst()); } @@ -162,7 +161,7 @@ public void testEmpty() { public void testSize() { TreeSet q = populatedSet(SIZE); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); q.pollFirst(); } for (int i = 0; i < SIZE; ++i) { @@ -242,7 +241,7 @@ public void testAddAll2() { public void testAddAll3() { TreeSet q = new TreeSet(); Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) + for (int i = 0; i < SIZE - 1; ++i) ints[i] = new Integer(i); try { q.addAll(Arrays.asList(ints)); @@ -257,7 +256,7 @@ public void testAddAll5() { Integer[] empty = new Integer[0]; Integer[] ints = new Integer[SIZE]; for (int i = 0; i < SIZE; ++i) - ints[i] = new Integer(SIZE-1-i); + ints[i] = new Integer(SIZE - 1 - i); TreeSet q = new TreeSet(); assertFalse(q.addAll(Arrays.asList(empty))); assertTrue(q.addAll(Arrays.asList(ints))); @@ -281,7 +280,7 @@ public void testPollFirst() { */ public void testPollLast() { TreeSet q = populatedSet(SIZE); - for (int i = SIZE-1; i >= 0; --i) { + for (int i = SIZE - 1; i >= 0; --i) { assertEquals(i, q.pollLast()); } assertNull(q.pollFirst()); @@ -296,14 +295,14 @@ public void testRemoveElement() { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertTrue(q.contains(i-1)); + assertTrue(q.contains(i - 1)); } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertFalse(q.remove(i+1)); - assertFalse(q.contains(i+1)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); } assertTrue(q.isEmpty()); } @@ -362,7 +361,7 @@ public void testRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.pollFirst(); } } @@ -375,7 +374,7 @@ public void testRemoveAll() { TreeSet q = populatedSet(SIZE); TreeSet p = populatedSet(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { Integer x = (Integer)(p.pollFirst()); assertFalse(q.contains(x)); @@ -909,18 +908,18 @@ int ceilingAscending(int element) { else if (element > max) return -1; int result = bs.nextSetBit(element); - return result > max ? -1 : result; + return (result > max) ? -1 : result; } int higherAscending(int element) { return ceilingAscending(element + 1); } private int firstAscending() { int result = ceilingAscending(min); - return result > max ? -1 : result; + return (result > max) ? -1 : result; } private int lastAscending() { int result = floorAscending(max); - return result < min ? -1 : result; + return (result < min) ? -1 : result; } } ReferenceSet rs = new ReferenceSet(); @@ -981,7 +980,7 @@ static void assertEq(Integer i, int j) { } static boolean eq(Integer i, int j) { - return i == null ? j == -1 : i == j; + return (i == null) ? j == -1 : i == j; } } diff --git a/jsr166-tests/src/test/java/jsr166/TreeSubMapTest.java b/jsr166-tests/src/test/java/jsr166/TreeSubMapTest.java index 18a9e37e9..09b809e6e 100644 --- a/jsr166-tests/src/test/java/jsr166/TreeSubMapTest.java +++ b/jsr166-tests/src/test/java/jsr166/TreeSubMapTest.java @@ -27,7 +27,7 @@ public class TreeSubMapTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(TreeSubMapTest.class); // } /** diff --git a/jsr166-tests/src/test/java/jsr166/TreeSubSetTest.java b/jsr166-tests/src/test/java/jsr166/TreeSubSetTest.java index 5398c4ece..31403bea7 100644 --- a/jsr166-tests/src/test/java/jsr166/TreeSubSetTest.java +++ b/jsr166-tests/src/test/java/jsr166/TreeSubSetTest.java @@ -25,7 +25,7 @@ public class TreeSubSetTest extends JSR166TestCase { // main(suite(), args); // } // public static Test suite() { - // return new TestSuite(...); + // return new TestSuite(TreeSubSetTest.class); // } static class MyReverseComparator implements Comparator { @@ -42,7 +42,7 @@ private NavigableSet populatedSet(int n) { TreeSet q = new TreeSet(); assertTrue(q.isEmpty()); - for (int i = n-1; i >= 0; i -= 2) + for (int i = n - 1; i >= 0; i -= 2) assertTrue(q.add(new Integer(i))); for (int i = (n & 1); i < n; i += 2) assertTrue(q.add(new Integer(i))); @@ -124,7 +124,7 @@ public void testEmpty() { public void testSize() { NavigableSet q = populatedSet(SIZE); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); q.pollFirst(); } for (int i = 0; i < SIZE; ++i) { @@ -203,8 +203,8 @@ public void testAddAll2() { public void testAddAll3() { NavigableSet q = set0(); Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i+SIZE); + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i + SIZE); try { q.addAll(Arrays.asList(ints)); shouldThrow(); @@ -218,7 +218,7 @@ public void testAddAll5() { Integer[] empty = new Integer[0]; Integer[] ints = new Integer[SIZE]; for (int i = 0; i < SIZE; ++i) - ints[i] = new Integer(SIZE-1- i); + ints[i] = new Integer(SIZE - 1 - i); NavigableSet q = set0(); assertFalse(q.addAll(Arrays.asList(empty))); assertTrue(q.addAll(Arrays.asList(ints))); @@ -246,14 +246,14 @@ public void testRemoveElement() { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertTrue(q.contains(i-1)); + assertTrue(q.contains(i - 1)); } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.contains(i)); assertTrue(q.remove(i)); assertFalse(q.contains(i)); - assertFalse(q.remove(i+1)); - assertFalse(q.contains(i+1)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); } assertTrue(q.isEmpty()); } @@ -312,7 +312,7 @@ public void testRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.pollFirst(); } } @@ -325,7 +325,7 @@ public void testRemoveAll() { NavigableSet q = populatedSet(SIZE); NavigableSet p = populatedSet(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { Integer x = (Integer)(p.pollFirst()); assertFalse(q.contains(x)); @@ -620,7 +620,7 @@ public void testTailSetContents() { public void testDescendingSize() { NavigableSet q = populatedSet(SIZE); for (int i = 0; i < SIZE; ++i) { - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); q.pollFirst(); } for (int i = 0; i < SIZE; ++i) { @@ -688,8 +688,8 @@ public void testDescendingAddAll2() { public void testDescendingAddAll3() { NavigableSet q = dset0(); Integer[] ints = new Integer[SIZE]; - for (int i = 0; i < SIZE-1; ++i) - ints[i] = new Integer(i+SIZE); + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i + SIZE); try { q.addAll(Arrays.asList(ints)); shouldThrow(); @@ -703,7 +703,7 @@ public void testDescendingAddAll5() { Integer[] empty = new Integer[0]; Integer[] ints = new Integer[SIZE]; for (int i = 0; i < SIZE; ++i) - ints[i] = new Integer(SIZE-1- i); + ints[i] = new Integer(SIZE - 1 - i); NavigableSet q = dset0(); assertFalse(q.addAll(Arrays.asList(empty))); assertTrue(q.addAll(Arrays.asList(ints))); @@ -732,7 +732,7 @@ public void testDescendingRemoveElement() { } for (int i = 0; i < SIZE; i += 2) { assertTrue(q.remove(new Integer(i))); - assertFalse(q.remove(new Integer(i+1))); + assertFalse(q.remove(new Integer(i + 1))); } assertTrue(q.isEmpty()); } @@ -791,7 +791,7 @@ public void testDescendingRetainAll() { assertTrue(changed); assertTrue(q.containsAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); p.pollFirst(); } } @@ -804,7 +804,7 @@ public void testDescendingRemoveAll() { NavigableSet q = populatedSet(SIZE); NavigableSet p = populatedSet(i); assertTrue(q.removeAll(p)); - assertEquals(SIZE-i, q.size()); + assertEquals(SIZE - i, q.size()); for (int j = 0; j < i; ++j) { Integer x = (Integer)(p.pollFirst()); assertFalse(q.contains(x)); diff --git a/luni/src/main/java/java/util/concurrent/AbstractExecutorService.java b/luni/src/main/java/java/util/concurrent/AbstractExecutorService.java index f444b2946..213baf2a9 100644 --- a/luni/src/main/java/java/util/concurrent/AbstractExecutorService.java +++ b/luni/src/main/java/java/util/concurrent/AbstractExecutorService.java @@ -6,7 +6,12 @@ package java.util.concurrent; -import java.util.*; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; /** * Provides default implementations of {@link ExecutorService} @@ -23,7 +28,7 @@ *

Extension example. Here is a sketch of a class * that customizes {@link ThreadPoolExecutor} to use * a {@code CustomTask} class instead of the default {@code FutureTask}: - *

 {@code
+ * 
 {@code
  * public class CustomThreadPoolExecutor extends ThreadPoolExecutor {
  *
  *   static class CustomTask implements RunnableFuture {...}
@@ -118,7 +123,7 @@ private  T doInvokeAny(Collection> tasks,
         int ntasks = tasks.size();
         if (ntasks == 0)
             throw new IllegalArgumentException();
-        ArrayList> futures = new ArrayList>(ntasks);
+        ArrayList> futures = new ArrayList<>(ntasks);
         ExecutorCompletionService ecs =
             new ExecutorCompletionService(this);
 
@@ -151,7 +156,7 @@ private  T doInvokeAny(Collection> tasks,
                     else if (active == 0)
                         break;
                     else if (timed) {
-                        f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
+                        f = ecs.poll(nanos, NANOSECONDS);
                         if (f == null)
                             throw new TimeoutException();
                         nanos = deadline - System.nanoTime();
@@ -176,8 +181,7 @@ else if (timed) {
             throw ee;
 
         } finally {
-            for (int i = 0, size = futures.size(); i < size; i++)
-                futures.get(i).cancel(true);
+            cancelAll(futures);
         }
     }
 
@@ -201,8 +205,7 @@ public  List> invokeAll(Collection> tasks)
         throws InterruptedException {
         if (tasks == null)
             throw new NullPointerException();
-        ArrayList> futures = new ArrayList>(tasks.size());
-        boolean done = false;
+        ArrayList> futures = new ArrayList<>(tasks.size());
         try {
             for (Callable t : tasks) {
                 RunnableFuture f = newTaskFor(t);
@@ -212,19 +215,15 @@ public  List> invokeAll(Collection> tasks)
             for (int i = 0, size = futures.size(); i < size; i++) {
                 Future f = futures.get(i);
                 if (!f.isDone()) {
-                    try {
-                        f.get();
-                    } catch (CancellationException ignore) {
-                    } catch (ExecutionException ignore) {
-                    }
+                    try { f.get(); }
+                    catch (CancellationException ignore) {}
+                    catch (ExecutionException ignore) {}
                 }
             }
-            done = true;
             return futures;
-        } finally {
-            if (!done)
-                for (int i = 0, size = futures.size(); i < size; i++)
-                    futures.get(i).cancel(true);
+        } catch (Throwable t) {
+            cancelAll(futures);
+            throw t;
         }
     }
 
@@ -233,47 +232,52 @@ public  List> invokeAll(Collection> tasks,
         throws InterruptedException {
         if (tasks == null)
             throw new NullPointerException();
-        long nanos = unit.toNanos(timeout);
-        ArrayList> futures = new ArrayList>(tasks.size());
-        boolean done = false;
-        try {
+        final long nanos = unit.toNanos(timeout);
+        final long deadline = System.nanoTime() + nanos;
+        ArrayList> futures = new ArrayList<>(tasks.size());
+        int j = 0;
+        timedOut: try {
             for (Callable t : tasks)
                 futures.add(newTaskFor(t));
 
-            final long deadline = System.nanoTime() + nanos;
             final int size = futures.size();
 
             // Interleave time checks and calls to execute in case
             // executor doesn't have any/much parallelism.
             for (int i = 0; i < size; i++) {
+                if (((i == 0) ? nanos : deadline - System.nanoTime()) <= 0L)
+                    break timedOut;
                 execute((Runnable)futures.get(i));
-                nanos = deadline - System.nanoTime();
-                if (nanos <= 0L)
-                    return futures;
             }
 
-            for (int i = 0; i < size; i++) {
-                Future f = futures.get(i);
+            for (; j < size; j++) {
+                Future f = futures.get(j);
                 if (!f.isDone()) {
-                    if (nanos <= 0L)
-                        return futures;
-                    try {
-                        f.get(nanos, TimeUnit.NANOSECONDS);
-                    } catch (CancellationException ignore) {
-                    } catch (ExecutionException ignore) {
-                    } catch (TimeoutException toe) {
-                        return futures;
+                    try { f.get(deadline - System.nanoTime(), NANOSECONDS); }
+                    catch (CancellationException ignore) {}
+                    catch (ExecutionException ignore) {}
+                    catch (TimeoutException timedOut) {
+                        break timedOut;
                     }
-                    nanos = deadline - System.nanoTime();
                 }
             }
-            done = true;
             return futures;
-        } finally {
-            if (!done)
-                for (int i = 0, size = futures.size(); i < size; i++)
-                    futures.get(i).cancel(true);
+        } catch (Throwable t) {
+            cancelAll(futures);
+            throw t;
         }
+        // Timed out before all the tasks could be completed; cancel remaining
+        cancelAll(futures, j);
+        return futures;
     }
 
+    private static  void cancelAll(ArrayList> futures) {
+        cancelAll(futures, 0);
+    }
+
+    /** Cancels all futures with index at least j. */
+    private static  void cancelAll(ArrayList> futures, int j) {
+        for (int size = futures.size(); j < size; j++)
+            futures.get(j).cancel(true);
+    }
 }
diff --git a/luni/src/main/java/java/util/concurrent/ArrayBlockingQueue.java b/luni/src/main/java/java/util/concurrent/ArrayBlockingQueue.java
index 9dca1b3d7..3183c01f6 100644
--- a/luni/src/main/java/java/util/concurrent/ArrayBlockingQueue.java
+++ b/luni/src/main/java/java/util/concurrent/ArrayBlockingQueue.java
@@ -7,11 +7,14 @@
 package java.util.concurrent;
 
 import java.lang.ref.WeakReference;
-import java.util.Arrays;
 import java.util.AbstractQueue;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Spliterator;
+import java.util.Spliterators;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -92,7 +95,7 @@ public class ArrayBlockingQueue extends AbstractQueue
      * are known not to be any.  Allows queue operations to update
      * iterator state.
      */
-    transient Itrs itrs = null;
+    transient Itrs itrs;
 
     // Internal helper methods
 
@@ -237,10 +240,8 @@ public ArrayBlockingQueue(int capacity, boolean fair,
         try {
             int i = 0;
             try {
-                for (E e : c) {
-                    if (e == null) throw new NullPointerException();
-                    items[i++] = e;
-                }
+                for (E e : c)
+                    items[i++] = Objects.requireNonNull(e);
             } catch (ArrayIndexOutOfBoundsException ex) {
                 throw new IllegalArgumentException();
             }
@@ -276,7 +277,7 @@ public boolean add(E e) {
      * @throws NullPointerException if the specified element is null
      */
     public boolean offer(E e) {
-        if (e == null) throw new NullPointerException();
+        Objects.requireNonNull(e);
         final ReentrantLock lock = this.lock;
         lock.lock();
         try {
@@ -299,7 +300,7 @@ public boolean offer(E e) {
      * @throws NullPointerException {@inheritDoc}
      */
     public void put(E e) throws InterruptedException {
-        if (e == null) throw new NullPointerException();
+        Objects.requireNonNull(e);
         final ReentrantLock lock = this.lock;
         lock.lockInterruptibly();
         try {
@@ -322,13 +323,13 @@ public void put(E e) throws InterruptedException {
     public boolean offer(E e, long timeout, TimeUnit unit)
         throws InterruptedException {
 
-        if (e == null) throw new NullPointerException();
+        Objects.requireNonNull(e);
         long nanos = unit.toNanos(timeout);
         final ReentrantLock lock = this.lock;
         lock.lockInterruptibly();
         try {
             while (count == items.length) {
-                if (nanos <= 0)
+                if (nanos <= 0L)
                     return false;
                 nanos = notFull.awaitNanos(nanos);
             }
@@ -367,7 +368,7 @@ public E poll(long timeout, TimeUnit unit) throws InterruptedException {
         lock.lockInterruptibly();
         try {
             while (count == 0) {
-                if (nanos <= 0)
+                if (nanos <= 0L)
                     return null;
                 nanos = notEmpty.awaitNanos(nanos);
             }
@@ -584,27 +585,7 @@ public  T[] toArray(T[] a) {
     }
 
     public String toString() {
-        final ReentrantLock lock = this.lock;
-        lock.lock();
-        try {
-            int k = count;
-            if (k == 0)
-                return "[]";
-
-            final Object[] items = this.items;
-            StringBuilder sb = new StringBuilder();
-            sb.append('[');
-            for (int i = takeIndex; ; ) {
-                Object e = items[i];
-                sb.append(e == this ? "(this Collection)" : e);
-                if (--k == 0)
-                    return sb.append(']').toString();
-                sb.append(',').append(' ');
-                if (++i == items.length) i = 0;
-            }
-        } finally {
-            lock.unlock();
-        }
+        return Helpers.collectionToString(this);
     }
 
     /**
@@ -612,12 +593,12 @@ public String toString() {
      * The queue will be empty after this call returns.
      */
     public void clear() {
-        final Object[] items = this.items;
         final ReentrantLock lock = this.lock;
         lock.lock();
         try {
             int k = count;
             if (k > 0) {
+                final Object[] items = this.items;
                 final int putIndex = this.putIndex;
                 int i = takeIndex;
                 do {
@@ -653,7 +634,7 @@ public int drainTo(Collection c) {
      * @throws IllegalArgumentException      {@inheritDoc}
      */
     public int drainTo(Collection c, int maxElements) {
-        if (c == null) throw new NullPointerException();
+        Objects.requireNonNull(c);
         if (c == this)
             throw new IllegalArgumentException();
         if (maxElements <= 0)
@@ -1333,4 +1314,27 @@ boolean takeIndexWrapped() {
 //         }
     }
 
+    /**
+     * Returns a {@link Spliterator} over the elements in this queue.
+     *
+     * 

The returned spliterator is + * weakly consistent. + * + *

The {@code Spliterator} reports {@link Spliterator#CONCURRENT}, + * {@link Spliterator#ORDERED}, and {@link Spliterator#NONNULL}. + * + * @implNote + * The {@code Spliterator} implements {@code trySplit} to permit limited + * parallelism. + * + * @return a {@code Spliterator} over the elements in this queue + * @since 1.8 + */ + public Spliterator spliterator() { + return Spliterators.spliterator + (this, (Spliterator.ORDERED | + Spliterator.NONNULL | + Spliterator.CONCURRENT)); + } + } diff --git a/luni/src/main/java/java/util/concurrent/BlockingDeque.java b/luni/src/main/java/java/util/concurrent/BlockingDeque.java index b1437cc3e..b52f71940 100644 --- a/luni/src/main/java/java/util/concurrent/BlockingDeque.java +++ b/luni/src/main/java/java/util/concurrent/BlockingDeque.java @@ -6,7 +6,13 @@ package java.util.concurrent; -import java.util.*; +import java.util.Deque; +import java.util.Iterator; +import java.util.NoSuchElementException; + +// BEGIN android-note +// fixed framework docs link to "Collection#optional" +// END android-note /** * A {@link Deque} that additionally supports blocking operations that wait @@ -22,7 +28,6 @@ * and the fourth blocks for only a given maximum time limit before giving * up. These methods are summarized in the following table: * - *

* * * @@ -98,7 +103,6 @@ * {@code BlockingQueue} interface are precisely equivalent to * {@code BlockingDeque} methods as indicated in the following table: * - *

*

Summary of BlockingDeque methods
* * @@ -169,7 +173,7 @@ * * @since 1.6 * @author Doug Lea - * @param the type of elements held in this collection + * @param the type of elements held in this deque */ public interface BlockingDeque extends BlockingQueue, Deque { /* @@ -375,9 +379,9 @@ E pollLast(long timeout, TimeUnit unit) * @return {@code true} if an element was removed as a result of this call * @throws ClassCastException if the class of the specified element * is incompatible with this deque - * (optional) + * (optional) * @throws NullPointerException if the specified element is null - * (optional) + * (optional) */ boolean removeFirstOccurrence(Object o); @@ -393,9 +397,9 @@ E pollLast(long timeout, TimeUnit unit) * @return {@code true} if an element was removed as a result of this call * @throws ClassCastException if the class of the specified element * is incompatible with this deque - * (optional) + * (optional) * @throws NullPointerException if the specified element is null - * (optional) + * (optional) */ boolean removeLastOccurrence(Object o); @@ -570,9 +574,9 @@ E poll(long timeout, TimeUnit unit) * @return {@code true} if this deque changed as a result of the call * @throws ClassCastException if the class of the specified element * is incompatible with this deque - * (optional) + * (optional) * @throws NullPointerException if the specified element is null - * (optional) + * (optional) */ boolean remove(Object o); @@ -585,18 +589,18 @@ E poll(long timeout, TimeUnit unit) * @return {@code true} if this deque contains the specified element * @throws ClassCastException if the class of the specified element * is incompatible with this deque - * (optional) + * (optional) * @throws NullPointerException if the specified element is null - * (optional) + * (optional) */ - public boolean contains(Object o); + boolean contains(Object o); /** * Returns the number of elements in this deque. * * @return the number of elements in this deque */ - public int size(); + int size(); /** * Returns an iterator over the elements in this deque in proper sequence. diff --git a/luni/src/main/java/java/util/concurrent/BlockingQueue.java b/luni/src/main/java/java/util/concurrent/BlockingQueue.java index 33d83b7d0..2a561792e 100644 --- a/luni/src/main/java/java/util/concurrent/BlockingQueue.java +++ b/luni/src/main/java/java/util/concurrent/BlockingQueue.java @@ -10,7 +10,8 @@ import java.util.Queue; // BEGIN android-note -// removed link to collections framework docs +// removed link to collections framework docs from header +// fixed framework docs link to "Collection#optional" // END android-note /** @@ -28,7 +29,6 @@ * and the fourth blocks for only a given maximum time limit before giving * up. These methods are summarized in the following table: * - *

*

Comparison of BlockingQueue and BlockingDeque methods
* * @@ -103,7 +103,7 @@ * Usage example, based on a typical producer-consumer scenario. * Note that a {@code BlockingQueue} can safely be used with multiple * producers and multiple consumers. - *
 {@code
+ * 
 {@code
  * class Producer implements Runnable {
  *   private final BlockingQueue queue;
  *   Producer(BlockingQueue q) { queue = q; }
@@ -147,7 +147,7 @@
  *
  * @since 1.5
  * @author Doug Lea
- * @param  the type of elements held in this collection
+ * @param  the type of elements held in this queue
  */
 public interface BlockingQueue extends Queue {
     /**
@@ -275,9 +275,9 @@ E poll(long timeout, TimeUnit unit)
      * @return {@code true} if this queue changed as a result of the call
      * @throws ClassCastException if the class of the specified element
      *         is incompatible with this queue
-     *         (optional)
+     * (optional)
      * @throws NullPointerException if the specified element is null
-     *         (optional)
+     * (optional)
      */
     boolean remove(Object o);
 
@@ -290,11 +290,11 @@ E poll(long timeout, TimeUnit unit)
      * @return {@code true} if this queue contains the specified element
      * @throws ClassCastException if the class of the specified element
      *         is incompatible with this queue
-     *         (optional)
+     * (optional)
      * @throws NullPointerException if the specified element is null
-     *         (optional)
+     * (optional)
      */
-    public boolean contains(Object o);
+    boolean contains(Object o);
 
     /**
      * Removes all available elements from this queue and adds them
diff --git a/luni/src/main/java/java/util/concurrent/Callable.java b/luni/src/main/java/java/util/concurrent/Callable.java
index a3b388314..a22ec500f 100644
--- a/luni/src/main/java/java/util/concurrent/Callable.java
+++ b/luni/src/main/java/java/util/concurrent/Callable.java
@@ -25,6 +25,7 @@
  * @author Doug Lea
  * @param  the result type of method {@code call}
  */
+@FunctionalInterface
 public interface Callable {
     /**
      * Computes a result, or throws an exception if unable to do so.
diff --git a/luni/src/main/java/java/util/concurrent/CompletableFuture.java b/luni/src/main/java/java/util/concurrent/CompletableFuture.java
new file mode 100644
index 000000000..8383a68b4
--- /dev/null
+++ b/luni/src/main/java/java/util/concurrent/CompletableFuture.java
@@ -0,0 +1,2770 @@
+/*
+ * Written by Doug Lea with assistance from members of JCP JSR-166
+ * Expert Group and released to the public domain, as explained at
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+package java.util.concurrent;
+
+import java.util.concurrent.locks.LockSupport;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * A {@link Future} that may be explicitly completed (setting its
+ * value and status), and may be used as a {@link CompletionStage},
+ * supporting dependent functions and actions that trigger upon its
+ * completion.
+ *
+ * 

When two or more threads attempt to + * {@link #complete complete}, + * {@link #completeExceptionally completeExceptionally}, or + * {@link #cancel cancel} + * a CompletableFuture, only one of them succeeds. + * + *

In addition to these and related methods for directly + * manipulating status and results, CompletableFuture implements + * interface {@link CompletionStage} with the following policies:

    + * + *
  • Actions supplied for dependent completions of + * non-async methods may be performed by the thread that + * completes the current CompletableFuture, or by any other caller of + * a completion method. + * + *
  • All async methods without an explicit Executor + * argument are performed using the {@link ForkJoinPool#commonPool()} + * (unless it does not support a parallelism level of at least two, in + * which case, a new Thread is created to run each task). + * To simplify monitoring, debugging, + * and tracking, all generated asynchronous tasks are instances of the + * marker interface {@link AsynchronousCompletionTask}. Operations + * with time-delays can use adapter methods defined in this class, for + * example: {@code supplyAsync(supplier, delayedExecutor(timeout, + * timeUnit))}. To support methods with delays and timeouts, this + * class maintains at most one daemon thread for triggering and + * cancelling actions, not for running them. + * + *
  • All CompletionStage methods are implemented independently of + * other public methods, so the behavior of one method is not impacted + * by overrides of others in subclasses. + * + *
+ * + *

CompletableFuture also implements {@link Future} with the following + * policies:

    + * + *
  • Since (unlike {@link FutureTask}) this class has no direct + * control over the computation that causes it to be completed, + * cancellation is treated as just another form of exceptional + * completion. Method {@link #cancel cancel} has the same effect as + * {@code completeExceptionally(new CancellationException())}. Method + * {@link #isCompletedExceptionally} can be used to determine if a + * CompletableFuture completed in any exceptional fashion. + * + *
  • In case of exceptional completion with a CompletionException, + * methods {@link #get()} and {@link #get(long, TimeUnit)} throw an + * {@link ExecutionException} with the same cause as held in the + * corresponding CompletionException. To simplify usage in most + * contexts, this class also defines methods {@link #join()} and + * {@link #getNow} that instead throw the CompletionException directly + * in these cases. + *
+ * + *

Arguments used to pass a completion result (that is, for + * parameters of type {@code T}) for methods accepting them may be + * null, but passing a null value for any other parameter will result + * in a {@link NullPointerException} being thrown. + * + * @author Doug Lea + * @since 1.8 + * @param The result type returned by this future's {@code join} + * and {@code get} methods + */ +public class CompletableFuture implements Future, CompletionStage { + + /* + * Overview: + * + * A CompletableFuture may have dependent completion actions, + * collected in a linked stack. It atomically completes by CASing + * a result field, and then pops off and runs those actions. This + * applies across normal vs exceptional outcomes, sync vs async + * actions, binary triggers, and various forms of completions. + * + * Non-nullness of field result (set via CAS) indicates done. An + * AltResult is used to box null as a result, as well as to hold + * exceptions. Using a single field makes completion simple to + * detect and trigger. Encoding and decoding is straightforward + * but adds to the sprawl of trapping and associating exceptions + * with targets. Minor simplifications rely on (static) NIL (to + * box null results) being the only AltResult with a null + * exception field, so we don't usually need explicit comparisons. + * Even though some of the generics casts are unchecked (see + * SuppressWarnings annotations), they are placed to be + * appropriate even if checked. + * + * Dependent actions are represented by Completion objects linked + * as Treiber stacks headed by field "stack". There are Completion + * classes for each kind of action, grouped into single-input + * (UniCompletion), two-input (BiCompletion), projected + * (BiCompletions using either (not both) of two inputs), shared + * (CoCompletion, used by the second of two sources), zero-input + * source actions, and Signallers that unblock waiters. Class + * Completion extends ForkJoinTask to enable async execution + * (adding no space overhead because we exploit its "tag" methods + * to maintain claims). It is also declared as Runnable to allow + * usage with arbitrary executors. + * + * Support for each kind of CompletionStage relies on a separate + * class, along with two CompletableFuture methods: + * + * * A Completion class with name X corresponding to function, + * prefaced with "Uni", "Bi", or "Or". Each class contains + * fields for source(s), actions, and dependent. They are + * boringly similar, differing from others only with respect to + * underlying functional forms. We do this so that users don't + * encounter layers of adapters in common usages. + * + * * Boolean CompletableFuture method x(...) (for example + * uniApply) takes all of the arguments needed to check that an + * action is triggerable, and then either runs the action or + * arranges its async execution by executing its Completion + * argument, if present. The method returns true if known to be + * complete. + * + * * Completion method tryFire(int mode) invokes the associated x + * method with its held arguments, and on success cleans up. + * The mode argument allows tryFire to be called twice (SYNC, + * then ASYNC); the first to screen and trap exceptions while + * arranging to execute, and the second when called from a + * task. (A few classes are not used async so take slightly + * different forms.) The claim() callback suppresses function + * invocation if already claimed by another thread. + * + * * CompletableFuture method xStage(...) is called from a public + * stage method of CompletableFuture x. It screens user + * arguments and invokes and/or creates the stage object. If + * not async and x is already complete, the action is run + * immediately. Otherwise a Completion c is created, pushed to + * x's stack (unless done), and started or triggered via + * c.tryFire. This also covers races possible if x completes + * while pushing. Classes with two inputs (for example BiApply) + * deal with races across both while pushing actions. The + * second completion is a CoCompletion pointing to the first, + * shared so that at most one performs the action. The + * multiple-arity methods allOf and anyOf do this pairwise to + * form trees of completions. + * + * Note that the generic type parameters of methods vary according + * to whether "this" is a source, dependent, or completion. + * + * Method postComplete is called upon completion unless the target + * is guaranteed not to be observable (i.e., not yet returned or + * linked). Multiple threads can call postComplete, which + * atomically pops each dependent action, and tries to trigger it + * via method tryFire, in NESTED mode. Triggering can propagate + * recursively, so NESTED mode returns its completed dependent (if + * one exists) for further processing by its caller (see method + * postFire). + * + * Blocking methods get() and join() rely on Signaller Completions + * that wake up waiting threads. The mechanics are similar to + * Treiber stack wait-nodes used in FutureTask, Phaser, and + * SynchronousQueue. See their internal documentation for + * algorithmic details. + * + * Without precautions, CompletableFutures would be prone to + * garbage accumulation as chains of Completions build up, each + * pointing back to its sources. So we null out fields as soon as + * possible. The screening checks needed anyway harmlessly ignore + * null arguments that may have been obtained during races with + * threads nulling out fields. We also try to unlink fired + * Completions from stacks that might never be popped (see method + * postFire). Completion fields need not be declared as final or + * volatile because they are only visible to other threads upon + * safe publication. + */ + + volatile Object result; // Either the result or boxed AltResult + volatile Completion stack; // Top of Treiber stack of dependent actions + + final boolean internalComplete(Object r) { // CAS from null to r + return U.compareAndSwapObject(this, RESULT, null, r); + } + + final boolean casStack(Completion cmp, Completion val) { + return U.compareAndSwapObject(this, STACK, cmp, val); + } + + /** Returns true if successfully pushed c onto stack. */ + final boolean tryPushStack(Completion c) { + Completion h = stack; + lazySetNext(c, h); + return U.compareAndSwapObject(this, STACK, h, c); + } + + /** Unconditionally pushes c onto stack, retrying if necessary. */ + final void pushStack(Completion c) { + do {} while (!tryPushStack(c)); + } + + /* ------------- Encoding and decoding outcomes -------------- */ + + static final class AltResult { // See above + final Throwable ex; // null only for NIL + AltResult(Throwable x) { this.ex = x; } + } + + /** The encoding of the null value. */ + static final AltResult NIL = new AltResult(null); + + /** Completes with the null value, unless already completed. */ + final boolean completeNull() { + return U.compareAndSwapObject(this, RESULT, null, + NIL); + } + + /** Returns the encoding of the given non-exceptional value. */ + final Object encodeValue(T t) { + return (t == null) ? NIL : t; + } + + /** Completes with a non-exceptional result, unless already completed. */ + final boolean completeValue(T t) { + return U.compareAndSwapObject(this, RESULT, null, + (t == null) ? NIL : t); + } + + /** + * Returns the encoding of the given (non-null) exception as a + * wrapped CompletionException unless it is one already. + */ + static AltResult encodeThrowable(Throwable x) { + return new AltResult((x instanceof CompletionException) ? x : + new CompletionException(x)); + } + + /** Completes with an exceptional result, unless already completed. */ + final boolean completeThrowable(Throwable x) { + return U.compareAndSwapObject(this, RESULT, null, + encodeThrowable(x)); + } + + /** + * Returns the encoding of the given (non-null) exception as a + * wrapped CompletionException unless it is one already. May + * return the given Object r (which must have been the result of a + * source future) if it is equivalent, i.e. if this is a simple + * relay of an existing CompletionException. + */ + static Object encodeThrowable(Throwable x, Object r) { + if (!(x instanceof CompletionException)) + x = new CompletionException(x); + else if (r instanceof AltResult && x == ((AltResult)r).ex) + return r; + return new AltResult(x); + } + + /** + * Completes with the given (non-null) exceptional result as a + * wrapped CompletionException unless it is one already, unless + * already completed. May complete with the given Object r + * (which must have been the result of a source future) if it is + * equivalent, i.e. if this is a simple propagation of an + * existing CompletionException. + */ + final boolean completeThrowable(Throwable x, Object r) { + return U.compareAndSwapObject(this, RESULT, null, + encodeThrowable(x, r)); + } + + /** + * Returns the encoding of the given arguments: if the exception + * is non-null, encodes as AltResult. Otherwise uses the given + * value, boxed as NIL if null. + */ + Object encodeOutcome(T t, Throwable x) { + return (x == null) ? (t == null) ? NIL : t : encodeThrowable(x); + } + + /** + * Returns the encoding of a copied outcome; if exceptional, + * rewraps as a CompletionException, else returns argument. + */ + static Object encodeRelay(Object r) { + Throwable x; + return (((r instanceof AltResult) && + (x = ((AltResult)r).ex) != null && + !(x instanceof CompletionException)) ? + new AltResult(new CompletionException(x)) : r); + } + + /** + * Completes with r or a copy of r, unless already completed. + * If exceptional, r is first coerced to a CompletionException. + */ + final boolean completeRelay(Object r) { + return U.compareAndSwapObject(this, RESULT, null, + encodeRelay(r)); + } + + /** + * Reports result using Future.get conventions. + */ + private static T reportGet(Object r) + throws InterruptedException, ExecutionException { + if (r == null) // by convention below, null means interrupted + throw new InterruptedException(); + if (r instanceof AltResult) { + Throwable x, cause; + if ((x = ((AltResult)r).ex) == null) + return null; + if (x instanceof CancellationException) + throw (CancellationException)x; + if ((x instanceof CompletionException) && + (cause = x.getCause()) != null) + x = cause; + throw new ExecutionException(x); + } + @SuppressWarnings("unchecked") T t = (T) r; + return t; + } + + /** + * Decodes outcome to return result or throw unchecked exception. + */ + private static T reportJoin(Object r) { + if (r instanceof AltResult) { + Throwable x; + if ((x = ((AltResult)r).ex) == null) + return null; + if (x instanceof CancellationException) + throw (CancellationException)x; + if (x instanceof CompletionException) + throw (CompletionException)x; + throw new CompletionException(x); + } + @SuppressWarnings("unchecked") T t = (T) r; + return t; + } + + /* ------------- Async task preliminaries -------------- */ + + /** + * A marker interface identifying asynchronous tasks produced by + * {@code async} methods. This may be useful for monitoring, + * debugging, and tracking asynchronous activities. + * + * @since 1.8 + */ + public static interface AsynchronousCompletionTask { + } + + private static final boolean USE_COMMON_POOL = + (ForkJoinPool.getCommonPoolParallelism() > 1); + + /** + * Default executor -- ForkJoinPool.commonPool() unless it cannot + * support parallelism. + */ + private static final Executor ASYNC_POOL = USE_COMMON_POOL ? + ForkJoinPool.commonPool() : new ThreadPerTaskExecutor(); + + /** Fallback if ForkJoinPool.commonPool() cannot support parallelism */ + static final class ThreadPerTaskExecutor implements Executor { + public void execute(Runnable r) { new Thread(r).start(); } + } + + /** + * Null-checks user executor argument, and translates uses of + * commonPool to ASYNC_POOL in case parallelism disabled. + */ + static Executor screenExecutor(Executor e) { + if (!USE_COMMON_POOL && e == ForkJoinPool.commonPool()) + return ASYNC_POOL; + if (e == null) throw new NullPointerException(); + return e; + } + + // Modes for Completion.tryFire. Signedness matters. + static final int SYNC = 0; + static final int ASYNC = 1; + static final int NESTED = -1; + + /** + * Spins before blocking in waitingGet + */ + static final int SPINS = (Runtime.getRuntime().availableProcessors() > 1 ? + 1 << 8 : 0); + + /* ------------- Base Completion classes and operations -------------- */ + + @SuppressWarnings("serial") + abstract static class Completion extends ForkJoinTask + implements Runnable, AsynchronousCompletionTask { + volatile Completion next; // Treiber stack link + + /** + * Performs completion action if triggered, returning a + * dependent that may need propagation, if one exists. + * + * @param mode SYNC, ASYNC, or NESTED + */ + abstract CompletableFuture tryFire(int mode); + + /** Returns true if possibly still triggerable. Used by cleanStack. */ + abstract boolean isLive(); + + public final void run() { tryFire(ASYNC); } + public final boolean exec() { tryFire(ASYNC); return false; } + public final Void getRawResult() { return null; } + public final void setRawResult(Void v) {} + } + + static void lazySetNext(Completion c, Completion next) { + U.putOrderedObject(c, NEXT, next); + } + + /** + * Pops and tries to trigger all reachable dependents. Call only + * when known to be done. + */ + final void postComplete() { + /* + * On each step, variable f holds current dependents to pop + * and run. It is extended along only one path at a time, + * pushing others to avoid unbounded recursion. + */ + CompletableFuture f = this; Completion h; + while ((h = f.stack) != null || + (f != this && (h = (f = this).stack) != null)) { + CompletableFuture d; Completion t; + if (f.casStack(h, t = h.next)) { + if (t != null) { + if (f != this) { + pushStack(h); + continue; + } + h.next = null; // detach + } + f = (d = h.tryFire(NESTED)) == null ? this : d; + } + } + } + + /** Traverses stack and unlinks dead Completions. */ + final void cleanStack() { + for (Completion p = null, q = stack; q != null;) { + Completion s = q.next; + if (q.isLive()) { + p = q; + q = s; + } + else if (p == null) { + casStack(q, s); + q = stack; + } + else { + p.next = s; + if (p.isLive()) + q = s; + else { + p = null; // restart + q = stack; + } + } + } + } + + /* ------------- One-input Completions -------------- */ + + /** A Completion with a source, dependent, and executor. */ + @SuppressWarnings("serial") + abstract static class UniCompletion extends Completion { + Executor executor; // executor to use (null if none) + CompletableFuture dep; // the dependent to complete + CompletableFuture src; // source for action + + UniCompletion(Executor executor, CompletableFuture dep, + CompletableFuture src) { + this.executor = executor; this.dep = dep; this.src = src; + } + + /** + * Returns true if action can be run. Call only when known to + * be triggerable. Uses FJ tag bit to ensure that only one + * thread claims ownership. If async, starts as task -- a + * later call to tryFire will run action. + */ + final boolean claim() { + Executor e = executor; + if (compareAndSetForkJoinTaskTag((short)0, (short)1)) { + if (e == null) + return true; + executor = null; // disable + e.execute(this); + } + return false; + } + + final boolean isLive() { return dep != null; } + } + + /** Pushes the given completion (if it exists) unless done. */ + final void push(UniCompletion c) { + if (c != null) { + while (result == null && !tryPushStack(c)) + lazySetNext(c, null); // clear on failure + } + } + + /** + * Post-processing by dependent after successful UniCompletion + * tryFire. Tries to clean stack of source a, and then either runs + * postComplete or returns this to caller, depending on mode. + */ + final CompletableFuture postFire(CompletableFuture a, int mode) { + if (a != null && a.stack != null) { + if (mode < 0 || a.result == null) + a.cleanStack(); + else + a.postComplete(); + } + if (result != null && stack != null) { + if (mode < 0) + return this; + else + postComplete(); + } + return null; + } + + @SuppressWarnings("serial") + static final class UniApply extends UniCompletion { + Function fn; + UniApply(Executor executor, CompletableFuture dep, + CompletableFuture src, + Function fn) { + super(executor, dep, src); this.fn = fn; + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; CompletableFuture a; + if ((d = dep) == null || + !d.uniApply(a = src, fn, mode > 0 ? null : this)) + return null; + dep = null; src = null; fn = null; + return d.postFire(a, mode); + } + } + + final boolean uniApply(CompletableFuture a, + Function f, + UniApply c) { + Object r; Throwable x; + if (a == null || (r = a.result) == null || f == null) + return false; + tryComplete: if (result == null) { + if (r instanceof AltResult) { + if ((x = ((AltResult)r).ex) != null) { + completeThrowable(x, r); + break tryComplete; + } + r = null; + } + try { + if (c != null && !c.claim()) + return false; + @SuppressWarnings("unchecked") S s = (S) r; + completeValue(f.apply(s)); + } catch (Throwable ex) { + completeThrowable(ex); + } + } + return true; + } + + private CompletableFuture uniApplyStage( + Executor e, Function f) { + if (f == null) throw new NullPointerException(); + CompletableFuture d = newIncompleteFuture(); + if (e != null || !d.uniApply(this, f, null)) { + UniApply c = new UniApply(e, d, this, f); + push(c); + c.tryFire(SYNC); + } + return d; + } + + @SuppressWarnings("serial") + static final class UniAccept extends UniCompletion { + Consumer fn; + UniAccept(Executor executor, CompletableFuture dep, + CompletableFuture src, Consumer fn) { + super(executor, dep, src); this.fn = fn; + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; CompletableFuture a; + if ((d = dep) == null || + !d.uniAccept(a = src, fn, mode > 0 ? null : this)) + return null; + dep = null; src = null; fn = null; + return d.postFire(a, mode); + } + } + + final boolean uniAccept(CompletableFuture a, + Consumer f, UniAccept c) { + Object r; Throwable x; + if (a == null || (r = a.result) == null || f == null) + return false; + tryComplete: if (result == null) { + if (r instanceof AltResult) { + if ((x = ((AltResult)r).ex) != null) { + completeThrowable(x, r); + break tryComplete; + } + r = null; + } + try { + if (c != null && !c.claim()) + return false; + @SuppressWarnings("unchecked") S s = (S) r; + f.accept(s); + completeNull(); + } catch (Throwable ex) { + completeThrowable(ex); + } + } + return true; + } + + private CompletableFuture uniAcceptStage(Executor e, + Consumer f) { + if (f == null) throw new NullPointerException(); + CompletableFuture d = newIncompleteFuture(); + if (e != null || !d.uniAccept(this, f, null)) { + UniAccept c = new UniAccept(e, d, this, f); + push(c); + c.tryFire(SYNC); + } + return d; + } + + @SuppressWarnings("serial") + static final class UniRun extends UniCompletion { + Runnable fn; + UniRun(Executor executor, CompletableFuture dep, + CompletableFuture src, Runnable fn) { + super(executor, dep, src); this.fn = fn; + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; CompletableFuture a; + if ((d = dep) == null || + !d.uniRun(a = src, fn, mode > 0 ? null : this)) + return null; + dep = null; src = null; fn = null; + return d.postFire(a, mode); + } + } + + final boolean uniRun(CompletableFuture a, Runnable f, UniRun c) { + Object r; Throwable x; + if (a == null || (r = a.result) == null || f == null) + return false; + if (result == null) { + if (r instanceof AltResult && (x = ((AltResult)r).ex) != null) + completeThrowable(x, r); + else + try { + if (c != null && !c.claim()) + return false; + f.run(); + completeNull(); + } catch (Throwable ex) { + completeThrowable(ex); + } + } + return true; + } + + private CompletableFuture uniRunStage(Executor e, Runnable f) { + if (f == null) throw new NullPointerException(); + CompletableFuture d = newIncompleteFuture(); + if (e != null || !d.uniRun(this, f, null)) { + UniRun c = new UniRun(e, d, this, f); + push(c); + c.tryFire(SYNC); + } + return d; + } + + @SuppressWarnings("serial") + static final class UniWhenComplete extends UniCompletion { + BiConsumer fn; + UniWhenComplete(Executor executor, CompletableFuture dep, + CompletableFuture src, + BiConsumer fn) { + super(executor, dep, src); this.fn = fn; + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; CompletableFuture a; + if ((d = dep) == null || + !d.uniWhenComplete(a = src, fn, mode > 0 ? null : this)) + return null; + dep = null; src = null; fn = null; + return d.postFire(a, mode); + } + } + + final boolean uniWhenComplete(CompletableFuture a, + BiConsumer f, + UniWhenComplete c) { + Object r; T t; Throwable x = null; + if (a == null || (r = a.result) == null || f == null) + return false; + if (result == null) { + try { + if (c != null && !c.claim()) + return false; + if (r instanceof AltResult) { + x = ((AltResult)r).ex; + t = null; + } else { + @SuppressWarnings("unchecked") T tr = (T) r; + t = tr; + } + f.accept(t, x); + if (x == null) { + internalComplete(r); + return true; + } + } catch (Throwable ex) { + if (x == null) + x = ex; + else if (x != ex) + x.addSuppressed(ex); + } + completeThrowable(x, r); + } + return true; + } + + private CompletableFuture uniWhenCompleteStage( + Executor e, BiConsumer f) { + if (f == null) throw new NullPointerException(); + CompletableFuture d = newIncompleteFuture(); + if (e != null || !d.uniWhenComplete(this, f, null)) { + UniWhenComplete c = new UniWhenComplete(e, d, this, f); + push(c); + c.tryFire(SYNC); + } + return d; + } + + @SuppressWarnings("serial") + static final class UniHandle extends UniCompletion { + BiFunction fn; + UniHandle(Executor executor, CompletableFuture dep, + CompletableFuture src, + BiFunction fn) { + super(executor, dep, src); this.fn = fn; + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; CompletableFuture a; + if ((d = dep) == null || + !d.uniHandle(a = src, fn, mode > 0 ? null : this)) + return null; + dep = null; src = null; fn = null; + return d.postFire(a, mode); + } + } + + final boolean uniHandle(CompletableFuture a, + BiFunction f, + UniHandle c) { + Object r; S s; Throwable x; + if (a == null || (r = a.result) == null || f == null) + return false; + if (result == null) { + try { + if (c != null && !c.claim()) + return false; + if (r instanceof AltResult) { + x = ((AltResult)r).ex; + s = null; + } else { + x = null; + @SuppressWarnings("unchecked") S ss = (S) r; + s = ss; + } + completeValue(f.apply(s, x)); + } catch (Throwable ex) { + completeThrowable(ex); + } + } + return true; + } + + private CompletableFuture uniHandleStage( + Executor e, BiFunction f) { + if (f == null) throw new NullPointerException(); + CompletableFuture d = newIncompleteFuture(); + if (e != null || !d.uniHandle(this, f, null)) { + UniHandle c = new UniHandle(e, d, this, f); + push(c); + c.tryFire(SYNC); + } + return d; + } + + @SuppressWarnings("serial") + static final class UniExceptionally extends UniCompletion { + Function fn; + UniExceptionally(CompletableFuture dep, CompletableFuture src, + Function fn) { + super(null, dep, src); this.fn = fn; + } + final CompletableFuture tryFire(int mode) { // never ASYNC + // assert mode != ASYNC; + CompletableFuture d; CompletableFuture a; + if ((d = dep) == null || !d.uniExceptionally(a = src, fn, this)) + return null; + dep = null; src = null; fn = null; + return d.postFire(a, mode); + } + } + + final boolean uniExceptionally(CompletableFuture a, + Function f, + UniExceptionally c) { + Object r; Throwable x; + if (a == null || (r = a.result) == null || f == null) + return false; + if (result == null) { + try { + if (r instanceof AltResult && (x = ((AltResult)r).ex) != null) { + if (c != null && !c.claim()) + return false; + completeValue(f.apply(x)); + } else + internalComplete(r); + } catch (Throwable ex) { + completeThrowable(ex); + } + } + return true; + } + + private CompletableFuture uniExceptionallyStage( + Function f) { + if (f == null) throw new NullPointerException(); + CompletableFuture d = newIncompleteFuture(); + if (!d.uniExceptionally(this, f, null)) { + UniExceptionally c = new UniExceptionally(d, this, f); + push(c); + c.tryFire(SYNC); + } + return d; + } + + @SuppressWarnings("serial") + static final class UniRelay extends UniCompletion { // for Compose + UniRelay(CompletableFuture dep, CompletableFuture src) { + super(null, dep, src); + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; CompletableFuture a; + if ((d = dep) == null || !d.uniRelay(a = src)) + return null; + src = null; dep = null; + return d.postFire(a, mode); + } + } + + final boolean uniRelay(CompletableFuture a) { + Object r; + if (a == null || (r = a.result) == null) + return false; + if (result == null) // no need to claim + completeRelay(r); + return true; + } + + private CompletableFuture uniCopyStage() { + Object r; + CompletableFuture d = newIncompleteFuture(); + if ((r = result) != null) + d.completeRelay(r); + else { + UniRelay c = new UniRelay(d, this); + push(c); + c.tryFire(SYNC); + } + return d; + } + + private MinimalStage uniAsMinimalStage() { + Object r; + if ((r = result) != null) + return new MinimalStage(encodeRelay(r)); + MinimalStage d = new MinimalStage(); + UniRelay c = new UniRelay(d, this); + push(c); + c.tryFire(SYNC); + return d; + } + + @SuppressWarnings("serial") + static final class UniCompose extends UniCompletion { + Function> fn; + UniCompose(Executor executor, CompletableFuture dep, + CompletableFuture src, + Function> fn) { + super(executor, dep, src); this.fn = fn; + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; CompletableFuture a; + if ((d = dep) == null || + !d.uniCompose(a = src, fn, mode > 0 ? null : this)) + return null; + dep = null; src = null; fn = null; + return d.postFire(a, mode); + } + } + + final boolean uniCompose( + CompletableFuture a, + Function> f, + UniCompose c) { + Object r; Throwable x; + if (a == null || (r = a.result) == null || f == null) + return false; + tryComplete: if (result == null) { + if (r instanceof AltResult) { + if ((x = ((AltResult)r).ex) != null) { + completeThrowable(x, r); + break tryComplete; + } + r = null; + } + try { + if (c != null && !c.claim()) + return false; + @SuppressWarnings("unchecked") S s = (S) r; + CompletableFuture g = f.apply(s).toCompletableFuture(); + if (g.result == null || !uniRelay(g)) { + UniRelay copy = new UniRelay(this, g); + g.push(copy); + copy.tryFire(SYNC); + if (result == null) + return false; + } + } catch (Throwable ex) { + completeThrowable(ex); + } + } + return true; + } + + private CompletableFuture uniComposeStage( + Executor e, Function> f) { + if (f == null) throw new NullPointerException(); + Object r, s; Throwable x; + CompletableFuture d = newIncompleteFuture(); + if (e == null && (r = result) != null) { + if (r instanceof AltResult) { + if ((x = ((AltResult)r).ex) != null) { + d.result = encodeThrowable(x, r); + return d; + } + r = null; + } + try { + @SuppressWarnings("unchecked") T t = (T) r; + CompletableFuture g = f.apply(t).toCompletableFuture(); + if ((s = g.result) != null) + d.completeRelay(s); + else { + UniRelay c = new UniRelay(d, g); + g.push(c); + c.tryFire(SYNC); + } + return d; + } catch (Throwable ex) { + d.result = encodeThrowable(ex); + return d; + } + } + UniCompose c = new UniCompose(e, d, this, f); + push(c); + c.tryFire(SYNC); + return d; + } + + /* ------------- Two-input Completions -------------- */ + + /** A Completion for an action with two sources */ + @SuppressWarnings("serial") + abstract static class BiCompletion extends UniCompletion { + CompletableFuture snd; // second source for action + BiCompletion(Executor executor, CompletableFuture dep, + CompletableFuture src, CompletableFuture snd) { + super(executor, dep, src); this.snd = snd; + } + } + + /** A Completion delegating to a BiCompletion */ + @SuppressWarnings("serial") + static final class CoCompletion extends Completion { + BiCompletion base; + CoCompletion(BiCompletion base) { this.base = base; } + final CompletableFuture tryFire(int mode) { + BiCompletion c; CompletableFuture d; + if ((c = base) == null || (d = c.tryFire(mode)) == null) + return null; + base = null; // detach + return d; + } + final boolean isLive() { + BiCompletion c; + return (c = base) != null && c.dep != null; + } + } + + /** Pushes completion to this and b unless both done. */ + final void bipush(CompletableFuture b, BiCompletion c) { + if (c != null) { + Object r; + while ((r = result) == null && !tryPushStack(c)) + lazySetNext(c, null); // clear on failure + if (b != null && b != this && b.result == null) { + Completion q = (r != null) ? c : new CoCompletion(c); + while (b.result == null && !b.tryPushStack(q)) + lazySetNext(q, null); // clear on failure + } + } + } + + /** Post-processing after successful BiCompletion tryFire. */ + final CompletableFuture postFire(CompletableFuture a, + CompletableFuture b, int mode) { + if (b != null && b.stack != null) { // clean second source + if (mode < 0 || b.result == null) + b.cleanStack(); + else + b.postComplete(); + } + return postFire(a, mode); + } + + @SuppressWarnings("serial") + static final class BiApply extends BiCompletion { + BiFunction fn; + BiApply(Executor executor, CompletableFuture dep, + CompletableFuture src, CompletableFuture snd, + BiFunction fn) { + super(executor, dep, src, snd); this.fn = fn; + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; + CompletableFuture a; + CompletableFuture b; + if ((d = dep) == null || + !d.biApply(a = src, b = snd, fn, mode > 0 ? null : this)) + return null; + dep = null; src = null; snd = null; fn = null; + return d.postFire(a, b, mode); + } + } + + final boolean biApply(CompletableFuture a, + CompletableFuture b, + BiFunction f, + BiApply c) { + Object r, s; Throwable x; + if (a == null || (r = a.result) == null || + b == null || (s = b.result) == null || f == null) + return false; + tryComplete: if (result == null) { + if (r instanceof AltResult) { + if ((x = ((AltResult)r).ex) != null) { + completeThrowable(x, r); + break tryComplete; + } + r = null; + } + if (s instanceof AltResult) { + if ((x = ((AltResult)s).ex) != null) { + completeThrowable(x, s); + break tryComplete; + } + s = null; + } + try { + if (c != null && !c.claim()) + return false; + @SuppressWarnings("unchecked") R rr = (R) r; + @SuppressWarnings("unchecked") S ss = (S) s; + completeValue(f.apply(rr, ss)); + } catch (Throwable ex) { + completeThrowable(ex); + } + } + return true; + } + + private CompletableFuture biApplyStage( + Executor e, CompletionStage o, + BiFunction f) { + CompletableFuture b; + if (f == null || (b = o.toCompletableFuture()) == null) + throw new NullPointerException(); + CompletableFuture d = newIncompleteFuture(); + if (e != null || !d.biApply(this, b, f, null)) { + BiApply c = new BiApply(e, d, this, b, f); + bipush(b, c); + c.tryFire(SYNC); + } + return d; + } + + @SuppressWarnings("serial") + static final class BiAccept extends BiCompletion { + BiConsumer fn; + BiAccept(Executor executor, CompletableFuture dep, + CompletableFuture src, CompletableFuture snd, + BiConsumer fn) { + super(executor, dep, src, snd); this.fn = fn; + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; + CompletableFuture a; + CompletableFuture b; + if ((d = dep) == null || + !d.biAccept(a = src, b = snd, fn, mode > 0 ? null : this)) + return null; + dep = null; src = null; snd = null; fn = null; + return d.postFire(a, b, mode); + } + } + + final boolean biAccept(CompletableFuture a, + CompletableFuture b, + BiConsumer f, + BiAccept c) { + Object r, s; Throwable x; + if (a == null || (r = a.result) == null || + b == null || (s = b.result) == null || f == null) + return false; + tryComplete: if (result == null) { + if (r instanceof AltResult) { + if ((x = ((AltResult)r).ex) != null) { + completeThrowable(x, r); + break tryComplete; + } + r = null; + } + if (s instanceof AltResult) { + if ((x = ((AltResult)s).ex) != null) { + completeThrowable(x, s); + break tryComplete; + } + s = null; + } + try { + if (c != null && !c.claim()) + return false; + @SuppressWarnings("unchecked") R rr = (R) r; + @SuppressWarnings("unchecked") S ss = (S) s; + f.accept(rr, ss); + completeNull(); + } catch (Throwable ex) { + completeThrowable(ex); + } + } + return true; + } + + private CompletableFuture biAcceptStage( + Executor e, CompletionStage o, + BiConsumer f) { + CompletableFuture b; + if (f == null || (b = o.toCompletableFuture()) == null) + throw new NullPointerException(); + CompletableFuture d = newIncompleteFuture(); + if (e != null || !d.biAccept(this, b, f, null)) { + BiAccept c = new BiAccept(e, d, this, b, f); + bipush(b, c); + c.tryFire(SYNC); + } + return d; + } + + @SuppressWarnings("serial") + static final class BiRun extends BiCompletion { + Runnable fn; + BiRun(Executor executor, CompletableFuture dep, + CompletableFuture src, + CompletableFuture snd, + Runnable fn) { + super(executor, dep, src, snd); this.fn = fn; + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; + CompletableFuture a; + CompletableFuture b; + if ((d = dep) == null || + !d.biRun(a = src, b = snd, fn, mode > 0 ? null : this)) + return null; + dep = null; src = null; snd = null; fn = null; + return d.postFire(a, b, mode); + } + } + + final boolean biRun(CompletableFuture a, CompletableFuture b, + Runnable f, BiRun c) { + Object r, s; Throwable x; + if (a == null || (r = a.result) == null || + b == null || (s = b.result) == null || f == null) + return false; + if (result == null) { + if (r instanceof AltResult && (x = ((AltResult)r).ex) != null) + completeThrowable(x, r); + else if (s instanceof AltResult && (x = ((AltResult)s).ex) != null) + completeThrowable(x, s); + else + try { + if (c != null && !c.claim()) + return false; + f.run(); + completeNull(); + } catch (Throwable ex) { + completeThrowable(ex); + } + } + return true; + } + + private CompletableFuture biRunStage(Executor e, CompletionStage o, + Runnable f) { + CompletableFuture b; + if (f == null || (b = o.toCompletableFuture()) == null) + throw new NullPointerException(); + CompletableFuture d = newIncompleteFuture(); + if (e != null || !d.biRun(this, b, f, null)) { + BiRun c = new BiRun<>(e, d, this, b, f); + bipush(b, c); + c.tryFire(SYNC); + } + return d; + } + + @SuppressWarnings("serial") + static final class BiRelay extends BiCompletion { // for And + BiRelay(CompletableFuture dep, + CompletableFuture src, + CompletableFuture snd) { + super(null, dep, src, snd); + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; + CompletableFuture a; + CompletableFuture b; + if ((d = dep) == null || !d.biRelay(a = src, b = snd)) + return null; + src = null; snd = null; dep = null; + return d.postFire(a, b, mode); + } + } + + boolean biRelay(CompletableFuture a, CompletableFuture b) { + Object r, s; Throwable x; + if (a == null || (r = a.result) == null || + b == null || (s = b.result) == null) + return false; + if (result == null) { + if (r instanceof AltResult && (x = ((AltResult)r).ex) != null) + completeThrowable(x, r); + else if (s instanceof AltResult && (x = ((AltResult)s).ex) != null) + completeThrowable(x, s); + else + completeNull(); + } + return true; + } + + /** Recursively constructs a tree of completions. */ + static CompletableFuture andTree(CompletableFuture[] cfs, + int lo, int hi) { + CompletableFuture d = new CompletableFuture(); + if (lo > hi) // empty + d.result = NIL; + else { + CompletableFuture a, b; + int mid = (lo + hi) >>> 1; + if ((a = (lo == mid ? cfs[lo] : + andTree(cfs, lo, mid))) == null || + (b = (lo == hi ? a : (hi == mid+1) ? cfs[hi] : + andTree(cfs, mid+1, hi))) == null) + throw new NullPointerException(); + if (!d.biRelay(a, b)) { + BiRelay c = new BiRelay<>(d, a, b); + a.bipush(b, c); + c.tryFire(SYNC); + } + } + return d; + } + + /* ------------- Projected (Ored) BiCompletions -------------- */ + + /** Pushes completion to this and b unless either done. */ + final void orpush(CompletableFuture b, BiCompletion c) { + if (c != null) { + while ((b == null || b.result == null) && result == null) { + if (tryPushStack(c)) { + if (b != null && b != this && b.result == null) { + Completion q = new CoCompletion(c); + while (result == null && b.result == null && + !b.tryPushStack(q)) + lazySetNext(q, null); // clear on failure + } + break; + } + lazySetNext(c, null); // clear on failure + } + } + } + + @SuppressWarnings("serial") + static final class OrApply extends BiCompletion { + Function fn; + OrApply(Executor executor, CompletableFuture dep, + CompletableFuture src, + CompletableFuture snd, + Function fn) { + super(executor, dep, src, snd); this.fn = fn; + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; + CompletableFuture a; + CompletableFuture b; + if ((d = dep) == null || + !d.orApply(a = src, b = snd, fn, mode > 0 ? null : this)) + return null; + dep = null; src = null; snd = null; fn = null; + return d.postFire(a, b, mode); + } + } + + final boolean orApply(CompletableFuture a, + CompletableFuture b, + Function f, + OrApply c) { + Object r; Throwable x; + if (a == null || b == null || + ((r = a.result) == null && (r = b.result) == null) || f == null) + return false; + tryComplete: if (result == null) { + try { + if (c != null && !c.claim()) + return false; + if (r instanceof AltResult) { + if ((x = ((AltResult)r).ex) != null) { + completeThrowable(x, r); + break tryComplete; + } + r = null; + } + @SuppressWarnings("unchecked") R rr = (R) r; + completeValue(f.apply(rr)); + } catch (Throwable ex) { + completeThrowable(ex); + } + } + return true; + } + + private CompletableFuture orApplyStage( + Executor e, CompletionStage o, + Function f) { + CompletableFuture b; + if (f == null || (b = o.toCompletableFuture()) == null) + throw new NullPointerException(); + CompletableFuture d = newIncompleteFuture(); + if (e != null || !d.orApply(this, b, f, null)) { + OrApply c = new OrApply(e, d, this, b, f); + orpush(b, c); + c.tryFire(SYNC); + } + return d; + } + + @SuppressWarnings("serial") + static final class OrAccept extends BiCompletion { + Consumer fn; + OrAccept(Executor executor, CompletableFuture dep, + CompletableFuture src, + CompletableFuture snd, + Consumer fn) { + super(executor, dep, src, snd); this.fn = fn; + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; + CompletableFuture a; + CompletableFuture b; + if ((d = dep) == null || + !d.orAccept(a = src, b = snd, fn, mode > 0 ? null : this)) + return null; + dep = null; src = null; snd = null; fn = null; + return d.postFire(a, b, mode); + } + } + + final boolean orAccept(CompletableFuture a, + CompletableFuture b, + Consumer f, + OrAccept c) { + Object r; Throwable x; + if (a == null || b == null || + ((r = a.result) == null && (r = b.result) == null) || f == null) + return false; + tryComplete: if (result == null) { + try { + if (c != null && !c.claim()) + return false; + if (r instanceof AltResult) { + if ((x = ((AltResult)r).ex) != null) { + completeThrowable(x, r); + break tryComplete; + } + r = null; + } + @SuppressWarnings("unchecked") R rr = (R) r; + f.accept(rr); + completeNull(); + } catch (Throwable ex) { + completeThrowable(ex); + } + } + return true; + } + + private CompletableFuture orAcceptStage( + Executor e, CompletionStage o, Consumer f) { + CompletableFuture b; + if (f == null || (b = o.toCompletableFuture()) == null) + throw new NullPointerException(); + CompletableFuture d = newIncompleteFuture(); + if (e != null || !d.orAccept(this, b, f, null)) { + OrAccept c = new OrAccept(e, d, this, b, f); + orpush(b, c); + c.tryFire(SYNC); + } + return d; + } + + @SuppressWarnings("serial") + static final class OrRun extends BiCompletion { + Runnable fn; + OrRun(Executor executor, CompletableFuture dep, + CompletableFuture src, + CompletableFuture snd, + Runnable fn) { + super(executor, dep, src, snd); this.fn = fn; + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; + CompletableFuture a; + CompletableFuture b; + if ((d = dep) == null || + !d.orRun(a = src, b = snd, fn, mode > 0 ? null : this)) + return null; + dep = null; src = null; snd = null; fn = null; + return d.postFire(a, b, mode); + } + } + + final boolean orRun(CompletableFuture a, CompletableFuture b, + Runnable f, OrRun c) { + Object r; Throwable x; + if (a == null || b == null || + ((r = a.result) == null && (r = b.result) == null) || f == null) + return false; + if (result == null) { + try { + if (c != null && !c.claim()) + return false; + if (r instanceof AltResult && (x = ((AltResult)r).ex) != null) + completeThrowable(x, r); + else { + f.run(); + completeNull(); + } + } catch (Throwable ex) { + completeThrowable(ex); + } + } + return true; + } + + private CompletableFuture orRunStage(Executor e, CompletionStage o, + Runnable f) { + CompletableFuture b; + if (f == null || (b = o.toCompletableFuture()) == null) + throw new NullPointerException(); + CompletableFuture d = newIncompleteFuture(); + if (e != null || !d.orRun(this, b, f, null)) { + OrRun c = new OrRun<>(e, d, this, b, f); + orpush(b, c); + c.tryFire(SYNC); + } + return d; + } + + @SuppressWarnings("serial") + static final class OrRelay extends BiCompletion { // for Or + OrRelay(CompletableFuture dep, CompletableFuture src, + CompletableFuture snd) { + super(null, dep, src, snd); + } + final CompletableFuture tryFire(int mode) { + CompletableFuture d; + CompletableFuture a; + CompletableFuture b; + if ((d = dep) == null || !d.orRelay(a = src, b = snd)) + return null; + src = null; snd = null; dep = null; + return d.postFire(a, b, mode); + } + } + + final boolean orRelay(CompletableFuture a, CompletableFuture b) { + Object r; + if (a == null || b == null || + ((r = a.result) == null && (r = b.result) == null)) + return false; + if (result == null) + completeRelay(r); + return true; + } + + /** Recursively constructs a tree of completions. */ + static CompletableFuture orTree(CompletableFuture[] cfs, + int lo, int hi) { + CompletableFuture d = new CompletableFuture(); + if (lo <= hi) { + CompletableFuture a, b; + int mid = (lo + hi) >>> 1; + if ((a = (lo == mid ? cfs[lo] : + orTree(cfs, lo, mid))) == null || + (b = (lo == hi ? a : (hi == mid+1) ? cfs[hi] : + orTree(cfs, mid+1, hi))) == null) + throw new NullPointerException(); + if (!d.orRelay(a, b)) { + OrRelay c = new OrRelay<>(d, a, b); + a.orpush(b, c); + c.tryFire(SYNC); + } + } + return d; + } + + /* ------------- Zero-input Async forms -------------- */ + + @SuppressWarnings("serial") + static final class AsyncSupply extends ForkJoinTask + implements Runnable, AsynchronousCompletionTask { + CompletableFuture dep; Supplier fn; + AsyncSupply(CompletableFuture dep, Supplier fn) { + this.dep = dep; this.fn = fn; + } + + public final Void getRawResult() { return null; } + public final void setRawResult(Void v) {} + public final boolean exec() { run(); return true; } + + public void run() { + CompletableFuture d; Supplier f; + if ((d = dep) != null && (f = fn) != null) { + dep = null; fn = null; + if (d.result == null) { + try { + d.completeValue(f.get()); + } catch (Throwable ex) { + d.completeThrowable(ex); + } + } + d.postComplete(); + } + } + } + + static CompletableFuture asyncSupplyStage(Executor e, + Supplier f) { + if (f == null) throw new NullPointerException(); + CompletableFuture d = new CompletableFuture(); + e.execute(new AsyncSupply(d, f)); + return d; + } + + @SuppressWarnings("serial") + static final class AsyncRun extends ForkJoinTask + implements Runnable, AsynchronousCompletionTask { + CompletableFuture dep; Runnable fn; + AsyncRun(CompletableFuture dep, Runnable fn) { + this.dep = dep; this.fn = fn; + } + + public final Void getRawResult() { return null; } + public final void setRawResult(Void v) {} + public final boolean exec() { run(); return true; } + + public void run() { + CompletableFuture d; Runnable f; + if ((d = dep) != null && (f = fn) != null) { + dep = null; fn = null; + if (d.result == null) { + try { + f.run(); + d.completeNull(); + } catch (Throwable ex) { + d.completeThrowable(ex); + } + } + d.postComplete(); + } + } + } + + static CompletableFuture asyncRunStage(Executor e, Runnable f) { + if (f == null) throw new NullPointerException(); + CompletableFuture d = new CompletableFuture(); + e.execute(new AsyncRun(d, f)); + return d; + } + + /* ------------- Signallers -------------- */ + + /** + * Completion for recording and releasing a waiting thread. This + * class implements ManagedBlocker to avoid starvation when + * blocking actions pile up in ForkJoinPools. + */ + @SuppressWarnings("serial") + static final class Signaller extends Completion + implements ForkJoinPool.ManagedBlocker { + long nanos; // remaining wait time if timed + final long deadline; // non-zero if timed + final boolean interruptible; + boolean interrupted; + volatile Thread thread; + + Signaller(boolean interruptible, long nanos, long deadline) { + this.thread = Thread.currentThread(); + this.interruptible = interruptible; + this.nanos = nanos; + this.deadline = deadline; + } + final CompletableFuture tryFire(int ignore) { + Thread w; // no need to atomically claim + if ((w = thread) != null) { + thread = null; + LockSupport.unpark(w); + } + return null; + } + public boolean isReleasable() { + if (Thread.interrupted()) + interrupted = true; + return ((interrupted && interruptible) || + (deadline != 0L && + (nanos <= 0L || + (nanos = deadline - System.nanoTime()) <= 0L)) || + thread == null); + } + public boolean block() { + while (!isReleasable()) { + if (deadline == 0L) + LockSupport.park(this); + else + LockSupport.parkNanos(this, nanos); + } + return true; + } + final boolean isLive() { return thread != null; } + } + + /** + * Returns raw result after waiting, or null if interruptible and + * interrupted. + */ + private Object waitingGet(boolean interruptible) { + Signaller q = null; + boolean queued = false; + int spins = SPINS; + Object r; + while ((r = result) == null) { + if (spins > 0) { + if (ThreadLocalRandom.nextSecondarySeed() >= 0) + --spins; + } + else if (q == null) + q = new Signaller(interruptible, 0L, 0L); + else if (!queued) + queued = tryPushStack(q); + else { + try { + ForkJoinPool.managedBlock(q); + } catch (InterruptedException ie) { // currently cannot happen + q.interrupted = true; + } + if (q.interrupted && interruptible) + break; + } + } + if (q != null) { + q.thread = null; + if (q.interrupted) { + if (interruptible) + cleanStack(); + else + Thread.currentThread().interrupt(); + } + } + if (r != null) + postComplete(); + return r; + } + + /** + * Returns raw result after waiting, or null if interrupted, or + * throws TimeoutException on timeout. + */ + private Object timedGet(long nanos) throws TimeoutException { + if (Thread.interrupted()) + return null; + if (nanos > 0L) { + long d = System.nanoTime() + nanos; + long deadline = (d == 0L) ? 1L : d; // avoid 0 + Signaller q = null; + boolean queued = false; + Object r; + while ((r = result) == null) { // similar to untimed, without spins + if (q == null) + q = new Signaller(true, nanos, deadline); + else if (!queued) + queued = tryPushStack(q); + else if (q.nanos <= 0L) + break; + else { + try { + ForkJoinPool.managedBlock(q); + } catch (InterruptedException ie) { + q.interrupted = true; + } + if (q.interrupted) + break; + } + } + if (q != null) + q.thread = null; + if (r != null) + postComplete(); + else + cleanStack(); + if (r != null || (q != null && q.interrupted)) + return r; + } + throw new TimeoutException(); + } + + /* ------------- public methods -------------- */ + + /** + * Creates a new incomplete CompletableFuture. + */ + public CompletableFuture() { + } + + /** + * Creates a new complete CompletableFuture with given encoded result. + */ + CompletableFuture(Object r) { + this.result = r; + } + + /** + * Returns a new CompletableFuture that is asynchronously completed + * by a task running in the {@link ForkJoinPool#commonPool()} with + * the value obtained by calling the given Supplier. + * + * @param supplier a function returning the value to be used + * to complete the returned CompletableFuture + * @param the function's return type + * @return the new CompletableFuture + */ + public static CompletableFuture supplyAsync(Supplier supplier) { + return asyncSupplyStage(ASYNC_POOL, supplier); + } + + /** + * Returns a new CompletableFuture that is asynchronously completed + * by a task running in the given executor with the value obtained + * by calling the given Supplier. + * + * @param supplier a function returning the value to be used + * to complete the returned CompletableFuture + * @param executor the executor to use for asynchronous execution + * @param the function's return type + * @return the new CompletableFuture + */ + public static CompletableFuture supplyAsync(Supplier supplier, + Executor executor) { + return asyncSupplyStage(screenExecutor(executor), supplier); + } + + /** + * Returns a new CompletableFuture that is asynchronously completed + * by a task running in the {@link ForkJoinPool#commonPool()} after + * it runs the given action. + * + * @param runnable the action to run before completing the + * returned CompletableFuture + * @return the new CompletableFuture + */ + public static CompletableFuture runAsync(Runnable runnable) { + return asyncRunStage(ASYNC_POOL, runnable); + } + + /** + * Returns a new CompletableFuture that is asynchronously completed + * by a task running in the given executor after it runs the given + * action. + * + * @param runnable the action to run before completing the + * returned CompletableFuture + * @param executor the executor to use for asynchronous execution + * @return the new CompletableFuture + */ + public static CompletableFuture runAsync(Runnable runnable, + Executor executor) { + return asyncRunStage(screenExecutor(executor), runnable); + } + + /** + * Returns a new CompletableFuture that is already completed with + * the given value. + * + * @param value the value + * @param the type of the value + * @return the completed CompletableFuture + */ + public static CompletableFuture completedFuture(U value) { + return new CompletableFuture((value == null) ? NIL : value); + } + + /** + * Returns {@code true} if completed in any fashion: normally, + * exceptionally, or via cancellation. + * + * @return {@code true} if completed + */ + public boolean isDone() { + return result != null; + } + + /** + * Waits if necessary for this future to complete, and then + * returns its result. + * + * @return the result value + * @throws CancellationException if this future was cancelled + * @throws ExecutionException if this future completed exceptionally + * @throws InterruptedException if the current thread was interrupted + * while waiting + */ + public T get() throws InterruptedException, ExecutionException { + Object r; + return reportGet((r = result) == null ? waitingGet(true) : r); + } + + /** + * Waits if necessary for at most the given time for this future + * to complete, and then returns its result, if available. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the timeout argument + * @return the result value + * @throws CancellationException if this future was cancelled + * @throws ExecutionException if this future completed exceptionally + * @throws InterruptedException if the current thread was interrupted + * while waiting + * @throws TimeoutException if the wait timed out + */ + public T get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + Object r; + long nanos = unit.toNanos(timeout); + return reportGet((r = result) == null ? timedGet(nanos) : r); + } + + /** + * Returns the result value when complete, or throws an + * (unchecked) exception if completed exceptionally. To better + * conform with the use of common functional forms, if a + * computation involved in the completion of this + * CompletableFuture threw an exception, this method throws an + * (unchecked) {@link CompletionException} with the underlying + * exception as its cause. + * + * @return the result value + * @throws CancellationException if the computation was cancelled + * @throws CompletionException if this future completed + * exceptionally or a completion computation threw an exception + */ + public T join() { + Object r; + return reportJoin((r = result) == null ? waitingGet(false) : r); + } + + /** + * Returns the result value (or throws any encountered exception) + * if completed, else returns the given valueIfAbsent. + * + * @param valueIfAbsent the value to return if not completed + * @return the result value, if completed, else the given valueIfAbsent + * @throws CancellationException if the computation was cancelled + * @throws CompletionException if this future completed + * exceptionally or a completion computation threw an exception + */ + public T getNow(T valueIfAbsent) { + Object r; + return ((r = result) == null) ? valueIfAbsent : reportJoin(r); + } + + /** + * If not already completed, sets the value returned by {@link + * #get()} and related methods to the given value. + * + * @param value the result value + * @return {@code true} if this invocation caused this CompletableFuture + * to transition to a completed state, else {@code false} + */ + public boolean complete(T value) { + boolean triggered = completeValue(value); + postComplete(); + return triggered; + } + + /** + * If not already completed, causes invocations of {@link #get()} + * and related methods to throw the given exception. + * + * @param ex the exception + * @return {@code true} if this invocation caused this CompletableFuture + * to transition to a completed state, else {@code false} + */ + public boolean completeExceptionally(Throwable ex) { + if (ex == null) throw new NullPointerException(); + boolean triggered = internalComplete(new AltResult(ex)); + postComplete(); + return triggered; + } + + public CompletableFuture thenApply( + Function fn) { + return uniApplyStage(null, fn); + } + + public CompletableFuture thenApplyAsync( + Function fn) { + return uniApplyStage(defaultExecutor(), fn); + } + + public CompletableFuture thenApplyAsync( + Function fn, Executor executor) { + return uniApplyStage(screenExecutor(executor), fn); + } + + public CompletableFuture thenAccept(Consumer action) { + return uniAcceptStage(null, action); + } + + public CompletableFuture thenAcceptAsync(Consumer action) { + return uniAcceptStage(defaultExecutor(), action); + } + + public CompletableFuture thenAcceptAsync(Consumer action, + Executor executor) { + return uniAcceptStage(screenExecutor(executor), action); + } + + public CompletableFuture thenRun(Runnable action) { + return uniRunStage(null, action); + } + + public CompletableFuture thenRunAsync(Runnable action) { + return uniRunStage(defaultExecutor(), action); + } + + public CompletableFuture thenRunAsync(Runnable action, + Executor executor) { + return uniRunStage(screenExecutor(executor), action); + } + + public CompletableFuture thenCombine( + CompletionStage other, + BiFunction fn) { + return biApplyStage(null, other, fn); + } + + public CompletableFuture thenCombineAsync( + CompletionStage other, + BiFunction fn) { + return biApplyStage(defaultExecutor(), other, fn); + } + + public CompletableFuture thenCombineAsync( + CompletionStage other, + BiFunction fn, Executor executor) { + return biApplyStage(screenExecutor(executor), other, fn); + } + + public CompletableFuture thenAcceptBoth( + CompletionStage other, + BiConsumer action) { + return biAcceptStage(null, other, action); + } + + public CompletableFuture thenAcceptBothAsync( + CompletionStage other, + BiConsumer action) { + return biAcceptStage(defaultExecutor(), other, action); + } + + public CompletableFuture thenAcceptBothAsync( + CompletionStage other, + BiConsumer action, Executor executor) { + return biAcceptStage(screenExecutor(executor), other, action); + } + + public CompletableFuture runAfterBoth(CompletionStage other, + Runnable action) { + return biRunStage(null, other, action); + } + + public CompletableFuture runAfterBothAsync(CompletionStage other, + Runnable action) { + return biRunStage(defaultExecutor(), other, action); + } + + public CompletableFuture runAfterBothAsync(CompletionStage other, + Runnable action, + Executor executor) { + return biRunStage(screenExecutor(executor), other, action); + } + + public CompletableFuture applyToEither( + CompletionStage other, Function fn) { + return orApplyStage(null, other, fn); + } + + public CompletableFuture applyToEitherAsync( + CompletionStage other, Function fn) { + return orApplyStage(defaultExecutor(), other, fn); + } + + public CompletableFuture applyToEitherAsync( + CompletionStage other, Function fn, + Executor executor) { + return orApplyStage(screenExecutor(executor), other, fn); + } + + public CompletableFuture acceptEither( + CompletionStage other, Consumer action) { + return orAcceptStage(null, other, action); + } + + public CompletableFuture acceptEitherAsync( + CompletionStage other, Consumer action) { + return orAcceptStage(defaultExecutor(), other, action); + } + + public CompletableFuture acceptEitherAsync( + CompletionStage other, Consumer action, + Executor executor) { + return orAcceptStage(screenExecutor(executor), other, action); + } + + public CompletableFuture runAfterEither(CompletionStage other, + Runnable action) { + return orRunStage(null, other, action); + } + + public CompletableFuture runAfterEitherAsync(CompletionStage other, + Runnable action) { + return orRunStage(defaultExecutor(), other, action); + } + + public CompletableFuture runAfterEitherAsync(CompletionStage other, + Runnable action, + Executor executor) { + return orRunStage(screenExecutor(executor), other, action); + } + + public CompletableFuture thenCompose( + Function> fn) { + return uniComposeStage(null, fn); + } + + public CompletableFuture thenComposeAsync( + Function> fn) { + return uniComposeStage(defaultExecutor(), fn); + } + + public CompletableFuture thenComposeAsync( + Function> fn, + Executor executor) { + return uniComposeStage(screenExecutor(executor), fn); + } + + public CompletableFuture whenComplete( + BiConsumer action) { + return uniWhenCompleteStage(null, action); + } + + public CompletableFuture whenCompleteAsync( + BiConsumer action) { + return uniWhenCompleteStage(defaultExecutor(), action); + } + + public CompletableFuture whenCompleteAsync( + BiConsumer action, Executor executor) { + return uniWhenCompleteStage(screenExecutor(executor), action); + } + + public CompletableFuture handle( + BiFunction fn) { + return uniHandleStage(null, fn); + } + + public CompletableFuture handleAsync( + BiFunction fn) { + return uniHandleStage(defaultExecutor(), fn); + } + + public CompletableFuture handleAsync( + BiFunction fn, Executor executor) { + return uniHandleStage(screenExecutor(executor), fn); + } + + /** + * Returns this CompletableFuture. + * + * @return this CompletableFuture + */ + public CompletableFuture toCompletableFuture() { + return this; + } + + // not in interface CompletionStage + + /** + * Returns a new CompletableFuture that is completed when this + * CompletableFuture completes, with the result of the given + * function of the exception triggering this CompletableFuture's + * completion when it completes exceptionally; otherwise, if this + * CompletableFuture completes normally, then the returned + * CompletableFuture also completes normally with the same value. + * Note: More flexible versions of this functionality are + * available using methods {@code whenComplete} and {@code handle}. + * + * @param fn the function to use to compute the value of the + * returned CompletableFuture if this CompletableFuture completed + * exceptionally + * @return the new CompletableFuture + */ + public CompletableFuture exceptionally( + Function fn) { + return uniExceptionallyStage(fn); + } + + + /* ------------- Arbitrary-arity constructions -------------- */ + + /** + * Returns a new CompletableFuture that is completed when all of + * the given CompletableFutures complete. If any of the given + * CompletableFutures complete exceptionally, then the returned + * CompletableFuture also does so, with a CompletionException + * holding this exception as its cause. Otherwise, the results, + * if any, of the given CompletableFutures are not reflected in + * the returned CompletableFuture, but may be obtained by + * inspecting them individually. If no CompletableFutures are + * provided, returns a CompletableFuture completed with the value + * {@code null}. + * + *

Among the applications of this method is to await completion + * of a set of independent CompletableFutures before continuing a + * program, as in: {@code CompletableFuture.allOf(c1, c2, + * c3).join();}. + * + * @param cfs the CompletableFutures + * @return a new CompletableFuture that is completed when all of the + * given CompletableFutures complete + * @throws NullPointerException if the array or any of its elements are + * {@code null} + */ + public static CompletableFuture allOf(CompletableFuture... cfs) { + return andTree(cfs, 0, cfs.length - 1); + } + + /** + * Returns a new CompletableFuture that is completed when any of + * the given CompletableFutures complete, with the same result. + * Otherwise, if it completed exceptionally, the returned + * CompletableFuture also does so, with a CompletionException + * holding this exception as its cause. If no CompletableFutures + * are provided, returns an incomplete CompletableFuture. + * + * @param cfs the CompletableFutures + * @return a new CompletableFuture that is completed with the + * result or exception of any of the given CompletableFutures when + * one completes + * @throws NullPointerException if the array or any of its elements are + * {@code null} + */ + public static CompletableFuture anyOf(CompletableFuture... cfs) { + return orTree(cfs, 0, cfs.length - 1); + } + + /* ------------- Control and status methods -------------- */ + + /** + * If not already completed, completes this CompletableFuture with + * a {@link CancellationException}. Dependent CompletableFutures + * that have not already completed will also complete + * exceptionally, with a {@link CompletionException} caused by + * this {@code CancellationException}. + * + * @param mayInterruptIfRunning this value has no effect in this + * implementation because interrupts are not used to control + * processing. + * + * @return {@code true} if this task is now cancelled + */ + public boolean cancel(boolean mayInterruptIfRunning) { + boolean cancelled = (result == null) && + internalComplete(new AltResult(new CancellationException())); + postComplete(); + return cancelled || isCancelled(); + } + + /** + * Returns {@code true} if this CompletableFuture was cancelled + * before it completed normally. + * + * @return {@code true} if this CompletableFuture was cancelled + * before it completed normally + */ + public boolean isCancelled() { + Object r; + return ((r = result) instanceof AltResult) && + (((AltResult)r).ex instanceof CancellationException); + } + + /** + * Returns {@code true} if this CompletableFuture completed + * exceptionally, in any way. Possible causes include + * cancellation, explicit invocation of {@code + * completeExceptionally}, and abrupt termination of a + * CompletionStage action. + * + * @return {@code true} if this CompletableFuture completed + * exceptionally + */ + public boolean isCompletedExceptionally() { + Object r; + return ((r = result) instanceof AltResult) && r != NIL; + } + + /** + * Forcibly sets or resets the value subsequently returned by + * method {@link #get()} and related methods, whether or not + * already completed. This method is designed for use only in + * error recovery actions, and even in such situations may result + * in ongoing dependent completions using established versus + * overwritten outcomes. + * + * @param value the completion value + */ + public void obtrudeValue(T value) { + result = (value == null) ? NIL : value; + postComplete(); + } + + /** + * Forcibly causes subsequent invocations of method {@link #get()} + * and related methods to throw the given exception, whether or + * not already completed. This method is designed for use only in + * error recovery actions, and even in such situations may result + * in ongoing dependent completions using established versus + * overwritten outcomes. + * + * @param ex the exception + * @throws NullPointerException if the exception is null + */ + public void obtrudeException(Throwable ex) { + if (ex == null) throw new NullPointerException(); + result = new AltResult(ex); + postComplete(); + } + + /** + * Returns the estimated number of CompletableFutures whose + * completions are awaiting completion of this CompletableFuture. + * This method is designed for use in monitoring system state, not + * for synchronization control. + * + * @return the number of dependent CompletableFutures + */ + public int getNumberOfDependents() { + int count = 0; + for (Completion p = stack; p != null; p = p.next) + ++count; + return count; + } + + /** + * Returns a string identifying this CompletableFuture, as well as + * its completion state. The state, in brackets, contains the + * String {@code "Completed Normally"} or the String {@code + * "Completed Exceptionally"}, or the String {@code "Not + * completed"} followed by the number of CompletableFutures + * dependent upon its completion, if any. + * + * @return a string identifying this CompletableFuture, as well as its state + */ + public String toString() { + Object r = result; + int count = 0; // avoid call to getNumberOfDependents in case disabled + for (Completion p = stack; p != null; p = p.next) + ++count; + return super.toString() + + ((r == null) ? + ((count == 0) ? + "[Not completed]" : + "[Not completed, " + count + " dependents]") : + (((r instanceof AltResult) && ((AltResult)r).ex != null) ? + "[Completed exceptionally]" : + "[Completed normally]")); + } + + // jdk9 additions + + /** + * Returns a new incomplete CompletableFuture of the type to be + * returned by a CompletionStage method. Subclasses should + * normally override this method to return an instance of the same + * class as this CompletableFuture. The default implementation + * returns an instance of class CompletableFuture. + * + * @param the type of the value + * @return a new CompletableFuture + * @since 9 + * @hide + */ + // android-changed - hidden + public CompletableFuture newIncompleteFuture() { + return new CompletableFuture(); + } + + /** + * Returns the default Executor used for async methods that do not + * specify an Executor. This class uses the {@link + * ForkJoinPool#commonPool()} if it supports more than one + * parallel thread, or else an Executor using one thread per async + * task. This method may be overridden in subclasses to return + * an Executor that provides at least one independent thread. + * + * @return the executor + * @since 9 + * @hide + */ + // android-changed - hidden + public Executor defaultExecutor() { + return ASYNC_POOL; + } + + /** + * Returns a new CompletableFuture that is completed normally with + * the same value as this CompletableFuture when it completes + * normally. If this CompletableFuture completes exceptionally, + * then the returned CompletableFuture completes exceptionally + * with a CompletionException with this exception as cause. The + * behavior is equivalent to {@code thenApply(x -> x)}. This + * method may be useful as a form of "defensive copying", to + * prevent clients from completing, while still being able to + * arrange dependent actions. + * + * @return the new CompletableFuture + * @since 9 + * @hide + */ + // android-changed - hidden + public CompletableFuture copy() { + return uniCopyStage(); + } + + /** + * Returns a new CompletionStage that is completed normally with + * the same value as this CompletableFuture when it completes + * normally, and cannot be independently completed or otherwise + * used in ways not defined by the methods of interface {@link + * CompletionStage}. If this CompletableFuture completes + * exceptionally, then the returned CompletionStage completes + * exceptionally with a CompletionException with this exception as + * cause. + * + * @return the new CompletionStage + * @since 9 + * @hide + */ + // android-changed - hidden + public CompletionStage minimalCompletionStage() { + return uniAsMinimalStage(); + } + + /** + * Completes this CompletableFuture with the result of + * the given Supplier function invoked from an asynchronous + * task using the given executor. + * + * @param supplier a function returning the value to be used + * to complete this CompletableFuture + * @param executor the executor to use for asynchronous execution + * @return this CompletableFuture + * @since 9 + * @hide + */ + // android-changed - hidden + public CompletableFuture completeAsync(Supplier supplier, + Executor executor) { + if (supplier == null || executor == null) + throw new NullPointerException(); + executor.execute(new AsyncSupply(this, supplier)); + return this; + } + + /** + * Completes this CompletableFuture with the result of the given + * Supplier function invoked from an asynchronous task using the + * default executor. + * + * @param supplier a function returning the value to be used + * to complete this CompletableFuture + * @return this CompletableFuture + * @since 9 + * @hide + */ + // android-changed - hidden + public CompletableFuture completeAsync(Supplier supplier) { + return completeAsync(supplier, defaultExecutor()); + } + + /** + * Exceptionally completes this CompletableFuture with + * a {@link TimeoutException} if not otherwise completed + * before the given timeout. + * + * @param timeout how long to wait before completing exceptionally + * with a TimeoutException, in units of {@code unit} + * @param unit a {@code TimeUnit} determining how to interpret the + * {@code timeout} parameter + * @return this CompletableFuture + * @since 9 + * @hide + */ + // android-changed - hidden + public CompletableFuture orTimeout(long timeout, TimeUnit unit) { + if (unit == null) + throw new NullPointerException(); + if (result == null) + whenComplete(new Canceller(Delayer.delay(new Timeout(this), + timeout, unit))); + return this; + } + + /** + * Completes this CompletableFuture with the given value if not + * otherwise completed before the given timeout. + * + * @param value the value to use upon timeout + * @param timeout how long to wait before completing normally + * with the given value, in units of {@code unit} + * @param unit a {@code TimeUnit} determining how to interpret the + * {@code timeout} parameter + * @return this CompletableFuture + * @since 9 + * @hide + */ + // android-changed - hidden + public CompletableFuture completeOnTimeout(T value, long timeout, + TimeUnit unit) { + if (unit == null) + throw new NullPointerException(); + if (result == null) + whenComplete(new Canceller(Delayer.delay( + new DelayedCompleter(this, value), + timeout, unit))); + return this; + } + + /** + * Returns a new Executor that submits a task to the given base + * executor after the given delay (or no delay if non-positive). + * Each delay commences upon invocation of the returned executor's + * {@code execute} method. + * + * @param delay how long to delay, in units of {@code unit} + * @param unit a {@code TimeUnit} determining how to interpret the + * {@code delay} parameter + * @param executor the base executor + * @return the new delayed executor + * @since 9 + * @hide + */ + // android-changed - hidden + public static Executor delayedExecutor(long delay, TimeUnit unit, + Executor executor) { + if (unit == null || executor == null) + throw new NullPointerException(); + return new DelayedExecutor(delay, unit, executor); + } + + /** + * Returns a new Executor that submits a task to the default + * executor after the given delay (or no delay if non-positive). + * Each delay commences upon invocation of the returned executor's + * {@code execute} method. + * + * @param delay how long to delay, in units of {@code unit} + * @param unit a {@code TimeUnit} determining how to interpret the + * {@code delay} parameter + * @return the new delayed executor + * @since 9 + * @hide + */ + // android-changed - hidden + public static Executor delayedExecutor(long delay, TimeUnit unit) { + if (unit == null) + throw new NullPointerException(); + return new DelayedExecutor(delay, unit, ASYNC_POOL); + } + + /** + * Returns a new CompletionStage that is already completed with + * the given value and supports only those methods in + * interface {@link CompletionStage}. + * + * @param value the value + * @param the type of the value + * @return the completed CompletionStage + * @since 9 + * @hide + */ + // android-changed - hidden + public static CompletionStage completedStage(U value) { + return new MinimalStage((value == null) ? NIL : value); + } + + /** + * Returns a new CompletableFuture that is already completed + * exceptionally with the given exception. + * + * @param ex the exception + * @param the type of the value + * @return the exceptionally completed CompletableFuture + * @since 9 + * @hide + */ + // android-changed - hidden + public static CompletableFuture failedFuture(Throwable ex) { + if (ex == null) throw new NullPointerException(); + return new CompletableFuture(new AltResult(ex)); + } + + /** + * Returns a new CompletionStage that is already completed + * exceptionally with the given exception and supports only those + * methods in interface {@link CompletionStage}. + * + * @param ex the exception + * @param the type of the value + * @return the exceptionally completed CompletionStage + * @since 9 + * @hide + */ + // android-changed - hidden + public static CompletionStage failedStage(Throwable ex) { + if (ex == null) throw new NullPointerException(); + return new MinimalStage(new AltResult(ex)); + } + + /** + * Singleton delay scheduler, used only for starting and + * cancelling tasks. + */ + static final class Delayer { + static ScheduledFuture delay(Runnable command, long delay, + TimeUnit unit) { + return delayer.schedule(command, delay, unit); + } + + static final class DaemonThreadFactory implements ThreadFactory { + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("CompletableFutureDelayScheduler"); + return t; + } + } + + static final ScheduledThreadPoolExecutor delayer; + static { + (delayer = new ScheduledThreadPoolExecutor( + 1, new DaemonThreadFactory())). + setRemoveOnCancelPolicy(true); + } + } + + // Little class-ified lambdas to better support monitoring + + static final class DelayedExecutor implements Executor { + final long delay; + final TimeUnit unit; + final Executor executor; + DelayedExecutor(long delay, TimeUnit unit, Executor executor) { + this.delay = delay; this.unit = unit; this.executor = executor; + } + public void execute(Runnable r) { + Delayer.delay(new TaskSubmitter(executor, r), delay, unit); + } + } + + /** Action to submit user task */ + static final class TaskSubmitter implements Runnable { + final Executor executor; + final Runnable action; + TaskSubmitter(Executor executor, Runnable action) { + this.executor = executor; + this.action = action; + } + public void run() { executor.execute(action); } + } + + /** Action to completeExceptionally on timeout */ + static final class Timeout implements Runnable { + final CompletableFuture f; + Timeout(CompletableFuture f) { this.f = f; } + public void run() { + if (f != null && !f.isDone()) + f.completeExceptionally(new TimeoutException()); + } + } + + /** Action to complete on timeout */ + static final class DelayedCompleter implements Runnable { + final CompletableFuture f; + final U u; + DelayedCompleter(CompletableFuture f, U u) { this.f = f; this.u = u; } + public void run() { + if (f != null) + f.complete(u); + } + } + + /** Action to cancel unneeded timeouts */ + static final class Canceller implements BiConsumer { + final Future f; + Canceller(Future f) { this.f = f; } + public void accept(Object ignore, Throwable ex) { + if (ex == null && f != null && !f.isDone()) + f.cancel(false); + } + } + + /** + * A subclass that just throws UOE for most non-CompletionStage methods. + */ + static final class MinimalStage extends CompletableFuture { + MinimalStage() { } + MinimalStage(Object r) { super(r); } + @Override public CompletableFuture newIncompleteFuture() { + return new MinimalStage(); } + @Override public T get() { + throw new UnsupportedOperationException(); } + @Override public T get(long timeout, TimeUnit unit) { + throw new UnsupportedOperationException(); } + @Override public T getNow(T valueIfAbsent) { + throw new UnsupportedOperationException(); } + @Override public T join() { + throw new UnsupportedOperationException(); } + @Override public boolean complete(T value) { + throw new UnsupportedOperationException(); } + @Override public boolean completeExceptionally(Throwable ex) { + throw new UnsupportedOperationException(); } + @Override public boolean cancel(boolean mayInterruptIfRunning) { + throw new UnsupportedOperationException(); } + @Override public void obtrudeValue(T value) { + throw new UnsupportedOperationException(); } + @Override public void obtrudeException(Throwable ex) { + throw new UnsupportedOperationException(); } + @Override public boolean isDone() { + throw new UnsupportedOperationException(); } + @Override public boolean isCancelled() { + throw new UnsupportedOperationException(); } + @Override public boolean isCompletedExceptionally() { + throw new UnsupportedOperationException(); } + @Override public int getNumberOfDependents() { + throw new UnsupportedOperationException(); } + @Override public CompletableFuture completeAsync + (Supplier supplier, Executor executor) { + throw new UnsupportedOperationException(); } + @Override public CompletableFuture completeAsync + (Supplier supplier) { + throw new UnsupportedOperationException(); } + @Override public CompletableFuture orTimeout + (long timeout, TimeUnit unit) { + throw new UnsupportedOperationException(); } + @Override public CompletableFuture completeOnTimeout + (T value, long timeout, TimeUnit unit) { + throw new UnsupportedOperationException(); } + } + + // Unsafe mechanics + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long RESULT; + private static final long STACK; + private static final long NEXT; + static { + try { + RESULT = U.objectFieldOffset + (CompletableFuture.class.getDeclaredField("result")); + STACK = U.objectFieldOffset + (CompletableFuture.class.getDeclaredField("stack")); + NEXT = U.objectFieldOffset + (Completion.class.getDeclaredField("next")); + } catch (ReflectiveOperationException e) { + throw new Error(e); + } + + // Reduce the risk of rare disastrous classloading in first call to + // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773 + Class ensureLoaded = LockSupport.class; + } +} diff --git a/luni/src/main/java/java/util/concurrent/CompletionException.java b/luni/src/main/java/java/util/concurrent/CompletionException.java new file mode 100644 index 000000000..9b905d2d3 --- /dev/null +++ b/luni/src/main/java/java/util/concurrent/CompletionException.java @@ -0,0 +1,61 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent; + +/** + * Exception thrown when an error or other exception is encountered + * in the course of completing a result or task. + * + * @since 1.8 + * @author Doug Lea + */ +public class CompletionException extends RuntimeException { + private static final long serialVersionUID = 7830266012832686185L; + + /** + * Constructs a {@code CompletionException} with no detail message. + * The cause is not initialized, and may subsequently be + * initialized by a call to {@link #initCause(Throwable) initCause}. + */ + protected CompletionException() { } + + /** + * Constructs a {@code CompletionException} with the specified detail + * message. The cause is not initialized, and may subsequently be + * initialized by a call to {@link #initCause(Throwable) initCause}. + * + * @param message the detail message + */ + protected CompletionException(String message) { + super(message); + } + + /** + * Constructs a {@code CompletionException} with the specified detail + * message and cause. + * + * @param message the detail message + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method) + */ + public CompletionException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a {@code CompletionException} with the specified cause. + * The detail message is set to {@code (cause == null ? null : + * cause.toString())} (which typically contains the class and + * detail message of {@code cause}). + * + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method) + */ + public CompletionException(Throwable cause) { + super(cause); + } +} diff --git a/luni/src/main/java/java/util/concurrent/CompletionStage.java b/luni/src/main/java/java/util/concurrent/CompletionStage.java new file mode 100644 index 000000000..ccb1aa4d3 --- /dev/null +++ b/luni/src/main/java/java/util/concurrent/CompletionStage.java @@ -0,0 +1,840 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent; + +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * A stage of a possibly asynchronous computation, that performs an + * action or computes a value when another CompletionStage completes. + * A stage completes upon termination of its computation, but this may + * in turn trigger other dependent stages. The functionality defined + * in this interface takes only a few basic forms, which expand out to + * a larger set of methods to capture a range of usage styles: + * + *
    + * + *
  • The computation performed by a stage may be expressed as a + * Function, Consumer, or Runnable (using methods with names including + * apply, accept, or run, respectively) + * depending on whether it requires arguments and/or produces results. + * For example: + *
     {@code
    + * stage.thenApply(x -> square(x))
    + *      .thenAccept(x -> System.out.print(x))
    + *      .thenRun(() -> System.out.println());}
    + * + * An additional form (compose) allows the construction of + * computation pipelines from functions returning completion stages. + * + *

    Any argument to a stage's computation is the outcome of a + * triggering stage's computation. + * + *

  • One stage's execution may be triggered by completion of a + * single stage, or both of two stages, or either of two stages. + * Dependencies on a single stage are arranged using methods with + * prefix then. Those triggered by completion of + * both of two stages may combine their results or + * effects, using correspondingly named methods. Those triggered by + * either of two stages make no guarantees about which of the + * results or effects are used for the dependent stage's computation. + * + *
  • Dependencies among stages control the triggering of + * computations, but do not otherwise guarantee any particular + * ordering. Additionally, execution of a new stage's computations may + * be arranged in any of three ways: default execution, default + * asynchronous execution (using methods with suffix async + * that employ the stage's default asynchronous execution facility), + * or custom (via a supplied {@link Executor}). The execution + * properties of default and async modes are specified by + * CompletionStage implementations, not this interface. Methods with + * explicit Executor arguments may have arbitrary execution + * properties, and might not even support concurrent execution, but + * are arranged for processing in a way that accommodates asynchrony. + * + *
  • Two method forms ({@link #handle handle} and {@link + * #whenComplete whenComplete}) support unconditional computation + * whether the triggering stage completed normally or exceptionally. + * Method {@link #exceptionally exceptionally} supports computation + * only when the triggering stage completes exceptionally, computing a + * replacement result, similarly to the java {@code catch} keyword. + * In all other cases, if a stage's computation terminates abruptly + * with an (unchecked) exception or error, then all dependent stages + * requiring its completion complete exceptionally as well, with a + * {@link CompletionException} holding the exception as its cause. If + * a stage is dependent on both of two stages, and both + * complete exceptionally, then the CompletionException may correspond + * to either one of these exceptions. If a stage is dependent on + * either of two others, and only one of them completes + * exceptionally, no guarantees are made about whether the dependent + * stage completes normally or exceptionally. In the case of method + * {@code whenComplete}, when the supplied action itself encounters an + * exception, then the stage completes exceptionally with this + * exception unless the source stage also completed exceptionally, in + * which case the exceptional completion from the source stage is + * given preference and propagated to the dependent stage. + * + *
+ * + *

All methods adhere to the above triggering, execution, and + * exceptional completion specifications (which are not repeated in + * individual method specifications). Additionally, while arguments + * used to pass a completion result (that is, for parameters of type + * {@code T}) for methods accepting them may be null, passing a null + * value for any other parameter will result in a {@link + * NullPointerException} being thrown. + * + *

Method form {@link #handle handle} is the most general way of + * creating a continuation stage, unconditionally performing a + * computation that is given both the result and exception (if any) of + * the triggering CompletionStage, and computing an arbitrary result. + * Method {@link #whenComplete whenComplete} is similar, but preserves + * the result of the triggering stage instead of computing a new one. + * Because a stage's normal result may be {@code null}, both methods + * should have a computation structured thus: + * + *

{@code (result, exception) -> {
+ *   if (exception == null) {
+ *     // triggering stage completed normally
+ *   } else {
+ *     // triggering stage completed exceptionally
+ *   }
+ * }}
+ * + *

This interface does not define methods for initially creating, + * forcibly completing normally or exceptionally, probing completion + * status or results, or awaiting completion of a stage. + * Implementations of CompletionStage may provide means of achieving + * such effects, as appropriate. Method {@link #toCompletableFuture} + * enables interoperability among different implementations of this + * interface by providing a common conversion type. + * + * @author Doug Lea + * @since 1.8 + */ +public interface CompletionStage { + + /** + * Returns a new CompletionStage that, when this stage completes + * normally, is executed with this stage's result as the argument + * to the supplied function. + * + *

This method is analogous to + * {@link java.util.Optional#map Optional.map} and + * TODO(streams): make a link to java.util.stream.Stream#map Stream.map. + * + *

See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param fn the function to use to compute the value of the + * returned CompletionStage + * @param the function's return type + * @return the new CompletionStage + */ + public CompletionStage thenApply(Function fn); + + /** + * Returns a new CompletionStage that, when this stage completes + * normally, is executed using this stage's default asynchronous + * execution facility, with this stage's result as the argument to + * the supplied function. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param fn the function to use to compute the value of the + * returned CompletionStage + * @param the function's return type + * @return the new CompletionStage + */ + public CompletionStage thenApplyAsync + (Function fn); + + /** + * Returns a new CompletionStage that, when this stage completes + * normally, is executed using the supplied Executor, with this + * stage's result as the argument to the supplied function. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param fn the function to use to compute the value of the + * returned CompletionStage + * @param executor the executor to use for asynchronous execution + * @param the function's return type + * @return the new CompletionStage + */ + public CompletionStage thenApplyAsync + (Function fn, + Executor executor); + + /** + * Returns a new CompletionStage that, when this stage completes + * normally, is executed with this stage's result as the argument + * to the supplied action. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param action the action to perform before completing the + * returned CompletionStage + * @return the new CompletionStage + */ + public CompletionStage thenAccept(Consumer action); + + /** + * Returns a new CompletionStage that, when this stage completes + * normally, is executed using this stage's default asynchronous + * execution facility, with this stage's result as the argument to + * the supplied action. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param action the action to perform before completing the + * returned CompletionStage + * @return the new CompletionStage + */ + public CompletionStage thenAcceptAsync(Consumer action); + + /** + * Returns a new CompletionStage that, when this stage completes + * normally, is executed using the supplied Executor, with this + * stage's result as the argument to the supplied action. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param action the action to perform before completing the + * returned CompletionStage + * @param executor the executor to use for asynchronous execution + * @return the new CompletionStage + */ + public CompletionStage thenAcceptAsync(Consumer action, + Executor executor); + /** + * Returns a new CompletionStage that, when this stage completes + * normally, executes the given action. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param action the action to perform before completing the + * returned CompletionStage + * @return the new CompletionStage + */ + public CompletionStage thenRun(Runnable action); + + /** + * Returns a new CompletionStage that, when this stage completes + * normally, executes the given action using this stage's default + * asynchronous execution facility. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param action the action to perform before completing the + * returned CompletionStage + * @return the new CompletionStage + */ + public CompletionStage thenRunAsync(Runnable action); + + /** + * Returns a new CompletionStage that, when this stage completes + * normally, executes the given action using the supplied Executor. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param action the action to perform before completing the + * returned CompletionStage + * @param executor the executor to use for asynchronous execution + * @return the new CompletionStage + */ + public CompletionStage thenRunAsync(Runnable action, + Executor executor); + + /** + * Returns a new CompletionStage that, when this and the other + * given stage both complete normally, is executed with the two + * results as arguments to the supplied function. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param fn the function to use to compute the value of the + * returned CompletionStage + * @param the type of the other CompletionStage's result + * @param the function's return type + * @return the new CompletionStage + */ + public CompletionStage thenCombine + (CompletionStage other, + BiFunction fn); + + /** + * Returns a new CompletionStage that, when this and the other + * given stage both complete normally, is executed using this + * stage's default asynchronous execution facility, with the two + * results as arguments to the supplied function. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param fn the function to use to compute the value of the + * returned CompletionStage + * @param the type of the other CompletionStage's result + * @param the function's return type + * @return the new CompletionStage + */ + public CompletionStage thenCombineAsync + (CompletionStage other, + BiFunction fn); + + /** + * Returns a new CompletionStage that, when this and the other + * given stage both complete normally, is executed using the + * supplied executor, with the two results as arguments to the + * supplied function. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param fn the function to use to compute the value of the + * returned CompletionStage + * @param executor the executor to use for asynchronous execution + * @param the type of the other CompletionStage's result + * @param the function's return type + * @return the new CompletionStage + */ + public CompletionStage thenCombineAsync + (CompletionStage other, + BiFunction fn, + Executor executor); + + /** + * Returns a new CompletionStage that, when this and the other + * given stage both complete normally, is executed with the two + * results as arguments to the supplied action. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param action the action to perform before completing the + * returned CompletionStage + * @param the type of the other CompletionStage's result + * @return the new CompletionStage + */ + public CompletionStage thenAcceptBoth + (CompletionStage other, + BiConsumer action); + + /** + * Returns a new CompletionStage that, when this and the other + * given stage both complete normally, is executed using this + * stage's default asynchronous execution facility, with the two + * results as arguments to the supplied action. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param action the action to perform before completing the + * returned CompletionStage + * @param the type of the other CompletionStage's result + * @return the new CompletionStage + */ + public CompletionStage thenAcceptBothAsync + (CompletionStage other, + BiConsumer action); + + /** + * Returns a new CompletionStage that, when this and the other + * given stage both complete normally, is executed using the + * supplied executor, with the two results as arguments to the + * supplied action. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param action the action to perform before completing the + * returned CompletionStage + * @param executor the executor to use for asynchronous execution + * @param the type of the other CompletionStage's result + * @return the new CompletionStage + */ + public CompletionStage thenAcceptBothAsync + (CompletionStage other, + BiConsumer action, + Executor executor); + + /** + * Returns a new CompletionStage that, when this and the other + * given stage both complete normally, executes the given action. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param action the action to perform before completing the + * returned CompletionStage + * @return the new CompletionStage + */ + public CompletionStage runAfterBoth(CompletionStage other, + Runnable action); + /** + * Returns a new CompletionStage that, when this and the other + * given stage both complete normally, executes the given action + * using this stage's default asynchronous execution facility. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param action the action to perform before completing the + * returned CompletionStage + * @return the new CompletionStage + */ + public CompletionStage runAfterBothAsync(CompletionStage other, + Runnable action); + + /** + * Returns a new CompletionStage that, when this and the other + * given stage both complete normally, executes the given action + * using the supplied executor. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param action the action to perform before completing the + * returned CompletionStage + * @param executor the executor to use for asynchronous execution + * @return the new CompletionStage + */ + public CompletionStage runAfterBothAsync(CompletionStage other, + Runnable action, + Executor executor); + /** + * Returns a new CompletionStage that, when either this or the + * other given stage complete normally, is executed with the + * corresponding result as argument to the supplied function. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param fn the function to use to compute the value of the + * returned CompletionStage + * @param the function's return type + * @return the new CompletionStage + */ + public CompletionStage applyToEither + (CompletionStage other, + Function fn); + + /** + * Returns a new CompletionStage that, when either this or the + * other given stage complete normally, is executed using this + * stage's default asynchronous execution facility, with the + * corresponding result as argument to the supplied function. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param fn the function to use to compute the value of the + * returned CompletionStage + * @param the function's return type + * @return the new CompletionStage + */ + public CompletionStage applyToEitherAsync + (CompletionStage other, + Function fn); + + /** + * Returns a new CompletionStage that, when either this or the + * other given stage complete normally, is executed using the + * supplied executor, with the corresponding result as argument to + * the supplied function. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param fn the function to use to compute the value of the + * returned CompletionStage + * @param executor the executor to use for asynchronous execution + * @param the function's return type + * @return the new CompletionStage + */ + public CompletionStage applyToEitherAsync + (CompletionStage other, + Function fn, + Executor executor); + + /** + * Returns a new CompletionStage that, when either this or the + * other given stage complete normally, is executed with the + * corresponding result as argument to the supplied action. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param action the action to perform before completing the + * returned CompletionStage + * @return the new CompletionStage + */ + public CompletionStage acceptEither + (CompletionStage other, + Consumer action); + + /** + * Returns a new CompletionStage that, when either this or the + * other given stage complete normally, is executed using this + * stage's default asynchronous execution facility, with the + * corresponding result as argument to the supplied action. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param action the action to perform before completing the + * returned CompletionStage + * @return the new CompletionStage + */ + public CompletionStage acceptEitherAsync + (CompletionStage other, + Consumer action); + + /** + * Returns a new CompletionStage that, when either this or the + * other given stage complete normally, is executed using the + * supplied executor, with the corresponding result as argument to + * the supplied action. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param action the action to perform before completing the + * returned CompletionStage + * @param executor the executor to use for asynchronous execution + * @return the new CompletionStage + */ + public CompletionStage acceptEitherAsync + (CompletionStage other, + Consumer action, + Executor executor); + + /** + * Returns a new CompletionStage that, when either this or the + * other given stage complete normally, executes the given action. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param action the action to perform before completing the + * returned CompletionStage + * @return the new CompletionStage + */ + public CompletionStage runAfterEither(CompletionStage other, + Runnable action); + + /** + * Returns a new CompletionStage that, when either this or the + * other given stage complete normally, executes the given action + * using this stage's default asynchronous execution facility. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param action the action to perform before completing the + * returned CompletionStage + * @return the new CompletionStage + */ + public CompletionStage runAfterEitherAsync + (CompletionStage other, + Runnable action); + + /** + * Returns a new CompletionStage that, when either this or the + * other given stage complete normally, executes the given action + * using the supplied executor. + * + * See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param other the other CompletionStage + * @param action the action to perform before completing the + * returned CompletionStage + * @param executor the executor to use for asynchronous execution + * @return the new CompletionStage + */ + public CompletionStage runAfterEitherAsync + (CompletionStage other, + Runnable action, + Executor executor); + + /** + * Returns a new CompletionStage that is completed with the same + * value as the CompletionStage returned by the given function. + * + *

When this stage completes normally, the given function is + * invoked with this stage's result as the argument, returning + * another CompletionStage. When that stage completes normally, + * the CompletionStage returned by this method is completed with + * the same value. + * + *

To ensure progress, the supplied function must arrange + * eventual completion of its result. + * + *

This method is analogous to + * {@link java.util.Optional#flatMap Optional.flatMap} and + * TODO(streams): make a link to java.util.stream.Stream#flatMap Stream.flatMap. + * + *

See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param fn the function to use to compute another CompletionStage + * @param the type of the returned CompletionStage's result + * @return the new CompletionStage + */ + public CompletionStage thenCompose + (Function> fn); + + /** + * Returns a new CompletionStage that is completed with the same + * value as the CompletionStage returned by the given function, + * executed using this stage's default asynchronous execution + * facility. + * + *

When this stage completes normally, the given function is + * invoked with this stage's result as the argument, returning + * another CompletionStage. When that stage completes normally, + * the CompletionStage returned by this method is completed with + * the same value. + * + *

To ensure progress, the supplied function must arrange + * eventual completion of its result. + * + *

See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param fn the function to use to compute another CompletionStage + * @param the type of the returned CompletionStage's result + * @return the new CompletionStage + */ + public CompletionStage thenComposeAsync + (Function> fn); + + /** + * Returns a new CompletionStage that is completed with the same + * value as the CompletionStage returned by the given function, + * executed using the supplied Executor. + * + *

When this stage completes normally, the given function is + * invoked with this stage's result as the argument, returning + * another CompletionStage. When that stage completes normally, + * the CompletionStage returned by this method is completed with + * the same value. + * + *

To ensure progress, the supplied function must arrange + * eventual completion of its result. + * + *

See the {@link CompletionStage} documentation for rules + * covering exceptional completion. + * + * @param fn the function to use to compute another CompletionStage + * @param executor the executor to use for asynchronous execution + * @param the type of the returned CompletionStage's result + * @return the new CompletionStage + */ + public CompletionStage thenComposeAsync + (Function> fn, + Executor executor); + + /** + * Returns a new CompletionStage that, when this stage completes + * either normally or exceptionally, is executed with this stage's + * result and exception as arguments to the supplied function. + * + *

When this stage is complete, the given function is invoked + * with the result (or {@code null} if none) and the exception (or + * {@code null} if none) of this stage as arguments, and the + * function's result is used to complete the returned stage. + * + * @param fn the function to use to compute the value of the + * returned CompletionStage + * @param the function's return type + * @return the new CompletionStage + */ + public CompletionStage handle + (BiFunction fn); + + /** + * Returns a new CompletionStage that, when this stage completes + * either normally or exceptionally, is executed using this stage's + * default asynchronous execution facility, with this stage's + * result and exception as arguments to the supplied function. + * + *

When this stage is complete, the given function is invoked + * with the result (or {@code null} if none) and the exception (or + * {@code null} if none) of this stage as arguments, and the + * function's result is used to complete the returned stage. + * + * @param fn the function to use to compute the value of the + * returned CompletionStage + * @param the function's return type + * @return the new CompletionStage + */ + public CompletionStage handleAsync + (BiFunction fn); + + /** + * Returns a new CompletionStage that, when this stage completes + * either normally or exceptionally, is executed using the + * supplied executor, with this stage's result and exception as + * arguments to the supplied function. + * + *

When this stage is complete, the given function is invoked + * with the result (or {@code null} if none) and the exception (or + * {@code null} if none) of this stage as arguments, and the + * function's result is used to complete the returned stage. + * + * @param fn the function to use to compute the value of the + * returned CompletionStage + * @param executor the executor to use for asynchronous execution + * @param the function's return type + * @return the new CompletionStage + */ + public CompletionStage handleAsync + (BiFunction fn, + Executor executor); + + /** + * Returns a new CompletionStage with the same result or exception as + * this stage, that executes the given action when this stage completes. + * + *

When this stage is complete, the given action is invoked + * with the result (or {@code null} if none) and the exception (or + * {@code null} if none) of this stage as arguments. The returned + * stage is completed when the action returns. + * + *

Unlike method {@link #handle handle}, + * this method is not designed to translate completion outcomes, + * so the supplied action should not throw an exception. However, + * if it does, the following rules apply: if this stage completed + * normally but the supplied action throws an exception, then the + * returned stage completes exceptionally with the supplied + * action's exception. Or, if this stage completed exceptionally + * and the supplied action throws an exception, then the returned + * stage completes exceptionally with this stage's exception. + * + * @param action the action to perform + * @return the new CompletionStage + */ + public CompletionStage whenComplete + (BiConsumer action); + + /** + * Returns a new CompletionStage with the same result or exception as + * this stage, that executes the given action using this stage's + * default asynchronous execution facility when this stage completes. + * + *

When this stage is complete, the given action is invoked with the + * result (or {@code null} if none) and the exception (or {@code null} + * if none) of this stage as arguments. The returned stage is completed + * when the action returns. + * + *

Unlike method {@link #handleAsync(BiFunction) handleAsync}, + * this method is not designed to translate completion outcomes, + * so the supplied action should not throw an exception. However, + * if it does, the following rules apply: If this stage completed + * normally but the supplied action throws an exception, then the + * returned stage completes exceptionally with the supplied + * action's exception. Or, if this stage completed exceptionally + * and the supplied action throws an exception, then the returned + * stage completes exceptionally with this stage's exception. + * + * @param action the action to perform + * @return the new CompletionStage + */ + public CompletionStage whenCompleteAsync + (BiConsumer action); + + /** + * Returns a new CompletionStage with the same result or exception as + * this stage, that executes the given action using the supplied + * Executor when this stage completes. + * + *

When this stage is complete, the given action is invoked with the + * result (or {@code null} if none) and the exception (or {@code null} + * if none) of this stage as arguments. The returned stage is completed + * when the action returns. + * + *

Unlike method {@link #handleAsync(BiFunction,Executor) handleAsync}, + * this method is not designed to translate completion outcomes, + * so the supplied action should not throw an exception. However, + * if it does, the following rules apply: If this stage completed + * normally but the supplied action throws an exception, then the + * returned stage completes exceptionally with the supplied + * action's exception. Or, if this stage completed exceptionally + * and the supplied action throws an exception, then the returned + * stage completes exceptionally with this stage's exception. + * + * @param action the action to perform + * @param executor the executor to use for asynchronous execution + * @return the new CompletionStage + */ + public CompletionStage whenCompleteAsync + (BiConsumer action, + Executor executor); + + /** + * Returns a new CompletionStage that, when this stage completes + * exceptionally, is executed with this stage's exception as the + * argument to the supplied function. Otherwise, if this stage + * completes normally, then the returned stage also completes + * normally with the same value. + * + * @param fn the function to use to compute the value of the + * returned CompletionStage if this CompletionStage completed + * exceptionally + * @return the new CompletionStage + */ + public CompletionStage exceptionally + (Function fn); + + /** + * Returns a {@link CompletableFuture} maintaining the same + * completion properties as this stage. If this stage is already a + * CompletableFuture, this method may return this stage itself. + * Otherwise, invocation of this method may be equivalent in + * effect to {@code thenApply(x -> x)}, but returning an instance + * of type {@code CompletableFuture}. A CompletionStage + * implementation that does not choose to interoperate with others + * may throw {@code UnsupportedOperationException}. + * + * @return the CompletableFuture + * @throws UnsupportedOperationException if this implementation + * does not interoperate with CompletableFuture + */ + public CompletableFuture toCompletableFuture(); + +} diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java b/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java index 3ed54cf42..b4fa8aa8a 100644 --- a/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java +++ b/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java @@ -13,7 +13,6 @@ import java.util.AbstractMap; import java.util.Arrays; import java.util.Collection; -import java.util.ConcurrentModificationException; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; @@ -21,14 +20,29 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.Spliterator; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.DoubleBinaryOperator; +import java.util.function.Function; +import java.util.function.IntBinaryOperator; +import java.util.function.LongBinaryOperator; +import java.util.function.Predicate; +import java.util.function.ToDoubleBiFunction; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntBiFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongBiFunction; +import java.util.function.ToLongFunction; +// TODO(streams): +//import java.util.stream.Stream; // BEGIN android-note // removed link to collections framework docs -// removed links to hidden api // END android-note /** @@ -52,14 +66,14 @@ * that key reporting the updated value.) For aggregate operations * such as {@code putAll} and {@code clear}, concurrent retrievals may * reflect insertion or removal of only some entries. Similarly, - * Iterators and Enumerations return elements reflecting the state of - * the hash table at some point at or since the creation of the + * Iterators, Spliterators and Enumerations return elements reflecting the + * state of the hash table at some point at or since the creation of the * iterator/enumeration. They do not throw {@link - * ConcurrentModificationException}. However, iterators are designed - * to be used by only one thread at a time. Bear in mind that the - * results of aggregate status methods including {@code size}, {@code - * isEmpty}, and {@code containsValue} are typically useful only when - * a map is not undergoing concurrent updates in other threads. + * java.util.ConcurrentModificationException ConcurrentModificationException}. + * However, iterators are designed to be used by only one thread at a time. + * Bear in mind that the results of aggregate status methods including + * {@code size}, {@code isEmpty}, and {@code containsValue} are typically + * useful only when a map is not undergoing concurrent updates in other threads. * Otherwise the results of these methods reflect transient states * that may be adequate for monitoring or estimation purposes, but not * for program control. @@ -86,6 +100,19 @@ * hash table. To ameliorate impact, when keys are {@link Comparable}, * this class may use comparison order among keys to help break ties. * + *

A {@link Set} projection of a ConcurrentHashMap may be created + * (using {@link #newKeySet()} or {@link #newKeySet(int)}), or viewed + * (using {@link #keySet(Object)} when only keys are of interest, and the + * mapped values are (perhaps transiently) not used or all take the + * same mapping value. + * + *

A ConcurrentHashMap can be used as a scalable frequency map (a + * form of histogram or multiset) by using {@link + * java.util.concurrent.atomic.LongAdder} values and initializing via + * {@link #computeIfAbsent computeIfAbsent}. For example, to add a count + * to a {@code ConcurrentHashMap freqs}, you can use + * {@code freqs.computeIfAbsent(key, k -> new LongAdder()).increment();} + * *

This class and its views and iterators implement all of the * optional methods of the {@link Map} and {@link Iterator} * interfaces. @@ -93,15 +120,121 @@ *

Like {@link Hashtable} but unlike {@link HashMap}, this class * does not allow {@code null} to be used as a key or value. * + *

ConcurrentHashMaps support a set of sequential and parallel bulk + * operations that, unlike most (TODO(streams): link to Stream) methods, are designed + * to be safely, and often sensibly, applied even with maps that are + * being concurrently updated by other threads; for example, when + * computing a snapshot summary of the values in a shared registry. + * There are three kinds of operation, each with four forms, accepting + * functions with keys, values, entries, and (key, value) pairs as + * arguments and/or return values. Because the elements of a + * ConcurrentHashMap are not ordered in any particular way, and may be + * processed in different orders in different parallel executions, the + * correctness of supplied functions should not depend on any + * ordering, or on any other objects or values that may transiently + * change while computation is in progress; and except for forEach + * actions, should ideally be side-effect-free. Bulk operations on + * {@link java.util.Map.Entry} objects do not support method {@code + * setValue}. + * + *

    + *
  • forEach: Performs a given action on each element. + * A variant form applies a given transformation on each element + * before performing the action. + * + *
  • search: Returns the first available non-null result of + * applying a given function on each element; skipping further + * search when a result is found. + * + *
  • reduce: Accumulates each element. The supplied reduction + * function cannot rely on ordering (more formally, it should be + * both associative and commutative). There are five variants: + * + *
      + * + *
    • Plain reductions. (There is not a form of this method for + * (key, value) function arguments since there is no corresponding + * return type.) + * + *
    • Mapped reductions that accumulate the results of a given + * function applied to each element. + * + *
    • Reductions to scalar doubles, longs, and ints, using a + * given basis value. + * + *
    + *
+ * + *

These bulk operations accept a {@code parallelismThreshold} + * argument. Methods proceed sequentially if the current map size is + * estimated to be less than the given threshold. Using a value of + * {@code Long.MAX_VALUE} suppresses all parallelism. Using a value + * of {@code 1} results in maximal parallelism by partitioning into + * enough subtasks to fully utilize the {@link + * ForkJoinPool#commonPool()} that is used for all parallel + * computations. Normally, you would initially choose one of these + * extreme values, and then measure performance of using in-between + * values that trade off overhead versus throughput. + * + *

The concurrency properties of bulk operations follow + * from those of ConcurrentHashMap: Any non-null result returned + * from {@code get(key)} and related access methods bears a + * happens-before relation with the associated insertion or + * update. The result of any bulk operation reflects the + * composition of these per-element relations (but is not + * necessarily atomic with respect to the map as a whole unless it + * is somehow known to be quiescent). Conversely, because keys + * and values in the map are never null, null serves as a reliable + * atomic indicator of the current lack of any result. To + * maintain this property, null serves as an implicit basis for + * all non-scalar reduction operations. For the double, long, and + * int versions, the basis should be one that, when combined with + * any other value, returns that other value (more formally, it + * should be the identity element for the reduction). Most common + * reductions have these properties; for example, computing a sum + * with basis 0 or a minimum with basis MAX_VALUE. + * + *

Search and transformation functions provided as arguments + * should similarly return null to indicate the lack of any result + * (in which case it is not used). In the case of mapped + * reductions, this also enables transformations to serve as + * filters, returning null (or, in the case of primitive + * specializations, the identity basis) if the element should not + * be combined. You can create compound transformations and + * filterings by composing them yourself under this "null means + * there is nothing there now" rule before using them in search or + * reduce operations. + * + *

Methods accepting and/or returning Entry arguments maintain + * key-value associations. They may be useful for example when + * finding the key for the greatest value. Note that "plain" Entry + * arguments can be supplied using {@code new + * AbstractMap.SimpleEntry(k,v)}. + * + *

Bulk operations may complete abruptly, throwing an + * exception encountered in the application of a supplied + * function. Bear in mind when handling such exceptions that other + * concurrently executing functions could also have thrown + * exceptions, or would have done so if the first exception had + * not occurred. + * + *

Speedups for parallel compared to sequential forms are common + * but not guaranteed. Parallel operations involving brief functions + * on small maps may execute more slowly than sequential forms if the + * underlying work to parallelize the computation is more expensive + * than the computation itself. Similarly, parallelization may not + * lead to much actual parallelism if all processors are busy + * performing unrelated tasks. + * + *

All arguments to all task methods must be non-null. + * * @since 1.5 * @author Doug Lea * @param the type of keys maintained by this map * @param the type of mapped values */ -// android-note: removed documentation about hidden newKeySet and newKeySet(int) APIs. -// android-note: Added "extends AbstractMap. -public class ConcurrentHashMap extends AbstractMap - implements ConcurrentMap, Serializable { +public class ConcurrentHashMap extends AbstractMap + implements ConcurrentMap, Serializable { private static final long serialVersionUID = 7249069246763182397L; /* @@ -316,7 +449,7 @@ public class ConcurrentHashMap extends AbstractMap * * Maintaining API and serialization compatibility with previous * versions of this class introduces several oddities. Mainly: We - * leave untouched but unused constructor arguments refering to + * leave untouched but unused constructor arguments referring to * concurrencyLevel. We accept a loadFactor constructor argument, * but apply it only to initial table capacity (which is the only * time that we can guarantee to honor it.) We also declare an @@ -335,7 +468,6 @@ public class ConcurrentHashMap extends AbstractMap * bulk operations. */ - /* ---------------- Constants -------------- */ /** @@ -412,7 +544,7 @@ public class ConcurrentHashMap extends AbstractMap * The number of bits used for generation stamp in sizeCtl. * Must be at least 6 for 32bit arrays. */ - private static int RESIZE_STAMP_BITS = 16; + private static final int RESIZE_STAMP_BITS = 16; /** * The maximum number of threads that can help resize. @@ -428,19 +560,28 @@ public class ConcurrentHashMap extends AbstractMap /* * Encodings for Node hash fields. See above for explanation. */ - static final int MOVED = 0x8fffffff; // (-1) hash for forwarding nodes - static final int TREEBIN = 0x80000000; // hash for roots of trees - static final int RESERVED = 0x80000001; // hash for transient reservations + static final int MOVED = -1; // hash for forwarding nodes + static final int TREEBIN = -2; // hash for roots of trees + static final int RESERVED = -3; // hash for transient reservations static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash /** Number of CPUS, to place bounds on some sizings */ static final int NCPU = Runtime.getRuntime().availableProcessors(); - /** For serialization compatibility. */ + /** + * Serialized pseudo-fields, provided only for jdk7 compatibility. + * @serialField segments Segment[] + * The segments, each of which is a specialized hash table. + * @serialField segmentMask int + * Mask value for indexing into segments. The upper bits of a + * key's hash code are used to choose the segment. + * @serialField segmentShift int + * Shift value for indexing within segments. + */ private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("segments", Segment[].class), new ObjectStreamField("segmentMask", Integer.TYPE), - new ObjectStreamField("segmentShift", Integer.TYPE) + new ObjectStreamField("segmentShift", Integer.TYPE), }; /* ---------------- Nodes -------------- */ @@ -457,7 +598,7 @@ static class Node implements Map.Entry { final int hash; final K key; volatile V val; - Node next; + volatile Node next; Node(int hash, K key, V val, Node next) { this.hash = hash; @@ -466,10 +607,12 @@ static class Node implements Map.Entry { this.next = next; } - public final K getKey() { return key; } - public final V getValue() { return val; } - public final int hashCode() { return key.hashCode() ^ val.hashCode(); } - public final String toString(){ return key + "=" + val; } + public final K getKey() { return key; } + public final V getValue() { return val; } + public final int hashCode() { return key.hashCode() ^ val.hashCode(); } + public final String toString() { + return Helpers.mapEntryToString(key, val); + } public final V setValue(V value) { throw new UnsupportedOperationException(); } @@ -582,8 +725,9 @@ static int compareComparables(Class kc, Object k, Object x) { * errors by users, these checks must operate on local variables, * which accounts for some odd-looking inline assignments below. * Note that calls to setTabAt always occur within locked regions, - * and so do not need full volatile semantics, but still require - * ordering to maintain concurrent readability. + * and so in principle require only release ordering, not + * full volatile semantics, but are currently coded as volatile + * writes to be conservative. */ @SuppressWarnings("unchecked") @@ -597,7 +741,7 @@ static final boolean casTabAt(Node[] tab, int i, } static final void setTabAt(Node[] tab, int i, Node v) { - U.putOrderedObject(tab, ((long)i << ASHIFT) + ABASE, v); + U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); } /* ---------------- Fields -------------- */ @@ -892,6 +1036,8 @@ else if (f instanceof TreeBin) { p.val = value; } } + else if (f instanceof ReservationNode) + throw new IllegalStateException("Recursive update"); } } if (binCount != 0) { @@ -994,6 +1140,8 @@ else if (t.removeTreeNode(p)) } } } + else if (f instanceof ReservationNode) + throw new IllegalStateException("Recursive update"); } } if (validated) { @@ -1054,16 +1202,15 @@ else if ((fh = f.hash) == MOVED) { * operations. It does not support the {@code add} or * {@code addAll} operations. * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + *

The view's iterators and spliterators are + * weakly consistent. + * + *

The view's {@code spliterator} reports {@link Spliterator#CONCURRENT}, + * {@link Spliterator#DISTINCT}, and {@link Spliterator#NONNULL}. * * @return the set view */ - // android-note : changed KeySetView to Set to maintain API compatibility. - public Set keySet() { + public KeySetView keySet() { KeySetView ks; return (ks = keySet) != null ? ks : (keySet = new KeySetView(this, null)); } @@ -1078,11 +1225,11 @@ public Set keySet() { * {@code retainAll}, and {@code clear} operations. It does not * support the {@code add} or {@code addAll} operations. * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + *

The view's iterators and spliterators are + * weakly consistent. + * + *

The view's {@code spliterator} reports {@link Spliterator#CONCURRENT} + * and {@link Spliterator#NONNULL}. * * @return the collection view */ @@ -1100,11 +1247,11 @@ public Collection values() { * {@code removeAll}, {@code retainAll}, and {@code clear} * operations. * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + *

The view's iterators and spliterators are + * weakly consistent. + * + *

The view's {@code spliterator} reports {@link Spliterator#CONCURRENT}, + * {@link Spliterator#DISTINCT}, and {@link Spliterator#NONNULL}. * * @return the set view */ @@ -1202,7 +1349,7 @@ public boolean equals(Object o) { /** * Stripped-down version of helper class used in previous version, - * declared for the sake of serialization compatibility + * declared for the sake of serialization compatibility. */ static class Segment extends ReentrantLock implements Serializable { private static final long serialVersionUID = 2249069246763182397L; @@ -1214,9 +1361,10 @@ static class Segment extends ReentrantLock implements Serializable { * Saves the state of the {@code ConcurrentHashMap} instance to a * stream (i.e., serializes it). * @param s the stream + * @throws java.io.IOException if an I/O error occurs * @serialData - * the key (Object) and value (Object) - * for each key-value mapping, followed by a null pair. + * the serialized fields, followed by the key (Object) and value + * (Object) for each key-value mapping, followed by a null pair. * The key-value mappings are emitted in no particular order. */ private void writeObject(java.io.ObjectOutputStream s) @@ -1231,7 +1379,8 @@ private void writeObject(java.io.ObjectOutputStream s) } int segmentShift = 32 - sshift; int segmentMask = ssize - 1; - @SuppressWarnings("unchecked") Segment[] segments = (Segment[]) + @SuppressWarnings("unchecked") + Segment[] segments = (Segment[]) new Segment[DEFAULT_CONCURRENCY_LEVEL]; for (int i = 0; i < segments.length; ++i) segments[i] = new Segment(LOAD_FACTOR); @@ -1251,12 +1400,14 @@ private void writeObject(java.io.ObjectOutputStream s) } s.writeObject(null); s.writeObject(null); - segments = null; // throw away } /** * Reconstitutes the instance from a stream (that is, deserializes it). * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws java.io.IOException if an I/O error occurs */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { @@ -1272,8 +1423,10 @@ private void readObject(java.io.ObjectInputStream s) long size = 0L; Node p = null; for (;;) { - @SuppressWarnings("unchecked") K k = (K) s.readObject(); - @SuppressWarnings("unchecked") V v = (V) s.readObject(); + @SuppressWarnings("unchecked") + K k = (K) s.readObject(); + @SuppressWarnings("unchecked") + V v = (V) s.readObject(); if (k != null && v != null) { p = new Node(spread(k.hashCode()), k, v, p); ++size; @@ -1292,7 +1445,7 @@ private void readObject(java.io.ObjectInputStream s) n = tableSizeFor(sz + (sz >>> 1) + 1); } @SuppressWarnings("unchecked") - Node[] tab = (Node[])new Node[n]; + Node[] tab = (Node[])new Node[n]; int mask = n - 1; long added = 0L; while (p != null) { @@ -1400,164 +1553,688 @@ public V replace(K key, V value) { throw new NullPointerException(); return replaceNode(key, value, null); } - // Hashtable legacy methods + + // Overrides of JDK8+ Map extension method defaults /** - * Legacy method testing if some key maps into the specified value - * in this table. + * Returns the value to which the specified key is mapped, or the + * given default value if this map contains no mapping for the + * key. * - * This method is identical in functionality to - * {@link #containsValue(Object)}, and exists solely to ensure - * full compatibility with class {@link java.util.Hashtable}, - * which supported this method prior to introduction of the - * Java Collections framework. - * - * @param value a value to search for - * @return {@code true} if and only if some key maps to the - * {@code value} argument in this table as - * determined by the {@code equals} method; - * {@code false} otherwise - * @throws NullPointerException if the specified value is null + * @param key the key whose associated value is to be returned + * @param defaultValue the value to return if this map contains + * no mapping for the given key + * @return the mapping for the key, if present; else the default value + * @throws NullPointerException if the specified key is null */ - // android-note : removed @deprecated tag from javadoc. - public boolean contains(Object value) { - // BEGIN android-note - // removed deprecation - // END android-note - return containsValue(value); + public V getOrDefault(Object key, V defaultValue) { + V v; + return (v = get(key)) == null ? defaultValue : v; } - /** - * Returns an enumeration of the keys in this table. - * - * @return an enumeration of the keys in this table - * @see #keySet() - */ - public Enumeration keys() { + public void forEach(BiConsumer action) { + if (action == null) throw new NullPointerException(); Node[] t; - int f = (t = table) == null ? 0 : t.length; - return new KeyIterator(t, f, 0, f, this); + if ((t = table) != null) { + Traverser it = new Traverser(t, t.length, 0, t.length); + for (Node p; (p = it.advance()) != null; ) { + action.accept(p.key, p.val); + } + } } - /** - * Returns an enumeration of the values in this table. - * - * @return an enumeration of the values in this table - * @see #values() - */ - public Enumeration elements() { + public void replaceAll(BiFunction function) { + if (function == null) throw new NullPointerException(); Node[] t; - int f = (t = table) == null ? 0 : t.length; - return new ValueIterator(t, f, 0, f, this); + if ((t = table) != null) { + Traverser it = new Traverser(t, t.length, 0, t.length); + for (Node p; (p = it.advance()) != null; ) { + V oldValue = p.val; + for (K key = p.key;;) { + V newValue = function.apply(key, oldValue); + if (newValue == null) + throw new NullPointerException(); + if (replaceNode(key, newValue, oldValue) != null || + (oldValue = get(key)) == null) + break; + } + } + } } - // ConcurrentHashMap-only methods - /** - * Returns the number of mappings. This method should be used - * instead of {@link #size} because a ConcurrentHashMap may - * contain more mappings than can be represented as an int. The - * value returned is an estimate; the actual count may differ if - * there are concurrent insertions or removals. - * - * @return the number of mappings - * @since 1.8 - * - * @hide + * Helper method for EntrySetView.removeIf. */ - public long mappingCount() { - long n = sumCount(); - return (n < 0L) ? 0L : n; // ignore transient negative values + boolean removeEntryIf(Predicate> function) { + if (function == null) throw new NullPointerException(); + Node[] t; + boolean removed = false; + if ((t = table) != null) { + Traverser it = new Traverser(t, t.length, 0, t.length); + for (Node p; (p = it.advance()) != null; ) { + K k = p.key; + V v = p.val; + Map.Entry e = new AbstractMap.SimpleImmutableEntry<>(k, v); + if (function.test(e) && replaceNode(k, null, v) != null) + removed = true; + } + } + return removed; } /** - * Creates a new {@link Set} backed by a ConcurrentHashMap - * from the given type to {@code Boolean.TRUE}. - * - * @param the element type of the returned set - * @return the new set - * @since 1.8 - * - * @hide + * Helper method for ValuesView.removeIf. */ - public static KeySetView newKeySet() { - return new KeySetView - (new ConcurrentHashMap(), Boolean.TRUE); + boolean removeValueIf(Predicate function) { + if (function == null) throw new NullPointerException(); + Node[] t; + boolean removed = false; + if ((t = table) != null) { + Traverser it = new Traverser(t, t.length, 0, t.length); + for (Node p; (p = it.advance()) != null; ) { + K k = p.key; + V v = p.val; + if (function.test(v) && replaceNode(k, null, v) != null) + removed = true; + } + } + return removed; } /** - * Creates a new {@link Set} backed by a ConcurrentHashMap - * from the given type to {@code Boolean.TRUE}. - * - * @param initialCapacity The implementation performs internal - * sizing to accommodate this many elements. - * @param the element type of the returned set - * @return the new set - * @throws IllegalArgumentException if the initial capacity of - * elements is negative - * @since 1.8 + * If the specified key is not already associated with a value, + * attempts to compute its value using the given mapping function + * and enters it into this map unless {@code null}. The entire + * method invocation is performed atomically, so the function is + * applied at most once per key. Some attempted update operations + * on this map by other threads may be blocked while computation + * is in progress, so the computation should be short and simple, + * and must not attempt to update any other mappings of this map. * - * @hide + * @param key key with which the specified value is to be associated + * @param mappingFunction the function to compute a value + * @return the current (existing or computed) value associated with + * the specified key, or null if the computed value is null + * @throws NullPointerException if the specified key or mappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the mappingFunction does so, + * in which case the mapping is left unestablished */ - public static KeySetView newKeySet(int initialCapacity) { - return new KeySetView - (new ConcurrentHashMap(initialCapacity), Boolean.TRUE); + public V computeIfAbsent(K key, Function mappingFunction) { + if (key == null || mappingFunction == null) + throw new NullPointerException(); + int h = spread(key.hashCode()); + V val = null; + int binCount = 0; + for (Node[] tab = table;;) { + Node f; int n, i, fh; + if (tab == null || (n = tab.length) == 0) + tab = initTable(); + else if ((f = tabAt(tab, i = (n - 1) & h)) == null) { + Node r = new ReservationNode(); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Node node = null; + try { + if ((val = mappingFunction.apply(key)) != null) + node = new Node(h, key, val, null); + } finally { + setTabAt(tab, i, node); + } + } + } + if (binCount != 0) + break; + } + else if ((fh = f.hash) == MOVED) + tab = helpTransfer(tab, f); + else { + boolean added = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + binCount = 1; + for (Node e = f;; ++binCount) { + K ek; + if (e.hash == h && + ((ek = e.key) == key || + (ek != null && key.equals(ek)))) { + val = e.val; + break; + } + Node pred = e; + if ((e = e.next) == null) { + if ((val = mappingFunction.apply(key)) != null) { + if (pred.next != null) + throw new IllegalStateException("Recursive update"); + added = true; + pred.next = new Node(h, key, val, null); + } + break; + } + } + } + else if (f instanceof TreeBin) { + binCount = 2; + TreeBin t = (TreeBin)f; + TreeNode r, p; + if ((r = t.root) != null && + (p = r.findTreeNode(h, key, null)) != null) + val = p.val; + else if ((val = mappingFunction.apply(key)) != null) { + added = true; + t.putTreeVal(h, key, val); + } + } + else if (f instanceof ReservationNode) + throw new IllegalStateException("Recursive update"); + } + } + if (binCount != 0) { + if (binCount >= TREEIFY_THRESHOLD) + treeifyBin(tab, i); + if (!added) + return val; + break; + } + } + } + if (val != null) + addCount(1L, binCount); + return val; } /** - * Returns a {@link Set} view of the keys in this map, using the - * given common mapped value for any additions (i.e., {@link - * Collection#add} and {@link Collection#addAll(Collection)}). - * This is of course only appropriate if it is acceptable to use - * the same value for all additions from this view. + * If the value for the specified key is present, attempts to + * compute a new mapping given the key and its current mapped + * value. The entire method invocation is performed atomically. + * Some attempted update operations on this map by other threads + * may be blocked while computation is in progress, so the + * computation should be short and simple, and must not attempt to + * update any other mappings of this map. * - * @param mappedValue the mapped value to use for any additions - * @return the set view - * @throws NullPointerException if the mappedValue is null - * - * @hide + * @param key key with which a value may be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or remappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the remappingFunction does so, + * in which case the mapping is unchanged */ - public KeySetView keySet(V mappedValue) { - if (mappedValue == null) + public V computeIfPresent(K key, BiFunction remappingFunction) { + if (key == null || remappingFunction == null) throw new NullPointerException(); - return new KeySetView(this, mappedValue); - } - - /* ---------------- Special Nodes -------------- */ - - /** - * A node inserted at head of bins during transfer operations. - */ - static final class ForwardingNode extends Node { - final Node[] nextTable; - ForwardingNode(Node[] tab) { - super(MOVED, null, null, null); - this.nextTable = tab; - } - - Node find(int h, Object k) { - Node e; int n; - Node[] tab = nextTable; - if (k != null && tab != null && (n = tab.length) > 0 && - (e = tabAt(tab, (n - 1) & h)) != null) { - do { - int eh; K ek; - if ((eh = e.hash) == h && - ((ek = e.key) == k || (ek != null && k.equals(ek)))) - return e; - if (eh < 0) - return e.find(h, k); - } while ((e = e.next) != null); + int h = spread(key.hashCode()); + V val = null; + int delta = 0; + int binCount = 0; + for (Node[] tab = table;;) { + Node f; int n, i, fh; + if (tab == null || (n = tab.length) == 0) + tab = initTable(); + else if ((f = tabAt(tab, i = (n - 1) & h)) == null) + break; + else if ((fh = f.hash) == MOVED) + tab = helpTransfer(tab, f); + else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + binCount = 1; + for (Node e = f, pred = null;; ++binCount) { + K ek; + if (e.hash == h && + ((ek = e.key) == key || + (ek != null && key.equals(ek)))) { + val = remappingFunction.apply(key, e.val); + if (val != null) + e.val = val; + else { + delta = -1; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + break; + } + pred = e; + if ((e = e.next) == null) + break; + } + } + else if (f instanceof TreeBin) { + binCount = 2; + TreeBin t = (TreeBin)f; + TreeNode r, p; + if ((r = t.root) != null && + (p = r.findTreeNode(h, key, null)) != null) { + val = remappingFunction.apply(key, p.val); + if (val != null) + p.val = val; + else { + delta = -1; + if (t.removeTreeNode(p)) + setTabAt(tab, i, untreeify(t.first)); + } + } + } + else if (f instanceof ReservationNode) + throw new IllegalStateException("Recursive update"); + } + } + if (binCount != 0) + break; } - return null; } + if (delta != 0) + addCount((long)delta, binCount); + return val; } /** - * A place-holder node used in computeIfAbsent and compute + * Attempts to compute a mapping for the specified key and its + * current mapped value (or {@code null} if there is no current + * mapping). The entire method invocation is performed atomically. + * Some attempted update operations on this map by other threads + * may be blocked while computation is in progress, so the + * computation should be short and simple, and must not attempt to + * update any other mappings of this Map. + * + * @param key key with which the specified value is to be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or remappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the remappingFunction does so, + * in which case the mapping is unchanged */ - static final class ReservationNode extends Node { - ReservationNode() { + public V compute(K key, + BiFunction remappingFunction) { + if (key == null || remappingFunction == null) + throw new NullPointerException(); + int h = spread(key.hashCode()); + V val = null; + int delta = 0; + int binCount = 0; + for (Node[] tab = table;;) { + Node f; int n, i, fh; + if (tab == null || (n = tab.length) == 0) + tab = initTable(); + else if ((f = tabAt(tab, i = (n - 1) & h)) == null) { + Node r = new ReservationNode(); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Node node = null; + try { + if ((val = remappingFunction.apply(key, null)) != null) { + delta = 1; + node = new Node(h, key, val, null); + } + } finally { + setTabAt(tab, i, node); + } + } + } + if (binCount != 0) + break; + } + else if ((fh = f.hash) == MOVED) + tab = helpTransfer(tab, f); + else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + binCount = 1; + for (Node e = f, pred = null;; ++binCount) { + K ek; + if (e.hash == h && + ((ek = e.key) == key || + (ek != null && key.equals(ek)))) { + val = remappingFunction.apply(key, e.val); + if (val != null) + e.val = val; + else { + delta = -1; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + break; + } + pred = e; + if ((e = e.next) == null) { + val = remappingFunction.apply(key, null); + if (val != null) { + if (pred.next != null) + throw new IllegalStateException("Recursive update"); + delta = 1; + pred.next = + new Node(h, key, val, null); + } + break; + } + } + } + else if (f instanceof TreeBin) { + binCount = 1; + TreeBin t = (TreeBin)f; + TreeNode r, p; + if ((r = t.root) != null) + p = r.findTreeNode(h, key, null); + else + p = null; + V pv = (p == null) ? null : p.val; + val = remappingFunction.apply(key, pv); + if (val != null) { + if (p != null) + p.val = val; + else { + delta = 1; + t.putTreeVal(h, key, val); + } + } + else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) + setTabAt(tab, i, untreeify(t.first)); + } + } + else if (f instanceof ReservationNode) + throw new IllegalStateException("Recursive update"); + } + } + if (binCount != 0) { + if (binCount >= TREEIFY_THRESHOLD) + treeifyBin(tab, i); + break; + } + } + } + if (delta != 0) + addCount((long)delta, binCount); + return val; + } + + /** + * If the specified key is not already associated with a + * (non-null) value, associates it with the given value. + * Otherwise, replaces the value with the results of the given + * remapping function, or removes if {@code null}. The entire + * method invocation is performed atomically. Some attempted + * update operations on this map by other threads may be blocked + * while computation is in progress, so the computation should be + * short and simple, and must not attempt to update any other + * mappings of this Map. + * + * @param key key with which the specified value is to be associated + * @param value the value to use if absent + * @param remappingFunction the function to recompute a value if present + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or the + * remappingFunction is null + * @throws RuntimeException or Error if the remappingFunction does so, + * in which case the mapping is unchanged + */ + public V merge(K key, V value, BiFunction remappingFunction) { + if (key == null || value == null || remappingFunction == null) + throw new NullPointerException(); + int h = spread(key.hashCode()); + V val = null; + int delta = 0; + int binCount = 0; + for (Node[] tab = table;;) { + Node f; int n, i, fh; + if (tab == null || (n = tab.length) == 0) + tab = initTable(); + else if ((f = tabAt(tab, i = (n - 1) & h)) == null) { + if (casTabAt(tab, i, null, new Node(h, key, value, null))) { + delta = 1; + val = value; + break; + } + } + else if ((fh = f.hash) == MOVED) + tab = helpTransfer(tab, f); + else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + binCount = 1; + for (Node e = f, pred = null;; ++binCount) { + K ek; + if (e.hash == h && + ((ek = e.key) == key || + (ek != null && key.equals(ek)))) { + val = remappingFunction.apply(e.val, value); + if (val != null) + e.val = val; + else { + delta = -1; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + break; + } + pred = e; + if ((e = e.next) == null) { + delta = 1; + val = value; + pred.next = + new Node(h, key, val, null); + break; + } + } + } + else if (f instanceof TreeBin) { + binCount = 2; + TreeBin t = (TreeBin)f; + TreeNode r = t.root; + TreeNode p = (r == null) ? null : + r.findTreeNode(h, key, null); + val = (p == null) ? value : + remappingFunction.apply(p.val, value); + if (val != null) { + if (p != null) + p.val = val; + else { + delta = 1; + t.putTreeVal(h, key, val); + } + } + else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) + setTabAt(tab, i, untreeify(t.first)); + } + } + else if (f instanceof ReservationNode) + throw new IllegalStateException("Recursive update"); + } + } + if (binCount != 0) { + if (binCount >= TREEIFY_THRESHOLD) + treeifyBin(tab, i); + break; + } + } + } + if (delta != 0) + addCount((long)delta, binCount); + return val; + } + + // Hashtable legacy methods + + /** + * Tests if some key maps into the specified value in this table. + * + *

Note that this method is identical in functionality to + * {@link #containsValue(Object)}, and exists solely to ensure + * full compatibility with class {@link java.util.Hashtable}, + * which supported this method prior to introduction of the + * Java Collections Framework. + * + * @param value a value to search for + * @return {@code true} if and only if some key maps to the + * {@code value} argument in this table as + * determined by the {@code equals} method; + * {@code false} otherwise + * @throws NullPointerException if the specified value is null + */ + public boolean contains(Object value) { + return containsValue(value); + } + + /** + * Returns an enumeration of the keys in this table. + * + * @return an enumeration of the keys in this table + * @see #keySet() + */ + public Enumeration keys() { + Node[] t; + int f = (t = table) == null ? 0 : t.length; + return new KeyIterator(t, f, 0, f, this); + } + + /** + * Returns an enumeration of the values in this table. + * + * @return an enumeration of the values in this table + * @see #values() + */ + public Enumeration elements() { + Node[] t; + int f = (t = table) == null ? 0 : t.length; + return new ValueIterator(t, f, 0, f, this); + } + + // ConcurrentHashMap-only methods + + /** + * Returns the number of mappings. This method should be used + * instead of {@link #size} because a ConcurrentHashMap may + * contain more mappings than can be represented as an int. The + * value returned is an estimate; the actual count may differ if + * there are concurrent insertions or removals. + * + * @return the number of mappings + * @since 1.8 + */ + public long mappingCount() { + long n = sumCount(); + return (n < 0L) ? 0L : n; // ignore transient negative values + } + + /** + * Creates a new {@link Set} backed by a ConcurrentHashMap + * from the given type to {@code Boolean.TRUE}. + * + * @param the element type of the returned set + * @return the new set + * @since 1.8 + */ + public static KeySetView newKeySet() { + return new KeySetView + (new ConcurrentHashMap(), Boolean.TRUE); + } + + /** + * Creates a new {@link Set} backed by a ConcurrentHashMap + * from the given type to {@code Boolean.TRUE}. + * + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @param the element type of the returned set + * @return the new set + * @throws IllegalArgumentException if the initial capacity of + * elements is negative + * @since 1.8 + */ + public static KeySetView newKeySet(int initialCapacity) { + return new KeySetView + (new ConcurrentHashMap(initialCapacity), Boolean.TRUE); + } + + /** + * Returns a {@link Set} view of the keys in this map, using the + * given common mapped value for any additions (i.e., {@link + * Collection#add} and {@link Collection#addAll(Collection)}). + * This is of course only appropriate if it is acceptable to use + * the same value for all additions from this view. + * + * @param mappedValue the mapped value to use for any additions + * @return the set view + * @throws NullPointerException if the mappedValue is null + */ + public KeySetView keySet(V mappedValue) { + if (mappedValue == null) + throw new NullPointerException(); + return new KeySetView(this, mappedValue); + } + + /* ---------------- Special Nodes -------------- */ + + /** + * A node inserted at head of bins during transfer operations. + */ + static final class ForwardingNode extends Node { + final Node[] nextTable; + ForwardingNode(Node[] tab) { + super(MOVED, null, null, null); + this.nextTable = tab; + } + + Node find(int h, Object k) { + // loop to avoid arbitrarily deep recursion on forwarding nodes + outer: for (Node[] tab = nextTable;;) { + Node e; int n; + if (k == null || tab == null || (n = tab.length) == 0 || + (e = tabAt(tab, (n - 1) & h)) == null) + return null; + for (;;) { + int eh; K ek; + if ((eh = e.hash) == h && + ((ek = e.key) == k || (ek != null && k.equals(ek)))) + return e; + if (eh < 0) { + if (e instanceof ForwardingNode) { + tab = ((ForwardingNode)e).nextTable; + continue outer; + } + else + return e.find(h, k); + } + if ((e = e.next) == null) + return null; + } + } + } + } + + /** + * A place-holder node used in computeIfAbsent and compute. + */ + static final class ReservationNode extends Node { + ReservationNode() { super(RESERVED, null, null, null); } @@ -1616,14 +2293,13 @@ private final void addCount(long x, int check) { CounterCell[] as; long b, s; if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { - CounterHashCode hc; CounterCell a; long v; int m; + CounterCell a; long v; int m; boolean uncontended = true; - if ((hc = threadCounterHashCode.get()) == null || - as == null || (m = as.length - 1) < 0 || - (a = as[m & hc.code]) == null || + if (as == null || (m = as.length - 1) < 0 || + (a = as[ThreadLocalRandom.getProbe() & m]) == null || !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { - fullAddCount(x, hc, uncontended); + fullAddCount(x, uncontended); return; } if (check <= 1) @@ -1704,17 +2380,8 @@ else if (c <= sc || n >= MAXIMUM_CAPACITY) break; else if (tab == table) { int rs = resizeStamp(n); - if (sc < 0) { - Node[] nt; - if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || - sc == rs + MAX_RESIZERS || (nt = nextTable) == null || - transferIndex <= 0) - break; - if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) - transfer(tab, nt); - } - else if (U.compareAndSwapInt(this, SIZECTL, sc, - (rs << RESIZE_STAMP_SHIFT) + 2)) + if (U.compareAndSwapInt(this, SIZECTL, sc, + (rs << RESIZE_STAMP_SHIFT) + 2)) transfer(tab, null); } } @@ -1857,6 +2524,112 @@ else if (f instanceof TreeBin) { } } + /* ---------------- Counter support -------------- */ + + /** + * A padded cell for distributing counts. Adapted from LongAdder + * and Striped64. See their internal docs for explanation. + */ + //@jdk.internal.vm.annotation.Contended // android-removed + static final class CounterCell { + volatile long value; + CounterCell(long x) { value = x; } + } + + final long sumCount() { + CounterCell[] as = counterCells; CounterCell a; + long sum = baseCount; + if (as != null) { + for (int i = 0; i < as.length; ++i) { + if ((a = as[i]) != null) + sum += a.value; + } + } + return sum; + } + + // See LongAdder version for explanation + private final void fullAddCount(long x, boolean wasUncontended) { + int h; + if ((h = ThreadLocalRandom.getProbe()) == 0) { + ThreadLocalRandom.localInit(); // force initialization + h = ThreadLocalRandom.getProbe(); + wasUncontended = true; + } + boolean collide = false; // True if last slot nonempty + for (;;) { + CounterCell[] as; CounterCell a; int n; long v; + if ((as = counterCells) != null && (n = as.length) > 0) { + if ((a = as[(n - 1) & h]) == null) { + if (cellsBusy == 0) { // Try to attach new Cell + CounterCell r = new CounterCell(x); // Optimistic create + if (cellsBusy == 0 && + U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean created = false; + try { // Recheck under lock + CounterCell[] rs; int m, j; + if ((rs = counterCells) != null && + (m = rs.length) > 0 && + rs[j = (m - 1) & h] == null) { + rs[j] = r; + created = true; + } + } finally { + cellsBusy = 0; + } + if (created) + break; + continue; // Slot is now non-empty + } + } + collide = false; + } + else if (!wasUncontended) // CAS already known to fail + wasUncontended = true; // Continue after rehash + else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) + break; + else if (counterCells != as || n >= NCPU) + collide = false; // At max size or stale + else if (!collide) + collide = true; + else if (cellsBusy == 0 && + U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + try { + if (counterCells == as) {// Expand table unless stale + CounterCell[] rs = new CounterCell[n << 1]; + for (int i = 0; i < n; ++i) + rs[i] = as[i]; + counterCells = rs; + } + } finally { + cellsBusy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h = ThreadLocalRandom.advanceProbe(h); + } + else if (cellsBusy == 0 && counterCells == as && + U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean init = false; + try { // Initialize table + if (counterCells == as) { + CounterCell[] rs = new CounterCell[2]; + rs[h & 1] = new CounterCell(x); + counterCells = rs; + init = true; + } + } finally { + cellsBusy = 0; + } + if (init) + break; + } + else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x)) + break; // Fall back on using base + } + } + /* ---------------- Conversion from/to TreeBins -------------- */ /** @@ -1864,7 +2637,7 @@ else if (f instanceof TreeBin) { * too small, in which case resizes instead. */ private final void treeifyBin(Node[] tab, int index) { - Node b; int n, sc; + Node b; int n; if (tab != null) { if ((n = tab.length) < MIN_TREEIFY_CAPACITY) tryPresize(n << 1); @@ -1908,7 +2681,7 @@ static Node untreeify(Node b) { /* ---------------- TreeNodes -------------- */ /** - * Nodes for use in TreeBins + * Nodes for use in TreeBins. */ static final class TreeNode extends Node { TreeNode parent; // red-black tree links @@ -1961,7 +2734,6 @@ else if ((q = pr.findTreeNode(h, k, kc)) != null) } } - /* ---------------- TreeBins -------------- */ /** @@ -2028,7 +2800,7 @@ else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); - TreeNode xp = p; + TreeNode xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { x.parent = xp; if (dir <= 0) @@ -2106,13 +2878,9 @@ else if (U.compareAndSwapInt(this, LOCKSTATE, s, p = ((r = root) == null ? null : r.findTreeNode(h, k, null)); } finally { - Thread w; - int ls; - do {} while (!U.compareAndSwapInt - (this, LOCKSTATE, - ls = lockState, ls - READER)); - if (ls == (READER|WAITER) && (w = waiter) != null) + if (U.getAndAddInt(this, LOCKSTATE, -READER) == + (READER|WAITER) && (w = waiter) != null) LockSupport.unpark(w); } return p; @@ -2122,10 +2890,6 @@ else if (U.compareAndSwapInt(this, LOCKSTATE, s, return null; } - /** - * Finds or adds a node. - * @return null if added - */ /** * Finds or adds a node. * @return null if added @@ -2480,7 +3244,7 @@ else if ((xpl = xp.left) == x) { } /** - * Recursive invariant check + * Checks invariants recursively for the tree of Nodes rooted at t. */ static boolean checkInvariants(TreeNode t) { TreeNode tp = t.parent, tl = t.left, tr = t.right, @@ -2504,15 +3268,13 @@ static boolean checkInvariants(TreeNode t) { return true; } - private static final sun.misc.Unsafe U; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); private static final long LOCKSTATE; static { try { - U = sun.misc.Unsafe.getUnsafe(); - Class k = TreeBin.class; LOCKSTATE = U.objectFieldOffset - (k.getDeclaredField("lockState")); - } catch (Exception e) { + (TreeBin.class.getDeclaredField("lockState")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } @@ -2727,7 +3489,7 @@ public final Map.Entry next() { } /** - * Exported Entry for EntryIterator + * Exported Entry for EntryIterator. */ static final class MapEntry implements Map.Entry { final K key; // non-null @@ -2741,7 +3503,9 @@ static final class MapEntry implements Map.Entry { public K getKey() { return key; } public V getValue() { return val; } public int hashCode() { return key.hashCode() ^ val.hashCode(); } - public String toString() { return key + "=" + val; } + public String toString() { + return Helpers.mapEntryToString(key, val); + } public boolean equals(Object o) { Object k, v; Map.Entry e; @@ -2769,207 +3533,1068 @@ public V setValue(V value) { } } - /* ----------------Views -------------- */ + static final class KeySpliterator extends Traverser + implements Spliterator { + long est; // size estimate + KeySpliterator(Node[] tab, int size, int index, int limit, + long est) { + super(tab, size, index, limit); + this.est = est; + } - /** - * Base class for views. - */ - abstract static class CollectionView - implements Collection, java.io.Serializable { - private static final long serialVersionUID = 7249069246763182397L; - final ConcurrentHashMap map; - CollectionView(ConcurrentHashMap map) { this.map = map; } + public KeySpliterator trySplit() { + int i, f, h; + return (h = ((i = baseIndex) + (f = baseLimit)) >>> 1) <= i ? null : + new KeySpliterator(tab, baseSize, baseLimit = h, + f, est >>>= 1); + } - /** - * Returns the map backing this view. - * - * @return the map backing this view - */ - public ConcurrentHashMap getMap() { return map; } + public void forEachRemaining(Consumer action) { + if (action == null) throw new NullPointerException(); + for (Node p; (p = advance()) != null;) + action.accept(p.key); + } - /** - * Removes all of the elements from this view, by removing all - * the mappings from the map backing this view. - */ - public final void clear() { map.clear(); } - public final int size() { return map.size(); } - public final boolean isEmpty() { return map.isEmpty(); } + public boolean tryAdvance(Consumer action) { + if (action == null) throw new NullPointerException(); + Node p; + if ((p = advance()) == null) + return false; + action.accept(p.key); + return true; + } - // implementations below rely on concrete classes supplying these - // abstract methods - /** - * Returns a "weakly consistent" iterator that will never - * throw {@link ConcurrentModificationException}, and - * guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not - * guaranteed to) reflect any modifications subsequent to - * construction. - */ - public abstract Iterator iterator(); - public abstract boolean contains(Object o); - public abstract boolean remove(Object o); + public long estimateSize() { return est; } - private static final String oomeMsg = "Required array size too large"; + public int characteristics() { + return Spliterator.DISTINCT | Spliterator.CONCURRENT | + Spliterator.NONNULL; + } + } - public final Object[] toArray() { - long sz = map.mappingCount(); - if (sz > MAX_ARRAY_SIZE) - throw new OutOfMemoryError(oomeMsg); - int n = (int)sz; - Object[] r = new Object[n]; - int i = 0; - for (E e : this) { - if (i == n) { - if (n >= MAX_ARRAY_SIZE) - throw new OutOfMemoryError(oomeMsg); - if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) - n = MAX_ARRAY_SIZE; - else - n += (n >>> 1) + 1; - r = Arrays.copyOf(r, n); - } - r[i++] = e; - } - return (i == n) ? r : Arrays.copyOf(r, i); + static final class ValueSpliterator extends Traverser + implements Spliterator { + long est; // size estimate + ValueSpliterator(Node[] tab, int size, int index, int limit, + long est) { + super(tab, size, index, limit); + this.est = est; } - @SuppressWarnings("unchecked") - public final T[] toArray(T[] a) { - long sz = map.mappingCount(); - if (sz > MAX_ARRAY_SIZE) - throw new OutOfMemoryError(oomeMsg); - int m = (int)sz; - T[] r = (a.length >= m) ? a : - (T[])java.lang.reflect.Array - .newInstance(a.getClass().getComponentType(), m); - int n = r.length; - int i = 0; - for (E e : this) { - if (i == n) { - if (n >= MAX_ARRAY_SIZE) - throw new OutOfMemoryError(oomeMsg); - if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) - n = MAX_ARRAY_SIZE; - else - n += (n >>> 1) + 1; - r = Arrays.copyOf(r, n); - } - r[i++] = (T)e; - } - if (a == r && i < n) { - r[i] = null; // null-terminate - return r; - } - return (i == n) ? r : Arrays.copyOf(r, i); + public ValueSpliterator trySplit() { + int i, f, h; + return (h = ((i = baseIndex) + (f = baseLimit)) >>> 1) <= i ? null : + new ValueSpliterator(tab, baseSize, baseLimit = h, + f, est >>>= 1); } - /** - * Returns a string representation of this collection. - * The string representation consists of the string representations - * of the collection's elements in the order they are returned by - * its iterator, enclosed in square brackets ({@code "[]"}). - * Adjacent elements are separated by the characters {@code ", "} - * (comma and space). Elements are converted to strings as by - * {@link String#valueOf(Object)}. - * - * @return a string representation of this collection - */ - public final String toString() { - StringBuilder sb = new StringBuilder(); - sb.append('['); - Iterator it = iterator(); - if (it.hasNext()) { - for (;;) { - Object e = it.next(); - sb.append(e == this ? "(this Collection)" : e); - if (!it.hasNext()) - break; - sb.append(',').append(' '); - } - } - return sb.append(']').toString(); + public void forEachRemaining(Consumer action) { + if (action == null) throw new NullPointerException(); + for (Node p; (p = advance()) != null;) + action.accept(p.val); } - public final boolean containsAll(Collection c) { - if (c != this) { - for (Object e : c) { - if (e == null || !contains(e)) - return false; - } - } + public boolean tryAdvance(Consumer action) { + if (action == null) throw new NullPointerException(); + Node p; + if ((p = advance()) == null) + return false; + action.accept(p.val); return true; } - public final boolean removeAll(Collection c) { - boolean modified = false; - for (Iterator it = iterator(); it.hasNext();) { - if (c.contains(it.next())) { - it.remove(); - modified = true; - } - } - return modified; + public long estimateSize() { return est; } + + public int characteristics() { + return Spliterator.CONCURRENT | Spliterator.NONNULL; } + } - public final boolean retainAll(Collection c) { - boolean modified = false; - for (Iterator it = iterator(); it.hasNext();) { - if (!c.contains(it.next())) { - it.remove(); - modified = true; - } - } - return modified; + static final class EntrySpliterator extends Traverser + implements Spliterator> { + final ConcurrentHashMap map; // To export MapEntry + long est; // size estimate + EntrySpliterator(Node[] tab, int size, int index, int limit, + long est, ConcurrentHashMap map) { + super(tab, size, index, limit); + this.map = map; + this.est = est; + } + + public EntrySpliterator trySplit() { + int i, f, h; + return (h = ((i = baseIndex) + (f = baseLimit)) >>> 1) <= i ? null : + new EntrySpliterator(tab, baseSize, baseLimit = h, + f, est >>>= 1, map); + } + + public void forEachRemaining(Consumer> action) { + if (action == null) throw new NullPointerException(); + for (Node p; (p = advance()) != null; ) + action.accept(new MapEntry(p.key, p.val, map)); + } + + public boolean tryAdvance(Consumer> action) { + if (action == null) throw new NullPointerException(); + Node p; + if ((p = advance()) == null) + return false; + action.accept(new MapEntry(p.key, p.val, map)); + return true; } + public long estimateSize() { return est; } + + public int characteristics() { + return Spliterator.DISTINCT | Spliterator.CONCURRENT | + Spliterator.NONNULL; + } } + // Parallel bulk operations + /** - * A view of a ConcurrentHashMap as a {@link Set} of keys, in - * which additions may optionally be enabled by mapping to a - * common value. This class cannot be directly instantiated. - * See {@link #keySet() keySet()}, - * {@link #keySet(Object) keySet(V)}, + * Computes initial batch value for bulk tasks. The returned value + * is approximately exp2 of the number of times (minus one) to + * split task by two before executing leaf action. This value is + * faster to compute and more convenient to use as a guide to + * splitting than is the depth, since it is used while dividing by + * two anyway. + */ + final int batchFor(long b) { + long n; + if (b == Long.MAX_VALUE || (n = sumCount()) <= 1L || n < b) + return 0; + int sp = ForkJoinPool.getCommonPoolParallelism() << 2; // slack of 4 + return (b <= 0L || (n /= b) >= sp) ? sp : (int)n; + } + + /** + * Performs the given action for each (key, value). * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param action the action * @since 1.8 + */ + public void forEach(long parallelismThreshold, + BiConsumer action) { + if (action == null) throw new NullPointerException(); + new ForEachMappingTask + (null, batchFor(parallelismThreshold), 0, 0, table, + action).invoke(); + } + + /** + * Performs the given action for each non-null transformation + * of each (key, value). * - * @hide + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case the action is not applied) + * @param action the action + * @param the return type of the transformer + * @since 1.8 */ - // android-note: removed references to hidden APIs. - public static class KeySetView extends CollectionView - implements Set, java.io.Serializable { + public void forEach(long parallelismThreshold, + BiFunction transformer, + Consumer action) { + if (transformer == null || action == null) + throw new NullPointerException(); + new ForEachTransformedMappingTask + (null, batchFor(parallelismThreshold), 0, 0, table, + transformer, action).invoke(); + } + + /** + * Returns a non-null result from applying the given search + * function on each (key, value), or null if none. Upon + * success, further element processing is suppressed and the + * results of any other parallel invocations of the search + * function are ignored. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param searchFunction a function returning a non-null + * result on success, else null + * @param the return type of the search function + * @return a non-null result from applying the given search + * function on each (key, value), or null if none + * @since 1.8 + */ + public U search(long parallelismThreshold, + BiFunction searchFunction) { + if (searchFunction == null) throw new NullPointerException(); + return new SearchMappingsTask + (null, batchFor(parallelismThreshold), 0, 0, table, + searchFunction, new AtomicReference()).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all (key, value) pairs using the given reducer to + * combine values, or null if none. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case it is not combined) + * @param reducer a commutative associative combining function + * @param the return type of the transformer + * @return the result of accumulating the given transformation + * of all (key, value) pairs + * @since 1.8 + */ + public U reduce(long parallelismThreshold, + BiFunction transformer, + BiFunction reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceMappingsTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all (key, value) pairs using the given reducer to + * combine values, and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all (key, value) pairs + * @since 1.8 + */ + public double reduceToDouble(long parallelismThreshold, + ToDoubleBiFunction transformer, + double basis, + DoubleBinaryOperator reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceMappingsToDoubleTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all (key, value) pairs using the given reducer to + * combine values, and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all (key, value) pairs + * @since 1.8 + */ + public long reduceToLong(long parallelismThreshold, + ToLongBiFunction transformer, + long basis, + LongBinaryOperator reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceMappingsToLongTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all (key, value) pairs using the given reducer to + * combine values, and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all (key, value) pairs + * @since 1.8 + */ + public int reduceToInt(long parallelismThreshold, + ToIntBiFunction transformer, + int basis, + IntBinaryOperator reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceMappingsToIntTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + /** + * Performs the given action for each key. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param action the action + * @since 1.8 + */ + public void forEachKey(long parallelismThreshold, + Consumer action) { + if (action == null) throw new NullPointerException(); + new ForEachKeyTask + (null, batchFor(parallelismThreshold), 0, 0, table, + action).invoke(); + } + + /** + * Performs the given action for each non-null transformation + * of each key. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case the action is not applied) + * @param action the action + * @param the return type of the transformer + * @since 1.8 + */ + public void forEachKey(long parallelismThreshold, + Function transformer, + Consumer action) { + if (transformer == null || action == null) + throw new NullPointerException(); + new ForEachTransformedKeyTask + (null, batchFor(parallelismThreshold), 0, 0, table, + transformer, action).invoke(); + } + + /** + * Returns a non-null result from applying the given search + * function on each key, or null if none. Upon success, + * further element processing is suppressed and the results of + * any other parallel invocations of the search function are + * ignored. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param searchFunction a function returning a non-null + * result on success, else null + * @param the return type of the search function + * @return a non-null result from applying the given search + * function on each key, or null if none + * @since 1.8 + */ + public U searchKeys(long parallelismThreshold, + Function searchFunction) { + if (searchFunction == null) throw new NullPointerException(); + return new SearchKeysTask + (null, batchFor(parallelismThreshold), 0, 0, table, + searchFunction, new AtomicReference()).invoke(); + } + + /** + * Returns the result of accumulating all keys using the given + * reducer to combine values, or null if none. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param reducer a commutative associative combining function + * @return the result of accumulating all keys using the given + * reducer to combine values, or null if none + * @since 1.8 + */ + public K reduceKeys(long parallelismThreshold, + BiFunction reducer) { + if (reducer == null) throw new NullPointerException(); + return new ReduceKeysTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all keys using the given reducer to combine values, or + * null if none. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case it is not combined) + * @param reducer a commutative associative combining function + * @param the return type of the transformer + * @return the result of accumulating the given transformation + * of all keys + * @since 1.8 + */ + public U reduceKeys(long parallelismThreshold, + Function transformer, + BiFunction reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceKeysTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all keys using the given reducer to combine values, and + * the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all keys + * @since 1.8 + */ + public double reduceKeysToDouble(long parallelismThreshold, + ToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceKeysToDoubleTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all keys using the given reducer to combine values, and + * the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all keys + * @since 1.8 + */ + public long reduceKeysToLong(long parallelismThreshold, + ToLongFunction transformer, + long basis, + LongBinaryOperator reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceKeysToLongTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all keys using the given reducer to combine values, and + * the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all keys + * @since 1.8 + */ + public int reduceKeysToInt(long parallelismThreshold, + ToIntFunction transformer, + int basis, + IntBinaryOperator reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceKeysToIntTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + /** + * Performs the given action for each value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param action the action + * @since 1.8 + */ + public void forEachValue(long parallelismThreshold, + Consumer action) { + if (action == null) + throw new NullPointerException(); + new ForEachValueTask + (null, batchFor(parallelismThreshold), 0, 0, table, + action).invoke(); + } + + /** + * Performs the given action for each non-null transformation + * of each value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case the action is not applied) + * @param action the action + * @param the return type of the transformer + * @since 1.8 + */ + public void forEachValue(long parallelismThreshold, + Function transformer, + Consumer action) { + if (transformer == null || action == null) + throw new NullPointerException(); + new ForEachTransformedValueTask + (null, batchFor(parallelismThreshold), 0, 0, table, + transformer, action).invoke(); + } + + /** + * Returns a non-null result from applying the given search + * function on each value, or null if none. Upon success, + * further element processing is suppressed and the results of + * any other parallel invocations of the search function are + * ignored. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param searchFunction a function returning a non-null + * result on success, else null + * @param the return type of the search function + * @return a non-null result from applying the given search + * function on each value, or null if none + * @since 1.8 + */ + public U searchValues(long parallelismThreshold, + Function searchFunction) { + if (searchFunction == null) throw new NullPointerException(); + return new SearchValuesTask + (null, batchFor(parallelismThreshold), 0, 0, table, + searchFunction, new AtomicReference()).invoke(); + } + + /** + * Returns the result of accumulating all values using the + * given reducer to combine values, or null if none. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param reducer a commutative associative combining function + * @return the result of accumulating all values + * @since 1.8 + */ + public V reduceValues(long parallelismThreshold, + BiFunction reducer) { + if (reducer == null) throw new NullPointerException(); + return new ReduceValuesTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all values using the given reducer to combine values, or + * null if none. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case it is not combined) + * @param reducer a commutative associative combining function + * @param the return type of the transformer + * @return the result of accumulating the given transformation + * of all values + * @since 1.8 + */ + public U reduceValues(long parallelismThreshold, + Function transformer, + BiFunction reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceValuesTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all values using the given reducer to combine values, + * and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all values + * @since 1.8 + */ + public double reduceValuesToDouble(long parallelismThreshold, + ToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceValuesToDoubleTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all values using the given reducer to combine values, + * and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all values + * @since 1.8 + */ + public long reduceValuesToLong(long parallelismThreshold, + ToLongFunction transformer, + long basis, + LongBinaryOperator reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceValuesToLongTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all values using the given reducer to combine values, + * and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all values + * @since 1.8 + */ + public int reduceValuesToInt(long parallelismThreshold, + ToIntFunction transformer, + int basis, + IntBinaryOperator reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceValuesToIntTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + /** + * Performs the given action for each entry. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param action the action + * @since 1.8 + */ + public void forEachEntry(long parallelismThreshold, + Consumer> action) { + if (action == null) throw new NullPointerException(); + new ForEachEntryTask(null, batchFor(parallelismThreshold), 0, 0, table, + action).invoke(); + } + + /** + * Performs the given action for each non-null transformation + * of each entry. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case the action is not applied) + * @param action the action + * @param the return type of the transformer + * @since 1.8 + */ + public void forEachEntry(long parallelismThreshold, + Function, ? extends U> transformer, + Consumer action) { + if (transformer == null || action == null) + throw new NullPointerException(); + new ForEachTransformedEntryTask + (null, batchFor(parallelismThreshold), 0, 0, table, + transformer, action).invoke(); + } + + /** + * Returns a non-null result from applying the given search + * function on each entry, or null if none. Upon success, + * further element processing is suppressed and the results of + * any other parallel invocations of the search function are + * ignored. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param searchFunction a function returning a non-null + * result on success, else null + * @param the return type of the search function + * @return a non-null result from applying the given search + * function on each entry, or null if none + * @since 1.8 + */ + public U searchEntries(long parallelismThreshold, + Function, ? extends U> searchFunction) { + if (searchFunction == null) throw new NullPointerException(); + return new SearchEntriesTask + (null, batchFor(parallelismThreshold), 0, 0, table, + searchFunction, new AtomicReference()).invoke(); + } + + /** + * Returns the result of accumulating all entries using the + * given reducer to combine values, or null if none. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param reducer a commutative associative combining function + * @return the result of accumulating all entries + * @since 1.8 + */ + public Map.Entry reduceEntries(long parallelismThreshold, + BiFunction, Map.Entry, ? extends Map.Entry> reducer) { + if (reducer == null) throw new NullPointerException(); + return new ReduceEntriesTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all entries using the given reducer to combine values, + * or null if none. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case it is not combined) + * @param reducer a commutative associative combining function + * @param the return type of the transformer + * @return the result of accumulating the given transformation + * of all entries + * @since 1.8 + */ + public U reduceEntries(long parallelismThreshold, + Function, ? extends U> transformer, + BiFunction reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceEntriesTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all entries using the given reducer to combine values, + * and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all entries + * @since 1.8 + */ + public double reduceEntriesToDouble(long parallelismThreshold, + ToDoubleFunction> transformer, + double basis, + DoubleBinaryOperator reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceEntriesToDoubleTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all entries using the given reducer to combine values, + * and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all entries + * @since 1.8 + */ + public long reduceEntriesToLong(long parallelismThreshold, + ToLongFunction> transformer, + long basis, + LongBinaryOperator reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceEntriesToLongTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all entries using the given reducer to combine values, + * and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all entries + * @since 1.8 + */ + public int reduceEntriesToInt(long parallelismThreshold, + ToIntFunction> transformer, + int basis, + IntBinaryOperator reducer) { + if (transformer == null || reducer == null) + throw new NullPointerException(); + return new MapReduceEntriesToIntTask + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + + /* ----------------Views -------------- */ + + /** + * Base class for views. + */ + abstract static class CollectionView + implements Collection, java.io.Serializable { private static final long serialVersionUID = 7249069246763182397L; - private final V value; - KeySetView(ConcurrentHashMap map, V value) { // non-public - super(map); - this.value = value; - } + final ConcurrentHashMap map; + CollectionView(ConcurrentHashMap map) { this.map = map; } /** - * Returns the default mapped value for additions, - * or {@code null} if additions are not supported. + * Returns the map backing this view. * - * @return the default mapped value for additions, or {@code null} - * if not supported + * @return the map backing this view */ - public V getMappedValue() { return value; } + public ConcurrentHashMap getMap() { return map; } /** - * {@inheritDoc} - * @throws NullPointerException if the specified key is null + * Removes all of the elements from this view, by removing all + * the mappings from the map backing this view. */ - public boolean contains(Object o) { return map.containsKey(o); } + public final void clear() { map.clear(); } + public final int size() { return map.size(); } + public final boolean isEmpty() { return map.isEmpty(); } + // implementations below rely on concrete classes supplying these + // abstract methods /** - * Removes the key from this map view, by removing the key (and its - * corresponding value) from the backing map. This method does - * nothing if the key is not in the map. + * Returns an iterator over the elements in this collection. * - * @param o the key to be removed from the backing map - * @return {@code true} if the backing map contained the specified key - * @throws NullPointerException if the specified key is null + *

The returned iterator is + * weakly consistent. + * + * @return an iterator over the elements in this collection */ - public boolean remove(Object o) { return map.remove(o) != null; } + public abstract Iterator iterator(); + public abstract boolean contains(Object o); + public abstract boolean remove(Object o); + + private static final String OOME_MSG = "Required array size too large"; + + public final Object[] toArray() { + long sz = map.mappingCount(); + if (sz > MAX_ARRAY_SIZE) + throw new OutOfMemoryError(OOME_MSG); + int n = (int)sz; + Object[] r = new Object[n]; + int i = 0; + for (E e : this) { + if (i == n) { + if (n >= MAX_ARRAY_SIZE) + throw new OutOfMemoryError(OOME_MSG); + if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) + n = MAX_ARRAY_SIZE; + else + n += (n >>> 1) + 1; + r = Arrays.copyOf(r, n); + } + r[i++] = e; + } + return (i == n) ? r : Arrays.copyOf(r, i); + } + + @SuppressWarnings("unchecked") + public final T[] toArray(T[] a) { + long sz = map.mappingCount(); + if (sz > MAX_ARRAY_SIZE) + throw new OutOfMemoryError(OOME_MSG); + int m = (int)sz; + T[] r = (a.length >= m) ? a : + (T[])java.lang.reflect.Array + .newInstance(a.getClass().getComponentType(), m); + int n = r.length; + int i = 0; + for (E e : this) { + if (i == n) { + if (n >= MAX_ARRAY_SIZE) + throw new OutOfMemoryError(OOME_MSG); + if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) + n = MAX_ARRAY_SIZE; + else + n += (n >>> 1) + 1; + r = Arrays.copyOf(r, n); + } + r[i++] = (T)e; + } + if (a == r && i < n) { + r[i] = null; // null-terminate + return r; + } + return (i == n) ? r : Arrays.copyOf(r, i); + } + + /** + * Returns a string representation of this collection. + * The string representation consists of the string representations + * of the collection's elements in the order they are returned by + * its iterator, enclosed in square brackets ({@code "[]"}). + * Adjacent elements are separated by the characters {@code ", "} + * (comma and space). Elements are converted to strings as by + * {@link String#valueOf(Object)}. + * + * @return a string representation of this collection + */ + public final String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + Iterator it = iterator(); + if (it.hasNext()) { + for (;;) { + Object e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) + break; + sb.append(',').append(' '); + } + } + return sb.append(']').toString(); + } + + public final boolean containsAll(Collection c) { + if (c != this) { + for (Object e : c) { + if (e == null || !contains(e)) + return false; + } + } + return true; + } + + public final boolean removeAll(Collection c) { + if (c == null) throw new NullPointerException(); + boolean modified = false; + for (Iterator it = iterator(); it.hasNext();) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + public final boolean retainAll(Collection c) { + if (c == null) throw new NullPointerException(); + boolean modified = false; + for (Iterator it = iterator(); it.hasNext();) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + } + + /** + * A view of a ConcurrentHashMap as a {@link Set} of keys, in + * which additions may optionally be enabled by mapping to a + * common value. This class cannot be directly instantiated. + * See {@link #keySet() keySet()}, + * {@link #keySet(Object) keySet(V)}, + * {@link #newKeySet() newKeySet()}, + * {@link #newKeySet(int) newKeySet(int)}. + * + * @since 1.8 + */ + public static class KeySetView extends CollectionView + implements Set, java.io.Serializable { + private static final long serialVersionUID = 7249069246763182397L; + private final V value; + KeySetView(ConcurrentHashMap map, V value) { // non-public + super(map); + this.value = value; + } + + /** + * Returns the default mapped value for additions, + * or {@code null} if additions are not supported. + * + * @return the default mapped value for additions, or {@code null} + * if not supported + */ + public V getMappedValue() { return value; } + + /** + * {@inheritDoc} + * @throws NullPointerException if the specified key is null + */ + public boolean contains(Object o) { return map.containsKey(o); } + + /** + * Removes the key from this map view, by removing the key (and its + * corresponding value) from the backing map. This method does + * nothing if the key is not in the map. + * + * @param o the key to be removed from the backing map + * @return {@code true} if the backing map contained the specified key + * @throws NullPointerException if the specified key is null + */ + public boolean remove(Object o) { return map.remove(o) != null; } /** * @return an iterator over the keys of the backing map @@ -3018,310 +4643,1703 @@ public boolean addAll(Collection c) { if (map.putVal(e, v, true) == null) added = true; } - return added; + return added; + } + + public int hashCode() { + int h = 0; + for (K e : this) + h += e.hashCode(); + return h; + } + + public boolean equals(Object o) { + Set c; + return ((o instanceof Set) && + ((c = (Set)o) == this || + (containsAll(c) && c.containsAll(this)))); + } + + public Spliterator spliterator() { + Node[] t; + ConcurrentHashMap m = map; + long n = m.sumCount(); + int f = (t = m.table) == null ? 0 : t.length; + return new KeySpliterator(t, f, 0, f, n < 0L ? 0L : n); + } + + public void forEach(Consumer action) { + if (action == null) throw new NullPointerException(); + Node[] t; + if ((t = map.table) != null) { + Traverser it = new Traverser(t, t.length, 0, t.length); + for (Node p; (p = it.advance()) != null; ) + action.accept(p.key); + } + } + } + + /** + * A view of a ConcurrentHashMap as a {@link Collection} of + * values, in which additions are disabled. This class cannot be + * directly instantiated. See {@link #values()}. + */ + static final class ValuesView extends CollectionView + implements Collection, java.io.Serializable { + private static final long serialVersionUID = 2249069246763182397L; + ValuesView(ConcurrentHashMap map) { super(map); } + public final boolean contains(Object o) { + return map.containsValue(o); + } + + public final boolean remove(Object o) { + if (o != null) { + for (Iterator it = iterator(); it.hasNext();) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + return false; + } + + public final Iterator iterator() { + ConcurrentHashMap m = map; + Node[] t; + int f = (t = m.table) == null ? 0 : t.length; + return new ValueIterator(t, f, 0, f, m); + } + + public final boolean add(V e) { + throw new UnsupportedOperationException(); + } + public final boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public boolean removeIf(Predicate filter) { + return map.removeValueIf(filter); + } + + public Spliterator spliterator() { + Node[] t; + ConcurrentHashMap m = map; + long n = m.sumCount(); + int f = (t = m.table) == null ? 0 : t.length; + return new ValueSpliterator(t, f, 0, f, n < 0L ? 0L : n); + } + + public void forEach(Consumer action) { + if (action == null) throw new NullPointerException(); + Node[] t; + if ((t = map.table) != null) { + Traverser it = new Traverser(t, t.length, 0, t.length); + for (Node p; (p = it.advance()) != null; ) + action.accept(p.val); + } + } + } + + /** + * A view of a ConcurrentHashMap as a {@link Set} of (key, value) + * entries. This class cannot be directly instantiated. See + * {@link #entrySet()}. + */ + static final class EntrySetView extends CollectionView> + implements Set>, java.io.Serializable { + private static final long serialVersionUID = 2249069246763182397L; + EntrySetView(ConcurrentHashMap map) { super(map); } + + public boolean contains(Object o) { + Object k, v, r; Map.Entry e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry)o).getKey()) != null && + (r = map.get(k)) != null && + (v = e.getValue()) != null && + (v == r || v.equals(r))); + } + + public boolean remove(Object o) { + Object k, v; Map.Entry e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry)o).getKey()) != null && + (v = e.getValue()) != null && + map.remove(k, v)); + } + + /** + * @return an iterator over the entries of the backing map + */ + public Iterator> iterator() { + ConcurrentHashMap m = map; + Node[] t; + int f = (t = m.table) == null ? 0 : t.length; + return new EntryIterator(t, f, 0, f, m); + } + + public boolean add(Entry e) { + return map.putVal(e.getKey(), e.getValue(), false) == null; + } + + public boolean addAll(Collection> c) { + boolean added = false; + for (Entry e : c) { + if (add(e)) + added = true; + } + return added; + } + + public boolean removeIf(Predicate> filter) { + return map.removeEntryIf(filter); + } + + public final int hashCode() { + int h = 0; + Node[] t; + if ((t = map.table) != null) { + Traverser it = new Traverser(t, t.length, 0, t.length); + for (Node p; (p = it.advance()) != null; ) { + h += p.hashCode(); + } + } + return h; + } + + public final boolean equals(Object o) { + Set c; + return ((o instanceof Set) && + ((c = (Set)o) == this || + (containsAll(c) && c.containsAll(this)))); + } + + public Spliterator> spliterator() { + Node[] t; + ConcurrentHashMap m = map; + long n = m.sumCount(); + int f = (t = m.table) == null ? 0 : t.length; + return new EntrySpliterator(t, f, 0, f, n < 0L ? 0L : n, m); + } + + public void forEach(Consumer> action) { + if (action == null) throw new NullPointerException(); + Node[] t; + if ((t = map.table) != null) { + Traverser it = new Traverser(t, t.length, 0, t.length); + for (Node p; (p = it.advance()) != null; ) + action.accept(new MapEntry(p.key, p.val, map)); + } + } + + } + + // ------------------------------------------------------- + + /** + * Base class for bulk tasks. Repeats some fields and code from + * class Traverser, because we need to subclass CountedCompleter. + */ + @SuppressWarnings("serial") + abstract static class BulkTask extends CountedCompleter { + Node[] tab; // same as Traverser + Node next; + TableStack stack, spare; + int index; + int baseIndex; + int baseLimit; + final int baseSize; + int batch; // split control + + BulkTask(BulkTask par, int b, int i, int f, Node[] t) { + super(par); + this.batch = b; + this.index = this.baseIndex = i; + if ((this.tab = t) == null) + this.baseSize = this.baseLimit = 0; + else if (par == null) + this.baseSize = this.baseLimit = t.length; + else { + this.baseLimit = f; + this.baseSize = par.baseSize; + } + } + + /** + * Same as Traverser version. + */ + final Node advance() { + Node e; + if ((e = next) != null) + e = e.next; + for (;;) { + Node[] t; int i, n; + if (e != null) + return next = e; + if (baseIndex >= baseLimit || (t = tab) == null || + (n = t.length) <= (i = index) || i < 0) + return next = null; + if ((e = tabAt(t, i)) != null && e.hash < 0) { + if (e instanceof ForwardingNode) { + tab = ((ForwardingNode)e).nextTable; + e = null; + pushState(t, i, n); + continue; + } + else if (e instanceof TreeBin) + e = ((TreeBin)e).first; + else + e = null; + } + if (stack != null) + recoverState(n); + else if ((index = i + baseSize) >= n) + index = ++baseIndex; + } + } + + private void pushState(Node[] t, int i, int n) { + TableStack s = spare; + if (s != null) + spare = s.next; + else + s = new TableStack(); + s.tab = t; + s.length = n; + s.index = i; + s.next = stack; + stack = s; + } + + private void recoverState(int n) { + TableStack s; int len; + while ((s = stack) != null && (index += (len = s.length)) >= n) { + n = len; + index = s.index; + tab = s.tab; + s.tab = null; + TableStack next = s.next; + s.next = spare; // save for reuse + stack = next; + spare = s; + } + if (s == null && (index += baseSize) >= n) + index = ++baseIndex; + } + } + + /* + * Task classes. Coded in a regular but ugly format/style to + * simplify checks that each variant differs in the right way from + * others. The null screenings exist because compilers cannot tell + * that we've already null-checked task arguments, so we force + * simplest hoisted bypass to help avoid convoluted traps. + */ + @SuppressWarnings("serial") + static final class ForEachKeyTask + extends BulkTask { + final Consumer action; + ForEachKeyTask + (BulkTask p, int b, int i, int f, Node[] t, + Consumer action) { + super(p, b, i, f, t); + this.action = action; + } + public final void compute() { + final Consumer action; + if ((action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + new ForEachKeyTask + (this, batch >>>= 1, baseLimit = h, f, tab, + action).fork(); + } + for (Node p; (p = advance()) != null;) + action.accept(p.key); + propagateCompletion(); + } + } + } + + @SuppressWarnings("serial") + static final class ForEachValueTask + extends BulkTask { + final Consumer action; + ForEachValueTask + (BulkTask p, int b, int i, int f, Node[] t, + Consumer action) { + super(p, b, i, f, t); + this.action = action; + } + public final void compute() { + final Consumer action; + if ((action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + new ForEachValueTask + (this, batch >>>= 1, baseLimit = h, f, tab, + action).fork(); + } + for (Node p; (p = advance()) != null;) + action.accept(p.val); + propagateCompletion(); + } + } + } + + @SuppressWarnings("serial") + static final class ForEachEntryTask + extends BulkTask { + final Consumer> action; + ForEachEntryTask + (BulkTask p, int b, int i, int f, Node[] t, + Consumer> action) { + super(p, b, i, f, t); + this.action = action; + } + public final void compute() { + final Consumer> action; + if ((action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + new ForEachEntryTask + (this, batch >>>= 1, baseLimit = h, f, tab, + action).fork(); + } + for (Node p; (p = advance()) != null; ) + action.accept(p); + propagateCompletion(); + } + } + } + + @SuppressWarnings("serial") + static final class ForEachMappingTask + extends BulkTask { + final BiConsumer action; + ForEachMappingTask + (BulkTask p, int b, int i, int f, Node[] t, + BiConsumer action) { + super(p, b, i, f, t); + this.action = action; + } + public final void compute() { + final BiConsumer action; + if ((action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + new ForEachMappingTask + (this, batch >>>= 1, baseLimit = h, f, tab, + action).fork(); + } + for (Node p; (p = advance()) != null; ) + action.accept(p.key, p.val); + propagateCompletion(); + } + } + } + + @SuppressWarnings("serial") + static final class ForEachTransformedKeyTask + extends BulkTask { + final Function transformer; + final Consumer action; + ForEachTransformedKeyTask + (BulkTask p, int b, int i, int f, Node[] t, + Function transformer, Consumer action) { + super(p, b, i, f, t); + this.transformer = transformer; this.action = action; + } + public final void compute() { + final Function transformer; + final Consumer action; + if ((transformer = this.transformer) != null && + (action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + new ForEachTransformedKeyTask + (this, batch >>>= 1, baseLimit = h, f, tab, + transformer, action).fork(); + } + for (Node p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p.key)) != null) + action.accept(u); + } + propagateCompletion(); + } + } + } + + @SuppressWarnings("serial") + static final class ForEachTransformedValueTask + extends BulkTask { + final Function transformer; + final Consumer action; + ForEachTransformedValueTask + (BulkTask p, int b, int i, int f, Node[] t, + Function transformer, Consumer action) { + super(p, b, i, f, t); + this.transformer = transformer; this.action = action; + } + public final void compute() { + final Function transformer; + final Consumer action; + if ((transformer = this.transformer) != null && + (action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + new ForEachTransformedValueTask + (this, batch >>>= 1, baseLimit = h, f, tab, + transformer, action).fork(); + } + for (Node p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p.val)) != null) + action.accept(u); + } + propagateCompletion(); + } + } + } + + @SuppressWarnings("serial") + static final class ForEachTransformedEntryTask + extends BulkTask { + final Function, ? extends U> transformer; + final Consumer action; + ForEachTransformedEntryTask + (BulkTask p, int b, int i, int f, Node[] t, + Function, ? extends U> transformer, Consumer action) { + super(p, b, i, f, t); + this.transformer = transformer; this.action = action; + } + public final void compute() { + final Function, ? extends U> transformer; + final Consumer action; + if ((transformer = this.transformer) != null && + (action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + new ForEachTransformedEntryTask + (this, batch >>>= 1, baseLimit = h, f, tab, + transformer, action).fork(); + } + for (Node p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p)) != null) + action.accept(u); + } + propagateCompletion(); + } + } + } + + @SuppressWarnings("serial") + static final class ForEachTransformedMappingTask + extends BulkTask { + final BiFunction transformer; + final Consumer action; + ForEachTransformedMappingTask + (BulkTask p, int b, int i, int f, Node[] t, + BiFunction transformer, + Consumer action) { + super(p, b, i, f, t); + this.transformer = transformer; this.action = action; + } + public final void compute() { + final BiFunction transformer; + final Consumer action; + if ((transformer = this.transformer) != null && + (action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + new ForEachTransformedMappingTask + (this, batch >>>= 1, baseLimit = h, f, tab, + transformer, action).fork(); + } + for (Node p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p.key, p.val)) != null) + action.accept(u); + } + propagateCompletion(); + } + } + } + + @SuppressWarnings("serial") + static final class SearchKeysTask + extends BulkTask { + final Function searchFunction; + final AtomicReference result; + SearchKeysTask + (BulkTask p, int b, int i, int f, Node[] t, + Function searchFunction, + AtomicReference result) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; this.result = result; + } + public final U getRawResult() { return result.get(); } + public final void compute() { + final Function searchFunction; + final AtomicReference result; + if ((searchFunction = this.searchFunction) != null && + (result = this.result) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + if (result.get() != null) + return; + addToPendingCount(1); + new SearchKeysTask + (this, batch >>>= 1, baseLimit = h, f, tab, + searchFunction, result).fork(); + } + while (result.get() == null) { + U u; + Node p; + if ((p = advance()) == null) { + propagateCompletion(); + break; + } + if ((u = searchFunction.apply(p.key)) != null) { + if (result.compareAndSet(null, u)) + quietlyCompleteRoot(); + break; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class SearchValuesTask + extends BulkTask { + final Function searchFunction; + final AtomicReference result; + SearchValuesTask + (BulkTask p, int b, int i, int f, Node[] t, + Function searchFunction, + AtomicReference result) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; this.result = result; + } + public final U getRawResult() { return result.get(); } + public final void compute() { + final Function searchFunction; + final AtomicReference result; + if ((searchFunction = this.searchFunction) != null && + (result = this.result) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + if (result.get() != null) + return; + addToPendingCount(1); + new SearchValuesTask + (this, batch >>>= 1, baseLimit = h, f, tab, + searchFunction, result).fork(); + } + while (result.get() == null) { + U u; + Node p; + if ((p = advance()) == null) { + propagateCompletion(); + break; + } + if ((u = searchFunction.apply(p.val)) != null) { + if (result.compareAndSet(null, u)) + quietlyCompleteRoot(); + break; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class SearchEntriesTask + extends BulkTask { + final Function, ? extends U> searchFunction; + final AtomicReference result; + SearchEntriesTask + (BulkTask p, int b, int i, int f, Node[] t, + Function, ? extends U> searchFunction, + AtomicReference result) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; this.result = result; + } + public final U getRawResult() { return result.get(); } + public final void compute() { + final Function, ? extends U> searchFunction; + final AtomicReference result; + if ((searchFunction = this.searchFunction) != null && + (result = this.result) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + if (result.get() != null) + return; + addToPendingCount(1); + new SearchEntriesTask + (this, batch >>>= 1, baseLimit = h, f, tab, + searchFunction, result).fork(); + } + while (result.get() == null) { + U u; + Node p; + if ((p = advance()) == null) { + propagateCompletion(); + break; + } + if ((u = searchFunction.apply(p)) != null) { + if (result.compareAndSet(null, u)) + quietlyCompleteRoot(); + return; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class SearchMappingsTask + extends BulkTask { + final BiFunction searchFunction; + final AtomicReference result; + SearchMappingsTask + (BulkTask p, int b, int i, int f, Node[] t, + BiFunction searchFunction, + AtomicReference result) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; this.result = result; + } + public final U getRawResult() { return result.get(); } + public final void compute() { + final BiFunction searchFunction; + final AtomicReference result; + if ((searchFunction = this.searchFunction) != null && + (result = this.result) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + if (result.get() != null) + return; + addToPendingCount(1); + new SearchMappingsTask + (this, batch >>>= 1, baseLimit = h, f, tab, + searchFunction, result).fork(); + } + while (result.get() == null) { + U u; + Node p; + if ((p = advance()) == null) { + propagateCompletion(); + break; + } + if ((u = searchFunction.apply(p.key, p.val)) != null) { + if (result.compareAndSet(null, u)) + quietlyCompleteRoot(); + break; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class ReduceKeysTask + extends BulkTask { + final BiFunction reducer; + K result; + ReduceKeysTask rights, nextRight; + ReduceKeysTask + (BulkTask p, int b, int i, int f, Node[] t, + ReduceKeysTask nextRight, + BiFunction reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.reducer = reducer; + } + public final K getRawResult() { return result; } + public final void compute() { + final BiFunction reducer; + if ((reducer = this.reducer) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new ReduceKeysTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, reducer)).fork(); + } + K r = null; + for (Node p; (p = advance()) != null; ) { + K u = p.key; + r = (r == null) ? u : u == null ? r : reducer.apply(r, u); + } + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + ReduceKeysTask + t = (ReduceKeysTask)c, + s = t.rights; + while (s != null) { + K tr, sr; + if ((sr = s.result) != null) + t.result = (((tr = t.result) == null) ? sr : + reducer.apply(tr, sr)); + s = t.rights = s.nextRight; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class ReduceValuesTask + extends BulkTask { + final BiFunction reducer; + V result; + ReduceValuesTask rights, nextRight; + ReduceValuesTask + (BulkTask p, int b, int i, int f, Node[] t, + ReduceValuesTask nextRight, + BiFunction reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.reducer = reducer; + } + public final V getRawResult() { return result; } + public final void compute() { + final BiFunction reducer; + if ((reducer = this.reducer) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new ReduceValuesTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, reducer)).fork(); + } + V r = null; + for (Node p; (p = advance()) != null; ) { + V v = p.val; + r = (r == null) ? v : reducer.apply(r, v); + } + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + ReduceValuesTask + t = (ReduceValuesTask)c, + s = t.rights; + while (s != null) { + V tr, sr; + if ((sr = s.result) != null) + t.result = (((tr = t.result) == null) ? sr : + reducer.apply(tr, sr)); + s = t.rights = s.nextRight; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class ReduceEntriesTask + extends BulkTask> { + final BiFunction, Map.Entry, ? extends Map.Entry> reducer; + Map.Entry result; + ReduceEntriesTask rights, nextRight; + ReduceEntriesTask + (BulkTask p, int b, int i, int f, Node[] t, + ReduceEntriesTask nextRight, + BiFunction, Map.Entry, ? extends Map.Entry> reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.reducer = reducer; + } + public final Map.Entry getRawResult() { return result; } + public final void compute() { + final BiFunction, Map.Entry, ? extends Map.Entry> reducer; + if ((reducer = this.reducer) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new ReduceEntriesTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, reducer)).fork(); + } + Map.Entry r = null; + for (Node p; (p = advance()) != null; ) + r = (r == null) ? p : reducer.apply(r, p); + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + ReduceEntriesTask + t = (ReduceEntriesTask)c, + s = t.rights; + while (s != null) { + Map.Entry tr, sr; + if ((sr = s.result) != null) + t.result = (((tr = t.result) == null) ? sr : + reducer.apply(tr, sr)); + s = t.rights = s.nextRight; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class MapReduceKeysTask + extends BulkTask { + final Function transformer; + final BiFunction reducer; + U result; + MapReduceKeysTask rights, nextRight; + MapReduceKeysTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceKeysTask nextRight, + Function transformer, + BiFunction reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + public final U getRawResult() { return result; } + public final void compute() { + final Function transformer; + final BiFunction reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceKeysTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, reducer)).fork(); + } + U r = null; + for (Node p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p.key)) != null) + r = (r == null) ? u : reducer.apply(r, u); + } + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceKeysTask + t = (MapReduceKeysTask)c, + s = t.rights; + while (s != null) { + U tr, sr; + if ((sr = s.result) != null) + t.result = (((tr = t.result) == null) ? sr : + reducer.apply(tr, sr)); + s = t.rights = s.nextRight; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class MapReduceValuesTask + extends BulkTask { + final Function transformer; + final BiFunction reducer; + U result; + MapReduceValuesTask rights, nextRight; + MapReduceValuesTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceValuesTask nextRight, + Function transformer, + BiFunction reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + public final U getRawResult() { return result; } + public final void compute() { + final Function transformer; + final BiFunction reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceValuesTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, reducer)).fork(); + } + U r = null; + for (Node p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p.val)) != null) + r = (r == null) ? u : reducer.apply(r, u); + } + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceValuesTask + t = (MapReduceValuesTask)c, + s = t.rights; + while (s != null) { + U tr, sr; + if ((sr = s.result) != null) + t.result = (((tr = t.result) == null) ? sr : + reducer.apply(tr, sr)); + s = t.rights = s.nextRight; + } + } + } } + } - public int hashCode() { - int h = 0; - for (K e : this) - h += e.hashCode(); - return h; + @SuppressWarnings("serial") + static final class MapReduceEntriesTask + extends BulkTask { + final Function, ? extends U> transformer; + final BiFunction reducer; + U result; + MapReduceEntriesTask rights, nextRight; + MapReduceEntriesTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceEntriesTask nextRight, + Function, ? extends U> transformer, + BiFunction reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; } - - public boolean equals(Object o) { - Set c; - return ((o instanceof Set) && - ((c = (Set)o) == this || - (containsAll(c) && c.containsAll(this)))); + public final U getRawResult() { return result; } + public final void compute() { + final Function, ? extends U> transformer; + final BiFunction reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceEntriesTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, reducer)).fork(); + } + U r = null; + for (Node p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p)) != null) + r = (r == null) ? u : reducer.apply(r, u); + } + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceEntriesTask + t = (MapReduceEntriesTask)c, + s = t.rights; + while (s != null) { + U tr, sr; + if ((sr = s.result) != null) + t.result = (((tr = t.result) == null) ? sr : + reducer.apply(tr, sr)); + s = t.rights = s.nextRight; + } + } + } } - } - /** - * A view of a ConcurrentHashMap as a {@link Collection} of - * values, in which additions are disabled. This class cannot be - * directly instantiated. See {@link #values()}. - */ - static final class ValuesView extends CollectionView - implements Collection, java.io.Serializable { - private static final long serialVersionUID = 2249069246763182397L; - ValuesView(ConcurrentHashMap map) { super(map); } - public final boolean contains(Object o) { - return map.containsValue(o); + @SuppressWarnings("serial") + static final class MapReduceMappingsTask + extends BulkTask { + final BiFunction transformer; + final BiFunction reducer; + U result; + MapReduceMappingsTask rights, nextRight; + MapReduceMappingsTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceMappingsTask nextRight, + BiFunction transformer, + BiFunction reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; } - - public final boolean remove(Object o) { - if (o != null) { - for (Iterator it = iterator(); it.hasNext();) { - if (o.equals(it.next())) { - it.remove(); - return true; + public final U getRawResult() { return result; } + public final void compute() { + final BiFunction transformer; + final BiFunction reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceMappingsTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, reducer)).fork(); + } + U r = null; + for (Node p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p.key, p.val)) != null) + r = (r == null) ? u : reducer.apply(r, u); + } + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceMappingsTask + t = (MapReduceMappingsTask)c, + s = t.rights; + while (s != null) { + U tr, sr; + if ((sr = s.result) != null) + t.result = (((tr = t.result) == null) ? sr : + reducer.apply(tr, sr)); + s = t.rights = s.nextRight; } } } - return false; } + } - public final Iterator iterator() { - ConcurrentHashMap m = map; - Node[] t; - int f = (t = m.table) == null ? 0 : t.length; - return new ValueIterator(t, f, 0, f, m); + @SuppressWarnings("serial") + static final class MapReduceKeysToDoubleTask + extends BulkTask { + final ToDoubleFunction transformer; + final DoubleBinaryOperator reducer; + final double basis; + double result; + MapReduceKeysToDoubleTask rights, nextRight; + MapReduceKeysToDoubleTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceKeysToDoubleTask nextRight, + ToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; this.reducer = reducer; + } + public final Double getRawResult() { return result; } + public final void compute() { + final ToDoubleFunction transformer; + final DoubleBinaryOperator reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + double r = this.basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceKeysToDoubleTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node p; (p = advance()) != null; ) + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key)); + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceKeysToDoubleTask + t = (MapReduceKeysToDoubleTask)c, + s = t.rights; + while (s != null) { + t.result = reducer.applyAsDouble(t.result, s.result); + s = t.rights = s.nextRight; + } + } + } } + } - public final boolean add(V e) { - throw new UnsupportedOperationException(); + @SuppressWarnings("serial") + static final class MapReduceValuesToDoubleTask + extends BulkTask { + final ToDoubleFunction transformer; + final DoubleBinaryOperator reducer; + final double basis; + double result; + MapReduceValuesToDoubleTask rights, nextRight; + MapReduceValuesToDoubleTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceValuesToDoubleTask nextRight, + ToDoubleFunction transformer, + double basis, + DoubleBinaryOperator reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; this.reducer = reducer; } - public final boolean addAll(Collection c) { - throw new UnsupportedOperationException(); + public final Double getRawResult() { return result; } + public final void compute() { + final ToDoubleFunction transformer; + final DoubleBinaryOperator reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + double r = this.basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceValuesToDoubleTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node p; (p = advance()) != null; ) + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.val)); + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceValuesToDoubleTask + t = (MapReduceValuesToDoubleTask)c, + s = t.rights; + while (s != null) { + t.result = reducer.applyAsDouble(t.result, s.result); + s = t.rights = s.nextRight; + } + } + } } - } - /** - * A view of a ConcurrentHashMap as a {@link Set} of (key, value) - * entries. This class cannot be directly instantiated. See - * {@link #entrySet()}. - */ - static final class EntrySetView extends CollectionView> - implements Set>, java.io.Serializable { - private static final long serialVersionUID = 2249069246763182397L; - EntrySetView(ConcurrentHashMap map) { super(map); } - - public boolean contains(Object o) { - Object k, v, r; Map.Entry e; - return ((o instanceof Map.Entry) && - (k = (e = (Map.Entry)o).getKey()) != null && - (r = map.get(k)) != null && - (v = e.getValue()) != null && - (v == r || v.equals(r))); + @SuppressWarnings("serial") + static final class MapReduceEntriesToDoubleTask + extends BulkTask { + final ToDoubleFunction> transformer; + final DoubleBinaryOperator reducer; + final double basis; + double result; + MapReduceEntriesToDoubleTask rights, nextRight; + MapReduceEntriesToDoubleTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceEntriesToDoubleTask nextRight, + ToDoubleFunction> transformer, + double basis, + DoubleBinaryOperator reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; this.reducer = reducer; } - - public boolean remove(Object o) { - Object k, v; Map.Entry e; - return ((o instanceof Map.Entry) && - (k = (e = (Map.Entry)o).getKey()) != null && - (v = e.getValue()) != null && - map.remove(k, v)); + public final Double getRawResult() { return result; } + public final void compute() { + final ToDoubleFunction> transformer; + final DoubleBinaryOperator reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + double r = this.basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceEntriesToDoubleTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node p; (p = advance()) != null; ) + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p)); + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceEntriesToDoubleTask + t = (MapReduceEntriesToDoubleTask)c, + s = t.rights; + while (s != null) { + t.result = reducer.applyAsDouble(t.result, s.result); + s = t.rights = s.nextRight; + } + } + } } + } - /** - * @return an iterator over the entries of the backing map - */ - public Iterator> iterator() { - ConcurrentHashMap m = map; - Node[] t; - int f = (t = m.table) == null ? 0 : t.length; - return new EntryIterator(t, f, 0, f, m); + @SuppressWarnings("serial") + static final class MapReduceMappingsToDoubleTask + extends BulkTask { + final ToDoubleBiFunction transformer; + final DoubleBinaryOperator reducer; + final double basis; + double result; + MapReduceMappingsToDoubleTask rights, nextRight; + MapReduceMappingsToDoubleTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceMappingsToDoubleTask nextRight, + ToDoubleBiFunction transformer, + double basis, + DoubleBinaryOperator reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; this.reducer = reducer; } - - public boolean add(Entry e) { - return map.putVal(e.getKey(), e.getValue(), false) == null; + public final Double getRawResult() { return result; } + public final void compute() { + final ToDoubleBiFunction transformer; + final DoubleBinaryOperator reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + double r = this.basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceMappingsToDoubleTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node p; (p = advance()) != null; ) + r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key, p.val)); + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceMappingsToDoubleTask + t = (MapReduceMappingsToDoubleTask)c, + s = t.rights; + while (s != null) { + t.result = reducer.applyAsDouble(t.result, s.result); + s = t.rights = s.nextRight; + } + } + } } + } - public boolean addAll(Collection> c) { - boolean added = false; - for (Entry e : c) { - if (add(e)) - added = true; + @SuppressWarnings("serial") + static final class MapReduceKeysToLongTask + extends BulkTask { + final ToLongFunction transformer; + final LongBinaryOperator reducer; + final long basis; + long result; + MapReduceKeysToLongTask rights, nextRight; + MapReduceKeysToLongTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceKeysToLongTask nextRight, + ToLongFunction transformer, + long basis, + LongBinaryOperator reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; this.reducer = reducer; + } + public final Long getRawResult() { return result; } + public final void compute() { + final ToLongFunction transformer; + final LongBinaryOperator reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + long r = this.basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceKeysToLongTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node p; (p = advance()) != null; ) + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key)); + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceKeysToLongTask + t = (MapReduceKeysToLongTask)c, + s = t.rights; + while (s != null) { + t.result = reducer.applyAsLong(t.result, s.result); + s = t.rights = s.nextRight; + } + } } - return added; } + } - public final int hashCode() { - int h = 0; - Node[] t; - if ((t = map.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - h += p.hashCode(); + @SuppressWarnings("serial") + static final class MapReduceValuesToLongTask + extends BulkTask { + final ToLongFunction transformer; + final LongBinaryOperator reducer; + final long basis; + long result; + MapReduceValuesToLongTask rights, nextRight; + MapReduceValuesToLongTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceValuesToLongTask nextRight, + ToLongFunction transformer, + long basis, + LongBinaryOperator reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; this.reducer = reducer; + } + public final Long getRawResult() { return result; } + public final void compute() { + final ToLongFunction transformer; + final LongBinaryOperator reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + long r = this.basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceValuesToLongTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node p; (p = advance()) != null; ) + r = reducer.applyAsLong(r, transformer.applyAsLong(p.val)); + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceValuesToLongTask + t = (MapReduceValuesToLongTask)c, + s = t.rights; + while (s != null) { + t.result = reducer.applyAsLong(t.result, s.result); + s = t.rights = s.nextRight; + } } } - return h; } + } - public final boolean equals(Object o) { - Set c; - return ((o instanceof Set) && - ((c = (Set)o) == this || - (containsAll(c) && c.containsAll(this)))); + @SuppressWarnings("serial") + static final class MapReduceEntriesToLongTask + extends BulkTask { + final ToLongFunction> transformer; + final LongBinaryOperator reducer; + final long basis; + long result; + MapReduceEntriesToLongTask rights, nextRight; + MapReduceEntriesToLongTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceEntriesToLongTask nextRight, + ToLongFunction> transformer, + long basis, + LongBinaryOperator reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; this.reducer = reducer; + } + public final Long getRawResult() { return result; } + public final void compute() { + final ToLongFunction> transformer; + final LongBinaryOperator reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + long r = this.basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceEntriesToLongTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node p; (p = advance()) != null; ) + r = reducer.applyAsLong(r, transformer.applyAsLong(p)); + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceEntriesToLongTask + t = (MapReduceEntriesToLongTask)c, + s = t.rights; + while (s != null) { + t.result = reducer.applyAsLong(t.result, s.result); + s = t.rights = s.nextRight; + } + } + } } - } - - /* ---------------- Counters -------------- */ - - // Adapted from LongAdder and Striped64. - // See their internal docs for explanation. - - // A padded cell for distributing counts - static final class CounterCell { - volatile long p0, p1, p2, p3, p4, p5, p6; - volatile long value; - volatile long q0, q1, q2, q3, q4, q5, q6; - CounterCell(long x) { value = x; } + @SuppressWarnings("serial") + static final class MapReduceMappingsToLongTask + extends BulkTask { + final ToLongBiFunction transformer; + final LongBinaryOperator reducer; + final long basis; + long result; + MapReduceMappingsToLongTask rights, nextRight; + MapReduceMappingsToLongTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceMappingsToLongTask nextRight, + ToLongBiFunction transformer, + long basis, + LongBinaryOperator reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; this.reducer = reducer; + } + public final Long getRawResult() { return result; } + public final void compute() { + final ToLongBiFunction transformer; + final LongBinaryOperator reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + long r = this.basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceMappingsToLongTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node p; (p = advance()) != null; ) + r = reducer.applyAsLong(r, transformer.applyAsLong(p.key, p.val)); + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceMappingsToLongTask + t = (MapReduceMappingsToLongTask)c, + s = t.rights; + while (s != null) { + t.result = reducer.applyAsLong(t.result, s.result); + s = t.rights = s.nextRight; + } + } + } + } } - /** - * Holder for the thread-local hash code determining which - * CounterCell to use. The code is initialized via the - * counterHashCodeGenerator, but may be moved upon collisions. - */ - static final class CounterHashCode { - int code; + @SuppressWarnings("serial") + static final class MapReduceKeysToIntTask + extends BulkTask { + final ToIntFunction transformer; + final IntBinaryOperator reducer; + final int basis; + int result; + MapReduceKeysToIntTask rights, nextRight; + MapReduceKeysToIntTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceKeysToIntTask nextRight, + ToIntFunction transformer, + int basis, + IntBinaryOperator reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; this.reducer = reducer; + } + public final Integer getRawResult() { return result; } + public final void compute() { + final ToIntFunction transformer; + final IntBinaryOperator reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + int r = this.basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceKeysToIntTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node p; (p = advance()) != null; ) + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key)); + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceKeysToIntTask + t = (MapReduceKeysToIntTask)c, + s = t.rights; + while (s != null) { + t.result = reducer.applyAsInt(t.result, s.result); + s = t.rights = s.nextRight; + } + } + } + } } - /** - * Generates initial value for per-thread CounterHashCodes. - */ - static final AtomicInteger counterHashCodeGenerator = new AtomicInteger(); - - /** - * Increment for counterHashCodeGenerator. See class ThreadLocal - * for explanation. - */ - static final int SEED_INCREMENT = 0x61c88647; - - /** - * Per-thread counter hash codes. Shared across all instances. - */ - static final ThreadLocal threadCounterHashCode = - new ThreadLocal(); - - final long sumCount() { - CounterCell[] as = counterCells; CounterCell a; - long sum = baseCount; - if (as != null) { - for (int i = 0; i < as.length; ++i) { - if ((a = as[i]) != null) - sum += a.value; + @SuppressWarnings("serial") + static final class MapReduceValuesToIntTask + extends BulkTask { + final ToIntFunction transformer; + final IntBinaryOperator reducer; + final int basis; + int result; + MapReduceValuesToIntTask rights, nextRight; + MapReduceValuesToIntTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceValuesToIntTask nextRight, + ToIntFunction transformer, + int basis, + IntBinaryOperator reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; this.reducer = reducer; + } + public final Integer getRawResult() { return result; } + public final void compute() { + final ToIntFunction transformer; + final IntBinaryOperator reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + int r = this.basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceValuesToIntTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node p; (p = advance()) != null; ) + r = reducer.applyAsInt(r, transformer.applyAsInt(p.val)); + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceValuesToIntTask + t = (MapReduceValuesToIntTask)c, + s = t.rights; + while (s != null) { + t.result = reducer.applyAsInt(t.result, s.result); + s = t.rights = s.nextRight; + } + } } } - return sum; } - // See LongAdder version for explanation - private final void fullAddCount(long x, CounterHashCode hc, - boolean wasUncontended) { - int h; - if (hc == null) { - hc = new CounterHashCode(); - int s = counterHashCodeGenerator.addAndGet(SEED_INCREMENT); - h = hc.code = (s == 0) ? 1 : s; // Avoid zero - threadCounterHashCode.set(hc); - } - else - h = hc.code; - boolean collide = false; // True if last slot nonempty - for (;;) { - CounterCell[] as; CounterCell a; int n; long v; - if ((as = counterCells) != null && (n = as.length) > 0) { - if ((a = as[(n - 1) & h]) == null) { - if (cellsBusy == 0) { // Try to attach new Cell - CounterCell r = new CounterCell(x); // Optimistic create - if (cellsBusy == 0 && - U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { - boolean created = false; - try { // Recheck under lock - CounterCell[] rs; int m, j; - if ((rs = counterCells) != null && - (m = rs.length) > 0 && - rs[j = (m - 1) & h] == null) { - rs[j] = r; - created = true; - } - } finally { - cellsBusy = 0; - } - if (created) - break; - continue; // Slot is now non-empty - } - } - collide = false; + @SuppressWarnings("serial") + static final class MapReduceEntriesToIntTask + extends BulkTask { + final ToIntFunction> transformer; + final IntBinaryOperator reducer; + final int basis; + int result; + MapReduceEntriesToIntTask rights, nextRight; + MapReduceEntriesToIntTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceEntriesToIntTask nextRight, + ToIntFunction> transformer, + int basis, + IntBinaryOperator reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; this.reducer = reducer; + } + public final Integer getRawResult() { return result; } + public final void compute() { + final ToIntFunction> transformer; + final IntBinaryOperator reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + int r = this.basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceEntriesToIntTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); } - else if (!wasUncontended) // CAS already known to fail - wasUncontended = true; // Continue after rehash - else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) - break; - else if (counterCells != as || n >= NCPU) - collide = false; // At max size or stale - else if (!collide) - collide = true; - else if (cellsBusy == 0 && - U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { - try { - if (counterCells == as) {// Expand table unless stale - CounterCell[] rs = new CounterCell[n << 1]; - for (int i = 0; i < n; ++i) - rs[i] = as[i]; - counterCells = rs; - } - } finally { - cellsBusy = 0; + for (Node p; (p = advance()) != null; ) + r = reducer.applyAsInt(r, transformer.applyAsInt(p)); + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceEntriesToIntTask + t = (MapReduceEntriesToIntTask)c, + s = t.rights; + while (s != null) { + t.result = reducer.applyAsInt(t.result, s.result); + s = t.rights = s.nextRight; } - collide = false; - continue; // Retry with expanded table } - h ^= h << 13; // Rehash - h ^= h >>> 17; - h ^= h << 5; } - else if (cellsBusy == 0 && counterCells == as && - U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { - boolean init = false; - try { // Initialize table - if (counterCells == as) { - CounterCell[] rs = new CounterCell[2]; - rs[h & 1] = new CounterCell(x); - counterCells = rs; - init = true; + } + } + + @SuppressWarnings("serial") + static final class MapReduceMappingsToIntTask + extends BulkTask { + final ToIntBiFunction transformer; + final IntBinaryOperator reducer; + final int basis; + int result; + MapReduceMappingsToIntTask rights, nextRight; + MapReduceMappingsToIntTask + (BulkTask p, int b, int i, int f, Node[] t, + MapReduceMappingsToIntTask nextRight, + ToIntBiFunction transformer, + int basis, + IntBinaryOperator reducer) { + super(p, b, i, f, t); this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; this.reducer = reducer; + } + public final Integer getRawResult() { return result; } + public final void compute() { + final ToIntBiFunction transformer; + final IntBinaryOperator reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + int r = this.basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = ((f = baseLimit) + i) >>> 1) > i;) { + addToPendingCount(1); + (rights = new MapReduceMappingsToIntTask + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node p; (p = advance()) != null; ) + r = reducer.applyAsInt(r, transformer.applyAsInt(p.key, p.val)); + result = r; + CountedCompleter c; + for (c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") + MapReduceMappingsToIntTask + t = (MapReduceMappingsToIntTask)c, + s = t.rights; + while (s != null) { + t.result = reducer.applyAsInt(t.result, s.result); + s = t.rights = s.nextRight; } - } finally { - cellsBusy = 0; } - if (init) - break; } - else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x)) - break; // Fall back on using base } - hc.code = h; // Record index for next time } // Unsafe mechanics - private static final sun.misc.Unsafe U; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); private static final long SIZECTL; private static final long TRANSFERINDEX; private static final long BASECOUNT; private static final long CELLSBUSY; private static final long CELLVALUE; - private static final long ABASE; + private static final int ABASE; private static final int ASHIFT; static { try { - U = sun.misc.Unsafe.getUnsafe(); - Class k = ConcurrentHashMap.class; SIZECTL = U.objectFieldOffset - (k.getDeclaredField("sizeCtl")); + (ConcurrentHashMap.class.getDeclaredField("sizeCtl")); TRANSFERINDEX = U.objectFieldOffset - (k.getDeclaredField("transferIndex")); + (ConcurrentHashMap.class.getDeclaredField("transferIndex")); BASECOUNT = U.objectFieldOffset - (k.getDeclaredField("baseCount")); + (ConcurrentHashMap.class.getDeclaredField("baseCount")); CELLSBUSY = U.objectFieldOffset - (k.getDeclaredField("cellsBusy")); - Class ck = CounterCell.class; + (ConcurrentHashMap.class.getDeclaredField("cellsBusy")); + CELLVALUE = U.objectFieldOffset - (ck.getDeclaredField("value")); - Class ak = Node[].class; - ABASE = U.arrayBaseOffset(ak); - int scale = U.arrayIndexScale(ak); + (CounterCell.class.getDeclaredField("value")); + + ABASE = U.arrayBaseOffset(Node[].class); + int scale = U.arrayIndexScale(Node[].class); if ((scale & (scale - 1)) != 0) - throw new Error("data type scale not a power of two"); + throw new Error("array index scale not a power of two"); ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); - } catch (Exception e) { + } catch (ReflectiveOperationException e) { throw new Error(e); } @@ -3329,5 +6347,4 @@ else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x)) // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773 Class ensureLoaded = LockSupport.class; } - } diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentLinkedDeque.java b/luni/src/main/java/java/util/concurrent/ConcurrentLinkedDeque.java index b38d6a598..7084d1459 100644 --- a/luni/src/main/java/java/util/concurrent/ConcurrentLinkedDeque.java +++ b/luni/src/main/java/java/util/concurrent/ConcurrentLinkedDeque.java @@ -7,12 +7,16 @@ package java.util.concurrent; import java.util.AbstractCollection; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Deque; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Queue; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; // BEGIN android-note // removed link to collections framework docs @@ -27,12 +31,8 @@ * Like most other concurrent collection implementations, this class * does not permit the use of {@code null} elements. * - *

Iterators are weakly consistent, returning elements - * reflecting the state of the deque at some point at or since the - * creation of the iterator. They do not throw {@link - * java.util.ConcurrentModificationException - * ConcurrentModificationException}, and may proceed concurrently with - * other operations. + *

Iterators and spliterators are + * weakly consistent. * *

Beware that, unlike in most collections, the {@code size} method * is NOT a constant-time operation. Because of the @@ -59,7 +59,7 @@ * @since 1.7 * @author Doug Lea * @author Martin Buchholz - * @param the type of elements held in this collection + * @param the type of elements held in this deque */ public class ConcurrentLinkedDeque extends AbstractCollection @@ -272,47 +272,45 @@ static final class Node { * only be seen after publication via casNext or casPrev. */ Node(E item) { - UNSAFE.putObject(this, itemOffset, item); + U.putObject(this, ITEM, item); } boolean casItem(E cmp, E val) { - return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val); + return U.compareAndSwapObject(this, ITEM, cmp, val); } void lazySetNext(Node val) { - UNSAFE.putOrderedObject(this, nextOffset, val); + U.putOrderedObject(this, NEXT, val); } boolean casNext(Node cmp, Node val) { - return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); + return U.compareAndSwapObject(this, NEXT, cmp, val); } void lazySetPrev(Node val) { - UNSAFE.putOrderedObject(this, prevOffset, val); + U.putOrderedObject(this, PREV, val); } boolean casPrev(Node cmp, Node val) { - return UNSAFE.compareAndSwapObject(this, prevOffset, cmp, val); + return U.compareAndSwapObject(this, PREV, cmp, val); } // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long prevOffset; - private static final long itemOffset; - private static final long nextOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long PREV; + private static final long ITEM; + private static final long NEXT; static { try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = Node.class; - prevOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("prev")); - itemOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("item")); - nextOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("next")); - } catch (Exception e) { + PREV = U.objectFieldOffset + (Node.class.getDeclaredField("prev")); + ITEM = U.objectFieldOffset + (Node.class.getDeclaredField("item")); + NEXT = U.objectFieldOffset + (Node.class.getDeclaredField("next")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } @@ -322,8 +320,7 @@ boolean casPrev(Node cmp, Node val) { * Links e as first element. */ private void linkFirst(E e) { - checkNotNull(e); - final Node newNode = new Node(e); + final Node newNode = new Node(Objects.requireNonNull(e)); restartFromHead: for (;;) @@ -355,8 +352,7 @@ else if (p.next == p) // PREV_TERMINATOR * Links e as last element. */ private void linkLast(E e) { - checkNotNull(e); - final Node newNode = new Node(e); + final Node newNode = new Node(Objects.requireNonNull(e)); restartFromTail: for (;;) @@ -760,16 +756,6 @@ else if (p == t // Minor convenience utilities - /** - * Throws NullPointerException if argument is null. - * - * @param v the element - */ - private static void checkNotNull(Object v) { - if (v == null) - throw new NullPointerException(); - } - /** * Returns element unless it is null, in which case throws * NoSuchElementException. @@ -783,22 +769,6 @@ private E screenNullResult(E v) { return v; } - /** - * Creates an array list and fills it with elements of this list. - * Used by toArray. - * - * @return the array list - */ - private ArrayList toArrayList() { - ArrayList list = new ArrayList(); - for (Node p = first(); p != null; p = succ(p)) { - E item = p.item; - if (item != null) - list.add(item); - } - return list; - } - /** * Constructs an empty deque. */ @@ -819,8 +789,7 @@ public ConcurrentLinkedDeque(Collection c) { // Copy c into a private chain of Nodes Node h = null, t = null; for (E e : c) { - checkNotNull(e); - Node newNode = new Node(e); + Node newNode = new Node(Objects.requireNonNull(e)); if (h == null) h = t = newNode; else { @@ -995,23 +964,42 @@ public boolean add(E e) { } public E poll() { return pollFirst(); } - public E remove() { return removeFirst(); } public E peek() { return peekFirst(); } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E remove() { return removeFirst(); } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E pop() { return removeFirst(); } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ public E element() { return getFirst(); } + + /** + * @throws NullPointerException {@inheritDoc} + */ public void push(E e) { addFirst(e); } - public E pop() { return removeFirst(); } /** - * Removes the first element {@code e} such that - * {@code o.equals(e)}, if such an element exists in this deque. + * Removes the first occurrence of the specified element from this deque. * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element {@code e} such that + * {@code o.equals(e)} (if such an element exists). + * Returns {@code true} if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). * * @param o element to be removed from this deque, if present * @return {@code true} if the deque contained the specified element * @throws NullPointerException if the specified element is null */ public boolean removeFirstOccurrence(Object o) { - checkNotNull(o); + Objects.requireNonNull(o); for (Node p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null && o.equals(item) && p.casItem(item, null)) { @@ -1023,16 +1011,19 @@ public boolean removeFirstOccurrence(Object o) { } /** - * Removes the last element {@code e} such that - * {@code o.equals(e)}, if such an element exists in this deque. + * Removes the last occurrence of the specified element from this deque. * If the deque does not contain the element, it is unchanged. + * More formally, removes the last element {@code e} such that + * {@code o.equals(e)} (if such an element exists). + * Returns {@code true} if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). * * @param o element to be removed from this deque, if present * @return {@code true} if the deque contained the specified element * @throws NullPointerException if the specified element is null */ public boolean removeLastOccurrence(Object o) { - checkNotNull(o); + Objects.requireNonNull(o); for (Node p = last(); p != null; p = pred(p)) { E item = p.item; if (item != null && o.equals(item) && p.casItem(item, null)) { @@ -1044,18 +1035,20 @@ public boolean removeLastOccurrence(Object o) { } /** - * Returns {@code true} if this deque contains at least one - * element {@code e} such that {@code o.equals(e)}. + * Returns {@code true} if this deque contains the specified element. + * More formally, returns {@code true} if and only if this deque contains + * at least one element {@code e} such that {@code o.equals(e)}. * * @param o element whose presence in this deque is to be tested * @return {@code true} if this deque contains the specified element */ public boolean contains(Object o) { - if (o == null) return false; - for (Node p = first(); p != null; p = succ(p)) { - E item = p.item; - if (item != null && o.equals(item)) - return true; + if (o != null) { + for (Node p = first(); p != null; p = succ(p)) { + E item = p.item; + if (item != null && o.equals(item)) + return true; + } } return false; } @@ -1086,19 +1079,28 @@ public boolean isEmpty() { * @return the number of elements in this deque */ public int size() { - int count = 0; - for (Node p = first(); p != null; p = succ(p)) - if (p.item != null) - // Collection.size() spec says to max out - if (++count == Integer.MAX_VALUE) - break; - return count; + restartFromHead: for (;;) { + int count = 0; + for (Node p = first(); p != null;) { + if (p.item != null) + if (++count == Integer.MAX_VALUE) + break; // @see Collection.size() + if (p == (p = p.next)) + continue restartFromHead; + } + return count; + } } /** - * Removes the first element {@code e} such that - * {@code o.equals(e)}, if such an element exists in this deque. + * Removes the first occurrence of the specified element from this deque. * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element {@code e} such that + * {@code o.equals(e)} (if such an element exists). + * Returns {@code true} if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + *

This method is equivalent to {@link #removeFirstOccurrence(Object)}. * * @param o element to be removed from this deque, if present * @return {@code true} if the deque contained the specified element @@ -1128,8 +1130,7 @@ public boolean addAll(Collection c) { // Copy c into a private chain of Nodes Node beginningOfTheEnd = null, last = null; for (E e : c) { - checkNotNull(e); - Node newNode = new Node(e); + Node newNode = new Node(Objects.requireNonNull(e)); if (beginningOfTheEnd == null) beginningOfTheEnd = last = newNode; else { @@ -1180,6 +1181,62 @@ public void clear() { ; } + public String toString() { + String[] a = null; + restartFromHead: for (;;) { + int charLength = 0; + int size = 0; + for (Node p = first(); p != null;) { + E item = p.item; + if (item != null) { + if (a == null) + a = new String[4]; + else if (size == a.length) + a = Arrays.copyOf(a, 2 * size); + String s = item.toString(); + a[size++] = s; + charLength += s.length(); + } + if (p == (p = p.next)) + continue restartFromHead; + } + + if (size == 0) + return "[]"; + + return Helpers.toString(a, size, charLength); + } + } + + private Object[] toArrayInternal(Object[] a) { + Object[] x = a; + restartFromHead: for (;;) { + int size = 0; + for (Node p = first(); p != null;) { + E item = p.item; + if (item != null) { + if (x == null) + x = new Object[4]; + else if (size == x.length) + x = Arrays.copyOf(x, 2 * (size + 4)); + x[size++] = item; + } + if (p == (p = p.next)) + continue restartFromHead; + } + if (x == null) + return new Object[0]; + else if (a != null && size <= a.length) { + if (a != x) + System.arraycopy(x, 0, a, 0, size); + if (size < a.length) + a[size] = null; + return a; + } + return (size == x.length) ? x : Arrays.copyOf(x, size); + } + } + /** * Returns an array containing all of the elements in this deque, in * proper sequence (from first to last element). @@ -1194,7 +1251,7 @@ public void clear() { * @return an array containing all of the elements in this deque */ public Object[] toArray() { - return toArrayList().toArray(); + return toArrayInternal(null); } /** @@ -1220,7 +1277,7 @@ public Object[] toArray() { * The following code can be used to dump the deque into a newly * allocated array of {@code String}: * - *

 {@code String[] y = x.toArray(new String[0]);}
+ *
 {@code String[] y = x.toArray(new String[0]);}
* * Note that {@code toArray(new Object[0])} is identical in function to * {@code toArray()}. @@ -1234,20 +1291,18 @@ public Object[] toArray() { * this deque * @throws NullPointerException if the specified array is null */ + @SuppressWarnings("unchecked") public T[] toArray(T[] a) { - return toArrayList().toArray(a); + if (a == null) throw new NullPointerException(); + return (T[]) toArrayInternal(a); } /** * Returns an iterator over the elements in this deque in proper sequence. * The elements will be returned in order from first (head) to last (tail). * - *

The returned iterator is a "weakly consistent" iterator that - * will never throw {@link java.util.ConcurrentModificationException - * ConcurrentModificationException}, and guarantees to traverse - * elements as they existed upon construction of the iterator, and - * may (but is not guaranteed to) reflect any modifications - * subsequent to construction. + *

The returned iterator is + * weakly consistent. * * @return an iterator over the elements in this deque in proper sequence */ @@ -1260,12 +1315,8 @@ public Iterator iterator() { * sequential order. The elements will be returned in order from * last (tail) to first (head). * - *

The returned iterator is a "weakly consistent" iterator that - * will never throw {@link java.util.ConcurrentModificationException - * ConcurrentModificationException}, and guarantees to traverse - * elements as they existed upon construction of the iterator, and - * may (but is not guaranteed to) reflect any modifications - * subsequent to construction. + *

The returned iterator is + * weakly consistent. * * @return an iterator over the elements in this deque in reverse order */ @@ -1310,7 +1361,7 @@ private void advance() { Node p = (nextNode == null) ? startNode() : nextNode(nextNode); for (;; p = nextNode(p)) { if (p == null) { - // p might be active end or TERMINATOR node; both are OK + // might be at active end or TERMINATOR node; both are OK nextNode = null; nextItem = null; break; @@ -1356,9 +1407,121 @@ private class DescendingItr extends AbstractItr { Node nextNode(Node p) { return pred(p); } } + /** A customized variant of Spliterators.IteratorSpliterator */ + static final class CLDSpliterator implements Spliterator { + static final int MAX_BATCH = 1 << 25; // max batch array size; + final ConcurrentLinkedDeque queue; + Node current; // current node; null until initialized + int batch; // batch size for splits + boolean exhausted; // true when no more nodes + CLDSpliterator(ConcurrentLinkedDeque queue) { + this.queue = queue; + } + + public Spliterator trySplit() { + Node p; + final ConcurrentLinkedDeque q = this.queue; + int b = batch; + int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1; + if (!exhausted && + ((p = current) != null || (p = q.first()) != null)) { + if (p.item == null && p == (p = p.next)) + current = p = q.first(); + if (p != null && p.next != null) { + Object[] a = new Object[n]; + int i = 0; + do { + if ((a[i] = p.item) != null) + ++i; + if (p == (p = p.next)) + p = q.first(); + } while (p != null && i < n); + if ((current = p) == null) + exhausted = true; + if (i > 0) { + batch = i; + return Spliterators.spliterator + (a, 0, i, (Spliterator.ORDERED | + Spliterator.NONNULL | + Spliterator.CONCURRENT)); + } + } + } + return null; + } + + public void forEachRemaining(Consumer action) { + Node p; + if (action == null) throw new NullPointerException(); + final ConcurrentLinkedDeque q = this.queue; + if (!exhausted && + ((p = current) != null || (p = q.first()) != null)) { + exhausted = true; + do { + E e = p.item; + if (p == (p = p.next)) + p = q.first(); + if (e != null) + action.accept(e); + } while (p != null); + } + } + + public boolean tryAdvance(Consumer action) { + Node p; + if (action == null) throw new NullPointerException(); + final ConcurrentLinkedDeque q = this.queue; + if (!exhausted && + ((p = current) != null || (p = q.first()) != null)) { + E e; + do { + e = p.item; + if (p == (p = p.next)) + p = q.first(); + } while (e == null && p != null); + if ((current = p) == null) + exhausted = true; + if (e != null) { + action.accept(e); + return true; + } + } + return false; + } + + public long estimateSize() { return Long.MAX_VALUE; } + + public int characteristics() { + return Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT; + } + } + + /** + * Returns a {@link Spliterator} over the elements in this deque. + * + *

The returned spliterator is + * weakly consistent. + * + *

The {@code Spliterator} reports {@link Spliterator#CONCURRENT}, + * {@link Spliterator#ORDERED}, and {@link Spliterator#NONNULL}. + * + * @implNote + * The {@code Spliterator} implements {@code trySplit} to permit limited + * parallelism. + * + * @return a {@code Spliterator} over the elements in this deque + * @since 1.8 + */ + public Spliterator spliterator() { + return new CLDSpliterator(this); + } + /** * Saves this deque to a stream (that is, serializes it). * + * @param s the stream + * @throws java.io.IOException if an I/O error occurs * @serialData All of the elements (each an {@code E}) in * the proper order, followed by a null */ @@ -1381,6 +1544,10 @@ private void writeObject(java.io.ObjectOutputStream s) /** * Reconstitutes this deque from a stream (that is, deserializes it). + * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws java.io.IOException if an I/O error occurs */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { @@ -1388,8 +1555,7 @@ private void readObject(java.io.ObjectInputStream s) // Read in elements until trailing null sentinel found Node h = null, t = null; - Object item; - while ((item = s.readObject()) != null) { + for (Object item; (item = s.readObject()) != null; ) { @SuppressWarnings("unchecked") Node newNode = new Node((E) item); if (h == null) @@ -1404,31 +1570,29 @@ private void readObject(java.io.ObjectInputStream s) } private boolean casHead(Node cmp, Node val) { - return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val); + return U.compareAndSwapObject(this, HEAD, cmp, val); } private boolean casTail(Node cmp, Node val) { - return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val); + return U.compareAndSwapObject(this, TAIL, cmp, val); } // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long headOffset; - private static final long tailOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long HEAD; + private static final long TAIL; static { PREV_TERMINATOR = new Node(); PREV_TERMINATOR.next = PREV_TERMINATOR; NEXT_TERMINATOR = new Node(); NEXT_TERMINATOR.prev = NEXT_TERMINATOR; try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = ConcurrentLinkedDeque.class; - headOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("head")); - tailOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("tail")); - } catch (Exception e) { + HEAD = U.objectFieldOffset + (ConcurrentLinkedDeque.class.getDeclaredField("head")); + TAIL = U.objectFieldOffset + (ConcurrentLinkedDeque.class.getDeclaredField("tail")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentLinkedQueue.java b/luni/src/main/java/java/util/concurrent/ConcurrentLinkedQueue.java index 9010cbe76..e96b1b8a6 100644 --- a/luni/src/main/java/java/util/concurrent/ConcurrentLinkedQueue.java +++ b/luni/src/main/java/java/util/concurrent/ConcurrentLinkedQueue.java @@ -7,11 +7,15 @@ package java.util.concurrent; import java.util.AbstractQueue; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Queue; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; // BEGIN android-note // removed link to collections framework docs @@ -68,7 +72,7 @@ * * @since 1.5 * @author Doug Lea - * @param the type of elements held in this collection + * @param the type of elements held in this queue */ public class ConcurrentLinkedQueue extends AbstractQueue implements Queue, java.io.Serializable { @@ -148,45 +152,28 @@ public class ConcurrentLinkedQueue extends AbstractQueue private static class Node { volatile E item; volatile Node next; + } - /** - * Constructs a new node. Uses relaxed write because item can - * only be seen after publication via casNext. - */ - Node(E item) { - UNSAFE.putObject(this, itemOffset, item); - } - - boolean casItem(E cmp, E val) { - return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val); - } + /** + * Returns a new node holding item. Uses relaxed write because item + * can only be seen after piggy-backing publication via casNext. + */ + static Node newNode(E item) { + Node node = new Node(); + U.putObject(node, ITEM, item); + return node; + } - void lazySetNext(Node val) { - UNSAFE.putOrderedObject(this, nextOffset, val); - } + static boolean casItem(Node node, E cmp, E val) { + return U.compareAndSwapObject(node, ITEM, cmp, val); + } - boolean casNext(Node cmp, Node val) { - return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); - } + static void lazySetNext(Node node, Node val) { + U.putOrderedObject(node, NEXT, val); + } - // Unsafe mechanics - - private static final sun.misc.Unsafe UNSAFE; - private static final long itemOffset; - private static final long nextOffset; - - static { - try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = Node.class; - itemOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("item")); - nextOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("next")); - } catch (Exception e) { - throw new Error(e); - } - } + static boolean casNext(Node node, Node cmp, Node val) { + return U.compareAndSwapObject(node, NEXT, cmp, val); } /** @@ -201,7 +188,7 @@ boolean casNext(Node cmp, Node val) { * - it is permitted for tail to lag behind head, that is, for tail * to not be reachable from head! */ - private transient volatile Node head; + transient volatile Node head; /** * A node from which the last node on list (that is, the unique @@ -221,7 +208,7 @@ boolean casNext(Node cmp, Node val) { * Creates a {@code ConcurrentLinkedQueue} that is initially empty. */ public ConcurrentLinkedQueue() { - head = tail = new Node(null); + head = tail = newNode(null); } /** @@ -236,17 +223,16 @@ public ConcurrentLinkedQueue() { public ConcurrentLinkedQueue(Collection c) { Node h = null, t = null; for (E e : c) { - checkNotNull(e); - Node newNode = new Node(e); + Node newNode = newNode(Objects.requireNonNull(e)); if (h == null) h = t = newNode; else { - t.lazySetNext(newNode); + lazySetNext(t, newNode); t = newNode; } } if (h == null) - h = t = new Node(null); + h = t = newNode(null); head = h; tail = t; } @@ -270,8 +256,9 @@ public boolean add(E e) { * as sentinel for succ(), below. */ final void updateHead(Node h, Node p) { + // assert h != null && p != null && (h == p || h.item == null); if (h != p && casHead(h, p)) - h.lazySetNext(h); + lazySetNext(h, h); } /** @@ -292,14 +279,13 @@ final Node succ(Node p) { * @throws NullPointerException if the specified element is null */ public boolean offer(E e) { - checkNotNull(e); - final Node newNode = new Node(e); + final Node newNode = newNode(Objects.requireNonNull(e)); for (Node t = tail, p = t;;) { Node q = p.next; if (q == null) { // p is last node - if (p.casNext(null, newNode)) { + if (casNext(p, null, newNode)) { // Successful CAS is the linearization point // for e to become an element of this queue, // and for newNode to become "live". @@ -327,7 +313,7 @@ public E poll() { for (Node h = head, p = h, q;;) { E item = p.item; - if (item != null && p.casItem(item, null)) { + if (item != null && casItem(p, item, null)) { // Successful CAS is the linearization point // for item to be removed from this queue. if (p != h) // hop two nodes at a time @@ -414,13 +400,17 @@ public boolean isEmpty() { * @return the number of elements in this queue */ public int size() { - int count = 0; - for (Node p = first(); p != null; p = succ(p)) - if (p.item != null) - // Collection.size() spec says to max out - if (++count == Integer.MAX_VALUE) - break; - return count; + restartFromHead: for (;;) { + int count = 0; + for (Node p = first(); p != null;) { + if (p.item != null) + if (++count == Integer.MAX_VALUE) + break; // @see Collection.size() + if (p == (p = p.next)) + continue restartFromHead; + } + return count; + } } /** @@ -432,11 +422,12 @@ public int size() { * @return {@code true} if this queue contains the specified element */ public boolean contains(Object o) { - if (o == null) return false; - for (Node p = first(); p != null; p = succ(p)) { - E item = p.item; - if (item != null && o.equals(item)) - return true; + if (o != null) { + for (Node p = first(); p != null; p = succ(p)) { + E item = p.item; + if (item != null && o.equals(item)) + return true; + } } return false; } @@ -453,19 +444,25 @@ public boolean contains(Object o) { * @return {@code true} if this queue changed as a result of the call */ public boolean remove(Object o) { - if (o == null) return false; - Node pred = null; - for (Node p = first(); p != null; p = succ(p)) { - E item = p.item; - if (item != null && - o.equals(item) && - p.casItem(item, null)) { - Node next = succ(p); - if (pred != null && next != null) - pred.casNext(p, next); - return true; + if (o != null) { + Node next, pred = null; + for (Node p = first(); p != null; pred = p, p = next) { + boolean removed = false; + E item = p.item; + if (item != null) { + if (!o.equals(item)) { + next = succ(p); + continue; + } + removed = casItem(p, item, null); + } + + next = succ(p); + if (pred != null && next != null) // unlink + casNext(pred, p, next); + if (removed) + return true; } - pred = p; } return false; } @@ -490,12 +487,11 @@ public boolean addAll(Collection c) { // Copy c into a private chain of Nodes Node beginningOfTheEnd = null, last = null; for (E e : c) { - checkNotNull(e); - Node newNode = new Node(e); + Node newNode = newNode(Objects.requireNonNull(e)); if (beginningOfTheEnd == null) beginningOfTheEnd = last = newNode; else { - last.lazySetNext(newNode); + lazySetNext(last, newNode); last = newNode; } } @@ -507,7 +503,7 @@ public boolean addAll(Collection c) { Node q = p.next; if (q == null) { // p is last node - if (p.casNext(null, beginningOfTheEnd)) { + if (casNext(p, null, beginningOfTheEnd)) { // Successful CAS is the linearization point // for all elements to be added to this queue. if (!casTail(t, last)) { @@ -533,6 +529,62 @@ else if (p == q) } } + public String toString() { + String[] a = null; + restartFromHead: for (;;) { + int charLength = 0; + int size = 0; + for (Node p = first(); p != null;) { + E item = p.item; + if (item != null) { + if (a == null) + a = new String[4]; + else if (size == a.length) + a = Arrays.copyOf(a, 2 * size); + String s = item.toString(); + a[size++] = s; + charLength += s.length(); + } + if (p == (p = p.next)) + continue restartFromHead; + } + + if (size == 0) + return "[]"; + + return Helpers.toString(a, size, charLength); + } + } + + private Object[] toArrayInternal(Object[] a) { + Object[] x = a; + restartFromHead: for (;;) { + int size = 0; + for (Node p = first(); p != null;) { + E item = p.item; + if (item != null) { + if (x == null) + x = new Object[4]; + else if (size == x.length) + x = Arrays.copyOf(x, 2 * (size + 4)); + x[size++] = item; + } + if (p == (p = p.next)) + continue restartFromHead; + } + if (x == null) + return new Object[0]; + else if (a != null && size <= a.length) { + if (a != x) + System.arraycopy(x, 0, a, 0, size); + if (size < a.length) + a[size] = null; + return a; + } + return (size == x.length) ? x : Arrays.copyOf(x, size); + } + } + /** * Returns an array containing all of the elements in this queue, in * proper sequence. @@ -547,14 +599,7 @@ else if (p == q) * @return an array containing all of the elements in this queue */ public Object[] toArray() { - // Use ArrayList to deal with resizing. - ArrayList al = new ArrayList(); - for (Node p = first(); p != null; p = succ(p)) { - E item = p.item; - if (item != null) - al.add(item); - } - return al.toArray(); + return toArrayInternal(null); } /** @@ -578,7 +623,7 @@ public Object[] toArray() { * The following code can be used to dump the queue into a newly * allocated array of {@code String}: * - *
 {@code String[] y = x.toArray(new String[0]);}
+ *
 {@code String[] y = x.toArray(new String[0]);}
* * Note that {@code toArray(new Object[0])} is identical in function to * {@code toArray()}. @@ -594,40 +639,16 @@ public Object[] toArray() { */ @SuppressWarnings("unchecked") public T[] toArray(T[] a) { - // try to use sent-in array - int k = 0; - Node p; - for (p = first(); p != null && k < a.length; p = succ(p)) { - E item = p.item; - if (item != null) - a[k++] = (T)item; - } - if (p == null) { - if (k < a.length) - a[k] = null; - return a; - } - - // If won't fit, use ArrayList version - ArrayList al = new ArrayList(); - for (Node q = first(); q != null; q = succ(q)) { - E item = q.item; - if (item != null) - al.add(item); - } - return al.toArray(a); + if (a == null) throw new NullPointerException(); + return (T[]) toArrayInternal(a); } /** * Returns an iterator over the elements in this queue in proper sequence. * The elements will be returned in order from first (head) to last (tail). * - *

The returned iterator is a "weakly consistent" iterator that - * will never throw {@link java.util.ConcurrentModificationException - * ConcurrentModificationException}, and guarantees to traverse - * elements as they existed upon construction of the iterator, and - * may (but is not guaranteed to) reflect any modifications - * subsequent to construction. + *

The returned iterator is + * weakly consistent. * * @return an iterator over the elements in this queue in proper sequence */ @@ -655,54 +676,47 @@ private class Itr implements Iterator { private Node lastRet; Itr() { - advance(); - } - - /** - * Moves to next valid node and returns item to return for - * next(), or null if no such. - */ - private E advance() { - lastRet = nextNode; - E x = nextItem; - - Node pred, p; - if (nextNode == null) { - p = first(); - pred = null; - } else { - pred = nextNode; - p = succ(nextNode); - } - - for (;;) { - if (p == null) { - nextNode = null; - nextItem = null; - return x; - } - E item = p.item; - if (item != null) { - nextNode = p; - nextItem = item; - return x; - } else { - // skip over nulls - Node next = succ(p); - if (pred != null && next != null) - pred.casNext(p, next); - p = next; + restartFromHead: for (;;) { + Node h, p, q; + for (p = h = head;; p = q) { + E item; + if ((item = p.item) != null) { + nextNode = p; + nextItem = item; + break; + } + else if ((q = p.next) == null) + break; + else if (p == q) + continue restartFromHead; } + updateHead(h, p); + return; } } public boolean hasNext() { - return nextNode != null; + return nextItem != null; } public E next() { - if (nextNode == null) throw new NoSuchElementException(); - return advance(); + final Node pred = nextNode; + if (pred == null) throw new NoSuchElementException(); + // assert nextItem != null; + lastRet = pred; + E item = null; + + for (Node p = succ(pred), q;; p = q) { + if (p == null || (item = p.item) != null) { + nextNode = p; + E x = nextItem; + nextItem = item; + return x; + } + // unlink deleted nodes + if ((q = succ(p)) != null) + casNext(pred, p, q); + } } public void remove() { @@ -717,6 +731,8 @@ public void remove() { /** * Saves this queue to a stream (that is, serializes it). * + * @param s the stream + * @throws java.io.IOException if an I/O error occurs * @serialData All of the elements (each an {@code E}) in * the proper order, followed by a null */ @@ -739,6 +755,10 @@ private void writeObject(java.io.ObjectOutputStream s) /** * Reconstitutes this queue from a stream (that is, deserializes it). + * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws java.io.IOException if an I/O error occurs */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { @@ -746,55 +766,156 @@ private void readObject(java.io.ObjectInputStream s) // Read in elements until trailing null sentinel found Node h = null, t = null; - Object item; - while ((item = s.readObject()) != null) { + for (Object item; (item = s.readObject()) != null; ) { @SuppressWarnings("unchecked") - Node newNode = new Node((E) item); + Node newNode = newNode((E) item); if (h == null) h = t = newNode; else { - t.lazySetNext(newNode); + lazySetNext(t, newNode); t = newNode; } } if (h == null) - h = t = new Node(null); + h = t = newNode(null); head = h; tail = t; } + /** A customized variant of Spliterators.IteratorSpliterator */ + static final class CLQSpliterator implements Spliterator { + static final int MAX_BATCH = 1 << 25; // max batch array size; + final ConcurrentLinkedQueue queue; + Node current; // current node; null until initialized + int batch; // batch size for splits + boolean exhausted; // true when no more nodes + CLQSpliterator(ConcurrentLinkedQueue queue) { + this.queue = queue; + } + + public Spliterator trySplit() { + Node p; + final ConcurrentLinkedQueue q = this.queue; + int b = batch; + int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1; + if (!exhausted && + ((p = current) != null || (p = q.first()) != null) && + p.next != null) { + Object[] a = new Object[n]; + int i = 0; + do { + if ((a[i] = p.item) != null) + ++i; + if (p == (p = p.next)) + p = q.first(); + } while (p != null && i < n); + if ((current = p) == null) + exhausted = true; + if (i > 0) { + batch = i; + return Spliterators.spliterator + (a, 0, i, (Spliterator.ORDERED | + Spliterator.NONNULL | + Spliterator.CONCURRENT)); + } + } + return null; + } + + public void forEachRemaining(Consumer action) { + Node p; + if (action == null) throw new NullPointerException(); + final ConcurrentLinkedQueue q = this.queue; + if (!exhausted && + ((p = current) != null || (p = q.first()) != null)) { + exhausted = true; + do { + E e = p.item; + if (p == (p = p.next)) + p = q.first(); + if (e != null) + action.accept(e); + } while (p != null); + } + } + + public boolean tryAdvance(Consumer action) { + Node p; + if (action == null) throw new NullPointerException(); + final ConcurrentLinkedQueue q = this.queue; + if (!exhausted && + ((p = current) != null || (p = q.first()) != null)) { + E e; + do { + e = p.item; + if (p == (p = p.next)) + p = q.first(); + } while (e == null && p != null); + if ((current = p) == null) + exhausted = true; + if (e != null) { + action.accept(e); + return true; + } + } + return false; + } + + public long estimateSize() { return Long.MAX_VALUE; } + + public int characteristics() { + return Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT; + } + } + /** - * Throws NullPointerException if argument is null. + * Returns a {@link Spliterator} over the elements in this queue. + * + *

The returned spliterator is + * weakly consistent. + * + *

The {@code Spliterator} reports {@link Spliterator#CONCURRENT}, + * {@link Spliterator#ORDERED}, and {@link Spliterator#NONNULL}. + * + * @implNote + * The {@code Spliterator} implements {@code trySplit} to permit limited + * parallelism. * - * @param v the element + * @return a {@code Spliterator} over the elements in this queue + * @since 1.8 */ - private static void checkNotNull(Object v) { - if (v == null) - throw new NullPointerException(); + @Override + public Spliterator spliterator() { + return new CLQSpliterator(this); } private boolean casTail(Node cmp, Node val) { - return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val); + return U.compareAndSwapObject(this, TAIL, cmp, val); } private boolean casHead(Node cmp, Node val) { - return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val); + return U.compareAndSwapObject(this, HEAD, cmp, val); } // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long headOffset; - private static final long tailOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long HEAD; + private static final long TAIL; + private static final long ITEM; + private static final long NEXT; static { try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = ConcurrentLinkedQueue.class; - headOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("head")); - tailOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("tail")); - } catch (Exception e) { + HEAD = U.objectFieldOffset + (ConcurrentLinkedQueue.class.getDeclaredField("head")); + TAIL = U.objectFieldOffset + (ConcurrentLinkedQueue.class.getDeclaredField("tail")); + ITEM = U.objectFieldOffset + (Node.class.getDeclaredField("item")); + NEXT = U.objectFieldOffset + (Node.class.getDeclaredField("next")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentMap.java b/luni/src/main/java/java/util/concurrent/ConcurrentMap.java index 1391f0437..ae4d2214b 100644 --- a/luni/src/main/java/java/util/concurrent/ConcurrentMap.java +++ b/luni/src/main/java/java/util/concurrent/ConcurrentMap.java @@ -7,14 +7,27 @@ package java.util.concurrent; import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; // BEGIN android-note // removed link to collections framework docs +// fixed framework docs link to "Collection#optional" // END android-note /** - * A {@link java.util.Map} providing additional atomic - * {@code putIfAbsent}, {@code remove}, and {@code replace} methods. + * A {@link java.util.Map} providing thread safety and atomicity + * guarantees. + * + *

To maintain the specified guarantees, default implementations of + * methods including {@link #putIfAbsent} inherited from {@link Map} + * must be overridden by implementations of this interface. Similarly, + * implementations of the collections returned by methods {@link + * #keySet}, {@link #values}, and {@link #entrySet} must override + * methods such as {@code removeIf} when necessary to + * preserve atomicity guarantees. * *

Memory consistency effects: As with other concurrent * collections, actions in a thread prior to placing an object into a @@ -29,11 +42,65 @@ * @param the type of mapped values */ public interface ConcurrentMap extends Map { + + /** + * {@inheritDoc} + * + * @implNote This implementation assumes that the ConcurrentMap cannot + * contain null values and {@code get()} returning null unambiguously means + * the key is absent. Implementations which support null values + * must override this default implementation. + * + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @since 1.8 + */ + @Override + default V getOrDefault(Object key, V defaultValue) { + V v; + return ((v = get(key)) != null) ? v : defaultValue; + } + + /** + * {@inheritDoc} + * + * @implSpec The default implementation is equivalent to, for this + * {@code map}: + *

 {@code
+     * for (Map.Entry entry : map.entrySet()) {
+     *   action.accept(entry.getKey(), entry.getValue());
+     * }}
+ * + * @implNote The default implementation assumes that + * {@code IllegalStateException} thrown by {@code getKey()} or + * {@code getValue()} indicates that the entry has been removed and cannot + * be processed. Operation continues for subsequent entries. + * + * @throws NullPointerException {@inheritDoc} + * @since 1.8 + */ + @Override + default void forEach(BiConsumer action) { + Objects.requireNonNull(action); + for (Map.Entry entry : entrySet()) { + K k; + V v; + try { + k = entry.getKey(); + v = entry.getValue(); + } catch (IllegalStateException ise) { + // this usually means the entry is no longer in the map. + continue; + } + action.accept(k, v); + } + } + /** * If the specified key is not already associated - * with a value, associate it with the given value. - * This is equivalent to - *
 {@code
+     * with a value, associates it with the given value.
+     * This is equivalent to, for this {@code map}:
+     * 
 {@code
      * if (!map.containsKey(key))
      *   return map.put(key, value);
      * else
@@ -41,6 +108,9 @@ public interface ConcurrentMap extends Map {
      *
      * except that the action is performed atomically.
      *
+     * @implNote This implementation intentionally re-abstracts the
+     * inappropriate default provided in {@code Map}.
+     *
      * @param key key with which the specified value is to be associated
      * @param value value to be associated with the specified key
      * @return the previous value associated with the specified key, or
@@ -61,16 +131,21 @@ public interface ConcurrentMap extends Map {
 
     /**
      * Removes the entry for a key only if currently mapped to a given value.
-     * This is equivalent to
-     *  
 {@code
-     * if (map.containsKey(key) && map.get(key).equals(value)) {
+     * This is equivalent to, for this {@code map}:
+     * 
 {@code
+     * if (map.containsKey(key)
+     *     && Objects.equals(map.get(key), value)) {
      *   map.remove(key);
      *   return true;
-     * } else
-     *   return false;}
+ * } else { + * return false; + * }}
* * except that the action is performed atomically. * + * @implNote This implementation intentionally re-abstracts the + * inappropriate default provided in {@code Map}. + * * @param key key with which the specified value is associated * @param value value expected to be associated with the specified key * @return {@code true} if the value was removed @@ -78,25 +153,30 @@ public interface ConcurrentMap extends Map { * is not supported by this map * @throws ClassCastException if the key or value is of an inappropriate * type for this map - * (optional) + * (optional) * @throws NullPointerException if the specified key or value is null, * and this map does not permit null keys or values - * (optional) + * (optional) */ boolean remove(Object key, Object value); /** * Replaces the entry for a key only if currently mapped to a given value. - * This is equivalent to - *
 {@code
-     * if (map.containsKey(key) && map.get(key).equals(oldValue)) {
+     * This is equivalent to, for this {@code map}:
+     * 
 {@code
+     * if (map.containsKey(key)
+     *     && Objects.equals(map.get(key), oldValue)) {
      *   map.put(key, newValue);
      *   return true;
-     * } else
-     *   return false;}
+ * } else { + * return false; + * }}
* * except that the action is performed atomically. * + * @implNote This implementation intentionally re-abstracts the + * inappropriate default provided in {@code Map}. + * * @param key key with which the specified value is associated * @param oldValue value expected to be associated with the specified key * @param newValue value to be associated with the specified key @@ -114,15 +194,18 @@ public interface ConcurrentMap extends Map { /** * Replaces the entry for a key only if currently mapped to some value. - * This is equivalent to - *
 {@code
-     * if (map.containsKey(key)) {
+     * This is equivalent to, for this {@code map}:
+     * 
 {@code
+     * if (map.containsKey(key))
      *   return map.put(key, value);
-     * } else
+     * else
      *   return null;}
* * except that the action is performed atomically. * + * @implNote This implementation intentionally re-abstracts the + * inappropriate default provided in {@code Map}. + * * @param key key with which the specified value is associated * @param value value to be associated with the specified key * @return the previous value associated with the specified key, or @@ -140,4 +223,251 @@ public interface ConcurrentMap extends Map { * or value prevents it from being stored in this map */ V replace(K key, V value); + + /** + * {@inheritDoc} + * + * @implSpec + *

The default implementation is equivalent to, for this {@code map}: + *

 {@code
+     * for (Map.Entry entry : map.entrySet()) {
+     *   K k;
+     *   V v;
+     *   do {
+     *     k = entry.getKey();
+     *     v = entry.getValue();
+     *   } while (!map.replace(k, v, function.apply(k, v)));
+     * }}
+ * + * The default implementation may retry these steps when multiple + * threads attempt updates including potentially calling the function + * repeatedly for a given key. + * + *

This implementation assumes that the ConcurrentMap cannot contain null + * values and {@code get()} returning null unambiguously means the key is + * absent. Implementations which support null values must + * override this default implementation. + * + * @throws UnsupportedOperationException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws ClassCastException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + * @since 1.8 + */ + @Override + default void replaceAll(BiFunction function) { + Objects.requireNonNull(function); + forEach((k,v) -> { + while (!replace(k, v, function.apply(k, v))) { + // v changed or k is gone + if ( (v = get(k)) == null) { + // k is no longer in the map. + break; + } + } + }); + } + + /** + * {@inheritDoc} + * + * @implSpec + * The default implementation is equivalent to the following steps for this + * {@code map}: + * + *

 {@code
+     * V oldValue, newValue;
+     * return ((oldValue = map.get(key)) == null
+     *         && (newValue = mappingFunction.apply(key)) != null
+     *         && (oldValue = map.putIfAbsent(key, newValue)) == null)
+     *   ? newValue
+     *   : oldValue;}
+ * + *

This implementation assumes that the ConcurrentMap cannot contain null + * values and {@code get()} returning null unambiguously means the key is + * absent. Implementations which support null values must + * override this default implementation. + * + * @throws UnsupportedOperationException {@inheritDoc} + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + * @since 1.8 + */ + @Override + default V computeIfAbsent(K key, + Function mappingFunction) { + Objects.requireNonNull(mappingFunction); + V oldValue, newValue; + return ((oldValue = get(key)) == null + && (newValue = mappingFunction.apply(key)) != null + && (oldValue = putIfAbsent(key, newValue)) == null) + ? newValue + : oldValue; + } + + /** + * {@inheritDoc} + * + * @implSpec + * The default implementation is equivalent to performing the following + * steps for this {@code map}: + * + *

 {@code
+     * for (V oldValue; (oldValue = map.get(key)) != null; ) {
+     *   V newValue = remappingFunction.apply(key, oldValue);
+     *   if ((newValue == null)
+     *       ? map.remove(key, oldValue)
+     *       : map.replace(key, oldValue, newValue))
+     *     return newValue;
+     * }
+     * return null;}
+ * When multiple threads attempt updates, map operations and the + * remapping function may be called multiple times. + * + *

This implementation assumes that the ConcurrentMap cannot contain null + * values and {@code get()} returning null unambiguously means the key is + * absent. Implementations which support null values must + * override this default implementation. + * + * @throws UnsupportedOperationException {@inheritDoc} + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + * @since 1.8 + */ + @Override + default V computeIfPresent(K key, + BiFunction remappingFunction) { + Objects.requireNonNull(remappingFunction); + for (V oldValue; (oldValue = get(key)) != null; ) { + V newValue = remappingFunction.apply(key, oldValue); + if ((newValue == null) + ? remove(key, oldValue) + : replace(key, oldValue, newValue)) + return newValue; + } + return null; + } + + /** + * {@inheritDoc} + * + * @implSpec + * The default implementation is equivalent to performing the following + * steps for this {@code map}: + * + *

 {@code
+     * for (;;) {
+     *   V oldValue = map.get(key);
+     *   V newValue = remappingFunction.apply(key, oldValue);
+     *   if (newValue != null) {
+     *     if ((oldValue != null)
+     *       ? map.replace(key, oldValue, newValue)
+     *       : map.putIfAbsent(key, newValue) == null)
+     *       return newValue;
+     *   } else if (oldValue == null || map.remove(key, oldValue)) {
+     *     return null;
+     *   }
+     * }}
+ * When multiple threads attempt updates, map operations and the + * remapping function may be called multiple times. + * + *

This implementation assumes that the ConcurrentMap cannot contain null + * values and {@code get()} returning null unambiguously means the key is + * absent. Implementations which support null values must + * override this default implementation. + * + * @throws UnsupportedOperationException {@inheritDoc} + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + * @since 1.8 + */ + @Override + default V compute(K key, + BiFunction remappingFunction) { + retry: for (;;) { + V oldValue = get(key); + // if putIfAbsent fails, opportunistically use its return value + haveOldValue: for (;;) { + V newValue = remappingFunction.apply(key, oldValue); + if (newValue != null) { + if (oldValue != null) { + if (replace(key, oldValue, newValue)) + return newValue; + } + else if ((oldValue = putIfAbsent(key, newValue)) == null) + return newValue; + else continue haveOldValue; + } else if (oldValue == null || remove(key, oldValue)) { + return null; + } + continue retry; + } + } + } + + /** + * {@inheritDoc} + * + * @implSpec + * The default implementation is equivalent to performing the following + * steps for this {@code map}: + * + *

 {@code
+     * for (;;) {
+     *   V oldValue = map.get(key);
+     *   if (oldValue != null) {
+     *     V newValue = remappingFunction.apply(oldValue, value);
+     *     if (newValue != null) {
+     *       if (map.replace(key, oldValue, newValue))
+     *         return newValue;
+     *     } else if (map.remove(key, oldValue)) {
+     *       return null;
+     *     }
+     *   } else if (map.putIfAbsent(key, value) == null) {
+     *     return value;
+     *   }
+     * }}
+ * When multiple threads attempt updates, map operations and the + * remapping function may be called multiple times. + * + *

This implementation assumes that the ConcurrentMap cannot contain null + * values and {@code get()} returning null unambiguously means the key is + * absent. Implementations which support null values must + * override this default implementation. + * + * @throws UnsupportedOperationException {@inheritDoc} + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + * @since 1.8 + */ + @Override + default V merge(K key, V value, + BiFunction remappingFunction) { + Objects.requireNonNull(remappingFunction); + Objects.requireNonNull(value); + retry: for (;;) { + V oldValue = get(key); + // if putIfAbsent fails, opportunistically use its return value + haveOldValue: for (;;) { + if (oldValue != null) { + V newValue = remappingFunction.apply(oldValue, value); + if (newValue != null) { + if (replace(key, oldValue, newValue)) + return newValue; + } else if (remove(key, oldValue)) { + return null; + } + continue retry; + } else { + if ((oldValue = putIfAbsent(key, value)) == null) + return value; + continue haveOldValue; + } + } + } + } } diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentNavigableMap.java b/luni/src/main/java/java/util/concurrent/ConcurrentNavigableMap.java index 17890ff48..0d795b43a 100644 --- a/luni/src/main/java/java/util/concurrent/ConcurrentNavigableMap.java +++ b/luni/src/main/java/java/util/concurrent/ConcurrentNavigableMap.java @@ -6,7 +6,8 @@ package java.util.concurrent; -import java.util.*; +import java.util.NavigableMap; +import java.util.NavigableSet; // BEGIN android-note // removed link to collections framework docs @@ -73,7 +74,7 @@ ConcurrentNavigableMap subMap(K fromKey, boolean fromInclusive, * reflected in the descending map, and vice-versa. * *

The returned map has an ordering equivalent to - * {@link Collections#reverseOrder(Comparator) Collections.reverseOrder}{@code (comparator())}. + * {@link java.util.Collections#reverseOrder(Comparator) Collections.reverseOrder}{@code (comparator())}. * The expression {@code m.descendingMap().descendingMap()} returns a * view of {@code m} essentially equivalent to {@code m}. * @@ -92,15 +93,12 @@ ConcurrentNavigableMap subMap(K fromKey, boolean fromInclusive, * operations. It does not support the {@code add} or {@code addAll} * operations. * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + *

The view's iterators and spliterators are + * weakly consistent. * * @return a navigable set view of the keys in this map */ - public NavigableSet navigableKeySet(); + NavigableSet navigableKeySet(); /** * Returns a {@link NavigableSet} view of the keys contained in this map. @@ -113,11 +111,8 @@ ConcurrentNavigableMap subMap(K fromKey, boolean fromInclusive, * operations. It does not support the {@code add} or {@code addAll} * operations. * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + *

The view's iterators and spliterators are + * weakly consistent. * *

This method is equivalent to method {@code navigableKeySet}. * @@ -136,13 +131,10 @@ ConcurrentNavigableMap subMap(K fromKey, boolean fromInclusive, * operations. It does not support the {@code add} or {@code addAll} * operations. * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + *

The view's iterators and spliterators are + * weakly consistent. * * @return a reverse order navigable set view of the keys in this map */ - public NavigableSet descendingKeySet(); + NavigableSet descendingKeySet(); } diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentSkipListMap.java b/luni/src/main/java/java/util/concurrent/ConcurrentSkipListMap.java index 0e8b64a89..359d4f109 100644 --- a/luni/src/main/java/java/util/concurrent/ConcurrentSkipListMap.java +++ b/luni/src/main/java/java/util/concurrent/ConcurrentSkipListMap.java @@ -6,7 +6,28 @@ package java.util.concurrent; -import java.util.*; +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedMap; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; // BEGIN android-note // removed link to collections framework docs @@ -24,12 +45,13 @@ * {@code containsKey}, {@code get}, {@code put} and * {@code remove} operations and their variants. Insertion, removal, * update, and access operations safely execute concurrently by - * multiple threads. Iterators are weakly consistent, returning - * elements reflecting the state of the map at some point at or since - * the creation of the iterator. They do not throw {@link - * ConcurrentModificationException}, and may proceed concurrently with - * other operations. Ascending key ordered views and their iterators - * are faster than descending ones. + * multiple threads. + * + *

Iterators and spliterators are + * weakly consistent. + * + *

Ascending key ordered views and their iterators are faster than + * descending ones. * *

All {@code Map.Entry} pairs returned by methods in this class * and its views represent snapshots of mappings at the time they were @@ -61,11 +83,8 @@ * @param the type of mapped values * @since 1.6 */ -@SuppressWarnings("unchecked") public class ConcurrentSkipListMap extends AbstractMap - implements ConcurrentNavigableMap, - Cloneable, - java.io.Serializable { + implements ConcurrentNavigableMap, Cloneable, Serializable { /* * This class implements a tree-like two-dimensionally linked skip * list in which the index levels are represented in separate @@ -229,7 +248,7 @@ public class ConcurrentSkipListMap extends AbstractMap * * Indexing uses skip list parameters that maintain good search * performance while using sparser-than-usual indices: The - * hardwired parameters k=1, p=0.5 (see method randomLevel) mean + * hardwired parameters k=1, p=0.5 (see method doPut) mean * that about one-quarter of the nodes have indices. Of those that * do, half have one level, a quarter have two, and so on (see * Pugh's Skip List Cookbook, sec 3.4). The expected total space @@ -267,6 +286,20 @@ public class ConcurrentSkipListMap extends AbstractMap * there is a fair amount of near-duplication of code to handle * variants. * + * To produce random values without interference across threads, + * we use within-JDK thread local random support (via the + * "secondary seed", to avoid interference with user-level + * ThreadLocalRandom.) + * + * A previous version of this class wrapped non-comparable keys + * with their comparators to emulate Comparables when using + * comparators vs Comparables. However, JVMs now appear to better + * handle infusing comparator-vs-comparable choice into search + * loops. Static method cpr(comparator, x, y) is used for all + * comparisons, which works well as long as the comparator + * argument is set up outside of loops (thus sometimes passed as + * an argument to internal methods) to avoid field re-reads. + * * For explanation of algorithms sharing at least a couple of * features with this one, see Mikhail Fomitchev's thesis * (http://www.cs.yorku.ca/~mikhail/), Keir Fraser's thesis @@ -294,18 +327,10 @@ public class ConcurrentSkipListMap extends AbstractMap private static final long serialVersionUID = -8627078645895051609L; -// BEGIN android-removed -// /** -// * Generates the initial random seed for the cheaper per-instance -// * random number generators used in randomLevel. -// */ -// private static final Random seedGenerator = new Random(); -// END android-removed - /** - * Special value used to identify base-level header + * Special value used to identify base-level header. */ - private static final Object BASE_HEADER = new Object(); + static final Object BASE_HEADER = new Object(); /** * The topmost head index of the skiplist. @@ -313,24 +338,19 @@ public class ConcurrentSkipListMap extends AbstractMap private transient volatile HeadIndex head; /** - * The comparator used to maintain order in this map, or null - * if using natural ordering. + * The comparator used to maintain order in this map, or null if + * using natural ordering. (Non-private to simplify access in + * nested classes.) * @serial */ - private final Comparator comparator; - - /** - * Seed for simple random number generator. Not volatile since it - * doesn't matter too much if different threads don't see updates. - */ - private transient int randomSeed; + final Comparator comparator; /** Lazily initialized key set */ - private transient KeySet keySet; + private transient KeySet keySet; /** Lazily initialized entry set */ private transient EntrySet entrySet; /** Lazily initialized values collection */ - private transient Values values; + private transient Values values; /** Lazily initialized descending key set */ private transient ConcurrentNavigableMap descendingMap; @@ -339,27 +359,20 @@ public class ConcurrentSkipListMap extends AbstractMap * clear, readObject. and ConcurrentSkipListSet.clone. * (Note that comparator must be separately initialized.) */ - final void initialize() { + private void initialize() { keySet = null; entrySet = null; values = null; descendingMap = null; - // BEGIN android-changed - // - // Most processes are forked from the zygote, so they'll end up - // with the same random seed unless we take additional post fork - // measures. - randomSeed = Math.randomIntInternal() | 0x0100; // ensure nonzero - // END android-changed head = new HeadIndex(new Node(null, BASE_HEADER, null), null, null, 1); } /** - * compareAndSet head node + * compareAndSet head node. */ private boolean casHead(HeadIndex cmp, HeadIndex val) { - return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val); + return U.compareAndSwapObject(this, HEAD, cmp, val); } /* ---------------- Nodes -------------- */ @@ -399,17 +412,17 @@ static final class Node { } /** - * compareAndSet value field + * compareAndSet value field. */ boolean casValue(Object cmp, Object val) { - return UNSAFE.compareAndSwapObject(this, valueOffset, cmp, val); + return U.compareAndSwapObject(this, VALUE, cmp, val); } /** - * compareAndSet next field + * compareAndSet next field. */ boolean casNext(Node cmp, Node val) { - return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); + return U.compareAndSwapObject(this, NEXT, cmp, val); } /** @@ -457,7 +470,7 @@ void helpDelete(Node b, Node f) { */ if (f == next && this == b.next) { if (f == null || f.value != f) // not already marked - appendMarker(f); + casNext(f, new Node(f)); else b.casNext(this, f.next); } @@ -473,7 +486,8 @@ V getValidValue() { Object v = value; if (v == this || v == BASE_HEADER) return null; - return (V)v; + @SuppressWarnings("unchecked") V vv = (V)v; + return vv; } /** @@ -482,27 +496,26 @@ V getValidValue() { * @return new entry or null */ AbstractMap.SimpleImmutableEntry createSnapshot() { - V v = getValidValue(); - if (v == null) + Object v = value; + if (v == null || v == this || v == BASE_HEADER) return null; - return new AbstractMap.SimpleImmutableEntry(key, v); + @SuppressWarnings("unchecked") V vv = (V)v; + return new AbstractMap.SimpleImmutableEntry(key, vv); } - // UNSAFE mechanics + // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long valueOffset; - private static final long nextOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long VALUE; + private static final long NEXT; static { try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = Node.class; - valueOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("value")); - nextOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("next")); - } catch (Exception e) { + VALUE = U.objectFieldOffset + (Node.class.getDeclaredField("value")); + NEXT = U.objectFieldOffset + (Node.class.getDeclaredField("next")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } @@ -532,10 +545,10 @@ static class Index { } /** - * compareAndSet right field + * compareAndSet right field. */ final boolean casRight(Index cmp, Index val) { - return UNSAFE.compareAndSwapObject(this, rightOffset, cmp, val); + return U.compareAndSwapObject(this, RIGHT, cmp, val); } /** @@ -568,19 +581,17 @@ final boolean link(Index succ, Index newSucc) { * @return true if successful */ final boolean unlink(Index succ) { - return !indexesDeletedNode() && casRight(succ, succ.right); + return node.value != null && casRight(succ, succ.right); } // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long rightOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long RIGHT; static { try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = Index.class; - rightOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("right")); - } catch (Exception e) { + RIGHT = U.objectFieldOffset + (Index.class.getDeclaredField("right")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } @@ -602,80 +613,12 @@ static final class HeadIndex extends Index { /* ---------------- Comparison utilities -------------- */ /** - * Represents a key with a comparator as a Comparable. - * - * Because most sorted collections seem to use natural ordering on - * Comparables (Strings, Integers, etc), most internal methods are - * geared to use them. This is generally faster than checking - * per-comparison whether to use comparator or comparable because - * it doesn't require a (Comparable) cast for each comparison. - * (Optimizers can only sometimes remove such redundant checks - * themselves.) When Comparators are used, - * ComparableUsingComparators are created so that they act in the - * same way as natural orderings. This penalizes use of - * Comparators vs Comparables, which seems like the right - * tradeoff. + * Compares using comparator or natural ordering if null. + * Called only by methods that have performed required type checks. */ - static final class ComparableUsingComparator implements Comparable { - final K actualKey; - final Comparator cmp; - ComparableUsingComparator(K key, Comparator cmp) { - this.actualKey = key; - this.cmp = cmp; - } - public int compareTo(K k2) { - return cmp.compare(actualKey, k2); - } - } - - /** - * If using comparator, return a ComparableUsingComparator, else - * cast key as Comparable, which may cause ClassCastException, - * which is propagated back to caller. - */ - private Comparable comparable(Object key) - throws ClassCastException { - if (key == null) - throw new NullPointerException(); - if (comparator != null) - return new ComparableUsingComparator((K)key, comparator); - else - return (Comparable)key; - } - - /** - * Compares using comparator or natural ordering. Used when the - * ComparableUsingComparator approach doesn't apply. - */ - int compare(K k1, K k2) throws ClassCastException { - Comparator cmp = comparator; - if (cmp != null) - return cmp.compare(k1, k2); - else - return ((Comparable)k1).compareTo(k2); - } - - /** - * Returns true if given key greater than or equal to least and - * strictly less than fence, bypassing either test if least or - * fence are null. Needed mainly in submap operations. - */ - boolean inHalfOpenRange(K key, K least, K fence) { - if (key == null) - throw new NullPointerException(); - return ((least == null || compare(key, least) >= 0) && - (fence == null || compare(key, fence) < 0)); - } - - /** - * Returns true if given key greater than or equal to least and less - * or equal to fence. Needed mainly in submap operations. - */ - boolean inOpenRange(K key, K least, K fence) { - if (key == null) - throw new NullPointerException(); - return ((least == null || compare(key, least) >= 0) && - (fence == null || compare(key, fence) <= 0)); + @SuppressWarnings({"unchecked", "rawtypes"}) + static final int cpr(Comparator c, Object x, Object y) { + return (c != null) ? c.compare(x, y) : ((Comparable)x).compareTo(y); } /* ---------------- Traversal -------------- */ @@ -688,13 +631,11 @@ boolean inOpenRange(K key, K least, K fence) { * @param key the key * @return a predecessor of key */ - private Node findPredecessor(Comparable key) { + private Node findPredecessor(Object key, Comparator cmp) { if (key == null) throw new NullPointerException(); // don't postpone errors for (;;) { - Index q = head; - Index r = q.right; - for (;;) { + for (Index q = head, r = q.right, d;;) { if (r != null) { Node n = r.node; K k = n.key; @@ -704,18 +645,16 @@ private Node findPredecessor(Comparable key) { r = q.right; // reread r continue; } - if (key.compareTo(k) > 0) { + if (cpr(cmp, key, k) > 0) { q = r; r = r.right; continue; } } - Index d = q.down; - if (d != null) { - q = d; - r = d.right; - } else + if ((d = q.down) == null) return q.node; + q = d; + r = d.right; } } } @@ -756,62 +695,79 @@ private Node findPredecessor(Comparable key) { * * The traversal loops in doPut, doRemove, and findNear all * include the same three kinds of checks. And specialized - * versions appear in findFirst, and findLast and their - * variants. They can't easily share code because each uses the - * reads of fields held in locals occurring in the orders they - * were performed. + * versions appear in findFirst, and findLast and their variants. + * They can't easily share code because each uses the reads of + * fields held in locals occurring in the orders they were + * performed. * * @param key the key * @return node holding key, or null if no such */ - private Node findNode(Comparable key) { - for (;;) { - Node b = findPredecessor(key); - Node n = b.next; - for (;;) { + private Node findNode(Object key) { + if (key == null) + throw new NullPointerException(); // don't postpone errors + Comparator cmp = comparator; + outer: for (;;) { + for (Node b = findPredecessor(key, cmp), n = b.next;;) { + Object v; int c; if (n == null) - return null; + break outer; Node f = n.next; if (n != b.next) // inconsistent read break; - Object v = n.value; - if (v == null) { // n is deleted + if ((v = n.value) == null) { // n is deleted n.helpDelete(b, f); break; } - if (v == n || b.value == null) // b is deleted + if (b.value == null || v == n) // b is deleted break; - int c = key.compareTo(n.key); - if (c == 0) + if ((c = cpr(cmp, key, n.key)) == 0) return n; if (c < 0) - return null; + break outer; b = n; n = f; } } + return null; } /** - * Gets value for key using findNode. - * @param okey the key + * Gets value for key. Almost the same as findNode, but returns + * the found value (to avoid retries during re-reads) + * + * @param key the key * @return the value, or null if absent */ - private V doGet(Object okey) { - Comparable key = comparable(okey); - /* - * Loop needed here and elsewhere in case value field goes - * null just as it is about to be returned, in which case we - * lost a race with a deletion, so must retry. - */ - for (;;) { - Node n = findNode(key); - if (n == null) - return null; - Object v = n.value; - if (v != null) - return (V)v; + private V doGet(Object key) { + if (key == null) + throw new NullPointerException(); + Comparator cmp = comparator; + outer: for (;;) { + for (Node b = findPredecessor(key, cmp), n = b.next;;) { + Object v; int c; + if (n == null) + break outer; + Node f = n.next; + if (n != b.next) // inconsistent read + break; + if ((v = n.value) == null) { // n is deleted + n.helpDelete(b, f); + break; + } + if (b.value == null || v == n) // b is deleted + break; + if ((c = cpr(cmp, key, n.key)) == 0) { + @SuppressWarnings("unchecked") V vv = (V)v; + return vv; + } + if (c < 0) + break outer; + b = n; + n = f; + } } + return null; } /* ---------------- Insertion -------------- */ @@ -819,187 +775,126 @@ private V doGet(Object okey) { /** * Main insertion method. Adds element if not present, or * replaces value if present and onlyIfAbsent is false. - * @param kkey the key + * @param key the key * @param value the value that must be associated with key * @param onlyIfAbsent if should not insert if already present * @return the old value, or null if newly inserted */ - private V doPut(K kkey, V value, boolean onlyIfAbsent) { - Comparable key = comparable(kkey); - for (;;) { - Node b = findPredecessor(key); - Node n = b.next; - for (;;) { + private V doPut(K key, V value, boolean onlyIfAbsent) { + Node z; // added node + if (key == null) + throw new NullPointerException(); + Comparator cmp = comparator; + outer: for (;;) { + for (Node b = findPredecessor(key, cmp), n = b.next;;) { if (n != null) { + Object v; int c; Node f = n.next; if (n != b.next) // inconsistent read break; - Object v = n.value; - if (v == null) { // n is deleted + if ((v = n.value) == null) { // n is deleted n.helpDelete(b, f); break; } - if (v == n || b.value == null) // b is deleted + if (b.value == null || v == n) // b is deleted break; - int c = key.compareTo(n.key); - if (c > 0) { + if ((c = cpr(cmp, key, n.key)) > 0) { b = n; n = f; continue; } if (c == 0) { - if (onlyIfAbsent || n.casValue(v, value)) - return (V)v; - else - break; // restart if lost race to replace value + if (onlyIfAbsent || n.casValue(v, value)) { + @SuppressWarnings("unchecked") V vv = (V)v; + return vv; + } + break; // restart if lost race to replace value } // else c < 0; fall through } - Node z = new Node(kkey, value, n); + z = new Node(key, value, n); if (!b.casNext(n, z)) break; // restart if lost race to append to b - int level = randomLevel(); - if (level > 0) - insertIndex(z, level); - return null; + break outer; } } - } - - /** - * Returns a random level for inserting a new node. - * Hardwired to k=1, p=0.5, max 31 (see above and - * Pugh's "Skip List Cookbook", sec 3.4). - * - * This uses the simplest of the generators described in George - * Marsaglia's "Xorshift RNGs" paper. This is not a high-quality - * generator but is acceptable here. - */ - private int randomLevel() { - int x = randomSeed; - x ^= x << 13; - x ^= x >>> 17; - randomSeed = x ^= x << 5; - if ((x & 0x80000001) != 0) // test highest and lowest bits - return 0; - int level = 1; - while (((x >>>= 1) & 1) != 0) ++level; - return level; - } - - /** - * Creates and adds index nodes for the given node. - * @param z the node - * @param level the level of the index - */ - private void insertIndex(Node z, int level) { - HeadIndex h = head; - int max = h.level; - if (level <= max) { + int rnd = ThreadLocalRandom.nextSecondarySeed(); + if ((rnd & 0x80000001) == 0) { // test highest and lowest bits + int level = 1, max; + while (((rnd >>>= 1) & 1) != 0) + ++level; Index idx = null; - for (int i = 1; i <= level; ++i) - idx = new Index(z, idx, null); - addIndex(idx, h, level); - - } else { // Add a new level - /* - * To reduce interference by other threads checking for - * empty levels in tryReduceLevel, new levels are added - * with initialized right pointers. Which in turn requires - * keeping levels in an array to access them while - * creating new head index nodes from the opposite - * direction. - */ - level = max + 1; - Index[] idxs = (Index[])new Index[level+1]; - Index idx = null; - for (int i = 1; i <= level; ++i) - idxs[i] = idx = new Index(z, idx, null); - - HeadIndex oldh; - int k; - for (;;) { - oldh = head; - int oldLevel = oldh.level; - if (level <= oldLevel) { // lost race to add level - k = level; - break; - } - HeadIndex newh = oldh; - Node oldbase = oldh.node; - for (int j = oldLevel+1; j <= level; ++j) - newh = new HeadIndex(oldbase, newh, idxs[j], j); - if (casHead(oldh, newh)) { - k = oldLevel; - break; - } + HeadIndex h = head; + if (level <= (max = h.level)) { + for (int i = 1; i <= level; ++i) + idx = new Index(z, idx, null); } - addIndex(idxs[k], oldh, k); - } - } - - /** - * Adds given index nodes from given level down to 1. - * @param idx the topmost index node being inserted - * @param h the value of head to use to insert. This must be - * snapshotted by callers to provide correct insertion level. - * @param indexLevel the level of the index - */ - private void addIndex(Index idx, HeadIndex h, int indexLevel) { - // Track next level to insert in case of retries - int insertionLevel = indexLevel; - Comparable key = comparable(idx.node.key); - if (key == null) throw new NullPointerException(); - - // Similar to findPredecessor, but adding index nodes along - // path to key. - for (;;) { - int j = h.level; - Index q = h; - Index r = q.right; - Index t = idx; - for (;;) { - if (r != null) { - Node n = r.node; - // compare before deletion check avoids needing recheck - int c = key.compareTo(n.key); - if (n.value == null) { - if (!q.unlink(r)) - break; - r = q.right; - continue; - } - if (c > 0) { - q = r; - r = r.right; - continue; + else { // try to grow by one level + level = max + 1; // hold in array and later pick the one to use + @SuppressWarnings("unchecked")Index[] idxs = + (Index[])new Index[level+1]; + for (int i = 1; i <= level; ++i) + idxs[i] = idx = new Index(z, idx, null); + for (;;) { + h = head; + int oldLevel = h.level; + if (level <= oldLevel) // lost race to add level + break; + HeadIndex newh = h; + Node oldbase = h.node; + for (int j = oldLevel+1; j <= level; ++j) + newh = new HeadIndex(oldbase, newh, idxs[j], j); + if (casHead(h, newh)) { + h = newh; + idx = idxs[level = oldLevel]; + break; } } - - if (j == insertionLevel) { - // Don't insert index if node already deleted - if (t.indexesDeletedNode()) { - findNode(key); // cleans up - return; + } + // find insertion points and splice in + splice: for (int insertionLevel = level;;) { + int j = h.level; + for (Index q = h, r = q.right, t = idx;;) { + if (q == null || t == null) + break splice; + if (r != null) { + Node n = r.node; + // compare before deletion check avoids needing recheck + int c = cpr(cmp, key, n.key); + if (n.value == null) { + if (!q.unlink(r)) + break; + r = q.right; + continue; + } + if (c > 0) { + q = r; + r = r.right; + continue; + } } - if (!q.link(r, t)) - break; // restart - if (--insertionLevel == 0) { - // need final deletion check before return - if (t.indexesDeletedNode()) + + if (j == insertionLevel) { + if (!q.link(r, t)) + break; // restart + if (t.node.value == null) { findNode(key); - return; + break splice; + } + if (--insertionLevel == 0) + break splice; } - } - if (--j >= insertionLevel && j < indexLevel) - t = t.down; - q = q.down; - r = q.right; + if (--j >= insertionLevel && j < level) + t = t.down; + q = q.down; + r = q.right; + } } } + return null; } /* ---------------- Deletion -------------- */ @@ -1018,51 +913,52 @@ private void addIndex(Index idx, HeadIndex h, int indexLevel) { * search for it, and we'd like to ensure lack of garbage * retention, so must call to be sure. * - * @param okey the key + * @param key the key * @param value if non-null, the value that must be * associated with key * @return the node, or null if not found */ - final V doRemove(Object okey, Object value) { - Comparable key = comparable(okey); - for (;;) { - Node b = findPredecessor(key); - Node n = b.next; - for (;;) { + final V doRemove(Object key, Object value) { + if (key == null) + throw new NullPointerException(); + Comparator cmp = comparator; + outer: for (;;) { + for (Node b = findPredecessor(key, cmp), n = b.next;;) { + Object v; int c; if (n == null) - return null; + break outer; Node f = n.next; if (n != b.next) // inconsistent read break; - Object v = n.value; - if (v == null) { // n is deleted + if ((v = n.value) == null) { // n is deleted n.helpDelete(b, f); break; } - if (v == n || b.value == null) // b is deleted + if (b.value == null || v == n) // b is deleted break; - int c = key.compareTo(n.key); - if (c < 0) - return null; + if ((c = cpr(cmp, key, n.key)) < 0) + break outer; if (c > 0) { b = n; n = f; continue; } if (value != null && !value.equals(v)) - return null; + break outer; if (!n.casValue(v, null)) break; if (!n.appendMarker(f) || !b.casNext(n, f)) - findNode(key); // Retry via findNode + findNode(key); // retry via findNode else { - findPredecessor(key); // Clean index + findPredecessor(key, cmp); // clean index if (head.right == null) tryReduceLevel(); } - return (V)v; + @SuppressWarnings("unchecked") V vv = (V)v; + return vv; } } + return null; } /** @@ -1106,11 +1002,9 @@ private void tryReduceLevel() { * Specialized variant of findNode to get first valid node. * @return first node or null if empty */ - Node findFirst() { - for (;;) { - Node b = head.node; - Node n = b.next; - if (n == null) + final Node findFirst() { + for (Node b, n;;) { + if ((n = (b = head.node).next) == null) return null; if (n.value != null) return n; @@ -1122,11 +1016,9 @@ Node findFirst() { * Removes first entry; returns its snapshot. * @return null if empty, else snapshot of first entry */ - Map.Entry doRemoveFirstEntry() { - for (;;) { - Node b = head.node; - Node n = b.next; - if (n == null) + private Map.Entry doRemoveFirstEntry() { + for (Node b, n;;) { + if ((n = (b = head.node).next) == null) return null; Node f = n.next; if (n != b.next) @@ -1141,7 +1033,8 @@ Map.Entry doRemoveFirstEntry() { if (!n.appendMarker(f) || !b.casNext(n, f)) findFirst(); // retry clearIndexToFirst(); - return new AbstractMap.SimpleImmutableEntry(n.key, (V)v); + @SuppressWarnings("unchecked") V vv = (V)v; + return new AbstractMap.SimpleImmutableEntry(n.key, vv); } } @@ -1150,8 +1043,7 @@ Map.Entry doRemoveFirstEntry() { */ private void clearIndexToFirst() { for (;;) { - Index q = head; - for (;;) { + for (Index q = head;;) { Index r = q.right; if (r != null && r.indexesDeletedNode() && !q.unlink(r)) break; @@ -1164,6 +1056,52 @@ private void clearIndexToFirst() { } } + /** + * Removes last entry; returns its snapshot. + * Specialized variant of doRemove. + * @return null if empty, else snapshot of last entry + */ + private Map.Entry doRemoveLastEntry() { + for (;;) { + Node b = findPredecessorOfLast(); + Node n = b.next; + if (n == null) { + if (b.isBaseHeader()) // empty + return null; + else + continue; // all b's successors are deleted; retry + } + for (;;) { + Node f = n.next; + if (n != b.next) // inconsistent read + break; + Object v = n.value; + if (v == null) { // n is deleted + n.helpDelete(b, f); + break; + } + if (b.value == null || v == n) // b is deleted + break; + if (f != null) { + b = n; + n = f; + continue; + } + if (!n.casValue(v, null)) + break; + K key = n.key; + if (!n.appendMarker(f) || !b.casNext(n, f)) + findNode(key); // retry via findNode + else { // clean index + findPredecessor(key, comparator); + if (head.right == null) + tryReduceLevel(); + } + @SuppressWarnings("unchecked") V vv = (V)v; + return new AbstractMap.SimpleImmutableEntry(key, vv); + } + } + } /* ---------------- Finding and removing last element -------------- */ @@ -1171,7 +1109,7 @@ private void clearIndexToFirst() { * Specialized version of find to get last valid node. * @return last node or null if empty */ - Node findLast() { + final Node findLast() { /* * findPredecessor can't be used to traverse index level * because this doesn't use comparisons. So traversals of @@ -1190,9 +1128,7 @@ Node findLast() { } else if ((d = q.down) != null) { q = d; } else { - Node b = q.node; - Node n = b.next; - for (;;) { + for (Node b = q.node, n = b.next;;) { if (n == null) return b.isBaseHeader() ? null : b; Node f = n.next; // inconsistent read @@ -1203,7 +1139,7 @@ Node findLast() { n.helpDelete(b, f); break; } - if (v == n || b.value == null) // b is deleted + if (b.value == null || v == n) // b is deleted break; b = n; n = f; @@ -1222,8 +1158,7 @@ Node findLast() { */ private Node findPredecessorOfLast() { for (;;) { - Index q = head; - for (;;) { + for (Index q = head;;) { Index d, r; if ((r = q.right) != null) { if (r.indexesDeletedNode()) { @@ -1244,53 +1179,6 @@ private Node findPredecessorOfLast() { } } - /** - * Removes last entry; returns its snapshot. - * Specialized variant of doRemove. - * @return null if empty, else snapshot of last entry - */ - Map.Entry doRemoveLastEntry() { - for (;;) { - Node b = findPredecessorOfLast(); - Node n = b.next; - if (n == null) { - if (b.isBaseHeader()) // empty - return null; - else - continue; // all b's successors are deleted; retry - } - for (;;) { - Node f = n.next; - if (n != b.next) // inconsistent read - break; - Object v = n.value; - if (v == null) { // n is deleted - n.helpDelete(b, f); - break; - } - if (v == n || b.value == null) // b is deleted - break; - if (f != null) { - b = n; - n = f; - continue; - } - if (!n.casValue(v, null)) - break; - K key = n.key; - Comparable ck = comparable(key); - if (!n.appendMarker(f) || !b.casNext(n, f)) - findNode(ck); // Retry via findNode - else { - findPredecessor(ck); // Clean index - if (head.right == null) - tryReduceLevel(); - } - return new AbstractMap.SimpleImmutableEntry(key, (V)v); - } - } - } - /* ---------------- Relational operations -------------- */ // Control values OR'ed as arguments to findNear @@ -1301,29 +1189,28 @@ Map.Entry doRemoveLastEntry() { /** * Utility for ceiling, floor, lower, higher methods. - * @param kkey the key + * @param key the key * @param rel the relation -- OR'ed combination of EQ, LT, GT * @return nearest node fitting relation, or null if no such */ - Node findNear(K kkey, int rel) { - Comparable key = comparable(kkey); + final Node findNear(K key, int rel, Comparator cmp) { + if (key == null) + throw new NullPointerException(); for (;;) { - Node b = findPredecessor(key); - Node n = b.next; - for (;;) { + for (Node b = findPredecessor(key, cmp), n = b.next;;) { + Object v; if (n == null) return ((rel & LT) == 0 || b.isBaseHeader()) ? null : b; Node f = n.next; if (n != b.next) // inconsistent read break; - Object v = n.value; - if (v == null) { // n is deleted + if ((v = n.value) == null) { // n is deleted n.helpDelete(b, f); break; } - if (v == n || b.value == null) // b is deleted + if (b.value == null || v == n) // b is deleted break; - int c = key.compareTo(n.key); + int c = cpr(cmp, key, n.key); if ((c == 0 && (rel & EQ) != 0) || (c < 0 && (rel & LT) == 0)) return n; @@ -1341,9 +1228,10 @@ Node findNear(K kkey, int rel) { * @param rel the relation -- OR'ed combination of EQ, LT, GT * @return Entry fitting relation, or null if no such */ - AbstractMap.SimpleImmutableEntry getNear(K key, int rel) { + final AbstractMap.SimpleImmutableEntry getNear(K key, int rel) { + Comparator cmp = comparator; for (;;) { - Node n = findNear(key, rel); + Node n = findNear(key, rel, cmp); if (n == null) return null; AbstractMap.SimpleImmutableEntry e = n.createSnapshot(); @@ -1352,7 +1240,6 @@ AbstractMap.SimpleImmutableEntry getNear(K key, int rel) { } } - /* ---------------- Constructors -------------- */ /** @@ -1442,7 +1329,7 @@ private void buildFromSorted(SortedMap map) { // Track the current rightmost node at each level. Uses an // ArrayList to avoid committing to initial or maximum level. - ArrayList> preds = new ArrayList>(); + ArrayList> preds = new ArrayList<>(); // initialize for (int i = 0; i <= h.level; ++i) @@ -1457,8 +1344,14 @@ private void buildFromSorted(SortedMap map) { map.entrySet().iterator(); while (it.hasNext()) { Map.Entry e = it.next(); - int j = randomLevel(); - if (j > h.level) j = h.level + 1; + int rnd = ThreadLocalRandom.current().nextInt(); + int j = 0; + if ((rnd & 0x80000001) == 0) { + do { + ++j; + } while (((rnd >>>= 1) & 1) != 0); + if (j > h.level) j = h.level + 1; + } K k = e.getKey(); V v = e.getValue(); if (k == null || v == null) @@ -1489,6 +1382,8 @@ private void buildFromSorted(SortedMap map) { /** * Saves this map to a stream (that is, serializes it). * + * @param s the stream + * @throws java.io.IOException if an I/O error occurs * @serialData The key (Object) and value (Object) for each * key-value mapping represented by the map, followed by * {@code null}. The key-value mappings are emitted in key-order @@ -1513,7 +1408,12 @@ private void writeObject(java.io.ObjectOutputStream s) /** * Reconstitutes this map from a stream (that is, deserializes it). + * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws java.io.IOException if an I/O error occurs */ + @SuppressWarnings("unchecked") private void readObject(final java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in the Comparator and any hidden stuff @@ -1526,12 +1426,12 @@ private void readObject(final java.io.ObjectInputStream s) * distinct because readObject calls can't be nicely adapted * as the kind of iterator needed by buildFromSorted. (They * can be, but doing so requires type cheats and/or creation - * of adaptor classes.) It is simpler to just adapt the code. + * of adapter classes.) It is simpler to just adapt the code. */ HeadIndex h = head; Node basepred = h.node; - ArrayList> preds = new ArrayList>(); + ArrayList> preds = new ArrayList<>(); for (int i = 0; i <= h.level; ++i) preds.add(null); Index q = h; @@ -1549,8 +1449,14 @@ private void readObject(final java.io.ObjectInputStream s) throw new NullPointerException(); K key = (K) k; V val = (V) v; - int j = randomLevel(); - if (j > h.level) j = h.level + 1; + int rnd = ThreadLocalRandom.current().nextInt(); + int j = 0; + if ((rnd & 0x80000001) == 0) { + do { + ++j; + } while (((rnd >>>= 1) & 1) != 0); + if (j > h.level) j = h.level + 1; + } Node z = new Node(key, val, null); basepred.next = z; basepred = z; @@ -1606,6 +1512,22 @@ public V get(Object key) { return doGet(key); } + /** + * Returns the value to which the specified key is mapped, + * or the given defaultValue if this map contains no mapping for the key. + * + * @param key the key + * @param defaultValue the value to return if this map contains + * no mapping for the given key + * @return the mapping for the key, if present; else the defaultValue + * @throws NullPointerException if the specified key is null + * @since 1.8 + */ + public V getOrDefault(Object key, V defaultValue) { + V v; + return (v = doGet(key)) == null ? defaultValue : v; + } + /** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old @@ -1702,6 +1624,140 @@ public void clear() { initialize(); } + /** + * If the specified key is not already associated with a value, + * attempts to compute its value using the given mapping function + * and enters it into this map unless {@code null}. The function + * is NOT guaranteed to be applied once atomically only + * if the value is not present. + * + * @param key key with which the specified value is to be associated + * @param mappingFunction the function to compute a value + * @return the current (existing or computed) value associated with + * the specified key, or null if the computed value is null + * @throws NullPointerException if the specified key is null + * or the mappingFunction is null + * @since 1.8 + */ + public V computeIfAbsent(K key, + Function mappingFunction) { + if (key == null || mappingFunction == null) + throw new NullPointerException(); + V v, p, r; + if ((v = doGet(key)) == null && + (r = mappingFunction.apply(key)) != null) + v = (p = doPut(key, r, true)) == null ? r : p; + return v; + } + + /** + * If the value for the specified key is present, attempts to + * compute a new mapping given the key and its current mapped + * value. The function is NOT guaranteed to be applied + * once atomically. + * + * @param key key with which a value may be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key is null + * or the remappingFunction is null + * @since 1.8 + */ + public V computeIfPresent(K key, + BiFunction remappingFunction) { + if (key == null || remappingFunction == null) + throw new NullPointerException(); + Node n; Object v; + while ((n = findNode(key)) != null) { + if ((v = n.value) != null) { + @SuppressWarnings("unchecked") V vv = (V) v; + V r = remappingFunction.apply(key, vv); + if (r != null) { + if (n.casValue(vv, r)) + return r; + } + else if (doRemove(key, vv) != null) + break; + } + } + return null; + } + + /** + * Attempts to compute a mapping for the specified key and its + * current mapped value (or {@code null} if there is no current + * mapping). The function is NOT guaranteed to be applied + * once atomically. + * + * @param key key with which the specified value is to be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key is null + * or the remappingFunction is null + * @since 1.8 + */ + public V compute(K key, + BiFunction remappingFunction) { + if (key == null || remappingFunction == null) + throw new NullPointerException(); + for (;;) { + Node n; Object v; V r; + if ((n = findNode(key)) == null) { + if ((r = remappingFunction.apply(key, null)) == null) + break; + if (doPut(key, r, true) == null) + return r; + } + else if ((v = n.value) != null) { + @SuppressWarnings("unchecked") V vv = (V) v; + if ((r = remappingFunction.apply(key, vv)) != null) { + if (n.casValue(vv, r)) + return r; + } + else if (doRemove(key, vv) != null) + break; + } + } + return null; + } + + /** + * If the specified key is not already associated with a value, + * associates it with the given value. Otherwise, replaces the + * value with the results of the given remapping function, or + * removes if {@code null}. The function is NOT + * guaranteed to be applied once atomically. + * + * @param key key with which the specified value is to be associated + * @param value the value to use if absent + * @param remappingFunction the function to recompute a value if present + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or value is null + * or the remappingFunction is null + * @since 1.8 + */ + public V merge(K key, V value, + BiFunction remappingFunction) { + if (key == null || value == null || remappingFunction == null) + throw new NullPointerException(); + for (;;) { + Node n; Object v; V r; + if ((n = findNode(key)) == null) { + if (doPut(key, value, true) == null) + return value; + } + else if ((v = n.value) != null) { + @SuppressWarnings("unchecked") V vv = (V) v; + if ((r = remappingFunction.apply(vv, value)) != null) { + if (n.casValue(vv, r)) + return r; + } + else if (doRemove(key, vv) != null) + return null; + } + } + } + /* ---------------- View methods -------------- */ /* @@ -1715,8 +1771,18 @@ public void clear() { /** * Returns a {@link NavigableSet} view of the keys contained in this map. - * The set's iterator returns the keys in ascending order. - * The set is backed by the map, so changes to the map are + * + *

The set's iterator returns the keys in ascending order. + * The set's spliterator additionally reports {@link Spliterator#CONCURRENT}, + * {@link Spliterator#NONNULL}, {@link Spliterator#SORTED} and + * {@link Spliterator#ORDERED}, with an encounter order that is ascending + * key order. The spliterator's comparator (see + * {@link java.util.Spliterator#getComparator()}) is {@code null} if + * the map's comparator (see {@link #comparator()}) is {@code null}. + * Otherwise, the spliterator's comparator is the same as or imposes the + * same total ordering as the map's comparator. + * + *

The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa. The set supports element * removal, which removes the corresponding mapping from the map, * via the {@code Iterator.remove}, {@code Set.remove}, @@ -1724,31 +1790,32 @@ public void clear() { * operations. It does not support the {@code add} or {@code addAll} * operations. * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + *

The view's iterators and spliterators are + * weakly consistent. * *

This method is equivalent to method {@code navigableKeySet}. * * @return a navigable set view of the keys in this map */ public NavigableSet keySet() { - KeySet ks = keySet; - return (ks != null) ? ks : (keySet = new KeySet(this)); + KeySet ks = keySet; + return (ks != null) ? ks : (keySet = new KeySet<>(this)); } public NavigableSet navigableKeySet() { - KeySet ks = keySet; - return (ks != null) ? ks : (keySet = new KeySet(this)); + KeySet ks = keySet; + return (ks != null) ? ks : (keySet = new KeySet<>(this)); } /** * Returns a {@link Collection} view of the values contained in this map. - * The collection's iterator returns the values in ascending order - * of the corresponding keys. - * The collection is backed by the map, so changes to the map are + *

The collection's iterator returns the values in ascending order + * of the corresponding keys. The collections's spliterator additionally + * reports {@link Spliterator#CONCURRENT}, {@link Spliterator#NONNULL} and + * {@link Spliterator#ORDERED}, with an encounter order that is ascending + * order of the corresponding keys. + * + *

The collection is backed by the map, so changes to the map are * reflected in the collection, and vice-versa. The collection * supports element removal, which removes the corresponding * mapping from the map, via the {@code Iterator.remove}, @@ -1756,21 +1823,24 @@ public NavigableSet navigableKeySet() { * {@code retainAll} and {@code clear} operations. It does not * support the {@code add} or {@code addAll} operations. * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + *

The view's iterators and spliterators are + * weakly consistent. */ public Collection values() { - Values vs = values; - return (vs != null) ? vs : (values = new Values(this)); + Values vs = values; + return (vs != null) ? vs : (values = new Values<>(this)); } /** * Returns a {@link Set} view of the mappings contained in this map. - * The set's iterator returns the entries in ascending key order. - * The set is backed by the map, so changes to the map are + * + *

The set's iterator returns the entries in ascending key order. The + * set's spliterator additionally reports {@link Spliterator#CONCURRENT}, + * {@link Spliterator#NONNULL}, {@link Spliterator#SORTED} and + * {@link Spliterator#ORDERED}, with an encounter order that is ascending + * key order. + * + *

The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa. The set supports element * removal, which removes the corresponding mapping from the map, * via the {@code Iterator.remove}, {@code Set.remove}, @@ -1778,15 +1848,12 @@ public Collection values() { * operations. It does not support the {@code add} or * {@code addAll} operations. * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + *

The view's iterators and spliterators are + * weakly consistent. * - *

The {@code Map.Entry} elements returned by - * {@code iterator.next()} do not support the - * {@code setValue} operation. + *

The {@code Map.Entry} elements traversed by the {@code iterator} + * or {@code spliterator} do not support the {@code setValue} + * operation. * * @return a set view of the mappings contained in this map, * sorted in ascending key order @@ -1871,9 +1938,7 @@ public V putIfAbsent(K key, V value) { public boolean remove(Object key, Object value) { if (key == null) throw new NullPointerException(); - if (value == null) - return false; - return doRemove(key, value) != null; + return value != null && doRemove(key, value) != null; } /** @@ -1884,15 +1949,13 @@ public boolean remove(Object key, Object value) { * @throws NullPointerException if any of the arguments are null */ public boolean replace(K key, V oldValue, V newValue) { - if (oldValue == null || newValue == null) + if (key == null || oldValue == null || newValue == null) throw new NullPointerException(); - Comparable k = comparable(key); for (;;) { - Node n = findNode(k); - if (n == null) + Node n; Object v; + if ((n = findNode(key)) == null) return false; - Object v = n.value; - if (v != null) { + if ((v = n.value) != null) { if (!oldValue.equals(v)) return false; if (n.casValue(v, newValue)) @@ -1911,16 +1974,16 @@ public boolean replace(K key, V oldValue, V newValue) { * @throws NullPointerException if the specified key or value is null */ public V replace(K key, V value) { - if (value == null) + if (key == null || value == null) throw new NullPointerException(); - Comparable k = comparable(key); for (;;) { - Node n = findNode(k); - if (n == null) + Node n; Object v; + if ((n = findNode(key)) == null) return null; - Object v = n.value; - if (v != null && n.casValue(v, value)) - return (V)v; + if ((v = n.value) != null && n.casValue(v, value)) { + @SuppressWarnings("unchecked") V vv = (V)v; + return vv; + } } } @@ -2038,7 +2101,7 @@ public Map.Entry lowerEntry(K key) { * @throws NullPointerException if the specified key is null */ public K lowerKey(K key) { - Node n = findNear(key, LT); + Node n = findNear(key, LT, comparator); return (n == null) ? null : n.key; } @@ -2062,7 +2125,7 @@ public Map.Entry floorEntry(K key) { * @throws NullPointerException if the specified key is null */ public K floorKey(K key) { - Node n = findNear(key, LT|EQ); + Node n = findNear(key, LT|EQ, comparator); return (n == null) ? null : n.key; } @@ -2084,7 +2147,7 @@ public Map.Entry ceilingEntry(K key) { * @throws NullPointerException if the specified key is null */ public K ceilingKey(K key) { - Node n = findNear(key, GT|EQ); + Node n = findNear(key, GT|EQ, comparator); return (n == null) ? null : n.key; } @@ -2108,7 +2171,7 @@ public Map.Entry higherEntry(K key) { * @throws NullPointerException if the specified key is null */ public K higherKey(K key) { - Node n = findNear(key, GT); + Node n = findNear(key, GT, comparator); return (n == null) ? null : n.key; } @@ -2182,13 +2245,11 @@ abstract class Iter implements Iterator { /** Initializes ascending iterator for entire range. */ Iter() { - for (;;) { - next = findFirst(); - if (next == null) - break; + while ((next = findFirst()) != null) { Object x = next.value; if (x != null && x != next) { - nextValue = (V) x; + @SuppressWarnings("unchecked") V vv = (V)x; + nextValue = vv; break; } } @@ -2203,13 +2264,11 @@ final void advance() { if (next == null) throw new NoSuchElementException(); lastReturned = next; - for (;;) { - next = next.next; - if (next == null) - break; + while ((next = next.next) != null) { Object x = next.value; if (x != null && x != next) { - nextValue = (V) x; + @SuppressWarnings("unchecked") V vv = (V)x; + nextValue = vv; break; } } @@ -2252,20 +2311,6 @@ public Map.Entry next() { } } - // Factory methods for iterators needed by ConcurrentSkipListSet etc - - Iterator keyIterator() { - return new KeyIterator(); - } - - Iterator valueIterator() { - return new ValueIterator(); - } - - Iterator> entryIterator() { - return new EntryIterator(); - } - /* ---------------- View Classes -------------- */ /* @@ -2282,35 +2327,34 @@ static final List toList(Collection c) { return list; } - static final class KeySet - extends AbstractSet implements NavigableSet { - private final ConcurrentNavigableMap m; - KeySet(ConcurrentNavigableMap map) { m = map; } + static final class KeySet + extends AbstractSet implements NavigableSet { + final ConcurrentNavigableMap m; + KeySet(ConcurrentNavigableMap map) { m = map; } public int size() { return m.size(); } public boolean isEmpty() { return m.isEmpty(); } public boolean contains(Object o) { return m.containsKey(o); } public boolean remove(Object o) { return m.remove(o) != null; } public void clear() { m.clear(); } - public E lower(E e) { return m.lowerKey(e); } - public E floor(E e) { return m.floorKey(e); } - public E ceiling(E e) { return m.ceilingKey(e); } - public E higher(E e) { return m.higherKey(e); } - public Comparator comparator() { return m.comparator(); } - public E first() { return m.firstKey(); } - public E last() { return m.lastKey(); } - public E pollFirst() { - Map.Entry e = m.pollFirstEntry(); + public K lower(K e) { return m.lowerKey(e); } + public K floor(K e) { return m.floorKey(e); } + public K ceiling(K e) { return m.ceilingKey(e); } + public K higher(K e) { return m.higherKey(e); } + public Comparator comparator() { return m.comparator(); } + public K first() { return m.firstKey(); } + public K last() { return m.lastKey(); } + public K pollFirst() { + Map.Entry e = m.pollFirstEntry(); return (e == null) ? null : e.getKey(); } - public E pollLast() { - Map.Entry e = m.pollLastEntry(); + public K pollLast() { + Map.Entry e = m.pollLastEntry(); return (e == null) ? null : e.getKey(); } - public Iterator iterator() { - if (m instanceof ConcurrentSkipListMap) - return ((ConcurrentSkipListMap)m).keyIterator(); - else - return ((ConcurrentSkipListMap.SubMap)m).keyIterator(); + public Iterator iterator() { + return (m instanceof ConcurrentSkipListMap) + ? ((ConcurrentSkipListMap)m).new KeyIterator() + : ((SubMap)m).new SubMapKeyIterator(); } public boolean equals(Object o) { if (o == this) @@ -2328,81 +2372,99 @@ public boolean equals(Object o) { } public Object[] toArray() { return toList(this).toArray(); } public T[] toArray(T[] a) { return toList(this).toArray(a); } - public Iterator descendingIterator() { + public Iterator descendingIterator() { return descendingSet().iterator(); } - public NavigableSet subSet(E fromElement, + public NavigableSet subSet(K fromElement, boolean fromInclusive, - E toElement, + K toElement, boolean toInclusive) { - return new KeySet(m.subMap(fromElement, fromInclusive, - toElement, toInclusive)); + return new KeySet<>(m.subMap(fromElement, fromInclusive, + toElement, toInclusive)); } - public NavigableSet headSet(E toElement, boolean inclusive) { - return new KeySet(m.headMap(toElement, inclusive)); + public NavigableSet headSet(K toElement, boolean inclusive) { + return new KeySet<>(m.headMap(toElement, inclusive)); } - public NavigableSet tailSet(E fromElement, boolean inclusive) { - return new KeySet(m.tailMap(fromElement, inclusive)); + public NavigableSet tailSet(K fromElement, boolean inclusive) { + return new KeySet<>(m.tailMap(fromElement, inclusive)); } - public NavigableSet subSet(E fromElement, E toElement) { + public NavigableSet subSet(K fromElement, K toElement) { return subSet(fromElement, true, toElement, false); } - public NavigableSet headSet(E toElement) { + public NavigableSet headSet(K toElement) { return headSet(toElement, false); } - public NavigableSet tailSet(E fromElement) { + public NavigableSet tailSet(K fromElement) { return tailSet(fromElement, true); } - public NavigableSet descendingSet() { - return new KeySet(m.descendingMap()); + public NavigableSet descendingSet() { + return new KeySet<>(m.descendingMap()); + } + + public Spliterator spliterator() { + return (m instanceof ConcurrentSkipListMap) + ? ((ConcurrentSkipListMap)m).keySpliterator() + : ((SubMap)m).new SubMapKeyIterator(); } } - static final class Values extends AbstractCollection { - private final ConcurrentNavigableMap m; - Values(ConcurrentNavigableMap map) { + static final class Values extends AbstractCollection { + final ConcurrentNavigableMap m; + Values(ConcurrentNavigableMap map) { m = map; } - public Iterator iterator() { - if (m instanceof ConcurrentSkipListMap) - return ((ConcurrentSkipListMap)m).valueIterator(); - else - return ((SubMap)m).valueIterator(); - } - public boolean isEmpty() { - return m.isEmpty(); - } - public int size() { - return m.size(); - } - public boolean contains(Object o) { - return m.containsValue(o); - } - public void clear() { - m.clear(); + public Iterator iterator() { + return (m instanceof ConcurrentSkipListMap) + ? ((ConcurrentSkipListMap)m).new ValueIterator() + : ((SubMap)m).new SubMapValueIterator(); } + public int size() { return m.size(); } + public boolean isEmpty() { return m.isEmpty(); } + public boolean contains(Object o) { return m.containsValue(o); } + public void clear() { m.clear(); } public Object[] toArray() { return toList(this).toArray(); } public T[] toArray(T[] a) { return toList(this).toArray(a); } - } - static final class EntrySet extends AbstractSet> { - private final ConcurrentNavigableMap m; - EntrySet(ConcurrentNavigableMap map) { - m = map; + public Spliterator spliterator() { + return (m instanceof ConcurrentSkipListMap) + ? ((ConcurrentSkipListMap)m).valueSpliterator() + : ((SubMap)m).new SubMapValueIterator(); } - public Iterator> iterator() { + public boolean removeIf(Predicate filter) { + if (filter == null) throw new NullPointerException(); if (m instanceof ConcurrentSkipListMap) - return ((ConcurrentSkipListMap)m).entryIterator(); - else - return ((SubMap)m).entryIterator(); + return ((ConcurrentSkipListMap)m).removeValueIf(filter); + // else use iterator + Iterator> it = + ((SubMap)m).new SubMapEntryIterator(); + boolean removed = false; + while (it.hasNext()) { + Map.Entry e = it.next(); + V v = e.getValue(); + if (filter.test(v) && m.remove(e.getKey(), v)) + removed = true; + } + return removed; + } + } + + static final class EntrySet extends AbstractSet> { + final ConcurrentNavigableMap m; + EntrySet(ConcurrentNavigableMap map) { + m = map; + } + public Iterator> iterator() { + return (m instanceof ConcurrentSkipListMap) + ? ((ConcurrentSkipListMap)m).new EntryIterator() + : ((SubMap)m).new SubMapEntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; - V1 v = m.get(e.getKey()); + V v = m.get(e.getKey()); return v != null && v.equals(e.getValue()); } public boolean remove(Object o) { @@ -2437,27 +2499,47 @@ public boolean equals(Object o) { } public Object[] toArray() { return toList(this).toArray(); } public T[] toArray(T[] a) { return toList(this).toArray(a); } + + public Spliterator> spliterator() { + return (m instanceof ConcurrentSkipListMap) + ? ((ConcurrentSkipListMap)m).entrySpliterator() + : ((SubMap)m).new SubMapEntryIterator(); + } + public boolean removeIf(Predicate> filter) { + if (filter == null) throw new NullPointerException(); + if (m instanceof ConcurrentSkipListMap) + return ((ConcurrentSkipListMap)m).removeEntryIf(filter); + // else use iterator + Iterator> it = + ((SubMap)m).new SubMapEntryIterator(); + boolean removed = false; + while (it.hasNext()) { + Map.Entry e = it.next(); + if (filter.test(e) && m.remove(e.getKey(), e.getValue())) + removed = true; + } + return removed; + } } /** * Submaps returned by {@link ConcurrentSkipListMap} submap operations - * represent a subrange of mappings of their underlying - * maps. Instances of this class support all methods of their - * underlying maps, differing in that mappings outside their range are - * ignored, and attempts to add mappings outside their ranges result - * in {@link IllegalArgumentException}. Instances of this class are - * constructed only using the {@code subMap}, {@code headMap}, and - * {@code tailMap} methods of their underlying maps. + * represent a subrange of mappings of their underlying maps. + * Instances of this class support all methods of their underlying + * maps, differing in that mappings outside their range are ignored, + * and attempts to add mappings outside their ranges result in {@link + * IllegalArgumentException}. Instances of this class are constructed + * only using the {@code subMap}, {@code headMap}, and {@code tailMap} + * methods of their underlying maps. * * @serial include */ static final class SubMap extends AbstractMap - implements ConcurrentNavigableMap, Cloneable, - java.io.Serializable { + implements ConcurrentNavigableMap, Cloneable, Serializable { private static final long serialVersionUID = -7647078645895051609L; /** Underlying map */ - private final ConcurrentSkipListMap m; + final ConcurrentSkipListMap m; /** lower bound key, or null if from start */ private final K lo; /** upper bound key, or null if to end */ @@ -2467,10 +2549,10 @@ static final class SubMap extends AbstractMap /** inclusion flag for hi */ private final boolean hiInclusive; /** direction */ - private final boolean isDescending; + final boolean isDescending; // Lazily initialized view holders - private transient KeySet keySetView; + private transient KeySet keySetView; private transient Set> entrySetView; private transient Collection valuesView; @@ -2481,8 +2563,9 @@ static final class SubMap extends AbstractMap K fromKey, boolean fromInclusive, K toKey, boolean toInclusive, boolean isDescending) { + Comparator cmp = map.comparator; if (fromKey != null && toKey != null && - map.compare(fromKey, toKey) > 0) + cpr(cmp, fromKey, toKey) > 0) throw new IllegalArgumentException("inconsistent range"); this.m = map; this.lo = fromKey; @@ -2494,39 +2577,34 @@ static final class SubMap extends AbstractMap /* ---------------- Utilities -------------- */ - private boolean tooLow(K key) { - if (lo != null) { - int c = m.compare(key, lo); - if (c < 0 || (c == 0 && !loInclusive)) - return true; - } - return false; + boolean tooLow(Object key, Comparator cmp) { + int c; + return (lo != null && ((c = cpr(cmp, key, lo)) < 0 || + (c == 0 && !loInclusive))); } - private boolean tooHigh(K key) { - if (hi != null) { - int c = m.compare(key, hi); - if (c > 0 || (c == 0 && !hiInclusive)) - return true; - } - return false; + boolean tooHigh(Object key, Comparator cmp) { + int c; + return (hi != null && ((c = cpr(cmp, key, hi)) > 0 || + (c == 0 && !hiInclusive))); } - private boolean inBounds(K key) { - return !tooLow(key) && !tooHigh(key); + boolean inBounds(Object key, Comparator cmp) { + return !tooLow(key, cmp) && !tooHigh(key, cmp); } - private void checkKeyBounds(K key) throws IllegalArgumentException { + void checkKeyBounds(K key, Comparator cmp) { if (key == null) throw new NullPointerException(); - if (!inBounds(key)) + if (!inBounds(key, cmp)) throw new IllegalArgumentException("key out of range"); } /** * Returns true if node key is less than upper bound of range. */ - private boolean isBeforeEnd(ConcurrentSkipListMap.Node n) { + boolean isBeforeEnd(ConcurrentSkipListMap.Node n, + Comparator cmp) { if (n == null) return false; if (hi == null) @@ -2534,7 +2612,7 @@ private boolean isBeforeEnd(ConcurrentSkipListMap.Node n) { K k = n.key; if (k == null) // pass by markers and headers return true; - int c = m.compare(k, hi); + int c = cpr(cmp, k, hi); if (c > 0 || (c == 0 && !hiInclusive)) return false; return true; @@ -2544,34 +2622,35 @@ private boolean isBeforeEnd(ConcurrentSkipListMap.Node n) { * Returns lowest node. This node might not be in range, so * most usages need to check bounds. */ - private ConcurrentSkipListMap.Node loNode() { + ConcurrentSkipListMap.Node loNode(Comparator cmp) { if (lo == null) return m.findFirst(); else if (loInclusive) - return m.findNear(lo, GT|EQ); + return m.findNear(lo, GT|EQ, cmp); else - return m.findNear(lo, GT); + return m.findNear(lo, GT, cmp); } /** * Returns highest node. This node might not be in range, so * most usages need to check bounds. */ - private ConcurrentSkipListMap.Node hiNode() { + ConcurrentSkipListMap.Node hiNode(Comparator cmp) { if (hi == null) return m.findLast(); else if (hiInclusive) - return m.findNear(hi, LT|EQ); + return m.findNear(hi, LT|EQ, cmp); else - return m.findNear(hi, LT); + return m.findNear(hi, LT, cmp); } /** * Returns lowest absolute key (ignoring directionality). */ - private K lowestKey() { - ConcurrentSkipListMap.Node n = loNode(); - if (isBeforeEnd(n)) + K lowestKey() { + Comparator cmp = m.comparator; + ConcurrentSkipListMap.Node n = loNode(cmp); + if (isBeforeEnd(n, cmp)) return n.key; else throw new NoSuchElementException(); @@ -2580,20 +2659,22 @@ private K lowestKey() { /** * Returns highest absolute key (ignoring directionality). */ - private K highestKey() { - ConcurrentSkipListMap.Node n = hiNode(); + K highestKey() { + Comparator cmp = m.comparator; + ConcurrentSkipListMap.Node n = hiNode(cmp); if (n != null) { K last = n.key; - if (inBounds(last)) + if (inBounds(last, cmp)) return last; } throw new NoSuchElementException(); } - private Map.Entry lowestEntry() { + Map.Entry lowestEntry() { + Comparator cmp = m.comparator; for (;;) { - ConcurrentSkipListMap.Node n = loNode(); - if (!isBeforeEnd(n)) + ConcurrentSkipListMap.Node n = loNode(cmp); + if (!isBeforeEnd(n, cmp)) return null; Map.Entry e = n.createSnapshot(); if (e != null) @@ -2601,10 +2682,11 @@ private Map.Entry lowestEntry() { } } - private Map.Entry highestEntry() { + Map.Entry highestEntry() { + Comparator cmp = m.comparator; for (;;) { - ConcurrentSkipListMap.Node n = hiNode(); - if (n == null || !inBounds(n.key)) + ConcurrentSkipListMap.Node n = hiNode(cmp); + if (n == null || !inBounds(n.key, cmp)) return null; Map.Entry e = n.createSnapshot(); if (e != null) @@ -2612,13 +2694,14 @@ private Map.Entry highestEntry() { } } - private Map.Entry removeLowest() { + Map.Entry removeLowest() { + Comparator cmp = m.comparator; for (;;) { - Node n = loNode(); + Node n = loNode(cmp); if (n == null) return null; K k = n.key; - if (!inBounds(k)) + if (!inBounds(k, cmp)) return null; V v = m.doRemove(k, null); if (v != null) @@ -2626,13 +2709,14 @@ private Map.Entry removeLowest() { } } - private Map.Entry removeHighest() { + Map.Entry removeHighest() { + Comparator cmp = m.comparator; for (;;) { - Node n = hiNode(); + Node n = hiNode(cmp); if (n == null) return null; K k = n.key; - if (!inBounds(k)) + if (!inBounds(k, cmp)) return null; V v = m.doRemove(k, null); if (v != null) @@ -2641,22 +2725,23 @@ private Map.Entry removeHighest() { } /** - * Submap version of ConcurrentSkipListMap.getNearEntry + * Submap version of ConcurrentSkipListMap.getNearEntry. */ - private Map.Entry getNearEntry(K key, int rel) { + Map.Entry getNearEntry(K key, int rel) { + Comparator cmp = m.comparator; if (isDescending) { // adjust relation for direction if ((rel & LT) == 0) rel |= LT; else rel &= ~LT; } - if (tooLow(key)) + if (tooLow(key, cmp)) return ((rel & LT) != 0) ? null : lowestEntry(); - if (tooHigh(key)) + if (tooHigh(key, cmp)) return ((rel & LT) != 0) ? highestEntry() : null; for (;;) { - Node n = m.findNear(key, rel); - if (n == null || !inBounds(n.key)) + Node n = m.findNear(key, rel, cmp); + if (n == null || !inBounds(n.key, cmp)) return null; K k = n.key; V v = n.getValidValue(); @@ -2666,35 +2751,36 @@ private Map.Entry getNearEntry(K key, int rel) { } // Almost the same as getNearEntry, except for keys - private K getNearKey(K key, int rel) { + K getNearKey(K key, int rel) { + Comparator cmp = m.comparator; if (isDescending) { // adjust relation for direction if ((rel & LT) == 0) rel |= LT; else rel &= ~LT; } - if (tooLow(key)) { + if (tooLow(key, cmp)) { if ((rel & LT) == 0) { - ConcurrentSkipListMap.Node n = loNode(); - if (isBeforeEnd(n)) + ConcurrentSkipListMap.Node n = loNode(cmp); + if (isBeforeEnd(n, cmp)) return n.key; } return null; } - if (tooHigh(key)) { + if (tooHigh(key, cmp)) { if ((rel & LT) != 0) { - ConcurrentSkipListMap.Node n = hiNode(); + ConcurrentSkipListMap.Node n = hiNode(cmp); if (n != null) { K last = n.key; - if (inBounds(last)) + if (inBounds(last, cmp)) return last; } } return null; } for (;;) { - Node n = m.findNear(key, rel); - if (n == null || !inBounds(n.key)) + Node n = m.findNear(key, rel, cmp); + if (n == null || !inBounds(n.key, cmp)) return null; K k = n.key; V v = n.getValidValue(); @@ -2707,30 +2793,28 @@ private K getNearKey(K key, int rel) { public boolean containsKey(Object key) { if (key == null) throw new NullPointerException(); - K k = (K)key; - return inBounds(k) && m.containsKey(k); + return inBounds(key, m.comparator) && m.containsKey(key); } public V get(Object key) { if (key == null) throw new NullPointerException(); - K k = (K)key; - return (!inBounds(k)) ? null : m.get(k); + return (!inBounds(key, m.comparator)) ? null : m.get(key); } public V put(K key, V value) { - checkKeyBounds(key); + checkKeyBounds(key, m.comparator); return m.put(key, value); } public V remove(Object key) { - K k = (K)key; - return (!inBounds(k)) ? null : m.remove(k); + return (!inBounds(key, m.comparator)) ? null : m.remove(key); } public int size() { + Comparator cmp = m.comparator; long count = 0; - for (ConcurrentSkipListMap.Node n = loNode(); - isBeforeEnd(n); + for (ConcurrentSkipListMap.Node n = loNode(cmp); + isBeforeEnd(n, cmp); n = n.next) { if (n.getValidValue() != null) ++count; @@ -2739,14 +2823,16 @@ public int size() { } public boolean isEmpty() { - return !isBeforeEnd(loNode()); + Comparator cmp = m.comparator; + return !isBeforeEnd(loNode(cmp), cmp); } public boolean containsValue(Object value) { if (value == null) throw new NullPointerException(); - for (ConcurrentSkipListMap.Node n = loNode(); - isBeforeEnd(n); + Comparator cmp = m.comparator; + for (ConcurrentSkipListMap.Node n = loNode(cmp); + isBeforeEnd(n, cmp); n = n.next) { V v = n.getValidValue(); if (v != null && value.equals(v)) @@ -2756,8 +2842,9 @@ public boolean containsValue(Object value) { } public void clear() { - for (ConcurrentSkipListMap.Node n = loNode(); - isBeforeEnd(n); + Comparator cmp = m.comparator; + for (ConcurrentSkipListMap.Node n = loNode(cmp); + isBeforeEnd(n, cmp); n = n.next) { if (n.getValidValue() != null) m.remove(n.key); @@ -2767,22 +2854,21 @@ public void clear() { /* ---------------- ConcurrentMap API methods -------------- */ public V putIfAbsent(K key, V value) { - checkKeyBounds(key); + checkKeyBounds(key, m.comparator); return m.putIfAbsent(key, value); } public boolean remove(Object key, Object value) { - K k = (K)key; - return inBounds(k) && m.remove(k, value); + return inBounds(key, m.comparator) && m.remove(key, value); } public boolean replace(K key, V oldValue, V newValue) { - checkKeyBounds(key); + checkKeyBounds(key, m.comparator); return m.replace(key, oldValue, newValue); } public V replace(K key, V value) { - checkKeyBounds(key); + checkKeyBounds(key, m.comparator); return m.replace(key, value); } @@ -2800,10 +2886,9 @@ public Comparator comparator() { * Utility to create submaps, where given bounds override * unbounded(null) ones and/or are checked against bounded ones. */ - private SubMap newSubMap(K fromKey, - boolean fromInclusive, - K toKey, - boolean toInclusive) { + SubMap newSubMap(K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive) { + Comparator cmp = m.comparator; if (isDescending) { // flip senses K tk = fromKey; fromKey = toKey; @@ -2818,7 +2903,7 @@ private SubMap newSubMap(K fromKey, fromInclusive = loInclusive; } else { - int c = m.compare(fromKey, lo); + int c = cpr(cmp, fromKey, lo); if (c < 0 || (c == 0 && !loInclusive && fromInclusive)) throw new IllegalArgumentException("key out of range"); } @@ -2829,7 +2914,7 @@ private SubMap newSubMap(K fromKey, toInclusive = hiInclusive; } else { - int c = m.compare(toKey, hi); + int c = cpr(cmp, toKey, hi); if (c > 0 || (c == 0 && !hiInclusive && toInclusive)) throw new IllegalArgumentException("key out of range"); } @@ -2838,24 +2923,20 @@ private SubMap newSubMap(K fromKey, toKey, toInclusive, isDescending); } - public SubMap subMap(K fromKey, - boolean fromInclusive, - K toKey, - boolean toInclusive) { + public SubMap subMap(K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive) { if (fromKey == null || toKey == null) throw new NullPointerException(); return newSubMap(fromKey, fromInclusive, toKey, toInclusive); } - public SubMap headMap(K toKey, - boolean inclusive) { + public SubMap headMap(K toKey, boolean inclusive) { if (toKey == null) throw new NullPointerException(); return newSubMap(null, false, toKey, inclusive); } - public SubMap tailMap(K fromKey, - boolean inclusive) { + public SubMap tailMap(K fromKey, boolean inclusive) { if (fromKey == null) throw new NullPointerException(); return newSubMap(fromKey, inclusive, null, false); @@ -2939,18 +3020,18 @@ public Map.Entry pollLastEntry() { /* ---------------- Submap Views -------------- */ public NavigableSet keySet() { - KeySet ks = keySetView; - return (ks != null) ? ks : (keySetView = new KeySet(this)); + KeySet ks = keySetView; + return (ks != null) ? ks : (keySetView = new KeySet<>(this)); } public NavigableSet navigableKeySet() { - KeySet ks = keySetView; - return (ks != null) ? ks : (keySetView = new KeySet(this)); + KeySet ks = keySetView; + return (ks != null) ? ks : (keySetView = new KeySet<>(this)); } public Collection values() { Collection vs = valuesView; - return (vs != null) ? vs : (valuesView = new Values(this)); + return (vs != null) ? vs : (valuesView = new Values<>(this)); } public Set> entrySet() { @@ -2962,22 +3043,11 @@ public NavigableSet descendingKeySet() { return descendingMap().navigableKeySet(); } - Iterator keyIterator() { - return new SubMapKeyIterator(); - } - - Iterator valueIterator() { - return new SubMapValueIterator(); - } - - Iterator> entryIterator() { - return new SubMapEntryIterator(); - } - /** * Variant of main Iter class to traverse through submaps. + * Also serves as back-up Spliterator for views. */ - abstract class SubMapIter implements Iterator { + abstract class SubMapIter implements Iterator, Spliterator { /** the last node returned by next() */ Node lastReturned; /** the next node to return from next(); */ @@ -2986,16 +3056,19 @@ abstract class SubMapIter implements Iterator { V nextValue; SubMapIter() { + Comparator cmp = m.comparator; for (;;) { - next = isDescending ? hiNode() : loNode(); + next = isDescending ? hiNode(cmp) : loNode(cmp); if (next == null) break; Object x = next.value; if (x != null && x != next) { - if (! inBounds(next.key)) + if (! inBounds(next.key, cmp)) next = null; - else - nextValue = (V) x; + else { + @SuppressWarnings("unchecked") V vv = (V)x; + nextValue = vv; + } break; } } @@ -3016,32 +3089,38 @@ final void advance() { } private void ascend() { + Comparator cmp = m.comparator; for (;;) { next = next.next; if (next == null) break; Object x = next.value; if (x != null && x != next) { - if (tooHigh(next.key)) + if (tooHigh(next.key, cmp)) next = null; - else - nextValue = (V) x; + else { + @SuppressWarnings("unchecked") V vv = (V)x; + nextValue = vv; + } break; } } } private void descend() { + Comparator cmp = m.comparator; for (;;) { - next = m.findNear(lastReturned.key, LT); + next = m.findNear(lastReturned.key, LT, cmp); if (next == null) break; Object x = next.value; if (x != null && x != next) { - if (tooLow(next.key)) + if (tooLow(next.key, cmp)) next = null; - else - nextValue = (V) x; + else { + @SuppressWarnings("unchecked") V vv = (V)x; + nextValue = vv; + } break; } } @@ -3055,6 +3134,27 @@ public void remove() { lastReturned = null; } + public Spliterator trySplit() { + return null; + } + + public boolean tryAdvance(Consumer action) { + if (hasNext()) { + action.accept(next()); + return true; + } + return false; + } + + public void forEachRemaining(Consumer action) { + while (hasNext()) + action.accept(next()); + } + + public long estimateSize() { + return Long.MAX_VALUE; + } + } final class SubMapValueIterator extends SubMapIter { @@ -3063,6 +3163,9 @@ public V next() { advance(); return v; } + public int characteristics() { + return 0; + } } final class SubMapKeyIterator extends SubMapIter { @@ -3071,6 +3174,13 @@ public K next() { advance(); return n.key; } + public int characteristics() { + return Spliterator.DISTINCT | Spliterator.ORDERED | + Spliterator.SORTED; + } + public final Comparator getComparator() { + return SubMap.this.comparator(); + } } final class SubMapEntryIterator extends SubMapIter> { @@ -3080,19 +3190,390 @@ public Map.Entry next() { advance(); return new AbstractMap.SimpleImmutableEntry(n.key, v); } + public int characteristics() { + return Spliterator.DISTINCT; + } + } + } + + // default Map method overrides + + public void forEach(BiConsumer action) { + if (action == null) throw new NullPointerException(); + V v; + for (Node n = findFirst(); n != null; n = n.next) { + if ((v = n.getValidValue()) != null) + action.accept(n.key, v); + } + } + + public void replaceAll(BiFunction function) { + if (function == null) throw new NullPointerException(); + V v; + for (Node n = findFirst(); n != null; n = n.next) { + while ((v = n.getValidValue()) != null) { + V r = function.apply(n.key, v); + if (r == null) throw new NullPointerException(); + if (n.casValue(v, r)) + break; + } + } + } + + /** + * Helper method for EntrySet.removeIf. + */ + boolean removeEntryIf(Predicate> function) { + if (function == null) throw new NullPointerException(); + boolean removed = false; + for (Node n = findFirst(); n != null; n = n.next) { + V v; + if ((v = n.getValidValue()) != null) { + K k = n.key; + Map.Entry e = new AbstractMap.SimpleImmutableEntry<>(k, v); + if (function.test(e) && remove(k, v)) + removed = true; + } + } + return removed; + } + + /** + * Helper method for Values.removeIf. + */ + boolean removeValueIf(Predicate function) { + if (function == null) throw new NullPointerException(); + boolean removed = false; + for (Node n = findFirst(); n != null; n = n.next) { + V v; + if ((v = n.getValidValue()) != null) { + K k = n.key; + if (function.test(v) && remove(k, v)) + removed = true; + } + } + return removed; + } + + /** + * Base class providing common structure for Spliterators. + * (Although not all that much common functionality; as usual for + * view classes, details annoyingly vary in key, value, and entry + * subclasses in ways that are not worth abstracting out for + * internal classes.) + * + * The basic split strategy is to recursively descend from top + * level, row by row, descending to next row when either split + * off, or the end of row is encountered. Control of the number of + * splits relies on some statistical estimation: The expected + * remaining number of elements of a skip list when advancing + * either across or down decreases by about 25%. To make this + * observation useful, we need to know initial size, which we + * don't. But we can just use Integer.MAX_VALUE so that we + * don't prematurely zero out while splitting. + */ + abstract static class CSLMSpliterator { + final Comparator comparator; + final K fence; // exclusive upper bound for keys, or null if to end + Index row; // the level to split out + Node current; // current traversal node; initialize at origin + int est; // pseudo-size estimate + CSLMSpliterator(Comparator comparator, Index row, + Node origin, K fence, int est) { + this.comparator = comparator; this.row = row; + this.current = origin; this.fence = fence; this.est = est; + } + + public final long estimateSize() { return (long)est; } + } + + static final class KeySpliterator extends CSLMSpliterator + implements Spliterator { + KeySpliterator(Comparator comparator, Index row, + Node origin, K fence, int est) { + super(comparator, row, origin, fence, est); + } + + public KeySpliterator trySplit() { + Node e; K ek; + Comparator cmp = comparator; + K f = fence; + if ((e = current) != null && (ek = e.key) != null) { + for (Index q = row; q != null; q = row = q.down) { + Index s; Node b, n; K sk; + if ((s = q.right) != null && (b = s.node) != null && + (n = b.next) != null && n.value != null && + (sk = n.key) != null && cpr(cmp, sk, ek) > 0 && + (f == null || cpr(cmp, sk, f) < 0)) { + current = n; + Index r = q.down; + row = (s.right != null) ? s : s.down; + est -= est >>> 2; + return new KeySpliterator(cmp, r, e, sk, est); + } + } + } + return null; + } + + public void forEachRemaining(Consumer action) { + if (action == null) throw new NullPointerException(); + Comparator cmp = comparator; + K f = fence; + Node e = current; + current = null; + for (; e != null; e = e.next) { + K k; Object v; + if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) + break; + if ((v = e.value) != null && v != e) + action.accept(k); + } + } + + public boolean tryAdvance(Consumer action) { + if (action == null) throw new NullPointerException(); + Comparator cmp = comparator; + K f = fence; + Node e = current; + for (; e != null; e = e.next) { + K k; Object v; + if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) { + e = null; + break; + } + if ((v = e.value) != null && v != e) { + current = e.next; + action.accept(k); + return true; + } + } + current = e; + return false; + } + + public int characteristics() { + return Spliterator.DISTINCT | Spliterator.SORTED | + Spliterator.ORDERED | Spliterator.CONCURRENT | + Spliterator.NONNULL; + } + + public final Comparator getComparator() { + return comparator; + } + } + // factory method for KeySpliterator + final KeySpliterator keySpliterator() { + Comparator cmp = comparator; + for (;;) { // ensure h corresponds to origin p + HeadIndex h; Node p; + Node b = (h = head).node; + if ((p = b.next) == null || p.value != null) + return new KeySpliterator(cmp, h, p, null, (p == null) ? + 0 : Integer.MAX_VALUE); + p.helpDelete(b, p.next); + } + } + + static final class ValueSpliterator extends CSLMSpliterator + implements Spliterator { + ValueSpliterator(Comparator comparator, Index row, + Node origin, K fence, int est) { + super(comparator, row, origin, fence, est); + } + + public ValueSpliterator trySplit() { + Node e; K ek; + Comparator cmp = comparator; + K f = fence; + if ((e = current) != null && (ek = e.key) != null) { + for (Index q = row; q != null; q = row = q.down) { + Index s; Node b, n; K sk; + if ((s = q.right) != null && (b = s.node) != null && + (n = b.next) != null && n.value != null && + (sk = n.key) != null && cpr(cmp, sk, ek) > 0 && + (f == null || cpr(cmp, sk, f) < 0)) { + current = n; + Index r = q.down; + row = (s.right != null) ? s : s.down; + est -= est >>> 2; + return new ValueSpliterator(cmp, r, e, sk, est); + } + } + } + return null; + } + + public void forEachRemaining(Consumer action) { + if (action == null) throw new NullPointerException(); + Comparator cmp = comparator; + K f = fence; + Node e = current; + current = null; + for (; e != null; e = e.next) { + K k; Object v; + if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) + break; + if ((v = e.value) != null && v != e) { + @SuppressWarnings("unchecked") V vv = (V)v; + action.accept(vv); + } + } + } + + public boolean tryAdvance(Consumer action) { + if (action == null) throw new NullPointerException(); + Comparator cmp = comparator; + K f = fence; + Node e = current; + for (; e != null; e = e.next) { + K k; Object v; + if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) { + e = null; + break; + } + if ((v = e.value) != null && v != e) { + current = e.next; + @SuppressWarnings("unchecked") V vv = (V)v; + action.accept(vv); + return true; + } + } + current = e; + return false; + } + + public int characteristics() { + return Spliterator.CONCURRENT | Spliterator.ORDERED | + Spliterator.NONNULL; + } + } + + // Almost the same as keySpliterator() + final ValueSpliterator valueSpliterator() { + Comparator cmp = comparator; + for (;;) { + HeadIndex h; Node p; + Node b = (h = head).node; + if ((p = b.next) == null || p.value != null) + return new ValueSpliterator(cmp, h, p, null, (p == null) ? + 0 : Integer.MAX_VALUE); + p.helpDelete(b, p.next); + } + } + + static final class EntrySpliterator extends CSLMSpliterator + implements Spliterator> { + EntrySpliterator(Comparator comparator, Index row, + Node origin, K fence, int est) { + super(comparator, row, origin, fence, est); + } + + public EntrySpliterator trySplit() { + Node e; K ek; + Comparator cmp = comparator; + K f = fence; + if ((e = current) != null && (ek = e.key) != null) { + for (Index q = row; q != null; q = row = q.down) { + Index s; Node b, n; K sk; + if ((s = q.right) != null && (b = s.node) != null && + (n = b.next) != null && n.value != null && + (sk = n.key) != null && cpr(cmp, sk, ek) > 0 && + (f == null || cpr(cmp, sk, f) < 0)) { + current = n; + Index r = q.down; + row = (s.right != null) ? s : s.down; + est -= est >>> 2; + return new EntrySpliterator(cmp, r, e, sk, est); + } + } + } + return null; + } + + public void forEachRemaining(Consumer> action) { + if (action == null) throw new NullPointerException(); + Comparator cmp = comparator; + K f = fence; + Node e = current; + current = null; + for (; e != null; e = e.next) { + K k; Object v; + if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) + break; + if ((v = e.value) != null && v != e) { + @SuppressWarnings("unchecked") V vv = (V)v; + action.accept + (new AbstractMap.SimpleImmutableEntry(k, vv)); + } + } + } + + public boolean tryAdvance(Consumer> action) { + if (action == null) throw new NullPointerException(); + Comparator cmp = comparator; + K f = fence; + Node e = current; + for (; e != null; e = e.next) { + K k; Object v; + if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) { + e = null; + break; + } + if ((v = e.value) != null && v != e) { + current = e.next; + @SuppressWarnings("unchecked") V vv = (V)v; + action.accept + (new AbstractMap.SimpleImmutableEntry(k, vv)); + return true; + } + } + current = e; + return false; + } + + public int characteristics() { + return Spliterator.DISTINCT | Spliterator.SORTED | + Spliterator.ORDERED | Spliterator.CONCURRENT | + Spliterator.NONNULL; + } + + public final Comparator> getComparator() { + // Adapt or create a key-based comparator + if (comparator != null) { + return Map.Entry.comparingByKey(comparator); + } + else { + return (Comparator> & Serializable) (e1, e2) -> { + @SuppressWarnings("unchecked") + Comparable k1 = (Comparable) e1.getKey(); + return k1.compareTo(e2.getKey()); + }; + } + } + } + + // Almost the same as keySpliterator() + final EntrySpliterator entrySpliterator() { + Comparator cmp = comparator; + for (;;) { // almost same as key version + HeadIndex h; Node p; + Node b = (h = head).node; + if ((p = b.next) == null || p.value != null) + return new EntrySpliterator(cmp, h, p, null, (p == null) ? + 0 : Integer.MAX_VALUE); + p.helpDelete(b, p.next); } } // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long headOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long HEAD; static { try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = ConcurrentSkipListMap.class; - headOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("head")); - } catch (Exception e) { + HEAD = U.objectFieldOffset + (ConcurrentSkipListMap.class.getDeclaredField("head")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentSkipListSet.java b/luni/src/main/java/java/util/concurrent/ConcurrentSkipListSet.java index 13f1a43d8..171982299 100644 --- a/luni/src/main/java/java/util/concurrent/ConcurrentSkipListSet.java +++ b/luni/src/main/java/java/util/concurrent/ConcurrentSkipListSet.java @@ -6,10 +6,21 @@ package java.util.concurrent; -import java.util.*; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Map; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.Set; +import java.util.SortedSet; +import java.util.Spliterator; // BEGIN android-note // removed link to collections framework docs +// fixed framework docs link to "Collection#optional" // END android-note /** @@ -23,12 +34,12 @@ * cost for the {@code contains}, {@code add}, and {@code remove} * operations and their variants. Insertion, removal, and access * operations safely execute concurrently by multiple threads. - * Iterators are weakly consistent, returning elements - * reflecting the state of the set at some point at or since the - * creation of the iterator. They do not throw {@link - * ConcurrentModificationException}, and may proceed concurrently with - * other operations. Ascending ordered views and their iterators are - * faster than descending ones. + * + *

Iterators and spliterators are + * weakly consistent. + * + *

Ascending ordered views and their iterators are faster than + * descending ones. * *

Beware that, unlike in most collections, the {@code size} * method is not a constant-time operation. Because of the @@ -285,8 +296,9 @@ public boolean equals(Object o) { * * @param c collection containing elements to be removed from this set * @return {@code true} if this set changed as a result of the call - * @throws ClassCastException if the types of one or more elements in this - * set are incompatible with the specified collection + * @throws ClassCastException if the class of an element of this set + * is incompatible with the specified collection + * (optional) * @throws NullPointerException if the specified collection or any * of its elements are null */ @@ -346,20 +358,19 @@ public E pollLast() { /* ---------------- SortedSet operations -------------- */ - public Comparator comparator() { return m.comparator(); } /** - * @throws NoSuchElementException {@inheritDoc} + * @throws java.util.NoSuchElementException {@inheritDoc} */ public E first() { return m.firstKey(); } /** - * @throws NoSuchElementException {@inheritDoc} + * @throws java.util.NoSuchElementException {@inheritDoc} */ public E last() { return m.lastKey(); @@ -442,20 +453,42 @@ public NavigableSet descendingSet() { return new ConcurrentSkipListSet(m.descendingMap()); } + /** + * Returns a {@link Spliterator} over the elements in this set. + * + *

The {@code Spliterator} reports {@link Spliterator#CONCURRENT}, + * {@link Spliterator#NONNULL}, {@link Spliterator#DISTINCT}, + * {@link Spliterator#SORTED} and {@link Spliterator#ORDERED}, with an + * encounter order that is ascending order. Overriding implementations + * should document the reporting of additional characteristic values. + * + *

The spliterator's comparator (see + * {@link java.util.Spliterator#getComparator()}) is {@code null} if + * the set's comparator (see {@link #comparator()}) is {@code null}. + * Otherwise, the spliterator's comparator is the same as or imposes the + * same total ordering as the set's comparator. + * + * @return a {@code Spliterator} over the elements in this set + * @since 1.8 + */ + public Spliterator spliterator() { + return (m instanceof ConcurrentSkipListMap) + ? ((ConcurrentSkipListMap)m).keySpliterator() + : ((ConcurrentSkipListMap.SubMap)m).new SubMapKeyIterator(); + } + // Support for resetting map in clone private void setMap(ConcurrentNavigableMap map) { - UNSAFE.putObjectVolatile(this, mapOffset, map); + U.putObjectVolatile(this, MAP, map); } - private static final sun.misc.Unsafe UNSAFE; - private static final long mapOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long MAP; static { try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = ConcurrentSkipListSet.class; - mapOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("m")); - } catch (Exception e) { + MAP = U.objectFieldOffset + (ConcurrentSkipListSet.class.getDeclaredField("m")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } diff --git a/luni/src/main/java/java/util/concurrent/CopyOnWriteArrayList.java b/luni/src/main/java/java/util/concurrent/CopyOnWriteArrayList.java index 798eaafd9..c8e342ec5 100644 --- a/luni/src/main/java/java/util/concurrent/CopyOnWriteArrayList.java +++ b/luni/src/main/java/java/util/concurrent/CopyOnWriteArrayList.java @@ -1,783 +1,1542 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group. Adapted and released, under explicit permission, + * from JDK ArrayList.java which carries the following copyright: * - * Licensed 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 + * Copyright 1997 by Sun Microsystems, Inc., + * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A. + * All rights reserved. * - * 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. + * This software is the confidential and proprietary information + * of Sun Microsystems, Inc. ("Confidential Information"). You + * shall not disclose such Confidential Information and shall use + * it only in accordance with the terms of the license agreement + * you entered into with Sun. */ package java.util.concurrent; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; + import java.util.AbstractList; import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.function.Consumer; - +import java.util.function.Predicate; +import java.util.function.UnaryOperator; import libcore.util.EmptyArray; -import libcore.util.Objects; + +// BEGIN android-note +// removed link to collections framework docs from header +// END android-note /** - * A thread-safe random-access list. + * A thread-safe variant of {@link java.util.ArrayList} in which all mutative + * operations ({@code add}, {@code set}, and so on) are implemented by + * making a fresh copy of the underlying array. * - *

Read operations (including {@link #get}) do not block and may overlap with - * update operations. Reads reflect the results of the most recently completed - * operations. Aggregate operations like {@link #addAll} and {@link #clear} are - * atomic; they never expose an intermediate state. + *

This is ordinarily too costly, but may be more efficient + * than alternatives when traversal operations vastly outnumber + * mutations, and is useful when you cannot or don't want to + * synchronize traversals, yet need to preclude interference among + * concurrent threads. The "snapshot" style iterator method uses a + * reference to the state of the array at the point that the iterator + * was created. This array never changes during the lifetime of the + * iterator, so interference is impossible and the iterator is + * guaranteed not to throw {@code ConcurrentModificationException}. + * The iterator will not reflect additions, removals, or changes to + * the list since the iterator was created. Element-changing + * operations on iterators themselves ({@code remove}, {@code set}, and + * {@code add}) are not supported. These methods throw + * {@code UnsupportedOperationException}. * - *

Iterators of this list never throw {@link - * ConcurrentModificationException}. When an iterator is created, it keeps a - * copy of the list's contents. It is always safe to iterate this list, but - * iterations may not reflect the latest state of the list. + *

All elements are permitted, including {@code null}. * - *

Iterators returned by this list and its sub lists cannot modify the - * underlying list. In particular, {@link Iterator#remove}, {@link - * ListIterator#add} and {@link ListIterator#set} all throw {@link - * UnsupportedOperationException}. + *

Memory consistency effects: As with other concurrent + * collections, actions in a thread prior to placing an object into a + * {@code CopyOnWriteArrayList} + * happen-before + * actions subsequent to the access or removal of that element from + * the {@code CopyOnWriteArrayList} in another thread. * - *

This class offers extended API beyond the {@link List} interface. It - * includes additional overloads for indexed search ({@link #indexOf} and {@link - * #lastIndexOf}) and methods for conditional adds ({@link #addIfAbsent} and - * {@link #addAllAbsent}). + * @since 1.5 + * @author Doug Lea + * @param the type of elements held in this list */ -public class CopyOnWriteArrayList implements List, RandomAccess, Cloneable, Serializable { - +public class CopyOnWriteArrayList + implements List, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8673264195747942595L; /** - * Holds the latest snapshot of the list's data. This field is volatile so - * that data can be read without synchronization. As a consequence, all - * writes to this field must be atomic; it is an error to modify the - * contents of an array after it has been assigned to this field. - * - * Synchronization is required by all update operations. This defends - * against one update clobbering the result of another operation. For - * example, 100 threads simultaneously calling add() will grow the list's - * size by 100 when they have completed. No update operations are lost! - * - * Maintainers should be careful to read this field only once in - * non-blocking read methods. Write methods must be synchronized to avoid - * clobbering concurrent writes. + * The lock protecting all mutators. (We have a mild preference + * for builtin monitors over ReentrantLock when either will do.) */ - private transient volatile Object[] elements; + final transient Object lock = new Object(); + + /** The array, accessed only via getArray/setArray. */ + private transient volatile Object[] array; /** - * Creates a new empty instance. + * Gets the array. Non-private so as to also be accessible + * from CopyOnWriteArraySet class. */ - public CopyOnWriteArrayList() { - elements = EmptyArray.OBJECT; + final Object[] getArray() { + return array; } /** - * Creates a new instance containing the elements of {@code collection}. + * Sets the array. */ - @SuppressWarnings("unchecked") - public CopyOnWriteArrayList(Collection collection) { - this((E[]) collection.toArray()); + final void setArray(Object[] a) { + array = a; } /** - * Creates a new instance containing the elements of {@code array}. + * Creates an empty list. */ - public CopyOnWriteArrayList(E[] array) { - this.elements = Arrays.copyOf(array, array.length, Object[].class); + public CopyOnWriteArrayList() { + setArray(new Object[0]); } - @Override public Object clone() { - try { - CopyOnWriteArrayList result = (CopyOnWriteArrayList) super.clone(); - result.elements = result.elements.clone(); - return result; - } catch (CloneNotSupportedException e) { - throw new AssertionError(e); + /** + * Creates a list containing the elements of the specified + * collection, in the order they are returned by the collection's + * iterator. + * + * @param c the collection of initially held elements + * @throws NullPointerException if the specified collection is null + */ + public CopyOnWriteArrayList(Collection c) { + Object[] elements; + if (c.getClass() == CopyOnWriteArrayList.class) + elements = ((CopyOnWriteArrayList)c).getArray(); + else { + elements = c.toArray(); + // defend against c.toArray (incorrectly) not returning Object[] + // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652) + if (elements.getClass() != Object[].class) + elements = Arrays.copyOf(elements, elements.length, Object[].class); } + setArray(elements); } - public int size() { - return elements.length; + /** + * Creates a list holding a copy of the given array. + * + * @param toCopyIn the array (a copy of this array is used as the + * internal array) + * @throws NullPointerException if the specified array is null + */ + public CopyOnWriteArrayList(E[] toCopyIn) { + setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); } - @SuppressWarnings("unchecked") - public E get(int index) { - return (E) elements[index]; + /** + * Returns the number of elements in this list. + * + * @return the number of elements in this list + */ + public int size() { + return getArray().length; } - public boolean contains(Object o) { - return indexOf(o) != -1; + /** + * Returns {@code true} if this list contains no elements. + * + * @return {@code true} if this list contains no elements + */ + public boolean isEmpty() { + return size() == 0; } - public boolean containsAll(Collection collection) { - Object[] snapshot = elements; - return containsAll(collection, snapshot, 0, snapshot.length); + /** + * static version of indexOf, to allow repeated calls without + * needing to re-acquire array each time. + * @param o element to search for + * @param elements the array + * @param index first index to search + * @param fence one past last index to search + * @return index of element, or -1 if absent + */ + private static int indexOf(Object o, Object[] elements, + int index, int fence) { + if (o == null) { + for (int i = index; i < fence; i++) + if (elements[i] == null) + return i; + } else { + for (int i = index; i < fence; i++) + if (o.equals(elements[i])) + return i; + } + return -1; } - static boolean containsAll(Collection collection, Object[] snapshot, int from, int to) { - for (Object o : collection) { - if (indexOf(o, snapshot, from, to) == -1) { - return false; - } + /** + * static version of lastIndexOf. + * @param o element to search for + * @param elements the array + * @param index first index to search + * @return index of element, or -1 if absent + */ + private static int lastIndexOf(Object o, Object[] elements, int index) { + if (o == null) { + for (int i = index; i >= 0; i--) + if (elements[i] == null) + return i; + } else { + for (int i = index; i >= 0; i--) + if (o.equals(elements[i])) + return i; } - return true; + return -1; } /** - * Searches this list for {@code object} and returns the index of the first - * occurrence that is at or after {@code from}. + * Returns {@code true} if this list contains the specified element. + * More formally, returns {@code true} if and only if this list contains + * at least one element {@code e} such that {@code Objects.equals(o, e)}. * - * @return the index or -1 if the object was not found. + * @param o element whose presence in this list is to be tested + * @return {@code true} if this list contains the specified element */ - public int indexOf(E object, int from) { - Object[] snapshot = elements; - return indexOf(object, snapshot, from, snapshot.length); + public boolean contains(Object o) { + Object[] elements = getArray(); + return indexOf(o, elements, 0, elements.length) >= 0; } - public int indexOf(Object object) { - Object[] snapshot = elements; - return indexOf(object, snapshot, 0, snapshot.length); + /** + * {@inheritDoc} + */ + public int indexOf(Object o) { + Object[] elements = getArray(); + return indexOf(o, elements, 0, elements.length); } /** - * Searches this list for {@code object} and returns the index of the last - * occurrence that is before {@code to}. + * Returns the index of the first occurrence of the specified element in + * this list, searching forwards from {@code index}, or returns -1 if + * the element is not found. + * More formally, returns the lowest index {@code i} such that + * {@code i >= index && Objects.equals(get(i), e)}, + * or -1 if there is no such index. * - * @return the index or -1 if the object was not found. + * @param e element to search for + * @param index index to start searching from + * @return the index of the first occurrence of the element in + * this list at position {@code index} or later in the list; + * {@code -1} if the element is not found. + * @throws IndexOutOfBoundsException if the specified index is negative */ - public int lastIndexOf(E object, int to) { - Object[] snapshot = elements; - return lastIndexOf(object, snapshot, 0, to); + public int indexOf(E e, int index) { + Object[] elements = getArray(); + return indexOf(e, elements, index, elements.length); } - public int lastIndexOf(Object object) { - Object[] snapshot = elements; - return lastIndexOf(object, snapshot, 0, snapshot.length); + /** + * {@inheritDoc} + */ + public int lastIndexOf(Object o) { + Object[] elements = getArray(); + return lastIndexOf(o, elements, elements.length - 1); } - public boolean isEmpty() { - return elements.length == 0; + /** + * Returns the index of the last occurrence of the specified element in + * this list, searching backwards from {@code index}, or returns -1 if + * the element is not found. + * More formally, returns the highest index {@code i} such that + * {@code i <= index && Objects.equals(get(i), e)}, + * or -1 if there is no such index. + * + * @param e element to search for + * @param index index to start searching backwards from + * @return the index of the last occurrence of the element at position + * less than or equal to {@code index} in this list; + * -1 if the element is not found. + * @throws IndexOutOfBoundsException if the specified index is greater + * than or equal to the current size of this list + */ + public int lastIndexOf(E e, int index) { + Object[] elements = getArray(); + return lastIndexOf(e, elements, index); } /** - * Returns an {@link Iterator} that iterates over the elements of this list - * as they were at the time of this method call. Changes to the list made - * after this method call will not be reflected by the iterator, nor will - * they trigger a {@link ConcurrentModificationException}. + * Returns a shallow copy of this list. (The elements themselves + * are not copied.) * - *

The returned iterator does not support {@link Iterator#remove()}. + * @return a clone of this list */ - public Iterator iterator() { - Object[] snapshot = elements; - return new CowIterator(snapshot, 0, snapshot.length); + public Object clone() { + try { + @SuppressWarnings("unchecked") + CopyOnWriteArrayList clone = + (CopyOnWriteArrayList) super.clone(); + clone.resetLock(); + return clone; + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(); + } } /** - * Returns a {@link ListIterator} that iterates over the elements of this - * list as they were at the time of this method call. Changes to the list - * made after this method call will not be reflected by the iterator, nor - * will they trigger a {@link ConcurrentModificationException}. + * Returns an array containing all of the elements in this list + * in proper sequence (from first to last element). + * + *

The returned array will be "safe" in that no references to it are + * maintained by this list. (In other words, this method must allocate + * a new array). The caller is thus free to modify the returned array. * - *

The returned iterator does not support {@link ListIterator#add}, - * {@link ListIterator#set} or {@link Iterator#remove()}, + *

This method acts as bridge between array-based and collection-based + * APIs. + * + * @return an array containing all the elements in this list */ - public ListIterator listIterator(int index) { - Object[] snapshot = elements; - if (index < 0 || index > snapshot.length) { - throw new IndexOutOfBoundsException("index=" + index + ", length=" + snapshot.length); + public Object[] toArray() { + Object[] elements = getArray(); + return Arrays.copyOf(elements, elements.length); + } + + /** + * Returns an array containing all of the elements in this list in + * proper sequence (from first to last element); the runtime type of + * the returned array is that of the specified array. If the list fits + * in the specified array, it is returned therein. Otherwise, a new + * array is allocated with the runtime type of the specified array and + * the size of this list. + * + *

If this list fits in the specified array with room to spare + * (i.e., the array has more elements than this list), the element in + * the array immediately following the end of the list is set to + * {@code null}. (This is useful in determining the length of this + * list only if the caller knows that this list does not contain + * any null elements.) + * + *

Like the {@link #toArray()} method, this method acts as bridge between + * array-based and collection-based APIs. Further, this method allows + * precise control over the runtime type of the output array, and may, + * under certain circumstances, be used to save allocation costs. + * + *

Suppose {@code x} is a list known to contain only strings. + * The following code can be used to dump the list into a newly + * allocated array of {@code String}: + * + *

 {@code String[] y = x.toArray(new String[0]);}
+ * + * Note that {@code toArray(new Object[0])} is identical in function to + * {@code toArray()}. + * + * @param a the array into which the elements of the list are to + * be stored, if it is big enough; otherwise, a new array of the + * same runtime type is allocated for this purpose. + * @return an array containing all the elements in this list + * @throws ArrayStoreException if the runtime type of the specified array + * is not a supertype of the runtime type of every element in + * this list + * @throws NullPointerException if the specified array is null + */ + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + Object[] elements = getArray(); + int len = elements.length; + if (a.length < len) + return (T[]) Arrays.copyOf(elements, len, a.getClass()); + else { + System.arraycopy(elements, 0, a, 0, len); + if (a.length > len) + a[len] = null; + return a; } - CowIterator result = new CowIterator(snapshot, 0, snapshot.length); - result.index = index; - return result; + } + + // Positional Access Operations + + @SuppressWarnings("unchecked") + private E get(Object[] a, int index) { + return (E) a[index]; + } + + static String outOfBounds(int index, int size) { + return "Index: " + index + ", Size: " + size; } /** - * Equivalent to {@code listIterator(0)}. + * {@inheritDoc} + * + * @throws IndexOutOfBoundsException {@inheritDoc} */ - public ListIterator listIterator() { - Object[] snapshot = elements; - return new CowIterator(snapshot, 0, snapshot.length); + public E get(int index) { + return get(getArray(), index); } - public List subList(int from, int to) { - Object[] snapshot = elements; - if (from < 0 || from > to || to > snapshot.length) { - throw new IndexOutOfBoundsException("from=" + from + ", to=" + to + - ", list size=" + snapshot.length); + /** + * Replaces the element at the specified position in this list with the + * specified element. + * + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public E set(int index, E element) { + synchronized (lock) { + Object[] elements = getArray(); + E oldValue = get(elements, index); + + if (oldValue != element) { + int len = elements.length; + Object[] newElements = Arrays.copyOf(elements, len); + newElements[index] = element; + setArray(newElements); + } else { + // Not quite a no-op; ensures volatile write semantics + setArray(elements); + } + return oldValue; } - return new CowSubList(snapshot, from, to); } - public Object[] toArray() { - return elements.clone(); + /** + * Appends the specified element to the end of this list. + * + * @param e element to be appended to this list + * @return {@code true} (as specified by {@link Collection#add}) + */ + public boolean add(E e) { + synchronized (lock) { + Object[] elements = getArray(); + int len = elements.length; + Object[] newElements = Arrays.copyOf(elements, len + 1); + newElements[len] = e; + setArray(newElements); + return true; + } } - @SuppressWarnings({"unchecked","SuspiciousSystemArraycopy"}) - public T[] toArray(T[] contents) { - Object[] snapshot = elements; - if (snapshot.length > contents.length) { - return (T[]) Arrays.copyOf(snapshot, snapshot.length, contents.getClass()); + /** + * Inserts the specified element at the specified position in this + * list. Shifts the element currently at that position (if any) and + * any subsequent elements to the right (adds one to their indices). + * + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public void add(int index, E element) { + synchronized (lock) { + Object[] elements = getArray(); + int len = elements.length; + if (index > len || index < 0) + throw new IndexOutOfBoundsException(outOfBounds(index, len)); + Object[] newElements; + int numMoved = len - index; + if (numMoved == 0) + newElements = Arrays.copyOf(elements, len + 1); + else { + newElements = new Object[len + 1]; + System.arraycopy(elements, 0, newElements, 0, index); + System.arraycopy(elements, index, newElements, index + 1, + numMoved); + } + newElements[index] = element; + setArray(newElements); } - System.arraycopy(snapshot, 0, contents, 0, snapshot.length); - if (snapshot.length < contents.length) { - contents[snapshot.length] = null; + } + + /** + * Removes the element at the specified position in this list. + * Shifts any subsequent elements to the left (subtracts one from their + * indices). Returns the element that was removed from the list. + * + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public E remove(int index) { + synchronized (lock) { + Object[] elements = getArray(); + int len = elements.length; + E oldValue = get(elements, index); + int numMoved = len - index - 1; + if (numMoved == 0) + setArray(Arrays.copyOf(elements, len - 1)); + else { + Object[] newElements = new Object[len - 1]; + System.arraycopy(elements, 0, newElements, 0, index); + System.arraycopy(elements, index + 1, newElements, index, + numMoved); + setArray(newElements); + } + return oldValue; } - return contents; } - @Override public boolean equals(Object other) { - if (other instanceof CopyOnWriteArrayList) { - return this == other - || Arrays.equals(elements, ((CopyOnWriteArrayList) other).elements); - } else if (other instanceof List) { - Object[] snapshot = elements; - Iterator i = ((List) other).iterator(); - for (Object o : snapshot) { - if (!i.hasNext() || !Objects.equal(o, i.next())) { - return false; + /** + * Removes the first occurrence of the specified element from this list, + * if it is present. If this list does not contain the element, it is + * unchanged. More formally, removes the element with the lowest index + * {@code i} such that {@code Objects.equals(o, get(i))} + * (if such an element exists). Returns {@code true} if this list + * contained the specified element (or equivalently, if this list + * changed as a result of the call). + * + * @param o element to be removed from this list, if present + * @return {@code true} if this list contained the specified element + */ + public boolean remove(Object o) { + Object[] snapshot = getArray(); + int index = indexOf(o, snapshot, 0, snapshot.length); + return (index < 0) ? false : remove(o, snapshot, index); + } + + /** + * A version of remove(Object) using the strong hint that given + * recent snapshot contains o at the given index. + */ + private boolean remove(Object o, Object[] snapshot, int index) { + synchronized (lock) { + Object[] current = getArray(); + int len = current.length; + if (snapshot != current) findIndex: { + int prefix = Math.min(index, len); + for (int i = 0; i < prefix; i++) { + if (current[i] != snapshot[i] + && Objects.equals(o, current[i])) { + index = i; + break findIndex; + } } + if (index >= len) + return false; + if (current[index] == o) + break findIndex; + index = indexOf(o, current, index, len); + if (index < 0) + return false; } - return !i.hasNext(); - } else { - return false; + Object[] newElements = new Object[len - 1]; + System.arraycopy(current, 0, newElements, 0, index); + System.arraycopy(current, index + 1, + newElements, index, + len - index - 1); + setArray(newElements); + return true; } } - @Override public int hashCode() { - return Arrays.hashCode(elements); + /** + * Removes from this list all of the elements whose index is between + * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. + * Shifts any succeeding elements to the left (reduces their index). + * This call shortens the list by {@code (toIndex - fromIndex)} elements. + * (If {@code toIndex==fromIndex}, this operation has no effect.) + * + * @param fromIndex index of first element to be removed + * @param toIndex index after last element to be removed + * @throws IndexOutOfBoundsException if fromIndex or toIndex out of range + * ({@code fromIndex < 0 || toIndex > size() || toIndex < fromIndex}) + */ + void removeRange(int fromIndex, int toIndex) { + synchronized (lock) { + Object[] elements = getArray(); + int len = elements.length; + + if (fromIndex < 0 || toIndex > len || toIndex < fromIndex) + throw new IndexOutOfBoundsException(); + int newlen = len - (toIndex - fromIndex); + int numMoved = len - toIndex; + if (numMoved == 0) + setArray(Arrays.copyOf(elements, newlen)); + else { + Object[] newElements = new Object[newlen]; + System.arraycopy(elements, 0, newElements, 0, fromIndex); + System.arraycopy(elements, toIndex, newElements, + fromIndex, numMoved); + setArray(newElements); + } + } } - @Override public String toString() { - return Arrays.toString(elements); + /** + * Appends the element, if not present. + * + * @param e element to be added to this list, if absent + * @return {@code true} if the element was added + */ + public boolean addIfAbsent(E e) { + Object[] snapshot = getArray(); + return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : + addIfAbsent(e, snapshot); } - public synchronized boolean add(E e) { - Object[] newElements = new Object[elements.length + 1]; - System.arraycopy(elements, 0, newElements, 0, elements.length); - newElements[elements.length] = e; - elements = newElements; - return true; + /** + * A version of addIfAbsent using the strong hint that given + * recent snapshot does not contain e. + */ + private boolean addIfAbsent(E e, Object[] snapshot) { + synchronized (lock) { + Object[] current = getArray(); + int len = current.length; + if (snapshot != current) { + // Optimize for lost race to another addXXX operation + int common = Math.min(snapshot.length, len); + for (int i = 0; i < common; i++) + if (current[i] != snapshot[i] + && Objects.equals(e, current[i])) + return false; + if (indexOf(e, current, common, len) >= 0) + return false; + } + Object[] newElements = Arrays.copyOf(current, len + 1); + newElements[len] = e; + setArray(newElements); + return true; + } } - public synchronized void add(int index, E e) { - Object[] newElements = new Object[elements.length + 1]; - System.arraycopy(elements, 0, newElements, 0, index); - newElements[index] = e; - System.arraycopy(elements, index, newElements, index + 1, elements.length - index); - elements = newElements; + /** + * Returns {@code true} if this list contains all of the elements of the + * specified collection. + * + * @param c collection to be checked for containment in this list + * @return {@code true} if this list contains all of the elements of the + * specified collection + * @throws NullPointerException if the specified collection is null + * @see #contains(Object) + */ + public boolean containsAll(Collection c) { + Object[] elements = getArray(); + int len = elements.length; + for (Object e : c) { + if (indexOf(e, elements, 0, len) < 0) + return false; + } + return true; } - public synchronized boolean addAll(Collection collection) { - return addAll(elements.length, collection); + /** + * Removes from this list all of its elements that are contained in + * the specified collection. This is a particularly expensive operation + * in this class because of the need for an internal temporary array. + * + * @param c collection containing elements to be removed from this list + * @return {@code true} if this list changed as a result of the call + * @throws ClassCastException if the class of an element of this list + * is incompatible with the specified collection + * (optional) + * @throws NullPointerException if this list contains a null element and the + * specified collection does not permit null elements + * (optional), + * or if the specified collection is null + * @see #remove(Object) + */ + public boolean removeAll(Collection c) { + if (c == null) throw new NullPointerException(); + synchronized (lock) { + Object[] elements = getArray(); + int len = elements.length; + if (len != 0) { + // temp array holds those elements we know we want to keep + int newlen = 0; + Object[] temp = new Object[len]; + for (int i = 0; i < len; ++i) { + Object element = elements[i]; + if (!c.contains(element)) + temp[newlen++] = element; + } + if (newlen != len) { + setArray(Arrays.copyOf(temp, newlen)); + return true; + } + } + return false; + } } - public synchronized boolean addAll(int index, Collection collection) { - Object[] toAdd = collection.toArray(); - Object[] newElements = new Object[elements.length + toAdd.length]; - System.arraycopy(elements, 0, newElements, 0, index); - System.arraycopy(toAdd, 0, newElements, index, toAdd.length); - System.arraycopy(elements, index, - newElements, index + toAdd.length, elements.length - index); - elements = newElements; - return toAdd.length > 0; + /** + * Retains only the elements in this list that are contained in the + * specified collection. In other words, removes from this list all of + * its elements that are not contained in the specified collection. + * + * @param c collection containing elements to be retained in this list + * @return {@code true} if this list changed as a result of the call + * @throws ClassCastException if the class of an element of this list + * is incompatible with the specified collection + * (optional) + * @throws NullPointerException if this list contains a null element and the + * specified collection does not permit null elements + * (optional), + * or if the specified collection is null + * @see #remove(Object) + */ + public boolean retainAll(Collection c) { + if (c == null) throw new NullPointerException(); + synchronized (lock) { + Object[] elements = getArray(); + int len = elements.length; + if (len != 0) { + // temp array holds those elements we know we want to keep + int newlen = 0; + Object[] temp = new Object[len]; + for (int i = 0; i < len; ++i) { + Object element = elements[i]; + if (c.contains(element)) + temp[newlen++] = element; + } + if (newlen != len) { + setArray(Arrays.copyOf(temp, newlen)); + return true; + } + } + return false; + } } /** - * Adds the elements of {@code collection} that are not already present in - * this list. If {@code collection} includes a repeated value, at most one - * occurrence of that value will be added to this list. Elements are added - * at the end of this list. + * Appends all of the elements in the specified collection that + * are not already contained in this list, to the end of + * this list, in the order that they are returned by the + * specified collection's iterator. * - *

Callers of this method may prefer {@link CopyOnWriteArraySet}, whose - * API is more appropriate for set operations. + * @param c collection containing elements to be added to this list + * @return the number of elements added + * @throws NullPointerException if the specified collection is null + * @see #addIfAbsent(Object) */ - public synchronized int addAllAbsent(Collection collection) { - Object[] toAdd = collection.toArray(); - Object[] newElements = new Object[elements.length + toAdd.length]; - System.arraycopy(elements, 0, newElements, 0, elements.length); - int addedCount = 0; - for (Object o : toAdd) { - if (indexOf(o, newElements, 0, elements.length + addedCount) == -1) { - newElements[elements.length + addedCount++] = o; + public int addAllAbsent(Collection c) { + Object[] cs = c.toArray(); + if (cs.length == 0) + return 0; + synchronized (lock) { + Object[] elements = getArray(); + int len = elements.length; + int added = 0; + // uniquify and compact elements in cs + for (int i = 0; i < cs.length; ++i) { + Object e = cs[i]; + if (indexOf(e, elements, 0, len) < 0 && + indexOf(e, cs, 0, added) < 0) + cs[added++] = e; + } + if (added > 0) { + Object[] newElements = Arrays.copyOf(elements, len + added); + System.arraycopy(cs, 0, newElements, len, added); + setArray(newElements); } + return added; } - if (addedCount < toAdd.length) { - newElements = Arrays.copyOfRange( - newElements, 0, elements.length + addedCount); // trim to size + } + + /** + * Removes all of the elements from this list. + * The list will be empty after this call returns. + */ + public void clear() { + synchronized (lock) { + setArray(new Object[0]); } - elements = newElements; - return addedCount; } /** - * Adds {@code object} to the end of this list if it is not already present. + * Appends all of the elements in the specified collection to the end + * of this list, in the order that they are returned by the specified + * collection's iterator. * - *

Callers of this method may prefer {@link CopyOnWriteArraySet}, whose - * API is more appropriate for set operations. + * @param c collection containing elements to be added to this list + * @return {@code true} if this list changed as a result of the call + * @throws NullPointerException if the specified collection is null + * @see #add(Object) */ - public synchronized boolean addIfAbsent(E object) { - if (contains(object)) { + public boolean addAll(Collection c) { + Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ? + ((CopyOnWriteArrayList)c).getArray() : c.toArray(); + if (cs.length == 0) return false; + synchronized (lock) { + Object[] elements = getArray(); + int len = elements.length; + if (len == 0 && cs.getClass() == Object[].class) + setArray(cs); + else { + Object[] newElements = Arrays.copyOf(elements, len + cs.length); + System.arraycopy(cs, 0, newElements, len, cs.length); + setArray(newElements); + } + return true; } - add(object); - return true; } - @Override public synchronized void clear() { - elements = EmptyArray.OBJECT; + /** + * Inserts all of the elements in the specified collection into this + * list, starting at the specified position. Shifts the element + * currently at that position (if any) and any subsequent elements to + * the right (increases their indices). The new elements will appear + * in this list in the order that they are returned by the + * specified collection's iterator. + * + * @param index index at which to insert the first element + * from the specified collection + * @param c collection containing elements to be added to this list + * @return {@code true} if this list changed as a result of the call + * @throws IndexOutOfBoundsException {@inheritDoc} + * @throws NullPointerException if the specified collection is null + * @see #add(int,Object) + */ + public boolean addAll(int index, Collection c) { + Object[] cs = c.toArray(); + synchronized (lock) { + Object[] elements = getArray(); + int len = elements.length; + if (index > len || index < 0) + throw new IndexOutOfBoundsException(outOfBounds(index, len)); + if (cs.length == 0) + return false; + int numMoved = len - index; + Object[] newElements; + if (numMoved == 0) + newElements = Arrays.copyOf(elements, len + cs.length); + else { + newElements = new Object[len + cs.length]; + System.arraycopy(elements, 0, newElements, 0, index); + System.arraycopy(elements, index, + newElements, index + cs.length, + numMoved); + } + System.arraycopy(cs, 0, newElements, index, cs.length); + setArray(newElements); + return true; + } } - public synchronized E remove(int index) { - @SuppressWarnings("unchecked") - E removed = (E) elements[index]; - removeRange(index, index + 1); - return removed; + public void forEach(Consumer action) { + if (action == null) throw new NullPointerException(); + for (Object x : getArray()) { + @SuppressWarnings("unchecked") E e = (E) x; + action.accept(e); + } } - public synchronized boolean remove(Object o) { - int index = indexOf(o); - if (index == -1) { - return false; + public boolean removeIf(Predicate filter) { + if (filter == null) throw new NullPointerException(); + synchronized (lock) { + final Object[] elements = getArray(); + final int len = elements.length; + int i; + for (i = 0; i < len; i++) { + @SuppressWarnings("unchecked") E e = (E) elements[i]; + if (filter.test(e)) { + int newlen = i; + final Object[] newElements = new Object[len - 1]; + System.arraycopy(elements, 0, newElements, 0, newlen); + for (i++; i < len; i++) { + @SuppressWarnings("unchecked") E x = (E) elements[i]; + if (!filter.test(x)) + newElements[newlen++] = x; + } + setArray((newlen == len - 1) + ? newElements // one match => one copy + : Arrays.copyOf(newElements, newlen)); + return true; + } + } + return false; // zero matches => zero copies } - remove(index); - return true; } - public synchronized boolean removeAll(Collection collection) { - return removeOrRetain(collection, false, 0, elements.length) != 0; + public void replaceAll(UnaryOperator operator) { + if (operator == null) throw new NullPointerException(); + synchronized (lock) { + Object[] elements = getArray(); + int len = elements.length; + Object[] newElements = Arrays.copyOf(elements, len); + for (int i = 0; i < len; ++i) { + @SuppressWarnings("unchecked") E e = (E) elements[i]; + newElements[i] = operator.apply(e); + } + setArray(newElements); + } } - public synchronized boolean retainAll(Collection collection) { - return removeOrRetain(collection, true, 0, elements.length) != 0; + public void sort(Comparator c) { + synchronized (lock) { + Object[] elements = getArray(); + Object[] newElements = Arrays.copyOf(elements, elements.length); + @SuppressWarnings("unchecked") E[] es = (E[])newElements; + Arrays.sort(es, c); + setArray(newElements); + } } /** - * Removes or retains the elements in {@code collection}. Returns the number - * of elements removed. + * Saves this list to a stream (that is, serializes it). + * + * @param s the stream + * @throws java.io.IOException if an I/O error occurs + * @serialData The length of the array backing the list is emitted + * (int), followed by all of its elements (each an Object) + * in the proper order. */ - private int removeOrRetain(Collection collection, boolean retain, int from, int to) { - for (int i = from; i < to; i++) { - if (collection.contains(elements[i]) == retain) { - continue; - } + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { - /* - * We've encountered an element that must be removed! Create a new - * array and copy in the surviving elements one by one. - */ - Object[] newElements = new Object[elements.length - 1]; - System.arraycopy(elements, 0, newElements, 0, i); - int newSize = i; - for (int j = i + 1; j < to; j++) { - if (collection.contains(elements[j]) == retain) { - newElements[newSize++] = elements[j]; - } - } + s.defaultWriteObject(); - /* - * Copy the elements after 'to'. This is only useful for sub lists, - * where 'to' will be less than elements.length. - */ - System.arraycopy(elements, to, newElements, newSize, elements.length - to); - newSize += (elements.length - to); + Object[] elements = getArray(); + // Write out array length + s.writeInt(elements.length); - if (newSize < newElements.length) { - newElements = Arrays.copyOfRange(newElements, 0, newSize); // trim to size - } - int removed = elements.length - newElements.length; - elements = newElements; - return removed; - } + // Write out all elements in the proper order. + for (Object element : elements) + s.writeObject(element); + } + + /** + * Reconstitutes this list from a stream (that is, deserializes it). + * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws java.io.IOException if an I/O error occurs + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + + s.defaultReadObject(); - // we made it all the way through the loop without making any changes - return 0; + // bind to new lock + resetLock(); + + // Read in array length and allocate array + int len = s.readInt(); + Object[] elements = new Object[len]; + + // Read in all elements in the proper order. + for (int i = 0; i < len; i++) + elements[i] = s.readObject(); + setArray(elements); } - public synchronized E set(int index, E e) { - Object[] newElements = elements.clone(); - @SuppressWarnings("unchecked") - E result = (E) newElements[index]; - newElements[index] = e; - elements = newElements; - return result; + /** + * Returns a string representation of this list. The string + * representation consists of the string representations of the list's + * elements in the order they are returned by its iterator, enclosed in + * square brackets ({@code "[]"}). Adjacent elements are separated by + * the characters {@code ", "} (comma and space). Elements are + * converted to strings as by {@link String#valueOf(Object)}. + * + * @return a string representation of this list + */ + public String toString() { + return Arrays.toString(getArray()); } - private void removeRange(int from, int to) { - Object[] newElements = new Object[elements.length - (to - from)]; - System.arraycopy(elements, 0, newElements, 0, from); - System.arraycopy(elements, to, newElements, from, elements.length - to); - elements = newElements; + /** + * Compares the specified object with this list for equality. + * Returns {@code true} if the specified object is the same object + * as this object, or if it is also a {@link List} and the sequence + * of elements returned by an {@linkplain List#iterator() iterator} + * over the specified list is the same as the sequence returned by + * an iterator over this list. The two sequences are considered to + * be the same if they have the same length and corresponding + * elements at the same position in the sequence are equal. + * Two elements {@code e1} and {@code e2} are considered + * equal if {@code Objects.equals(e1, e2)}. + * + * @param o the object to be compared for equality with this list + * @return {@code true} if the specified object is equal to this list + */ + public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof List)) + return false; + + List list = (List)o; + Iterator it = list.iterator(); + Object[] elements = getArray(); + for (int i = 0, len = elements.length; i < len; i++) + if (!it.hasNext() || !Objects.equals(elements[i], it.next())) + return false; + if (it.hasNext()) + return false; + return true; } - static int lastIndexOf(Object o, Object[] data, int from, int to) { - if (o == null) { - for (int i = to - 1; i >= from; i--) { - if (data[i] == null) { - return i; - } - } - } else { - for (int i = to - 1; i >= from; i--) { - if (o.equals(data[i])) { - return i; - } - } - } - return -1; + /** + * Returns the hash code value for this list. + * + *

This implementation uses the definition in {@link List#hashCode}. + * + * @return the hash code value for this list + */ + public int hashCode() { + int hashCode = 1; + for (Object x : getArray()) + hashCode = 31 * hashCode + (x == null ? 0 : x.hashCode()); + return hashCode; } - static int indexOf(Object o, Object[] data, int from, int to) { - if (o == null) { - for (int i = from; i < to; i++) { - if (data[i] == null) { - return i; - } - } - } else { - for (int i = from; i < to; i++) { - if (o.equals(data[i])) { - return i; - } - } - } - return -1; + /** + * Returns an iterator over the elements in this list in proper sequence. + * + *

The returned iterator provides a snapshot of the state of the list + * when the iterator was constructed. No synchronization is needed while + * traversing the iterator. The iterator does NOT support the + * {@code remove} method. + * + * @return an iterator over the elements in this list in proper sequence + */ + public Iterator iterator() { + return new COWIterator(getArray(), 0); } - final Object[] getArray() { - // CopyOnWriteArraySet needs this. - return elements; + /** + * {@inheritDoc} + * + *

The returned iterator provides a snapshot of the state of the list + * when the iterator was constructed. No synchronization is needed while + * traversing the iterator. The iterator does NOT support the + * {@code remove}, {@code set} or {@code add} methods. + */ + public ListIterator listIterator() { + return new COWIterator(getArray(), 0); } /** - * The sub list is thread safe and supports non-blocking reads. Doing so is - * more difficult than in the full list, because each read needs to examine - * four fields worth of state: - * - the elements array of the full list - * - two integers for the bounds of this sub list - * - the expected elements array (to detect concurrent modification) + * {@inheritDoc} * - * This is accomplished by aggregating the sub list's three fields into a - * single snapshot object representing the current slice. This permits reads - * to be internally consistent without synchronization. This takes advantage - * of Java's concurrency semantics for final fields. + *

The returned iterator provides a snapshot of the state of the list + * when the iterator was constructed. No synchronization is needed while + * traversing the iterator. The iterator does NOT support the + * {@code remove}, {@code set} or {@code add} methods. + * + * @throws IndexOutOfBoundsException {@inheritDoc} */ - class CowSubList extends AbstractList { + public ListIterator listIterator(int index) { + Object[] elements = getArray(); + int len = elements.length; + if (index < 0 || index > len) + throw new IndexOutOfBoundsException(outOfBounds(index, len)); - /* - * An immutable snapshot of a sub list's state. By gathering all three - * of the sub list's fields in an immutable object, - */ - private volatile Slice slice; + return new COWIterator(elements, index); + } + + /** + * Returns a {@link Spliterator} over the elements in this list. + * + *

The {@code Spliterator} reports {@link Spliterator#IMMUTABLE}, + * {@link Spliterator#ORDERED}, {@link Spliterator#SIZED}, and + * {@link Spliterator#SUBSIZED}. + * + *

The spliterator provides a snapshot of the state of the list + * when the spliterator was constructed. No synchronization is needed while + * operating on the spliterator. + * + * @return a {@code Spliterator} over the elements in this list + * @since 1.8 + */ + public Spliterator spliterator() { + return Spliterators.spliterator + (getArray(), Spliterator.IMMUTABLE | Spliterator.ORDERED); + } - public CowSubList(Object[] expectedElements, int from, int to) { - this.slice = new Slice(expectedElements, from, to); + static final class COWIterator implements ListIterator { + /** Snapshot of the array */ + private final Object[] snapshot; + /** Index of element to be returned by subsequent call to next. */ + private int cursor; + + COWIterator(Object[] elements, int initialCursor) { + cursor = initialCursor; + snapshot = elements; + } + + public boolean hasNext() { + return cursor < snapshot.length; } - @Override public int size() { - Slice slice = this.slice; - return slice.to - slice.from; + public boolean hasPrevious() { + return cursor > 0; } - @Override public boolean isEmpty() { - Slice slice = this.slice; - return slice.from == slice.to; + @SuppressWarnings("unchecked") + public E next() { + if (! hasNext()) + throw new NoSuchElementException(); + return (E) snapshot[cursor++]; } @SuppressWarnings("unchecked") - @Override public E get(int index) { - Slice slice = this.slice; - Object[] snapshot = elements; - slice.checkElementIndex(index); - slice.checkConcurrentModification(snapshot); - return (E) snapshot[index + slice.from]; + public E previous() { + if (! hasPrevious()) + throw new NoSuchElementException(); + return (E) snapshot[--cursor]; } - @Override public Iterator iterator() { - return listIterator(0); + public int nextIndex() { + return cursor; } - @Override public ListIterator listIterator() { - return listIterator(0); + public int previousIndex() { + return cursor-1; } - @Override public ListIterator listIterator(int index) { - Slice slice = this.slice; - Object[] snapshot = elements; - slice.checkPositionIndex(index); - slice.checkConcurrentModification(snapshot); - CowIterator result = new CowIterator(snapshot, slice.from, slice.to); - result.index = slice.from + index; - return result; + /** + * Not supported. Always throws UnsupportedOperationException. + * @throws UnsupportedOperationException always; {@code remove} + * is not supported by this iterator. + */ + public void remove() { + throw new UnsupportedOperationException(); } - @Override public int indexOf(Object object) { - Slice slice = this.slice; - Object[] snapshot = elements; - slice.checkConcurrentModification(snapshot); - int result = CopyOnWriteArrayList.indexOf(object, snapshot, slice.from, slice.to); - return (result != -1) ? (result - slice.from) : -1; + /** + * Not supported. Always throws UnsupportedOperationException. + * @throws UnsupportedOperationException always; {@code set} + * is not supported by this iterator. + */ + public void set(E e) { + throw new UnsupportedOperationException(); } - @Override public int lastIndexOf(Object object) { - Slice slice = this.slice; - Object[] snapshot = elements; - slice.checkConcurrentModification(snapshot); - int result = CopyOnWriteArrayList.lastIndexOf(object, snapshot, slice.from, slice.to); - return (result != -1) ? (result - slice.from) : -1; + /** + * Not supported. Always throws UnsupportedOperationException. + * @throws UnsupportedOperationException always; {@code add} + * is not supported by this iterator. + */ + public void add(E e) { + throw new UnsupportedOperationException(); } - @Override public boolean contains(Object object) { - return indexOf(object) != -1; + @Override + @SuppressWarnings("unchecked") + public void forEachRemaining(Consumer action) { + Objects.requireNonNull(action); + final int size = snapshot.length; + for (int i = cursor; i < size; i++) { + action.accept((E) snapshot[i]); + } + cursor = size; } + } - @Override public boolean containsAll(Collection collection) { - Slice slice = this.slice; - Object[] snapshot = elements; - slice.checkConcurrentModification(snapshot); - return CopyOnWriteArrayList.containsAll(collection, snapshot, slice.from, slice.to); + /** + * Returns a view of the portion of this list between + * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. + * The returned list is backed by this list, so changes in the + * returned list are reflected in this list. + * + *

The semantics of the list returned by this method become + * undefined if the backing list (i.e., this list) is modified in + * any way other than via the returned list. + * + * @param fromIndex low endpoint (inclusive) of the subList + * @param toIndex high endpoint (exclusive) of the subList + * @return a view of the specified range within this list + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public List subList(int fromIndex, int toIndex) { + synchronized (lock) { + Object[] elements = getArray(); + int len = elements.length; + if (fromIndex < 0 || toIndex > len || fromIndex > toIndex) + throw new IndexOutOfBoundsException(); + return new COWSubList(this, fromIndex, toIndex); + } + } + + /** + * Sublist for CopyOnWriteArrayList. + * This class extends AbstractList merely for convenience, to + * avoid having to define addAll, etc. This doesn't hurt, but + * is wasteful. This class does not need or use modCount + * mechanics in AbstractList, but does need to check for + * concurrent modification using similar mechanics. On each + * operation, the array that we expect the backing list to use + * is checked and updated. Since we do this for all of the + * base operations invoked by those defined in AbstractList, + * all is well. While inefficient, this is not worth + * improving. The kinds of list operations inherited from + * AbstractList are already so slow on COW sublists that + * adding a bit more space/time doesn't seem even noticeable. + */ + private static class COWSubList + extends AbstractList + implements RandomAccess + { + private final CopyOnWriteArrayList l; + private final int offset; + private int size; + private Object[] expectedArray; + + // only call this holding l's lock + COWSubList(CopyOnWriteArrayList list, + int fromIndex, int toIndex) { + // assert Thread.holdsLock(list.lock); + l = list; + expectedArray = l.getArray(); + offset = fromIndex; + size = toIndex - fromIndex; } - @Override public List subList(int from, int to) { - Slice slice = this.slice; - if (from < 0 || from > to || to > size()) { - throw new IndexOutOfBoundsException("from=" + from + ", to=" + to + - ", list size=" + size()); + // only call this holding l's lock + private void checkForComodification() { + // assert Thread.holdsLock(l.lock); + if (l.getArray() != expectedArray) + throw new ConcurrentModificationException(); + } + + // only call this holding l's lock + private void rangeCheck(int index) { + // assert Thread.holdsLock(l.lock); + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException(outOfBounds(index, size)); + } + + public E set(int index, E element) { + synchronized (l.lock) { + rangeCheck(index); + checkForComodification(); + E x = l.set(index+offset, element); + expectedArray = l.getArray(); + return x; } - return new CowSubList(slice.expectedElements, slice.from + from, slice.from + to); } - @Override public E remove(int index) { - synchronized (CopyOnWriteArrayList.this) { - slice.checkElementIndex(index); - slice.checkConcurrentModification(elements); - E removed = CopyOnWriteArrayList.this.remove(slice.from + index); - slice = new Slice(elements, slice.from, slice.to - 1); - return removed; + public E get(int index) { + synchronized (l.lock) { + rangeCheck(index); + checkForComodification(); + return l.get(index+offset); } } - @Override public void clear() { - synchronized (CopyOnWriteArrayList.this) { - slice.checkConcurrentModification(elements); - CopyOnWriteArrayList.this.removeRange(slice.from, slice.to); - slice = new Slice(elements, slice.from, slice.from); + public int size() { + synchronized (l.lock) { + checkForComodification(); + return size; } } - @Override public void add(int index, E object) { - synchronized (CopyOnWriteArrayList.this) { - slice.checkPositionIndex(index); - slice.checkConcurrentModification(elements); - CopyOnWriteArrayList.this.add(index + slice.from, object); - slice = new Slice(elements, slice.from, slice.to + 1); + public void add(int index, E element) { + synchronized (l.lock) { + checkForComodification(); + if (index < 0 || index > size) + throw new IndexOutOfBoundsException + (outOfBounds(index, size)); + l.add(index+offset, element); + expectedArray = l.getArray(); + size++; } } - @Override public boolean add(E object) { - synchronized (CopyOnWriteArrayList.this) { - add(slice.to - slice.from, object); - return true; + public void clear() { + synchronized (l.lock) { + checkForComodification(); + l.removeRange(offset, offset+size); + expectedArray = l.getArray(); + size = 0; } } - @Override public boolean addAll(int index, Collection collection) { - synchronized (CopyOnWriteArrayList.this) { - slice.checkPositionIndex(index); - slice.checkConcurrentModification(elements); - int oldSize = elements.length; - boolean result = CopyOnWriteArrayList.this.addAll(index + slice.from, collection); - slice = new Slice(elements, slice.from, slice.to + (elements.length - oldSize)); + public E remove(int index) { + synchronized (l.lock) { + rangeCheck(index); + checkForComodification(); + E result = l.remove(index+offset); + expectedArray = l.getArray(); + size--; return result; } } - @Override public boolean addAll(Collection collection) { - synchronized (CopyOnWriteArrayList.this) { - return addAll(size(), collection); + public boolean remove(Object o) { + int index = indexOf(o); + if (index == -1) + return false; + remove(index); + return true; + } + + public Iterator iterator() { + synchronized (l.lock) { + checkForComodification(); + return new COWSubListIterator(l, 0, offset, size); } } - @Override public E set(int index, E object) { - synchronized (CopyOnWriteArrayList.this) { - slice.checkElementIndex(index); - slice.checkConcurrentModification(elements); - E result = CopyOnWriteArrayList.this.set(index + slice.from, object); - slice = new Slice(elements, slice.from, slice.to); - return result; + public ListIterator listIterator(int index) { + synchronized (l.lock) { + checkForComodification(); + if (index < 0 || index > size) + throw new IndexOutOfBoundsException + (outOfBounds(index, size)); + return new COWSubListIterator(l, index, offset, size); } } - @Override public boolean remove(Object object) { - synchronized (CopyOnWriteArrayList.this) { - int index = indexOf(object); - if (index == -1) { - return false; - } - remove(index); - return true; + public List subList(int fromIndex, int toIndex) { + synchronized (l.lock) { + checkForComodification(); + if (fromIndex < 0 || toIndex > size || fromIndex > toIndex) + throw new IndexOutOfBoundsException(); + return new COWSubList(l, fromIndex + offset, + toIndex + offset); } } - @Override public boolean removeAll(Collection collection) { - synchronized (CopyOnWriteArrayList.this) { - slice.checkConcurrentModification(elements); - int removed = removeOrRetain(collection, false, slice.from, slice.to); - slice = new Slice(elements, slice.from, slice.to - removed); - return removed != 0; + public void forEach(Consumer action) { + if (action == null) throw new NullPointerException(); + int lo = offset; + int hi = offset + size; + Object[] a = expectedArray; + if (l.getArray() != a) + throw new ConcurrentModificationException(); + if (lo < 0 || hi > a.length) + throw new IndexOutOfBoundsException(); + for (int i = lo; i < hi; ++i) { + @SuppressWarnings("unchecked") E e = (E) a[i]; + action.accept(e); } } - @Override public boolean retainAll(Collection collection) { - synchronized (CopyOnWriteArrayList.this) { - slice.checkConcurrentModification(elements); - int removed = removeOrRetain(collection, true, slice.from, slice.to); - slice = new Slice(elements, slice.from, slice.to - removed); - return removed != 0; + public void replaceAll(UnaryOperator operator) { + if (operator == null) throw new NullPointerException(); + synchronized (l.lock) { + int lo = offset; + int hi = offset + size; + Object[] elements = expectedArray; + if (l.getArray() != elements) + throw new ConcurrentModificationException(); + int len = elements.length; + if (lo < 0 || hi > len) + throw new IndexOutOfBoundsException(); + Object[] newElements = Arrays.copyOf(elements, len); + for (int i = lo; i < hi; ++i) { + @SuppressWarnings("unchecked") E e = (E) elements[i]; + newElements[i] = operator.apply(e); + } + l.setArray(expectedArray = newElements); } } - } - static class Slice { - private final Object[] expectedElements; - private final int from; - private final int to; + public void sort(Comparator c) { + synchronized (l.lock) { + int lo = offset; + int hi = offset + size; + Object[] elements = expectedArray; + if (l.getArray() != elements) + throw new ConcurrentModificationException(); + int len = elements.length; + if (lo < 0 || hi > len) + throw new IndexOutOfBoundsException(); + Object[] newElements = Arrays.copyOf(elements, len); + @SuppressWarnings("unchecked") E[] es = (E[])newElements; + Arrays.sort(es, lo, hi, c); + l.setArray(expectedArray = newElements); + } + } - Slice(Object[] expectedElements, int from, int to) { - this.expectedElements = expectedElements; - this.from = from; - this.to = to; + public boolean removeAll(Collection c) { + if (c == null) throw new NullPointerException(); + boolean removed = false; + synchronized (l.lock) { + int n = size; + if (n > 0) { + int lo = offset; + int hi = offset + n; + Object[] elements = expectedArray; + if (l.getArray() != elements) + throw new ConcurrentModificationException(); + int len = elements.length; + if (lo < 0 || hi > len) + throw new IndexOutOfBoundsException(); + int newSize = 0; + Object[] temp = new Object[n]; + for (int i = lo; i < hi; ++i) { + Object element = elements[i]; + if (!c.contains(element)) + temp[newSize++] = element; + } + if (newSize != n) { + Object[] newElements = new Object[len - n + newSize]; + System.arraycopy(elements, 0, newElements, 0, lo); + System.arraycopy(temp, 0, newElements, lo, newSize); + System.arraycopy(elements, hi, newElements, + lo + newSize, len - hi); + size = newSize; + removed = true; + l.setArray(expectedArray = newElements); + } + } + } + return removed; } - /** - * Throws if {@code index} doesn't identify an element in the array. - */ - void checkElementIndex(int index) { - if (index < 0 || index >= to - from) { - throw new IndexOutOfBoundsException("index=" + index + ", size=" + (to - from)); + public boolean retainAll(Collection c) { + if (c == null) throw new NullPointerException(); + boolean removed = false; + synchronized (l.lock) { + int n = size; + if (n > 0) { + int lo = offset; + int hi = offset + n; + Object[] elements = expectedArray; + if (l.getArray() != elements) + throw new ConcurrentModificationException(); + int len = elements.length; + if (lo < 0 || hi > len) + throw new IndexOutOfBoundsException(); + int newSize = 0; + Object[] temp = new Object[n]; + for (int i = lo; i < hi; ++i) { + Object element = elements[i]; + if (c.contains(element)) + temp[newSize++] = element; + } + if (newSize != n) { + Object[] newElements = new Object[len - n + newSize]; + System.arraycopy(elements, 0, newElements, 0, lo); + System.arraycopy(temp, 0, newElements, lo, newSize); + System.arraycopy(elements, hi, newElements, + lo + newSize, len - hi); + size = newSize; + removed = true; + l.setArray(expectedArray = newElements); + } + } } + return removed; } - /** - * Throws if {@code index} doesn't identify an insertion point in the - * array. Unlike element index, it's okay to add or iterate at size(). - */ - void checkPositionIndex(int index) { - if (index < 0 || index > to - from) { - throw new IndexOutOfBoundsException("index=" + index + ", size=" + (to - from)); + public boolean removeIf(Predicate filter) { + if (filter == null) throw new NullPointerException(); + boolean removed = false; + synchronized (l.lock) { + int n = size; + if (n > 0) { + int lo = offset; + int hi = offset + n; + Object[] elements = expectedArray; + if (l.getArray() != elements) + throw new ConcurrentModificationException(); + int len = elements.length; + if (lo < 0 || hi > len) + throw new IndexOutOfBoundsException(); + int newSize = 0; + Object[] temp = new Object[n]; + for (int i = lo; i < hi; ++i) { + @SuppressWarnings("unchecked") E e = (E) elements[i]; + if (!filter.test(e)) + temp[newSize++] = e; + } + if (newSize != n) { + Object[] newElements = new Object[len - n + newSize]; + System.arraycopy(elements, 0, newElements, 0, lo); + System.arraycopy(temp, 0, newElements, lo, newSize); + System.arraycopy(elements, hi, newElements, + lo + newSize, len - hi); + size = newSize; + removed = true; + l.setArray(expectedArray = newElements); + } + } } + return removed; } - void checkConcurrentModification(Object[] snapshot) { - if (expectedElements != snapshot) { + public Spliterator spliterator() { + int lo = offset; + int hi = offset + size; + Object[] a = expectedArray; + if (l.getArray() != a) throw new ConcurrentModificationException(); - } + if (lo < 0 || hi > a.length) + throw new IndexOutOfBoundsException(); + return Spliterators.spliterator + (a, lo, hi, Spliterator.IMMUTABLE | Spliterator.ORDERED); } - } - /** - * Iterates an immutable snapshot of the list. - */ - static class CowIterator implements ListIterator { - private final Object[] snapshot; - private final int from; - private final int to; - private int index = 0; + } - CowIterator(Object[] snapshot, int from, int to) { - this.snapshot = snapshot; - this.from = from; - this.to = to; - this.index = from; - } + private static class COWSubListIterator implements ListIterator { + private final ListIterator it; + private final int offset; + private final int size; - public void add(E object) { - throw new UnsupportedOperationException(); + COWSubListIterator(List l, int index, int offset, int size) { + this.offset = offset; + this.size = size; + it = l.listIterator(index+offset); } public boolean hasNext() { - return index < to; + return nextIndex() < size; } - public boolean hasPrevious() { - return index > from; - } - - @SuppressWarnings("unchecked") public E next() { - if (index < to) { - return (E) snapshot[index++]; - } else { + if (hasNext()) + return it.next(); + else throw new NoSuchElementException(); - } } - public int nextIndex() { - return index; + public boolean hasPrevious() { + return previousIndex() >= 0; } - @SuppressWarnings("unchecked") public E previous() { - if (index > from) { - return (E) snapshot[--index]; - } else { + if (hasPrevious()) + return it.previous(); + else throw new NoSuchElementException(); - } + } + + public int nextIndex() { + return it.nextIndex() - offset; } public int previousIndex() { - return index - 1; + return it.previousIndex() - offset; } public void remove() { throw new UnsupportedOperationException(); } - public void set(E object) { + public void set(E e) { + throw new UnsupportedOperationException(); + } + + public void add(E e) { throw new UnsupportedOperationException(); } @Override + @SuppressWarnings("unchecked") public void forEachRemaining(Consumer action) { - java.util.Objects.requireNonNull(action); - Object[] elements = snapshot; - for (int i = index; i < to; i++) { - @SuppressWarnings("unchecked") E e = (E) elements[i]; - action.accept(e); + Objects.requireNonNull(action); + while (nextIndex() < size) { + action.accept(it.next()); } - index = to; } } - private void writeObject(ObjectOutputStream out) throws IOException { - Object[] snapshot = elements; - out.defaultWriteObject(); - out.writeInt(snapshot.length); - for (Object o : snapshot) { - out.writeObject(o); - } + // Support for resetting lock while deserializing + private void resetLock() { + U.putObjectVolatile(this, LOCK, new Object()); } - - private synchronized void readObject(ObjectInputStream in) - throws IOException, ClassNotFoundException { - in.defaultReadObject(); - Object[] snapshot = new Object[in.readInt()]; - for (int i = 0; i < snapshot.length; i++) { - snapshot[i] = in.readObject(); + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long LOCK; + static { + try { + LOCK = U.objectFieldOffset + (CopyOnWriteArrayList.class.getDeclaredField("lock")); + } catch (ReflectiveOperationException e) { + throw new Error(e); } - elements = snapshot; } } diff --git a/luni/src/main/java/java/util/concurrent/CopyOnWriteArraySet.java b/luni/src/main/java/java/util/concurrent/CopyOnWriteArraySet.java index 347ed1434..0cf855849 100644 --- a/luni/src/main/java/java/util/concurrent/CopyOnWriteArraySet.java +++ b/luni/src/main/java/java/util/concurrent/CopyOnWriteArraySet.java @@ -6,10 +6,19 @@ package java.util.concurrent; -import java.util.*; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.function.Predicate; // BEGIN android-note // removed link to collections framework docs +// fixed framework docs link to "Collection#optional" // END android-note /** @@ -35,12 +44,12 @@ * copy-on-write set to maintain a set of Handler objects that * perform some action upon state updates. * - *

 {@code
+ * 
 {@code
  * class Handler { void handle(); ... }
  *
  * class X {
  *   private final CopyOnWriteArraySet handlers
- *     = new CopyOnWriteArraySet();
+ *     = new CopyOnWriteArraySet<>();
  *   public void addHandler(Handler h) { handlers.add(h); }
  *
  *   private long internalState;
@@ -56,7 +65,7 @@
  * @see CopyOnWriteArrayList
  * @since 1.5
  * @author Doug Lea
- * @param  the type of elements held in this collection
+ * @param  the type of elements held in this set
  */
 public class CopyOnWriteArraySet extends AbstractSet
         implements java.io.Serializable {
@@ -79,8 +88,15 @@ public CopyOnWriteArraySet() {
      * @throws NullPointerException if the specified collection is null
      */
     public CopyOnWriteArraySet(Collection c) {
-        al = new CopyOnWriteArrayList();
-        al.addAllAbsent(c);
+        if (c.getClass() == CopyOnWriteArraySet.class) {
+            @SuppressWarnings("unchecked") CopyOnWriteArraySet cc =
+                (CopyOnWriteArraySet)c;
+            al = new CopyOnWriteArrayList(cc.al);
+        }
+        else {
+            al = new CopyOnWriteArrayList();
+            al.addAllAbsent(c);
+        }
     }
 
     /**
@@ -104,8 +120,7 @@ public boolean isEmpty() {
     /**
      * Returns {@code true} if this set contains the specified element.
      * More formally, returns {@code true} if and only if this set
-     * contains an element {@code e} such that
-     * (o==null ? e==null : o.equals(e)).
+     * contains an element {@code e} such that {@code Objects.equals(o, e)}.
      *
      * @param o element whose presence in this set is to be tested
      * @return {@code true} if this set contains the specified element
@@ -161,7 +176,7 @@ public Object[] toArray() {
      * The following code can be used to dump the set into a newly allocated
      * array of {@code String}:
      *
-     *  
 {@code String[] y = x.toArray(new String[0]);}
+ *
 {@code String[] y = x.toArray(new String[0]);}
* * Note that {@code toArray(new Object[0])} is identical in function to * {@code toArray()}. @@ -190,11 +205,10 @@ public void clear() { /** * Removes the specified element from this set if it is present. * More formally, removes an element {@code e} such that - * (o==null ? e==null : o.equals(e)), - * if this set contains such an element. Returns {@code true} if - * this set contained the element (or equivalently, if this set - * changed as a result of the call). (This set will not contain the - * element once the call returns.) + * {@code Objects.equals(o, e)}, if this set contains such an element. + * Returns {@code true} if this set contained the element (or + * equivalently, if this set changed as a result of the call). + * (This set will not contain the element once the call returns.) * * @param o object to be removed from this set, if present * @return {@code true} if this set contained the specified element @@ -207,7 +221,7 @@ public boolean remove(Object o) { * Adds the specified element to this set if it is not already present. * More formally, adds the specified element {@code e} to this set if * the set contains no element {@code e2} such that - * (e==null ? e2==null : e.equals(e2)). + * {@code Objects.equals(e, e2)}. * If this set already contains the element, the call leaves the set * unchanged and returns {@code false}. * @@ -231,7 +245,44 @@ public boolean add(E e) { * @see #contains(Object) */ public boolean containsAll(Collection c) { - return al.containsAll(c); + return (c instanceof Set) + ? compareSets(al.getArray(), (Set) c) >= 0 + : al.containsAll(c); + } + + /** + * Tells whether the objects in snapshot (regarded as a set) are a + * superset of the given set. + * + * @return -1 if snapshot is not a superset, 0 if the two sets + * contain precisely the same elements, and 1 if snapshot is a + * proper superset of the given set + */ + private static int compareSets(Object[] snapshot, Set set) { + // Uses O(n^2) algorithm, that is only appropriate for small + // sets, which CopyOnWriteArraySets should be. + // + // Optimize up to O(n) if the two sets share a long common prefix, + // as might happen if one set was created as a copy of the other set. + + final int len = snapshot.length; + // Mark matched elements to avoid re-checking + final boolean[] matched = new boolean[len]; + + // j is the largest int with matched[i] true for { i | 0 <= i < j } + int j = 0; + outer: for (Object x : set) { + for (int i = j; i < len; i++) { + if (!matched[i] && Objects.equals(x, snapshot[i])) { + matched[i] = true; + if (i == j) + do { j++; } while (j < len && matched[j]); + continue outer; + } + } + return -1; + } + return (j == len) ? 0 : 1; } /** @@ -260,9 +311,11 @@ public boolean addAll(Collection c) { * @param c collection containing elements to be removed from this set * @return {@code true} if this set changed as a result of the call * @throws ClassCastException if the class of an element of this set - * is incompatible with the specified collection (optional) + * is incompatible with the specified collection + * (optional) * @throws NullPointerException if this set contains a null element and the - * specified collection does not permit null elements (optional), + * specified collection does not permit null elements + * (optional), * or if the specified collection is null * @see #remove(Object) */ @@ -281,9 +334,11 @@ public boolean removeAll(Collection c) { * @param c collection containing elements to be retained in this set * @return {@code true} if this set changed as a result of the call * @throws ClassCastException if the class of an element of this set - * is incompatible with the specified collection (optional) + * is incompatible with the specified collection + * (optional) * @throws NullPointerException if this set contains a null element and the - * specified collection does not permit null elements (optional), + * specified collection does not permit null elements + * (optional), * or if the specified collection is null * @see #remove(Object) */ @@ -310,54 +365,49 @@ public Iterator iterator() { * Compares the specified object with this set for equality. * Returns {@code true} if the specified object is the same object * as this object, or if it is also a {@link Set} and the elements - * returned by an {@linkplain List#iterator() iterator} over the + * returned by an {@linkplain Set#iterator() iterator} over the * specified set are the same as the elements returned by an * iterator over this set. More formally, the two iterators are * considered to return the same elements if they return the same * number of elements and for every element {@code e1} returned by * the iterator over the specified set, there is an element * {@code e2} returned by the iterator over this set such that - * {@code (e1==null ? e2==null : e1.equals(e2))}. + * {@code Objects.equals(e1, e2)}. * * @param o object to be compared for equality with this set * @return {@code true} if the specified object is equal to this set */ public boolean equals(Object o) { - if (o == this) - return true; - if (!(o instanceof Set)) - return false; - Set set = (Set)(o); - Iterator it = set.iterator(); + return (o == this) + || ((o instanceof Set) + && compareSets(al.getArray(), (Set) o) == 0); + } - // Uses O(n^2) algorithm that is only appropriate - // for small sets, which CopyOnWriteArraySets should be. + public boolean removeIf(Predicate filter) { + return al.removeIf(filter); + } - // Use a single snapshot of underlying array - Object[] elements = al.getArray(); - int len = elements.length; - // Mark matched elements to avoid re-checking - boolean[] matched = new boolean[len]; - int k = 0; - outer: while (it.hasNext()) { - if (++k > len) - return false; - Object x = it.next(); - for (int i = 0; i < len; ++i) { - if (!matched[i] && eq(x, elements[i])) { - matched[i] = true; - continue outer; - } - } - return false; - } - return k == len; + public void forEach(Consumer action) { + al.forEach(action); } /** - * Tests for equality, coping with nulls. + * Returns a {@link Spliterator} over the elements in this set in the order + * in which these elements were added. + * + *

The {@code Spliterator} reports {@link Spliterator#IMMUTABLE}, + * {@link Spliterator#DISTINCT}, {@link Spliterator#SIZED}, and + * {@link Spliterator#SUBSIZED}. + * + *

The spliterator provides a snapshot of the state of the set + * when the spliterator was constructed. No synchronization is needed while + * operating on the spliterator. + * + * @return a {@code Spliterator} over the elements in this set + * @since 1.8 */ - private static boolean eq(Object o1, Object o2) { - return (o1 == null) ? o2 == null : o1.equals(o2); + public Spliterator spliterator() { + return Spliterators.spliterator + (al.getArray(), Spliterator.IMMUTABLE | Spliterator.DISTINCT); } } diff --git a/luni/src/main/java/java/util/concurrent/CountDownLatch.java b/luni/src/main/java/java/util/concurrent/CountDownLatch.java index 77093f790..680ea1668 100644 --- a/luni/src/main/java/java/util/concurrent/CountDownLatch.java +++ b/luni/src/main/java/java/util/concurrent/CountDownLatch.java @@ -44,7 +44,7 @@ * until all workers have completed. * * - *

 {@code
+ * 
 {@code
  * class Driver { // ...
  *   void main() throws InterruptedException {
  *     CountDownLatch startSignal = new CountDownLatch(1);
@@ -85,7 +85,7 @@
  * will be able to pass through await. (When threads must repeatedly
  * count down in this way, instead use a {@link CyclicBarrier}.)
  *
- *  
 {@code
+ * 
 {@code
  * class Driver2 { // ...
  *   void main() throws InterruptedException {
  *     CountDownLatch doneSignal = new CountDownLatch(N);
@@ -151,7 +151,7 @@ protected boolean tryReleaseShared(int releases) {
                 int c = getState();
                 if (c == 0)
                     return false;
-                int nextc = c-1;
+                int nextc = c - 1;
                 if (compareAndSetState(c, nextc))
                     return nextc == 0;
             }
diff --git a/luni/src/main/java/java/util/concurrent/CountedCompleter.java b/luni/src/main/java/java/util/concurrent/CountedCompleter.java
index b8680378a..9c8b1b28e 100644
--- a/luni/src/main/java/java/util/concurrent/CountedCompleter.java
+++ b/luni/src/main/java/java/util/concurrent/CountedCompleter.java
@@ -13,7 +13,7 @@
  * presence of subtask stalls and blockage than are other forms of
  * ForkJoinTasks, but are less intuitive to program.  Uses of
  * CountedCompleter are similar to those of other completion based
- * components (such as {@link java.nio.channels.CompletionHandler})
+ * components
  * except that multiple pending completions may be necessary
  * to trigger the completion action {@link #onCompletion(CountedCompleter)},
  * not just one.
@@ -139,7 +139,8 @@
  * {@code tryComplete}) the pending count is set to one:
  *
  * 
 {@code
- * class ForEach ...
+ * class ForEach ... {
+ *   ...
  *   public void compute() { // version 2
  *     if (hi - lo >= 2) {
  *       int mid = (lo + hi) >>> 1;
@@ -153,18 +154,19 @@
  *       tryComplete();
  *     }
  *   }
- * }
+ * }}
* - * As a further improvement, notice that the left task need not even exist. + * As a further optimization, notice that the left task need not even exist. * Instead of creating a new one, we can iterate using the original task, * and add a pending count for each fork. Additionally, because no task * in this tree implements an {@link #onCompletion(CountedCompleter)} method, * {@code tryComplete()} can be replaced with {@link #propagateCompletion}. * *
 {@code
- * class ForEach ...
+ * class ForEach ... {
+ *   ...
  *   public void compute() { // version 3
- *     int l = lo,  h = hi;
+ *     int l = lo, h = hi;
  *     while (h - l >= 2) {
  *       int mid = (l + h) >>> 1;
  *       addToPendingCount(1);
@@ -175,9 +177,9 @@
  *       op.apply(array[l]);
  *     propagateCompletion();
  *   }
- * }
+ * }}
* - * Additional improvements of such classes might entail precomputing + * Additional optimizations of such classes might entail precomputing * pending counts so that they can be established in constructors, * specializing classes for leaf steps, subdividing by say, four, * instead of two per iteration, and using an adaptive threshold @@ -204,7 +206,7 @@ * } * public E getRawResult() { return result.get(); } * public void compute() { // similar to ForEach version 3 - * int l = lo, h = hi; + * int l = lo, h = hi; * while (result.get() == null && h >= l) { * if (h - l >= 2) { * int mid = (l + h) >>> 1; @@ -229,9 +231,9 @@ * }}
* * In this example, as well as others in which tasks have no other - * effects except to compareAndSet a common result, the trailing - * unconditional invocation of {@code tryComplete} could be made - * conditional ({@code if (result.get() == null) tryComplete();}) + * effects except to {@code compareAndSet} a common result, the + * trailing unconditional invocation of {@code tryComplete} could be + * made conditional ({@code if (result.get() == null) tryComplete();}) * because no further bookkeeping is required to manage completions * once the root task completes. * @@ -334,7 +336,7 @@ * this.next = next; * } * public void compute() { - * int l = lo, h = hi; + * int l = lo, h = hi; * while (h - l >= 2) { * int mid = (l + h) >>> 1; * addToPendingCount(1); @@ -345,7 +347,7 @@ * result = mapper.apply(array[l]); * // process completions by reducing along and advancing subtask links * for (CountedCompleter c = firstComplete(); c != null; c = c.nextComplete()) { - * for (MapReducer t = (MapReducer)c, s = t.forks; s != null; s = t.forks = s.next) + * for (MapReducer t = (MapReducer)c, s = t.forks; s != null; s = t.forks = s.next) * t.result = reducer.apply(t.result, s.result); * } * } @@ -373,11 +375,9 @@ * // sample use: * PacketSender p = new PacketSender(); * new HeaderBuilder(p, ...).fork(); - * new BodyBuilder(p, ...).fork(); - * }
+ * new BodyBuilder(p, ...).fork();}
* * @since 1.8 - * @hide * @author Doug Lea */ public abstract class CountedCompleter extends ForkJoinTask { @@ -495,8 +495,7 @@ public final void setPendingCount(int count) { * @param delta the value to add */ public final void addToPendingCount(int delta) { - int c; - do {} while (!U.compareAndSwapInt(this, PENDING, c = pending, c+delta)); + U.getAndAddInt(this, PENDING, delta); } /** @@ -596,7 +595,7 @@ else if (U.compareAndSwapInt(a, PENDING, c, c - 1)) * any one (versus all) of several subtask results are obtained. * However, in the common (and recommended) case in which {@code * setRawResult} is not overridden, this effect can be obtained - * more simply using {@code quietlyCompleteRoot();}. + * more simply using {@link #quietlyCompleteRoot()}. * * @param rawResult the raw result */ @@ -611,9 +610,9 @@ public void complete(T rawResult) { /** * If this task's pending count is zero, returns this task; - * otherwise decrements its pending count and returns {@code - * null}. This method is designed to be used with {@link - * #nextComplete} in completion traversal loops. + * otherwise decrements its pending count and returns {@code null}. + * This method is designed to be used with {@link #nextComplete} in + * completion traversal loops. * * @return this task, if pending count was zero, else {@code null} */ @@ -666,6 +665,26 @@ public final void quietlyCompleteRoot() { } } + /** + * If this task has not completed, attempts to process at most the + * given number of other unprocessed tasks for which this task is + * on the completion path, if any are known to exist. + * + * @param maxTasks the maximum number of tasks to process. If + * less than or equal to zero, then no tasks are + * processed. + */ + public final void helpComplete(int maxTasks) { + Thread t; ForkJoinWorkerThread wt; + if (maxTasks > 0 && status >= 0) { + if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) + (wt = (ForkJoinWorkerThread)t).pool. + helpComplete(wt.workQueue, this, maxTasks); + else + ForkJoinPool.common.externalHelpComplete(this, maxTasks); + } + } + /** * Supports ForkJoinTask exception propagation. */ @@ -706,14 +725,13 @@ protected final boolean exec() { protected void setRawResult(T t) { } // Unsafe mechanics - private static final sun.misc.Unsafe U; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); private static final long PENDING; static { try { - U = sun.misc.Unsafe.getUnsafe(); PENDING = U.objectFieldOffset (CountedCompleter.class.getDeclaredField("pending")); - } catch (Exception e) { + } catch (ReflectiveOperationException e) { throw new Error(e); } } diff --git a/luni/src/main/java/java/util/concurrent/CyclicBarrier.java b/luni/src/main/java/java/util/concurrent/CyclicBarrier.java index d6985011e..7219c93b7 100644 --- a/luni/src/main/java/java/util/concurrent/CyclicBarrier.java +++ b/luni/src/main/java/java/util/concurrent/CyclicBarrier.java @@ -23,10 +23,10 @@ * This barrier action is useful * for updating shared-state before any of the parties continue. * - *

Sample usage: Here is an example of - * using a barrier in a parallel decomposition design: + *

Sample usage: Here is an example of using a barrier in a + * parallel decomposition design: * - *

 {@code
+ * 
 {@code
  * class Solver {
  *   final int N;
  *   final float[][] data;
@@ -53,16 +53,20 @@
  *   public Solver(float[][] matrix) {
  *     data = matrix;
  *     N = matrix.length;
- *     barrier = new CyclicBarrier(N,
- *                                 new Runnable() {
- *                                   public void run() {
- *                                     mergeRows(...);
- *                                   }
- *                                 });
- *     for (int i = 0; i < N; ++i)
- *       new Thread(new Worker(i)).start();
+ *     Runnable barrierAction =
+ *       new Runnable() { public void run() { mergeRows(...); }};
+ *     barrier = new CyclicBarrier(N, barrierAction);
  *
- *     waitUntilDone();
+ *     List threads = new ArrayList<>(N);
+ *     for (int i = 0; i < N; i++) {
+ *       Thread thread = new Thread(new Worker(i));
+ *       threads.add(thread);
+ *       thread.start();
+ *     }
+ *
+ *     // wait until done
+ *     for (Thread thread : threads)
+ *       thread.join();
  *   }
  * }}
* @@ -79,7 +83,7 @@ * {@link #await} returns the arrival index of that thread at the barrier. * You can then choose which thread should execute the barrier action, for * example: - *
 {@code
+ * 
 {@code
  * if (barrier.await() == 0) {
  *   // log the completion of this iteration
  * }}
@@ -117,7 +121,7 @@ public class CyclicBarrier { * but no subsequent reset. */ private static class Generation { - boolean broken = false; + boolean broken; // initially false } /** The lock for guarding barrier entry */ @@ -388,7 +392,8 @@ public int await() throws InterruptedException, BrokenBarrierException { * to arrive and zero indicates the last to arrive * @throws InterruptedException if the current thread was interrupted * while waiting - * @throws TimeoutException if the specified timeout elapses + * @throws TimeoutException if the specified timeout elapses. + * In this case the barrier will be broken. * @throws BrokenBarrierException if another thread was * interrupted or timed out while the current thread was * waiting, or the barrier was reset, or the barrier was broken diff --git a/luni/src/main/java/java/util/concurrent/DelayQueue.java b/luni/src/main/java/java/util/concurrent/DelayQueue.java index e4a715ede..d100a9c23 100644 --- a/luni/src/main/java/java/util/concurrent/DelayQueue.java +++ b/luni/src/main/java/java/util/concurrent/DelayQueue.java @@ -7,9 +7,14 @@ package java.util.concurrent; import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import java.util.AbstractQueue; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.PriorityQueue; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; -import java.util.*; // BEGIN android-note // removed link to collections framework docs @@ -37,7 +42,7 @@ * * @since 1.5 * @author Doug Lea - * @param the type of elements held in this collection + * @param the type of elements held in this queue */ public class DelayQueue extends AbstractQueue implements BlockingQueue { @@ -157,10 +162,9 @@ public E poll() { lock.lock(); try { E first = q.peek(); - if (first == null || first.getDelay(NANOSECONDS) > 0) - return null; - else - return q.poll(); + return (first == null || first.getDelay(NANOSECONDS) > 0) + ? null + : q.poll(); } finally { lock.unlock(); } @@ -183,7 +187,7 @@ public E take() throws InterruptedException { available.await(); else { long delay = first.getDelay(NANOSECONDS); - if (delay <= 0) + if (delay <= 0L) return q.poll(); first = null; // don't retain ref while waiting if (leader != null) @@ -225,15 +229,15 @@ public E poll(long timeout, TimeUnit unit) throws InterruptedException { for (;;) { E first = q.peek(); if (first == null) { - if (nanos <= 0) + if (nanos <= 0L) return null; else nanos = available.awaitNanos(nanos); } else { long delay = first.getDelay(NANOSECONDS); - if (delay <= 0) + if (delay <= 0L) return q.poll(); - if (nanos <= 0) + if (nanos <= 0L) return null; first = null; // don't retain ref while waiting if (nanos < delay || leader != null) @@ -462,7 +466,7 @@ public boolean remove(Object o) { } /** - * Identity-based version for use in Itr.remove + * Identity-based version for use in Itr.remove. */ void removeEQ(Object o) { final ReentrantLock lock = this.lock; @@ -484,12 +488,8 @@ void removeEQ(Object o) { * unexpired) in this queue. The iterator does not return the * elements in any particular order. * - *

The returned iterator is a "weakly consistent" iterator that - * will never throw {@link java.util.ConcurrentModificationException - * ConcurrentModificationException}, and guarantees to traverse - * elements as they existed upon construction of the iterator, and - * may (but is not guaranteed to) reflect any modifications - * subsequent to construction. + *

The returned iterator is + * weakly consistent. * * @return an iterator over the elements in this queue */ diff --git a/luni/src/main/java/java/util/concurrent/Exchanger.java b/luni/src/main/java/java/util/concurrent/Exchanger.java index 60871b49e..5f4c53448 100644 --- a/luni/src/main/java/java/util/concurrent/Exchanger.java +++ b/luni/src/main/java/java/util/concurrent/Exchanger.java @@ -21,9 +21,9 @@ * to swap buffers between threads so that the thread filling the * buffer gets a freshly emptied one when it needs it, handing off the * filled one to the thread emptying the buffer. - *

 {@code
+ * 
 {@code
  * class FillAndEmpty {
- *   Exchanger exchanger = new Exchanger();
+ *   Exchanger exchanger = new Exchanger<>();
  *   DataBuffer initialEmptyBuffer = ... a made-up type
  *   DataBuffer initialFullBuffer = ...
  *
@@ -125,9 +125,10 @@ public class Exchanger {
      * writing, there is no way to determine cacheline size, we define
      * a value that is enough for common platforms.  Additionally,
      * extra care elsewhere is taken to avoid other false/unintended
-     * sharing and to enhance locality, including adding padding to
-     * Nodes, embedding "bound" as an Exchanger field, and reworking
-     * some park/unpark mechanics compared to LockSupport versions.
+     * sharing and to enhance locality, including adding padding (via
+     * @Contended) to Nodes, embedding "bound" as an Exchanger field,
+     * and reworking some park/unpark mechanics compared to
+     * LockSupport versions.
      *
      * The arena starts out with only one used slot. We expand the
      * effective arena size by tracking collisions; i.e., failed CASes
@@ -274,8 +275,9 @@ public class Exchanger {
 
     /**
      * Nodes hold partially exchanged data, plus other per-thread
-     * bookkeeping.
+     * bookkeeping. Padded via @Contended to reduce memory contention.
      */
+    //@jdk.internal.vm.annotation.Contended // android-removed
     static final class Node {
         int index;              // Arena index
         int bound;              // Last recorded value of Exchanger.bound
@@ -284,10 +286,6 @@ static final class Node {
         Object item;            // This thread's current item
         volatile Object match;  // Item provided by releasing thread
         volatile Thread parked; // Set to this thread when parked, else null
-
-        // Padding to ameliorate unfortunate memory placements
-        Object p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, pa, pb, pc, pd, pe, pf;
-        Object q0, q1, q2, q3, q4, q5, q6, q7, q8, q9, qa, qb, qc, qd, qe, qf;
     }
 
     /** The corresponding thread local class */
@@ -296,7 +294,7 @@ static final class Participant extends ThreadLocal {
     }
 
     /**
-     * Per-thread state
+     * Per-thread state.
      */
     private final Participant participant;
 
@@ -598,37 +596,33 @@ public V exchange(V x, long timeout, TimeUnit unit)
     }
 
     // Unsafe mechanics
-    private static final sun.misc.Unsafe U;
+    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
     private static final long BOUND;
     private static final long SLOT;
     private static final long MATCH;
     private static final long BLOCKER;
     private static final int ABASE;
     static {
-        int s;
         try {
-            U = sun.misc.Unsafe.getUnsafe();
-            Class ek = Exchanger.class;
-            Class nk = Node.class;
-            Class ak = Node[].class;
-            Class tk = Thread.class;
             BOUND = U.objectFieldOffset
-                (ek.getDeclaredField("bound"));
+                (Exchanger.class.getDeclaredField("bound"));
             SLOT = U.objectFieldOffset
-                (ek.getDeclaredField("slot"));
+                (Exchanger.class.getDeclaredField("slot"));
+
             MATCH = U.objectFieldOffset
-                (nk.getDeclaredField("match"));
+                (Node.class.getDeclaredField("match"));
+
             BLOCKER = U.objectFieldOffset
-                (tk.getDeclaredField("parkBlocker"));
-            s = U.arrayIndexScale(ak);
-            // ABASE absorbs padding in front of element 0
-            ABASE = U.arrayBaseOffset(ak) + (1 << ASHIFT);
+                (Thread.class.getDeclaredField("parkBlocker"));
 
-        } catch (Exception e) {
+            int scale = U.arrayIndexScale(Node[].class);
+            if ((scale & (scale - 1)) != 0 || scale > (1 << ASHIFT))
+                throw new Error("Unsupported array scale");
+            // ABASE absorbs padding in front of element 0
+            ABASE = U.arrayBaseOffset(Node[].class) + (1 << ASHIFT);
+        } catch (ReflectiveOperationException e) {
             throw new Error(e);
         }
-        if ((s & (s-1)) != 0 || s > (1 << ASHIFT))
-            throw new Error("Unsupported array scale");
     }
 
 }
diff --git a/luni/src/main/java/java/util/concurrent/Executor.java b/luni/src/main/java/java/util/concurrent/Executor.java
index 095ebfc2a..9dd3efb62 100644
--- a/luni/src/main/java/java/util/concurrent/Executor.java
+++ b/luni/src/main/java/java/util/concurrent/Executor.java
@@ -15,30 +15,28 @@
  * invoking {@code new Thread(new RunnableTask()).start()} for each
  * of a set of tasks, you might use:
  *
- * 
- * Executor executor = anExecutor;
+ * 
 {@code
+ * Executor executor = anExecutor();
  * executor.execute(new RunnableTask1());
  * executor.execute(new RunnableTask2());
- * ...
- * 
+ * ...}
* - * However, the {@code Executor} interface does not strictly - * require that execution be asynchronous. In the simplest case, an - * executor can run the submitted task immediately in the caller's - * thread: + * However, the {@code Executor} interface does not strictly require + * that execution be asynchronous. In the simplest case, an executor + * can run the submitted task immediately in the caller's thread: * - *
 {@code
+ * 
 {@code
  * class DirectExecutor implements Executor {
  *   public void execute(Runnable r) {
  *     r.run();
  *   }
  * }}
* - * More typically, tasks are executed in some thread other - * than the caller's thread. The executor below spawns a new thread - * for each task. + * More typically, tasks are executed in some thread other than the + * caller's thread. The executor below spawns a new thread for each + * task. * - *
 {@code
+ * 
 {@code
  * class ThreadPerTaskExecutor implements Executor {
  *   public void execute(Runnable r) {
  *     new Thread(r).start();
@@ -50,7 +48,7 @@
  * serializes the submission of tasks to a second executor,
  * illustrating a composite executor.
  *
- *  
 {@code
+ * 
 {@code
  * class SerialExecutor implements Executor {
  *   final Queue tasks = new ArrayDeque<>();
  *   final Executor executor;
diff --git a/luni/src/main/java/java/util/concurrent/ExecutorCompletionService.java b/luni/src/main/java/java/util/concurrent/ExecutorCompletionService.java
index 951424622..cea0384ef 100644
--- a/luni/src/main/java/java/util/concurrent/ExecutorCompletionService.java
+++ b/luni/src/main/java/java/util/concurrent/ExecutorCompletionService.java
@@ -27,16 +27,16 @@
  * void solve(Executor e,
  *            Collection> solvers)
  *     throws InterruptedException, ExecutionException {
- *     CompletionService ecs
- *         = new ExecutorCompletionService(e);
- *     for (Callable s : solvers)
- *         ecs.submit(s);
- *     int n = solvers.size();
- *     for (int i = 0; i < n; ++i) {
- *         Result r = ecs.take().get();
- *         if (r != null)
- *             use(r);
- *     }
+ *   CompletionService ecs
+ *       = new ExecutorCompletionService(e);
+ *   for (Callable s : solvers)
+ *     ecs.submit(s);
+ *   int n = solvers.size();
+ *   for (int i = 0; i < n; ++i) {
+ *     Result r = ecs.take().get();
+ *     if (r != null)
+ *       use(r);
+ *   }
  * }}
* * Suppose instead that you would like to use the first non-null result @@ -47,32 +47,31 @@ * void solve(Executor e, * Collection> solvers) * throws InterruptedException { - * CompletionService ecs - * = new ExecutorCompletionService(e); - * int n = solvers.size(); - * List> futures - * = new ArrayList>(n); - * Result result = null; - * try { - * for (Callable s : solvers) - * futures.add(ecs.submit(s)); - * for (int i = 0; i < n; ++i) { - * try { - * Result r = ecs.take().get(); - * if (r != null) { - * result = r; - * break; - * } - * } catch (ExecutionException ignore) {} + * CompletionService ecs + * = new ExecutorCompletionService(e); + * int n = solvers.size(); + * List> futures = new ArrayList<>(n); + * Result result = null; + * try { + * for (Callable s : solvers) + * futures.add(ecs.submit(s)); + * for (int i = 0; i < n; ++i) { + * try { + * Result r = ecs.take().get(); + * if (r != null) { + * result = r; + * break; * } + * } catch (ExecutionException ignore) {} * } - * finally { - * for (Future f : futures) - * f.cancel(true); - * } + * } + * finally { + * for (Future f : futures) + * f.cancel(true); + * } * - * if (result != null) - * use(result); + * if (result != null) + * use(result); * }}
*/ public class ExecutorCompletionService implements CompletionService { @@ -81,15 +80,18 @@ public class ExecutorCompletionService implements CompletionService { private final BlockingQueue> completionQueue; /** - * FutureTask extension to enqueue upon completion + * FutureTask extension to enqueue upon completion. */ - private class QueueingFuture extends FutureTask { - QueueingFuture(RunnableFuture task) { + private static class QueueingFuture extends FutureTask { + QueueingFuture(RunnableFuture task, + BlockingQueue> completionQueue) { super(task, null); this.task = task; + this.completionQueue = completionQueue; } - protected void done() { completionQueue.add(task); } private final Future task; + private final BlockingQueue> completionQueue; + protected void done() { completionQueue.add(task); } } private RunnableFuture newTaskFor(Callable task) { @@ -149,14 +151,14 @@ public ExecutorCompletionService(Executor executor, public Future submit(Callable task) { if (task == null) throw new NullPointerException(); RunnableFuture f = newTaskFor(task); - executor.execute(new QueueingFuture(f)); + executor.execute(new QueueingFuture(f, completionQueue)); return f; } public Future submit(Runnable task, V result) { if (task == null) throw new NullPointerException(); RunnableFuture f = newTaskFor(task, result); - executor.execute(new QueueingFuture(f)); + executor.execute(new QueueingFuture(f, completionQueue)); return f; } diff --git a/luni/src/main/java/java/util/concurrent/ExecutorService.java b/luni/src/main/java/java/util/concurrent/ExecutorService.java index 58a211332..ce7b2c6a6 100644 --- a/luni/src/main/java/java/util/concurrent/ExecutorService.java +++ b/luni/src/main/java/java/util/concurrent/ExecutorService.java @@ -6,8 +6,8 @@ package java.util.concurrent; -import java.util.List; import java.util.Collection; +import java.util.List; // BEGIN android-note // removed security manager docs @@ -47,7 +47,7 @@ * pool service incoming requests. It uses the preconfigured {@link * Executors#newFixedThreadPool} factory method: * - *
 {@code
+ * 
 {@code
  * class NetworkService implements Runnable {
  *   private final ServerSocket serverSocket;
  *   private final ExecutorService pool;
@@ -81,7 +81,7 @@
  * first by calling {@code shutdown} to reject incoming tasks, and then
  * calling {@code shutdownNow}, if necessary, to cancel any lingering tasks:
  *
- *  
 {@code
+ * 
 {@code
  * void shutdownAndAwaitTermination(ExecutorService pool) {
  *   pool.shutdown(); // Disable new tasks from being submitted
  *   try {
diff --git a/luni/src/main/java/java/util/concurrent/Executors.java b/luni/src/main/java/java/util/concurrent/Executors.java
index 2068fd722..3d49b82ef 100644
--- a/luni/src/main/java/java/util/concurrent/Executors.java
+++ b/luni/src/main/java/java/util/concurrent/Executors.java
@@ -6,17 +6,21 @@
 
 package java.util.concurrent;
 
-import java.util.*;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.security.AccessControlContext;
+import java.security.AccessControlException;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
-import java.security.PrivilegedExceptionAction;
 import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import sun.security.util.SecurityConstants;
 
 // BEGIN android-note
 // removed security manager docs
 // END android-note
+
 /**
  * Factory and utility methods for {@link Executor}, {@link
  * ExecutorService}, {@link ScheduledExecutorService}, {@link
@@ -24,18 +28,18 @@
  * package. This class supports the following kinds of methods:
  *
  * 
    - *
  • Methods that create and return an {@link ExecutorService} - * set up with commonly useful configuration settings. - *
  • Methods that create and return a {@link ScheduledExecutorService} - * set up with commonly useful configuration settings. - *
  • Methods that create and return a "wrapped" ExecutorService, that - * disables reconfiguration by making implementation-specific methods - * inaccessible. - *
  • Methods that create and return a {@link ThreadFactory} - * that sets newly created threads to a known state. - *
  • Methods that create and return a {@link Callable} - * out of other closure-like forms, so they can be used - * in execution methods requiring {@code Callable}. + *
  • Methods that create and return an {@link ExecutorService} + * set up with commonly useful configuration settings. + *
  • Methods that create and return a {@link ScheduledExecutorService} + * set up with commonly useful configuration settings. + *
  • Methods that create and return a "wrapped" ExecutorService, that + * disables reconfiguration by making implementation-specific methods + * inaccessible. + *
  • Methods that create and return a {@link ThreadFactory} + * that sets newly created threads to a known state. + *
  • Methods that create and return a {@link Callable} + * out of other closure-like forms, so they can be used + * in execution methods requiring {@code Callable}. *
* * @since 1.5 @@ -78,7 +82,6 @@ public static ExecutorService newFixedThreadPool(int nThreads) { * @return the newly created thread pool * @throws IllegalArgumentException if {@code parallelism <= 0} * @since 1.8 - * @hide */ public static ExecutorService newWorkStealingPool(int parallelism) { return new ForkJoinPool @@ -88,12 +91,13 @@ public static ExecutorService newWorkStealingPool(int parallelism) { } /** - * Creates a work-stealing thread pool using all - * {@link Runtime#availableProcessors available processors} + * Creates a work-stealing thread pool using the number of + * {@linkplain Runtime#availableProcessors available processors} * as its target parallelism level. + * * @return the newly created thread pool + * @see #newWorkStealingPool(int) * @since 1.8 - * @hide */ public static ExecutorService newWorkStealingPool() { return new ForkJoinPool @@ -411,11 +415,11 @@ public static Callable privilegedCallableUsingCurrentClassLoader(Callable // Non-public classes supporting the public methods /** - * A callable that runs given task and returns given result + * A callable that runs given task and returns given result. */ - static final class RunnableAdapter implements Callable { - final Runnable task; - final T result; + private static final class RunnableAdapter implements Callable { + private final Runnable task; + private final T result; RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; @@ -427,11 +431,11 @@ public T call() { } /** - * A callable that runs under established access control settings + * A callable that runs under established access control settings. */ - static final class PrivilegedCallable implements Callable { - private final Callable task; - private final AccessControlContext acc; + private static final class PrivilegedCallable implements Callable { + final Callable task; + final AccessControlContext acc; PrivilegedCallable(Callable task) { this.task = task; @@ -454,12 +458,13 @@ public T run() throws Exception { /** * A callable that runs under established access control settings and - * current ClassLoader + * current ClassLoader. */ - static final class PrivilegedCallableUsingCurrentClassLoader implements Callable { - private final Callable task; - private final AccessControlContext acc; - private final ClassLoader ccl; + private static final class PrivilegedCallableUsingCurrentClassLoader + implements Callable { + final Callable task; + final AccessControlContext acc; + final ClassLoader ccl; PrivilegedCallableUsingCurrentClassLoader(Callable task) { // BEGIN android-removed @@ -469,7 +474,7 @@ static final class PrivilegedCallableUsingCurrentClassLoader implements Calla // // never trigger a security check, but we check // // whether our callers have this permission anyways. // sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION); - // + // // Whether setContextClassLoader turns out to be necessary // // or not, we fail fast if permission is not available. // sm.checkPermission(new RuntimePermission("setContextClassLoader")); @@ -506,9 +511,9 @@ public T run() throws Exception { } /** - * The default thread factory + * The default thread factory. */ - static class DefaultThreadFactory implements ThreadFactory { + private static class DefaultThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); @@ -536,11 +541,11 @@ public Thread newThread(Runnable r) { } /** - * Thread factory capturing access control context and class loader + * Thread factory capturing access control context and class loader. */ - static class PrivilegedThreadFactory extends DefaultThreadFactory { - private final AccessControlContext acc; - private final ClassLoader ccl; + private static class PrivilegedThreadFactory extends DefaultThreadFactory { + final AccessControlContext acc; + final ClassLoader ccl; PrivilegedThreadFactory() { super(); @@ -551,7 +556,7 @@ static class PrivilegedThreadFactory extends DefaultThreadFactory { // // never trigger a security check, but we check // // whether our callers have this permission anyways. // sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION); - // + // // Fail fast // sm.checkPermission(new RuntimePermission("setContextClassLoader")); // } @@ -579,7 +584,8 @@ public Void run() { * A wrapper class that exposes only the ExecutorService methods * of an ExecutorService implementation. */ - static class DelegatedExecutorService extends AbstractExecutorService { + private static class DelegatedExecutorService + extends AbstractExecutorService { private final ExecutorService e; DelegatedExecutorService(ExecutorService executor) { e = executor; } public void execute(Runnable command) { e.execute(command); } @@ -620,8 +626,8 @@ public T invokeAny(Collection> tasks, } } - static class FinalizableDelegatedExecutorService - extends DelegatedExecutorService { + private static class FinalizableDelegatedExecutorService + extends DelegatedExecutorService { FinalizableDelegatedExecutorService(ExecutorService executor) { super(executor); } @@ -634,7 +640,7 @@ protected void finalize() { * A wrapper class that exposes only the ScheduledExecutorService * methods of a ScheduledExecutorService implementation. */ - static class DelegatedScheduledExecutorService + private static class DelegatedScheduledExecutorService extends DelegatedExecutorService implements ScheduledExecutorService { private final ScheduledExecutorService e; diff --git a/luni/src/main/java/java/util/concurrent/ForkJoinPool.java b/luni/src/main/java/java/util/concurrent/ForkJoinPool.java index 41dd1610d..184d07a0f 100644 --- a/luni/src/main/java/java/util/concurrent/ForkJoinPool.java +++ b/luni/src/main/java/java/util/concurrent/ForkJoinPool.java @@ -7,11 +7,16 @@ package java.util.concurrent; import java.lang.Thread.UncaughtExceptionHandler; +import java.security.AccessControlContext; +import java.security.Permissions; +import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.LockSupport; /** * An {@link ExecutorService} for running {@link ForkJoinTask}s. @@ -31,7 +36,7 @@ * ForkJoinPool}s may also be appropriate for use with event-style * tasks that are never joined. * - *

A static {@code commonPool()} is available and appropriate for + *

A static {@link #commonPool()} is available and appropriate for * most applications. The common pool is used by any ForkJoinTask that * is not explicitly submitted to a specified pool. Using the common * pool normally reduces resource usage (its threads are slowly @@ -40,9 +45,9 @@ * *

For applications that require separate or custom pools, a {@code * ForkJoinPool} may be constructed with a given target parallelism - * level; by default, equal to the number of available processors. The - * pool attempts to maintain enough active (or available) threads by - * dynamically adding, suspending, or resuming internal worker + * level; by default, equal to the number of available processors. + * The pool attempts to maintain enough active (or available) threads + * by dynamically adding, suspending, or resuming internal worker * threads, even if some tasks are stalled waiting to join others. * However, no such adjustments are guaranteed in the face of blocked * I/O or other unmanaged synchronization. The nested {@link @@ -102,12 +107,19 @@ * - the class name of a {@link ForkJoinWorkerThreadFactory} *

  • {@code java.util.concurrent.ForkJoinPool.common.exceptionHandler} * - the class name of a {@link UncaughtExceptionHandler} + *
  • {@code java.util.concurrent.ForkJoinPool.common.maximumSpares} + * - the maximum number of allowed extra threads to maintain target + * parallelism (default 256). * + * If a {@link SecurityManager} is present and no factory is + * specified, then the default pool uses a factory supplying + * threads that have no {@link Permissions} enabled. * The system class loader is used to load these classes. * Upon any error in establishing these settings, default parameters * are used. It is possible to disable or limit the use of threads in * the common pool by setting the parallelism property to zero, and/or - * using a factory that may return {@code null}. + * using a factory that may return {@code null}. However doing so may + * cause unjoined tasks to never be executed. * *

    Implementation notes: This implementation restricts the * maximum number of running threads to 32767. Attempts to create @@ -121,6 +133,7 @@ * @since 1.7 * @author Doug Lea */ +//@jdk.internal.vm.annotation.Contended // android-removed public class ForkJoinPool extends AbstractExecutorService { /* @@ -133,7 +146,14 @@ public class ForkJoinPool extends AbstractExecutorService { * that may be stolen by other workers. Preference rules give * first priority to processing tasks from their own queues (LIFO * or FIFO, depending on mode), then to randomized FIFO steals of - * tasks in other queues. + * tasks in other queues. This framework began as vehicle for + * supporting tree-structured parallelism using work-stealing. + * Over time, its scalability advantages led to extensions and + * changes to better support more diverse usage contexts. Because + * most internal methods and nested classes are interrelated, + * their main rationale and descriptions are presented here; + * individual methods and nested classes contain only brief + * comments about details. * * WorkQueues * ========== @@ -153,200 +173,305 @@ public class ForkJoinPool extends AbstractExecutorService { * (http://research.sun.com/scalable/pubs/index.html) and * "Idempotent work stealing" by Michael, Saraswat, and Vechev, * PPoPP 2009 (http://portal.acm.org/citation.cfm?id=1504186). - * See also "Correct and Efficient Work-Stealing for Weak Memory - * Models" by Le, Pop, Cohen, and Nardelli, PPoPP 2013 + * The main differences ultimately stem from GC requirements that + * we null out taken slots as soon as we can, to maintain as small + * a footprint as possible even in programs generating huge + * numbers of tasks. To accomplish this, we shift the CAS + * arbitrating pop vs poll (steal) from being on the indices + * ("base" and "top") to the slots themselves. + * + * Adding tasks then takes the form of a classic array push(task) + * in a circular buffer: + * q.array[q.top++ % length] = task; + * + * (The actual code needs to null-check and size-check the array, + * uses masking, not mod, for indexing a power-of-two-sized array, + * properly fences accesses, and possibly signals waiting workers + * to start scanning -- see below.) Both a successful pop and + * poll mainly entail a CAS of a slot from non-null to null. + * + * The pop operation (always performed by owner) is: + * if ((the task at top slot is not null) and + * (CAS slot to null)) + * decrement top and return task; + * + * And the poll operation (usually by a stealer) is + * if ((the task at base slot is not null) and + * (CAS slot to null)) + * increment base and return task; + * + * There are several variants of each of these; for example most + * versions of poll pre-screen the CAS by rechecking that the base + * has not changed since reading the slot, and most methods only + * attempt the CAS if base appears not to be equal to top. + * + * Memory ordering. See "Correct and Efficient Work-Stealing for + * Weak Memory Models" by Le, Pop, Cohen, and Nardelli, PPoPP 2013 * (http://www.di.ens.fr/~zappa/readings/ppopp13.pdf) for an - * analysis of memory ordering (atomic, volatile etc) issues. The - * main differences ultimately stem from GC requirements that we - * null out taken slots as soon as we can, to maintain as small a - * footprint as possible even in programs generating huge numbers - * of tasks. To accomplish this, we shift the CAS arbitrating pop - * vs poll (steal) from being on the indices ("base" and "top") to - * the slots themselves. So, both a successful pop and poll - * mainly entail a CAS of a slot from non-null to null. Because - * we rely on CASes of references, we do not need tag bits on base - * or top. They are simple ints as used in any circular - * array-based queue (see for example ArrayDeque). Updates to the - * indices must still be ordered in a way that guarantees that top - * == base means the queue is empty, but otherwise may err on the - * side of possibly making the queue appear nonempty when a push, - * pop, or poll have not fully committed. Note that this means - * that the poll operation, considered individually, is not - * wait-free. One thief cannot successfully continue until another - * in-progress one (or, if previously empty, a push) completes. + * analysis of memory ordering requirements in work-stealing + * algorithms similar to (but different than) the one used here. + * Extracting tasks in array slots via (fully fenced) CAS provides + * primary synchronization. The base and top indices imprecisely + * guide where to extract from. We do not always require strict + * orderings of array and index updates, so sometimes let them be + * subject to compiler and processor reorderings. However, the + * volatile "base" index also serves as a basis for memory + * ordering: Slot accesses are preceded by a read of base, + * ensuring happens-before ordering with respect to stealers (so + * the slots themselves can be read via plain array reads.) The + * only other memory orderings relied on are maintained in the + * course of signalling and activation (see below). A check that + * base == top indicates (momentary) emptiness, but otherwise may + * err on the side of possibly making the queue appear nonempty + * when a push, pop, or poll have not fully committed, or making + * it appear empty when an update of top has not yet been visibly + * written. (Method isEmpty() checks the case of a partially + * completed removal of the last element.) Because of this, the + * poll operation, considered individually, is not wait-free. One + * thief cannot successfully continue until another in-progress + * one (or, if previously empty, a push) visibly completes. * However, in the aggregate, we ensure at least probabilistic - * non-blockingness. If an attempted steal fails, a thief always - * chooses a different random victim target to try next. So, in - * order for one thief to progress, it suffices for any + * non-blockingness. If an attempted steal fails, a scanning + * thief chooses a different random victim target to try next. So, + * in order for one thief to progress, it suffices for any * in-progress poll or new push on any empty queue to * complete. (This is why we normally use method pollAt and its * variants that try once at the apparent base index, else - * consider alternative actions, rather than method poll.) - * - * This approach also enables support of a user mode in which local - * task processing is in FIFO, not LIFO order, simply by using - * poll rather than pop. This can be useful in message-passing - * frameworks in which tasks are never joined. However neither - * mode considers affinities, loads, cache localities, etc, so - * rarely provide the best possible performance on a given - * machine, but portably provide good throughput by averaging over - * these factors. (Further, even if we did try to use such - * information, we do not usually have a basis for exploiting it. - * For example, some sets of tasks profit from cache affinities, - * but others are harmed by cache pollution effects.) + * consider alternative actions, rather than method poll, which + * retries.) + * + * This approach also enables support of a user mode in which + * local task processing is in FIFO, not LIFO order, simply by + * using poll rather than pop. This can be useful in + * message-passing frameworks in which tasks are never joined. * * WorkQueues are also used in a similar way for tasks submitted * to the pool. We cannot mix these tasks in the same queues used - * for work-stealing (this would contaminate lifo/fifo - * processing). Instead, we randomly associate submission queues + * by workers. Instead, we randomly associate submission queues * with submitting threads, using a form of hashing. The - * Submitter probe value serves as a hash code for + * ThreadLocalRandom probe value serves as a hash code for * choosing existing queues, and may be randomly repositioned upon * contention with other submitters. In essence, submitters act * like workers except that they are restricted to executing local - * tasks that they submitted. However, because most - * shared/external queue operations are more expensive than - * internal, and because, at steady state, external submitters - * will compete for CPU with workers, ForkJoinTask.join and - * related methods disable them from repeatedly helping to process - * tasks if all workers are active. Insertion of tasks in shared - * mode requires a lock (mainly to protect in the case of - * resizing) but we use only a simple spinlock (using bits in + * tasks that they submitted (or in the case of CountedCompleters, + * others with the same root task). Insertion of tasks in shared + * mode requires a lock but we use only a simple spinlock (using * field qlock), because submitters encountering a busy queue move * on to try or create other queues -- they block only when - * creating and registering new queues. + * creating and registering new queues. Because it is used only as + * a spinlock, unlocking requires only a "releasing" store (using + * putOrderedInt). The qlock is also used during termination + * detection, in which case it is forced to a negative + * non-lockable value. * * Management * ========== * * The main throughput advantages of work-stealing stem from * decentralized control -- workers mostly take tasks from - * themselves or each other. We cannot negate this in the - * implementation of other management responsibilities. The main - * tactic for avoiding bottlenecks is packing nearly all - * essentially atomic control state into two volatile variables - * that are by far most often read (not written) as status and - * consistency checks. - * - * Field "ctl" contains 64 bits holding all the information needed - * to atomically decide to add, inactivate, enqueue (on an event + * themselves or each other, at rates that can exceed a billion + * per second. The pool itself creates, activates (enables + * scanning for and running tasks), deactivates, blocks, and + * terminates threads, all with minimal central information. + * There are only a few properties that we can globally track or + * maintain, so we pack them into a small number of variables, + * often maintaining atomicity without blocking or locking. + * Nearly all essentially atomic control state is held in two + * volatile variables that are by far most often read (not + * written) as status and consistency checks. (Also, field + * "config" holds unchanging configuration state.) + * + * Field "ctl" contains 64 bits holding information needed to + * atomically decide to add, inactivate, enqueue (on an event * queue), dequeue, and/or re-activate workers. To enable this * packing, we restrict maximum parallelism to (1<<15)-1 (which is * far in excess of normal operating range) to allow ids, counts, * and their negations (used for thresholding) to fit into 16bit - * fields. - * - * Field "plock" is a form of sequence lock with a saturating - * shutdown bit (similarly for per-queue "qlocks"), mainly - * protecting updates to the workQueues array, as well as to - * enable shutdown. When used as a lock, it is normally only very - * briefly held, so is nearly always available after at most a - * brief spin, but we use a monitor-based backup strategy to - * block when needed. - * - * Recording WorkQueues. WorkQueues are recorded in the - * "workQueues" array that is created upon first use and expanded - * if necessary. Updates to the array while recording new workers - * and unrecording terminated ones are protected from each other - * by a lock but the array is otherwise concurrently readable, and - * accessed directly. To simplify index-based operations, the - * array size is always a power of two, and all readers must - * tolerate null slots. Worker queues are at odd indices. Shared - * (submission) queues are at even indices, up to a maximum of 64 - * slots, to limit growth even if array needs to expand to add - * more workers. Grouping them together in this way simplifies and + * subfields. + * + * Field "runState" holds lifetime status, atomically and + * monotonically setting STARTED, SHUTDOWN, STOP, and finally + * TERMINATED bits. + * + * Field "auxState" is a ReentrantLock subclass that also + * opportunistically holds some other bookkeeping fields accessed + * only when locked. It is mainly used to lock (infrequent) + * updates to workQueues. The auxState instance is itself lazily + * constructed (see tryInitialize), requiring a double-check-style + * bootstrapping use of field runState, and locking a private + * static. + * + * Field "workQueues" holds references to WorkQueues. It is + * updated (only during worker creation and termination) under the + * lock, but is otherwise concurrently readable, and accessed + * directly. We also ensure that reads of the array reference + * itself never become too stale (for example, re-reading before + * each scan). To simplify index-based operations, the array size + * is always a power of two, and all readers must tolerate null + * slots. Worker queues are at odd indices. Shared (submission) + * queues are at even indices, up to a maximum of 64 slots, to + * limit growth even if array needs to expand to add more + * workers. Grouping them together in this way simplifies and * speeds up task scanning. * * All worker thread creation is on-demand, triggered by task * submissions, replacement of terminated workers, and/or * compensation for blocked workers. However, all other support * code is set up to work with other policies. To ensure that we - * do not hold on to worker references that would prevent GC, ALL + * do not hold on to worker references that would prevent GC, all * accesses to workQueues are via indices into the workQueues * array (which is one source of some of the messy code * constructions here). In essence, the workQueues array serves as - * a weak reference mechanism. Thus for example the wait queue - * field of ctl stores indices, not references. Access to the - * workQueues in associated methods (for example signalWork) must - * both index-check and null-check the IDs. All such accesses - * ignore bad IDs by returning out early from what they are doing, - * since this can only be associated with termination, in which - * case it is OK to give up. All uses of the workQueues array - * also check that it is non-null (even if previously - * non-null). This allows nulling during termination, which is - * currently not necessary, but remains an option for - * resource-revocation-based shutdown schemes. It also helps - * reduce JIT issuance of uncommon-trap code, which tends to - * unnecessarily complicate control flow in some methods. - * - * Event Queuing. Unlike HPC work-stealing frameworks, we cannot - * let workers spin indefinitely scanning for tasks when none can - * be found immediately, and we cannot start/resume workers unless - * there appear to be tasks available. On the other hand, we must - * quickly prod them into action when new tasks are submitted or - * generated. In many usages, ramp-up time to activate workers is - * the main limiting factor in overall performance (this is - * compounded at program start-up by JIT compilation and - * allocation). So we try to streamline this as much as possible. - * We park/unpark workers after placing in an event wait queue - * when they cannot find work. This "queue" is actually a simple - * Treiber stack, headed by the "id" field of ctl, plus a 15bit - * counter value (that reflects the number of times a worker has - * been inactivated) to avoid ABA effects (we need only as many - * version numbers as worker threads). Successors are held in - * field WorkQueue.nextWait. Queuing deals with several intrinsic - * races, mainly that a task-producing thread can miss seeing (and - * signalling) another thread that gave up looking for work but - * has not yet entered the wait queue. We solve this by requiring - * a full sweep of all workers (via repeated calls to method - * scan()) both before and after a newly waiting worker is added - * to the wait queue. Because enqueued workers may actually be - * rescanning rather than waiting, we set and clear the "parker" - * field of WorkQueues to reduce unnecessary calls to unpark. - * (This requires a secondary recheck to avoid missed signals.) - * Note the unusual conventions about Thread.interrupts - * surrounding parking and other blocking: Because interrupts are - * used solely to alert threads to check termination, which is - * checked anyway upon blocking, we clear status (using - * Thread.interrupted) before any call to park, so that park does - * not immediately return due to status being set via some other - * unrelated call to interrupt in user code. - * - * Signalling. We create or wake up workers only when there - * appears to be at least one task they might be able to find and - * execute. When a submission is added or another worker adds a - * task to a queue that has fewer than two tasks, they signal - * waiting workers (or trigger creation of new ones if fewer than - * the given parallelism level -- signalWork). These primary - * signals are buttressed by others whenever other threads remove - * a task from a queue and notice that there are other tasks there - * as well. So in general, pools will be over-signalled. On most - * platforms, signalling (unpark) overhead time is noticeably - * long, and the time between signalling a thread and it actually - * making progress can be very noticeably long, so it is worth - * offloading these delays from critical paths as much as - * possible. Additionally, workers spin-down gradually, by staying - * alive so long as they see the ctl state changing. Similar - * stability-sensing techniques are also used before blocking in - * awaitJoin and helpComplete. + * a weak reference mechanism. Thus for example the stack top + * subfield of ctl stores indices, not references. + * + * Queuing Idle Workers. Unlike HPC work-stealing frameworks, we + * cannot let workers spin indefinitely scanning for tasks when + * none can be found immediately, and we cannot start/resume + * workers unless there appear to be tasks available. On the + * other hand, we must quickly prod them into action when new + * tasks are submitted or generated. In many usages, ramp-up time + * to activate workers is the main limiting factor in overall + * performance, which is compounded at program start-up by JIT + * compilation and allocation. So we streamline this as much as + * possible. + * + * The "ctl" field atomically maintains active and total worker + * counts as well as a queue to place waiting threads so they can + * be located for signalling. Active counts also play the role of + * quiescence indicators, so are decremented when workers believe + * that there are no more tasks to execute. The "queue" is + * actually a form of Treiber stack. A stack is ideal for + * activating threads in most-recently used order. This improves + * performance and locality, outweighing the disadvantages of + * being prone to contention and inability to release a worker + * unless it is topmost on stack. We block/unblock workers after + * pushing on the idle worker stack (represented by the lower + * 32bit subfield of ctl) when they cannot find work. The top + * stack state holds the value of the "scanState" field of the + * worker: its index and status, plus a version counter that, in + * addition to the count subfields (also serving as version + * stamps) provide protection against Treiber stack ABA effects. + * + * Creating workers. To create a worker, we pre-increment total + * count (serving as a reservation), and attempt to construct a + * ForkJoinWorkerThread via its factory. Upon construction, the + * new thread invokes registerWorker, where it constructs a + * WorkQueue and is assigned an index in the workQueues array + * (expanding the array if necessary). The thread is then started. + * Upon any exception across these steps, or null return from + * factory, deregisterWorker adjusts counts and records + * accordingly. If a null return, the pool continues running with + * fewer than the target number workers. If exceptional, the + * exception is propagated, generally to some external caller. + * Worker index assignment avoids the bias in scanning that would + * occur if entries were sequentially packed starting at the front + * of the workQueues array. We treat the array as a simple + * power-of-two hash table, expanding as needed. The seedIndex + * increment ensures no collisions until a resize is needed or a + * worker is deregistered and replaced, and thereafter keeps + * probability of collision low. We cannot use + * ThreadLocalRandom.getProbe() for similar purposes here because + * the thread has not started yet, but do so for creating + * submission queues for existing external threads (see + * externalPush). + * + * WorkQueue field scanState is used by both workers and the pool + * to manage and track whether a worker is UNSIGNALLED (possibly + * blocked waiting for a signal). When a worker is inactivated, + * its scanState field is set, and is prevented from executing + * tasks, even though it must scan once for them to avoid queuing + * races. Note that scanState updates lag queue CAS releases so + * usage requires care. When queued, the lower 16 bits of + * scanState must hold its pool index. So we place the index there + * upon initialization (see registerWorker) and otherwise keep it + * there or restore it when necessary. + * + * The ctl field also serves as the basis for memory + * synchronization surrounding activation. This uses a more + * efficient version of a Dekker-like rule that task producers and + * consumers sync with each other by both writing/CASing ctl (even + * if to its current value). This would be extremely costly. So + * we relax it in several ways: (1) Producers only signal when + * their queue is empty. Other workers propagate this signal (in + * method scan) when they find tasks. (2) Workers only enqueue + * after scanning (see below) and not finding any tasks. (3) + * Rather than CASing ctl to its current value in the common case + * where no action is required, we reduce write contention by + * equivalently prefacing signalWork when called by an external + * task producer using a memory access with full-volatile + * semantics or a "fullFence". (4) For internal task producers we + * rely on the fact that even if no other workers awaken, the + * producer itself will eventually see the task and execute it. + * + * Almost always, too many signals are issued. A task producer + * cannot in general tell if some existing worker is in the midst + * of finishing one task (or already scanning) and ready to take + * another without being signalled. So the producer might instead + * activate a different worker that does not find any work, and + * then inactivates. This scarcely matters in steady-state + * computations involving all workers, but can create contention + * and bookkeeping bottlenecks during ramp-up, ramp-down, and small + * computations involving only a few workers. + * + * Scanning. Method scan() performs top-level scanning for tasks. + * Each scan traverses (and tries to poll from) each queue in + * pseudorandom permutation order by randomly selecting an origin + * index and a step value. (The pseudorandom generator need not + * have high-quality statistical properties in the long term, but + * just within computations; We use 64bit and 32bit Marsaglia + * XorShifts, which are cheap and suffice here.) Scanning also + * employs contention reduction: When scanning workers fail a CAS + * polling for work, they soon restart with a different + * pseudorandom scan order (thus likely retrying at different + * intervals). This improves throughput when many threads are + * trying to take tasks from few queues. Scans do not otherwise + * explicitly take into account core affinities, loads, cache + * localities, etc, However, they do exploit temporal locality + * (which usually approximates these) by preferring to re-poll (up + * to POLL_LIMIT times) from the same queue after a successful + * poll before trying others. Restricted forms of scanning occur + * in methods helpComplete and findNonEmptyStealQueue, and take + * similar but simpler forms. + * + * Deactivation and waiting. Queuing encounters several intrinsic + * races; most notably that an inactivating scanning worker can + * miss seeing a task produced during a scan. So when a worker + * cannot find a task to steal, it inactivates and enqueues, and + * then rescans to ensure that it didn't miss one, reactivating + * upon seeing one with probability approximately proportional to + * probability of a miss. (In most cases, the worker will be + * signalled before self-signalling, avoiding cascades of multiple + * signals for the same task). + * + * Workers block (in method awaitWork) using park/unpark; + * advertising the need for signallers to unpark by setting their + * "parker" fields. * * Trimming workers. To release resources after periods of lack of * use, a worker starting to wait when the pool is quiescent will - * time out and terminate if the pool has remained quiescent for a - * given period -- a short period if there are more threads than - * parallelism, longer as the number of threads decreases. This - * will slowly propagate, eventually terminating all workers after - * periods of non-use. - * - * Shutdown and Termination. A call to shutdownNow atomically sets - * a plock bit and then (non-atomically) sets each worker's - * qlock status, cancels all unprocessed tasks, and wakes up - * all waiting workers. Detecting whether termination should - * commence after a non-abrupt shutdown() call requires more work - * and bookkeeping. We need consensus about quiescence (i.e., that - * there is no more work). The active count provides a primary - * indication but non-abrupt shutdown still requires a rechecking - * scan for any workers that are inactive but not queued. + * time out and terminate (see awaitWork) if the pool has remained + * quiescent for period given by IDLE_TIMEOUT_MS, increasing the + * period as the number of threads decreases, eventually removing + * all workers. + * + * Shutdown and Termination. A call to shutdownNow invokes + * tryTerminate to atomically set a runState bit. The calling + * thread, as well as every other worker thereafter terminating, + * helps terminate others by setting their (qlock) status, + * cancelling their unprocessed tasks, and waking them up, doing + * so repeatedly until stable. Calls to non-abrupt shutdown() + * preface this by checking whether termination should commence. + * This relies primarily on the active count bits of "ctl" + * maintaining consensus -- tryTerminate is called from awaitWork + * whenever quiescent. However, external submitters do not take + * part in this consensus. So, tryTerminate sweeps through queues + * (until stable) to ensure lack of in-flight submissions and + * workers about to process them before triggering the "STOP" + * phase of termination. (Note: there is an intrinsic conflict if + * helpQuiescePool is called when shutdown is enabled. Both wait + * for quiescence, but tryTerminate is biased to not trigger until + * helpQuiescePool completes.) * * Joining Tasks * ============= @@ -357,9 +482,9 @@ public class ForkJoinPool extends AbstractExecutorService { * just let them block (as in Thread.join). We also cannot just * reassign the joiner's run-time stack with another and replace * it later, which would be a form of "continuation", that even if - * possible is not necessarily a good idea since we sometimes need - * both an unblocked task and its continuation to progress. - * Instead we combine two tactics: + * possible is not necessarily a good idea since we may need both + * an unblocked task and its continuation to progress. Instead we + * combine two tactics: * * Helping: Arranging for the joiner to execute some task that it * would be running if the steal had not occurred. @@ -379,16 +504,16 @@ public class ForkJoinPool extends AbstractExecutorService { * The ManagedBlocker extension API can't use helping so relies * only on compensation in method awaitBlocker. * - * The algorithm in tryHelpStealer entails a form of "linear" - * helping: Each worker records (in field currentSteal) the most - * recent task it stole from some other worker. Plus, it records - * (in field currentJoin) the task it is currently actively - * joining. Method tryHelpStealer uses these markers to try to - * find a worker to help (i.e., steal back a task from and execute - * it) that could hasten completion of the actively joined task. - * In essence, the joiner executes a task that would be on its own - * local deque had the to-be-joined task not been stolen. This may - * be seen as a conservative variant of the approach in Wagner & + * The algorithm in helpStealer entails a form of "linear + * helping". Each worker records (in field currentSteal) the most + * recent task it stole from some other worker (or a submission). + * It also records (in field currentJoin) the task it is currently + * actively joining. Method helpStealer uses these markers to try + * to find a worker to help (i.e., steal back a task from and + * execute it) that could hasten completion of the actively joined + * task. Thus, the joiner executes a task that would be on its + * own local deque had the to-be-joined task not been stolen. This + * is a conservative variant of the approach described in Wagner & * Calder "Leapfrogging: a portable technique for implementing * efficient futures" SIGPLAN Notices, 1993 * (http://portal.acm.org/citation.cfm?id=155354). It differs in @@ -406,31 +531,45 @@ public class ForkJoinPool extends AbstractExecutorService { * which means that we miss links in the chain during long-lived * tasks, GC stalls etc (which is OK since blocking in such cases * is usually a good idea). (4) We bound the number of attempts - * to find work (see MAX_HELP) and fall back to suspending the + * to find work using checksums and fall back to suspending the * worker and if necessary replacing it with another. * - * It is impossible to keep exactly the target parallelism number - * of threads running at any given time. Determining the - * existence of conservatively safe helping targets, the - * availability of already-created spares, and the apparent need - * to create new spares are all racy, so we rely on multiple - * retries of each. Compensation in the apparent absence of - * helping opportunities is challenging to control on JVMs, where - * GC and other activities can stall progress of tasks that in - * turn stall out many other dependent tasks, without us being - * able to determine whether they will ever require compensation. - * Even though work-stealing otherwise encounters little - * degradation in the presence of more threads than cores, - * aggressively adding new threads in such cases entails risk of - * unwanted positive feedback control loops in which more threads - * cause more dependent stalls (as well as delayed progress of - * unblocked threads to the point that we know they are available) - * leading to more situations requiring more threads, and so - * on. This aspect of control can be seen as an (analytically - * intractable) game with an opponent that may choose the worst - * (for us) active thread to stall at any time. We take several - * precautions to bound losses (and thus bound gains), mainly in - * methods tryCompensate and awaitJoin. + * Helping actions for CountedCompleters do not require tracking + * currentJoins: Method helpComplete takes and executes any task + * with the same root as the task being waited on (preferring + * local pops to non-local polls). However, this still entails + * some traversal of completer chains, so is less efficient than + * using CountedCompleters without explicit joins. + * + * Compensation does not aim to keep exactly the target + * parallelism number of unblocked threads running at any given + * time. Some previous versions of this class employed immediate + * compensations for any blocked join. However, in practice, the + * vast majority of blockages are transient byproducts of GC and + * other JVM or OS activities that are made worse by replacement. + * Currently, compensation is attempted only after validating that + * all purportedly active threads are processing tasks by checking + * field WorkQueue.scanState, which eliminates most false + * positives. Also, compensation is bypassed (tolerating fewer + * threads) in the most common case in which it is rarely + * beneficial: when a worker with an empty queue (thus no + * continuation tasks) blocks on a join and there still remain + * enough threads to ensure liveness. + * + * Spare threads are removed as soon as they notice that the + * target parallelism level has been exceeded, in method + * tryDropSpare. (Method scan arranges returns for rechecks upon + * each probe via the "bound" parameter.) + * + * The compensation mechanism may be bounded. Bounds for the + * commonPool (see COMMON_MAX_SPARES) better enable JVMs to cope + * with programming errors and abuse before running out of + * resources to do so. In other cases, users may supply factories + * that limit thread construction. The effects of bounding in this + * pool (like all others) is imprecise. Total worker counts are + * decremented when threads deregister, not when they exit and + * resources are reclaimed by the JVM and OS. So the number of + * simultaneously live threads may transiently exceed bounds. * * Common Pool * =========== @@ -440,24 +579,52 @@ public class ForkJoinPool extends AbstractExecutorService { * never be used, we minimize initial construction overhead and * footprint to the setup of about a dozen fields, with no nested * allocation. Most bootstrapping occurs within method - * fullExternalPush during the first submission to the pool. + * externalSubmit during the first submission to the pool. * * When external threads submit to the common pool, they can - * perform subtask processing (see externalHelpJoin and related - * methods). This caller-helps policy makes it sensible to set - * common pool parallelism level to one (or more) less than the - * total number of available cores, or even zero for pure - * caller-runs. We do not need to record whether external - * submissions are to the common pool -- if not, externalHelpJoin - * returns quickly (at the most helping to signal some common pool - * workers). These submitters would otherwise be blocked waiting - * for completion, so the extra effort (with liberally sprinkled - * task status checks) in inapplicable cases amounts to an odd - * form of limited spin-wait before blocking in ForkJoinTask.join. + * perform subtask processing (see externalHelpComplete and + * related methods) upon joins. This caller-helps policy makes it + * sensible to set common pool parallelism level to one (or more) + * less than the total number of available cores, or even zero for + * pure caller-runs. We do not need to record whether external + * submissions are to the common pool -- if not, external help + * methods return quickly. These submitters would otherwise be + * blocked waiting for completion, so the extra effort (with + * liberally sprinkled task status checks) in inapplicable cases + * amounts to an odd form of limited spin-wait before blocking in + * ForkJoinTask.join. + * + * As a more appropriate default in managed environments, unless + * overridden by system properties, we use workers of subclass + * InnocuousForkJoinWorkerThread when there is a SecurityManager + * present. These workers have no permissions set, do not belong + * to any user-defined ThreadGroup, and erase all ThreadLocals + * after executing any top-level task (see WorkQueue.runTask). + * The associated mechanics (mainly in ForkJoinWorkerThread) may + * be JVM-dependent and must access particular Thread class fields + * to achieve this effect. * * Style notes * =========== * + * Memory ordering relies mainly on Unsafe intrinsics that carry + * the further responsibility of explicitly performing null- and + * bounds- checks otherwise carried out implicitly by JVMs. This + * can be awkward and ugly, but also reflects the need to control + * outcomes across the unusual cases that arise in very racy code + * with very few invariants. So these explicit checks would exist + * in some form anyway. All fields are read into locals before + * use, and null-checked if they are references. This is usually + * done in a "C"-like style of listing declarations at the heads + * of methods or blocks, and using inline assignments on first + * encounter. Array bounds-checks are usually performed by + * masking with array.length-1, which relies on the invariant that + * these arrays are created with positive lengths, which is itself + * paranoically checked. Nearly all explicit checks lead to + * bypass/return, not exception throws, because they may + * legitimately arise due to cancellation/revocation during + * shutdown. + * * There is a lot of representation-level coupling among classes * ForkJoinPool, ForkJoinWorkerThread, and ForkJoinTask. The * fields of WorkQueue maintain data structures managed by @@ -465,22 +632,13 @@ public class ForkJoinPool extends AbstractExecutorService { * trying to reduce this, since any associated future changes in * representations will need to be accompanied by algorithmic * changes anyway. Several methods intrinsically sprawl because - * they must accumulate sets of consistent reads of volatiles held - * in local variables. Methods signalWork() and scan() are the - * main bottlenecks, so are especially heavily - * micro-optimized/mangled. There are lots of inline assignments - * (of form "while ((local = field) != 0)") which are usually the - * simplest way to ensure the required read orderings (which are - * sometimes critical). This leads to a "C"-like style of listing - * declarations of these locals at the heads of methods or blocks. - * There are several occurrences of the unusual "do {} while - * (!cas...)" which is the simplest way to force an update of a - * CAS'ed variable. There are also other coding oddities (including - * several unnecessary-looking hoisted null checks) that help - * some methods perform reasonably even when interpreted (not - * compiled). - * - * The order of declarations in this file is: + * they must accumulate sets of consistent reads of fields held in + * local variables. There are also other coding oddities + * (including several unnecessary-looking hoisted null checks) + * that help some methods perform reasonably even when interpreted + * (not compiled). + * + * The order of declarations in this file is (with a few exceptions): * (1) Static utility functions * (2) Nested (static) classes * (3) Static fields @@ -490,7 +648,6 @@ public class ForkJoinPool extends AbstractExecutorService { * (7) Exported methods * (8) Static block initializing statics in minimally dependent order */ - // android-note: Removed references to CountedCompleters. // Static utilities @@ -517,7 +674,8 @@ public static interface ForkJoinWorkerThreadFactory { * Returns a new worker thread operating in the given pool. * * @param pool the pool this thread works in - * @return the new worker thread + * @return the new worker thread, or {@code null} if the request + * to create a thread is rejected * @throws NullPointerException if the pool is null */ public ForkJoinWorkerThread newThread(ForkJoinPool pool); @@ -527,7 +685,7 @@ public static interface ForkJoinWorkerThreadFactory { * Default ForkJoinWorkerThreadFactory implementation; creates a * new ForkJoinWorkerThread. */ - static final class DefaultForkJoinWorkerThreadFactory + private static final class DefaultForkJoinWorkerThreadFactory implements ForkJoinWorkerThreadFactory { public final ForkJoinWorkerThread newThread(ForkJoinPool pool) { return new ForkJoinWorkerThread(pool); @@ -540,7 +698,7 @@ public final ForkJoinWorkerThread newThread(ForkJoinPool pool) { * in WorkQueue.tryRemoveAndExec. We don't need the proxy to * actually do anything beyond having a unique identity. */ - static final class EmptyTask extends ForkJoinTask { + private static final class EmptyTask extends ForkJoinTask { private static final long serialVersionUID = -7721805057305804111L; EmptyTask() { status = ForkJoinTask.NORMAL; } // force done public final Void getRawResult() { return null; } @@ -548,55 +706,55 @@ public final void setRawResult(Void x) {} public final boolean exec() { return true; } } + /** + * Additional fields and lock created upon initialization. + */ + private static final class AuxState extends ReentrantLock { + private static final long serialVersionUID = -6001602636862214147L; + volatile long stealCount; // cumulative steal count + long indexSeed; // index bits for registerWorker + AuxState() {} + } + + // Constants shared across ForkJoinPool and WorkQueue + + // Bounds + static final int SMASK = 0xffff; // short bits == max index + static final int MAX_CAP = 0x7fff; // max #workers - 1 + static final int EVENMASK = 0xfffe; // even short bits + static final int SQMASK = 0x007e; // max 64 (even) slots + + // Masks and units for WorkQueue.scanState and ctl sp subfield + static final int UNSIGNALLED = 1 << 31; // must be negative + static final int SS_SEQ = 1 << 16; // version count + + // Mode bits for ForkJoinPool.config and WorkQueue.config + static final int MODE_MASK = 0xffff << 16; // top half of int + static final int SPARE_WORKER = 1 << 17; // set if tc > 0 on creation + static final int UNREGISTERED = 1 << 18; // to skip some of deregister + static final int FIFO_QUEUE = 1 << 31; // must be negative + static final int LIFO_QUEUE = 0; // for clarity + static final int IS_OWNED = 1; // low bit 0 if shared + + /** + * The maximum number of task executions from the same queue + * before checking other queues, bounding unfairness and impact of + * infinite user task recursion. Must be a power of two minus 1. + */ + static final int POLL_LIMIT = (1 << 10) - 1; + /** * Queues supporting work-stealing as well as external task - * submission. See above for main rationale and algorithms. - * Implementation relies heavily on "Unsafe" intrinsics - * and selective use of "volatile": - * - * Field "base" is the index (mod array.length) of the least valid - * queue slot, which is always the next position to steal (poll) - * from if nonempty. Reads and writes require volatile orderings - * but not CAS, because updates are only performed after slot - * CASes. - * - * Field "top" is the index (mod array.length) of the next queue - * slot to push to or pop from. It is written only by owner thread - * for push, or under lock for external/shared push, and accessed - * by other threads only after reading (volatile) base. Both top - * and base are allowed to wrap around on overflow, but (top - - * base) (or more commonly -(base - top) to force volatile read of - * base before top) still estimates size. The lock ("qlock") is - * forced to -1 on termination, causing all further lock attempts - * to fail. (Note: we don't need CAS for termination state because - * upon pool shutdown, all shared-queues will stop being used - * anyway.) Nearly all lock bodies are set up so that exceptions - * within lock bodies are "impossible" (modulo JVM errors that - * would cause failure anyway.) - * - * The array slots are read and written using the emulation of - * volatiles/atomics provided by Unsafe. Insertions must in - * general use putOrderedObject as a form of releasing store to - * ensure that all writes to the task object are ordered before - * its publication in the queue. All removals entail a CAS to - * null. The array is always a power of two. To ensure safety of - * Unsafe array operations, all accesses perform explicit null - * checks and implicit bounds checks via power-of-two masking. - * - * In addition to basic queuing support, this class contains - * fields described elsewhere to control execution. It turns out - * to work better memory-layout-wise to include them in this class - * rather than a separate class. - * + * submission. See above for descriptions and algorithms. * Performance on most platforms is very sensitive to placement of * instances of both WorkQueues and their arrays -- we absolutely * do not want multiple WorkQueue instances or multiple queue - * arrays sharing cache lines. (It would be best for queue objects - * and their arrays to share, but there is nothing available to - * help arrange that). The @Contended annotation alerts JVMs to - * try to keep instances apart. + * arrays sharing cache lines. The @Contended annotation alerts + * JVMs to try to keep instances apart. */ + //@jdk.internal.vm.annotation.Contended // android-removed static final class WorkQueue { + /** * Capacity of work-stealing queue array upon initialization. * Must be a power of two; at least 4, but should be larger to @@ -617,43 +775,44 @@ static final class WorkQueue { */ static final int MAXIMUM_QUEUE_CAPACITY = 1 << 26; // 64M - // Heuristic padding to ameliorate unfortunate memory placements - volatile long pad00, pad01, pad02, pad03, pad04, pad05, pad06; + // Instance fields - volatile int eventCount; // encoded inactivation count; < 0 if inactive - int nextWait; // encoded record of next event waiter + volatile int scanState; // versioned, negative if inactive + int stackPred; // pool stack (ctl) predecessor int nsteals; // number of steals - int hint; // steal index hint - short poolIndex; // index of this queue in pool - final short mode; // 0: lifo, > 0: fifo, < 0: shared - volatile int qlock; // 1: locked, -1: terminate; else 0 + int hint; // randomization and stealer index hint + int config; // pool index and mode + volatile int qlock; // 1: locked, < 0: terminate; else 0 volatile int base; // index of next slot for poll int top; // index of next slot for push ForkJoinTask[] array; // the elements (initially unallocated) final ForkJoinPool pool; // the containing pool (may be null) final ForkJoinWorkerThread owner; // owning thread or null if shared volatile Thread parker; // == owner during call to park; else null - volatile ForkJoinTask currentJoin; // task being joined in awaitJoin - ForkJoinTask currentSteal; // current non-local task being executed + volatile ForkJoinTask currentJoin; // task being joined in awaitJoin - volatile Object pad10, pad11, pad12, pad13, pad14, pad15, pad16, pad17; - volatile Object pad18, pad19, pad1a, pad1b, pad1c, pad1d; + // @jdk.internal.vm.annotation.Contended("group2") // segregate // android-removed + volatile ForkJoinTask currentSteal; // nonnull when running some task - WorkQueue(ForkJoinPool pool, ForkJoinWorkerThread owner, int mode, - int seed) { + WorkQueue(ForkJoinPool pool, ForkJoinWorkerThread owner) { this.pool = pool; this.owner = owner; - this.mode = (short)mode; - this.hint = seed; // store initial seed for runWorker // Place indices in the center of array (that is not yet allocated) base = top = INITIAL_QUEUE_CAPACITY >>> 1; } + /** + * Returns an exportable index (used by ForkJoinWorkerThread). + */ + final int getPoolIndex() { + return (config & 0xffff) >>> 1; // ignore odd/even tag bit + } + /** * Returns the approximate number of tasks in the queue. */ final int queueSize() { - int n = base - top; // non-owner callers must read base first + int n = base - top; // read base first return (n >= 0) ? 0 : -n; // ignore transient negative } @@ -663,32 +822,31 @@ final int queueSize() { * near-empty queue has at least one unclaimed task. */ final boolean isEmpty() { - ForkJoinTask[] a; int m, s; - int n = base - (s = top); - return (n >= 0 || - (n == -1 && - ((a = array) == null || - (m = a.length - 1) < 0 || - U.getObject - (a, (long)((m & (s - 1)) << ASHIFT) + ABASE) == null))); + ForkJoinTask[] a; int n, al, s; + return ((n = base - (s = top)) >= 0 || // possibly one task + (n == -1 && ((a = array) == null || + (al = a.length) == 0 || + a[(al - 1) & (s - 1)] == null))); } /** - * Pushes a task. Call only by owner in unshared queues. (The - * shared-queue version is embedded in method externalPush.) + * Pushes a task. Call only by owner in unshared queues. * * @param task the task. Caller must ensure non-null. * @throws RejectedExecutionException if array cannot be resized */ final void push(ForkJoinTask task) { - ForkJoinTask[] a; ForkJoinPool p; - int s = top, n; - if ((a = array) != null) { // ignore if queue removed - int m = a.length - 1; - U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task); - if ((n = (top = s + 1) - base) <= 2) - (p = pool).signalWork(p.workQueues, this); - else if (n >= m) + U.storeFence(); // ensure safe publication + int s = top, al, d; ForkJoinTask[] a; + if ((a = array) != null && (al = a.length) > 0) { + a[(al - 1) & s] = task; // relaxed writes OK + top = s + 1; + ForkJoinPool p = pool; + if ((d = base - s) == 0 && p != null) { + U.fullFence(); + p.signalWork(); + } + else if (al + d == 1) growArray(); } } @@ -701,22 +859,23 @@ else if (n >= m) final ForkJoinTask[] growArray() { ForkJoinTask[] oldA = array; int size = oldA != null ? oldA.length << 1 : INITIAL_QUEUE_CAPACITY; - if (size > MAXIMUM_QUEUE_CAPACITY) + if (size < INITIAL_QUEUE_CAPACITY || size > MAXIMUM_QUEUE_CAPACITY) throw new RejectedExecutionException("Queue capacity exceeded"); int oldMask, t, b; ForkJoinTask[] a = array = new ForkJoinTask[size]; - if (oldA != null && (oldMask = oldA.length - 1) >= 0 && + if (oldA != null && (oldMask = oldA.length - 1) > 0 && (t = top) - (b = base) > 0) { int mask = size - 1; - do { - ForkJoinTask x; - int oldj = ((b & oldMask) << ASHIFT) + ABASE; - int j = ((b & mask) << ASHIFT) + ABASE; - x = (ForkJoinTask)U.getObjectVolatile(oldA, oldj); + do { // emulate poll from old array, push to new array + int index = b & oldMask; + long offset = ((long)index << ASHIFT) + ABASE; + ForkJoinTask x = (ForkJoinTask) + U.getObjectVolatile(oldA, offset); if (x != null && - U.compareAndSwapObject(oldA, oldj, x, null)) - U.putObjectVolatile(a, j, x); + U.compareAndSwapObject(oldA, offset, x, null)) + a[b & mask] = x; } while (++b != t); + U.storeFence(); } return a; } @@ -726,16 +885,16 @@ final ForkJoinTask[] growArray() { * by owner in unshared queues. */ final ForkJoinTask pop() { - ForkJoinTask[] a; ForkJoinTask t; int m; - if ((a = array) != null && (m = a.length - 1) >= 0) { - for (int s; (s = top - 1) - base >= 0;) { - long j = ((m & s) << ASHIFT) + ABASE; - if ((t = (ForkJoinTask)U.getObject(a, j)) == null) - break; - if (U.compareAndSwapObject(a, j, t, null)) { - top = s; - return t; - } + int b = base, s = top, al, i; ForkJoinTask[] a; + if ((a = array) != null && b != s && (al = a.length) > 0) { + int index = (al - 1) & --s; + long offset = ((long)index << ASHIFT) + ABASE; + ForkJoinTask t = (ForkJoinTask) + U.getObject(a, offset); + if (t != null && + U.compareAndSwapObject(a, offset, t, null)) { + top = s; + return t; } } return null; @@ -744,15 +903,18 @@ final ForkJoinTask pop() { /** * Takes a task in FIFO order if b is base of queue and a task * can be claimed without contention. Specialized versions - * appear in ForkJoinPool methods scan and tryHelpStealer. + * appear in ForkJoinPool methods scan and helpStealer. */ final ForkJoinTask pollAt(int b) { - ForkJoinTask t; ForkJoinTask[] a; - if ((a = array) != null) { - int j = (((a.length - 1) & b) << ASHIFT) + ABASE; - if ((t = (ForkJoinTask)U.getObjectVolatile(a, j)) != null && - base == b && U.compareAndSwapObject(a, j, t, null)) { - U.putOrderedInt(this, QBASE, b + 1); + ForkJoinTask[] a; int al; + if ((a = array) != null && (al = a.length) > 0) { + int index = (al - 1) & b; + long offset = ((long)index << ASHIFT) + ABASE; + ForkJoinTask t = (ForkJoinTask) + U.getObjectVolatile(a, offset); + if (t != null && b++ == base && + U.compareAndSwapObject(a, offset, t, null)) { + base = b; return t; } } @@ -763,21 +925,27 @@ final ForkJoinTask pollAt(int b) { * Takes next task, if one exists, in FIFO order. */ final ForkJoinTask poll() { - ForkJoinTask[] a; int b; ForkJoinTask t; - while ((b = base) - top < 0 && (a = array) != null) { - int j = (((a.length - 1) & b) << ASHIFT) + ABASE; - t = (ForkJoinTask)U.getObjectVolatile(a, j); - if (t != null) { - if (U.compareAndSwapObject(a, j, t, null)) { - U.putOrderedInt(this, QBASE, b + 1); - return t; + for (;;) { + int b = base, s = top, d, al; ForkJoinTask[] a; + if ((a = array) != null && (d = b - s) < 0 && + (al = a.length) > 0) { + int index = (al - 1) & b; + long offset = ((long)index << ASHIFT) + ABASE; + ForkJoinTask t = (ForkJoinTask) + U.getObjectVolatile(a, offset); + if (b++ == base) { + if (t != null) { + if (U.compareAndSwapObject(a, offset, t, null)) { + base = b; + return t; + } + } + else if (d == -1) + break; // now empty } } - else if (base == b) { - if (b + 1 == top) - break; - Thread.yield(); // wait for lagging update (very rare) - } + else + break; } return null; } @@ -786,218 +954,350 @@ else if (base == b) { * Takes next task, if one exists, in order specified by mode. */ final ForkJoinTask nextLocalTask() { - return mode == 0 ? pop() : poll(); + return (config < 0) ? poll() : pop(); } /** * Returns next task, if one exists, in order specified by mode. */ final ForkJoinTask peek() { - ForkJoinTask[] a = array; int m; - if (a == null || (m = a.length - 1) < 0) - return null; - int i = mode == 0 ? top - 1 : base; - int j = ((i & m) << ASHIFT) + ABASE; - return (ForkJoinTask)U.getObjectVolatile(a, j); + int al; ForkJoinTask[] a; + return ((a = array) != null && (al = a.length) > 0) ? + a[(al - 1) & (config < 0 ? base : top - 1)] : null; } /** * Pops the given task only if it is at the current top. - * (A shared version is available only via FJP.tryExternalUnpush) */ - final boolean tryUnpush(ForkJoinTask t) { - ForkJoinTask[] a; int s; - if ((a = array) != null && (s = top) != base && - U.compareAndSwapObject - (a, (((a.length - 1) & --s) << ASHIFT) + ABASE, t, null)) { - top = s; - return true; + final boolean tryUnpush(ForkJoinTask task) { + int b = base, s = top, al; ForkJoinTask[] a; + if ((a = array) != null && b != s && (al = a.length) > 0) { + int index = (al - 1) & --s; + long offset = ((long)index << ASHIFT) + ABASE; + if (U.compareAndSwapObject(a, offset, task, null)) { + top = s; + return true; + } } return false; } + /** + * Shared version of push. Fails if already locked. + * + * @return status: > 0 locked, 0 possibly was empty, < 0 was nonempty + */ + final int sharedPush(ForkJoinTask task) { + int stat; + if (U.compareAndSwapInt(this, QLOCK, 0, 1)) { + int b = base, s = top, al, d; ForkJoinTask[] a; + if ((a = array) != null && (al = a.length) > 0 && + al - 1 + (d = b - s) > 0) { + a[(al - 1) & s] = task; + top = s + 1; // relaxed writes OK here + qlock = 0; + stat = (d < 0 && b == base) ? d : 0; + } + else { + growAndSharedPush(task); + stat = 0; + } + } + else + stat = 1; + return stat; + } + + /** + * Helper for sharedPush; called only when locked and resize + * needed. + */ + private void growAndSharedPush(ForkJoinTask task) { + try { + growArray(); + int s = top, al; ForkJoinTask[] a; + if ((a = array) != null && (al = a.length) > 0) { + a[(al - 1) & s] = task; + top = s + 1; + } + } finally { + qlock = 0; + } + } + + /** + * Shared version of tryUnpush. + */ + final boolean trySharedUnpush(ForkJoinTask task) { + boolean popped = false; + int s = top - 1, al; ForkJoinTask[] a; + if ((a = array) != null && (al = a.length) > 0) { + int index = (al - 1) & s; + long offset = ((long)index << ASHIFT) + ABASE; + ForkJoinTask t = (ForkJoinTask) U.getObject(a, offset); + if (t == task && + U.compareAndSwapInt(this, QLOCK, 0, 1)) { + if (top == s + 1 && array == a && + U.compareAndSwapObject(a, offset, task, null)) { + popped = true; + top = s; + } + U.putOrderedInt(this, QLOCK, 0); + } + } + return popped; + } + /** * Removes and cancels all known tasks, ignoring any exceptions. */ final void cancelAll() { - ForkJoinTask.cancelIgnoringExceptions(currentJoin); - ForkJoinTask.cancelIgnoringExceptions(currentSteal); - for (ForkJoinTask t; (t = poll()) != null; ) + ForkJoinTask t; + if ((t = currentJoin) != null) { + currentJoin = null; + ForkJoinTask.cancelIgnoringExceptions(t); + } + if ((t = currentSteal) != null) { + currentSteal = null; + ForkJoinTask.cancelIgnoringExceptions(t); + } + while ((t = poll()) != null) ForkJoinTask.cancelIgnoringExceptions(t); } // Specialized execution methods /** - * Polls and runs tasks until empty. + * Pops and executes up to POLL_LIMIT tasks or until empty. */ - final void pollAndExecAll() { - for (ForkJoinTask t; (t = poll()) != null;) - t.doExec(); + final void localPopAndExec() { + for (int nexec = 0;;) { + int b = base, s = top, al; ForkJoinTask[] a; + if ((a = array) != null && b != s && (al = a.length) > 0) { + int index = (al - 1) & --s; + long offset = ((long)index << ASHIFT) + ABASE; + ForkJoinTask t = (ForkJoinTask) + U.getAndSetObject(a, offset, null); + if (t != null) { + top = s; + (currentSteal = t).doExec(); + if (++nexec > POLL_LIMIT) + break; + } + else + break; + } + else + break; + } } /** - * Executes a top-level task and any local tasks remaining - * after execution. + * Polls and executes up to POLL_LIMIT tasks or until empty. */ - final void runTask(ForkJoinTask task) { - if ((currentSteal = task) != null) { - task.doExec(); - ForkJoinTask[] a = array; - int md = mode; - ++nsteals; - currentSteal = null; - if (md != 0) - pollAndExecAll(); - else if (a != null) { - int s, m = a.length - 1; - while ((s = top - 1) - base >= 0) { - long i = ((m & s) << ASHIFT) + ABASE; - ForkJoinTask t = (ForkJoinTask)U.getObject(a, i); - if (t == null) + final void localPollAndExec() { + for (int nexec = 0;;) { + int b = base, s = top, al; ForkJoinTask[] a; + if ((a = array) != null && b != s && (al = a.length) > 0) { + int index = (al - 1) & b++; + long offset = ((long)index << ASHIFT) + ABASE; + ForkJoinTask t = (ForkJoinTask) + U.getAndSetObject(a, offset, null); + if (t != null) { + base = b; + t.doExec(); + if (++nexec > POLL_LIMIT) break; - if (U.compareAndSwapObject(a, i, t, null)) { - top = s; - t.doExec(); - } } } + else + break; } } /** - * If present, removes from queue and executes the given task, - * or any other cancelled task. Returns (true) on any CAS - * or consistency check failure so caller can retry. - * - * @return false if no progress can be made, else true + * Executes the given task and (some) remaining local tasks. */ - final boolean tryRemoveAndExec(ForkJoinTask task) { - boolean stat; - ForkJoinTask[] a; int m, s, b, n; - if (task != null && (a = array) != null && (m = a.length - 1) >= 0 && - (n = (s = top) - (b = base)) > 0) { - boolean removed = false, empty = true; - stat = true; - for (ForkJoinTask t;;) { // traverse from s to b - long j = ((--s & m) << ASHIFT) + ABASE; - t = (ForkJoinTask)U.getObject(a, j); - if (t == null) // inconsistent length - break; - else if (t == task) { - if (s + 1 == top) { // pop - if (!U.compareAndSwapObject(a, j, task, null)) - break; - top = s; - removed = true; - } - else if (base == b) // replace with proxy - removed = U.compareAndSwapObject(a, j, task, - new EmptyTask()); - break; - } - else if (t.status >= 0) - empty = false; - else if (s + 1 == top) { // pop and throw away - if (U.compareAndSwapObject(a, j, t, null)) - top = s; - break; - } - if (--n == 0) { - if (!empty && base == b) - stat = false; - break; - } + final void runTask(ForkJoinTask task) { + if (task != null) { + task.doExec(); + if (config < 0) + localPollAndExec(); + else + localPopAndExec(); + int ns = ++nsteals; + ForkJoinWorkerThread thread = owner; + currentSteal = null; + if (ns < 0) // collect on overflow + transferStealCount(pool); + if (thread != null) + thread.afterTopLevelExec(); + } + } + + /** + * Adds steal count to pool steal count if it exists, and resets. + */ + final void transferStealCount(ForkJoinPool p) { + AuxState aux; + if (p != null && (aux = p.auxState) != null) { + long s = nsteals; + nsteals = 0; // if negative, correct for overflow + if (s < 0) s = Integer.MAX_VALUE; + aux.lock(); + try { + aux.stealCount += s; + } finally { + aux.unlock(); } - if (removed) - task.doExec(); } - else - stat = false; - return stat; } /** - * Tries to poll for and execute the given task or any other - * task in its CountedCompleter computation. + * If present, removes from queue and executes the given task, + * or any other cancelled task. Used only by awaitJoin. + * + * @return true if queue empty and task not known to be done */ - final boolean pollAndExecCC(CountedCompleter root) { - ForkJoinTask[] a; int b; Object o; CountedCompleter t, r; - if ((b = base) - top < 0 && (a = array) != null) { - long j = (((a.length - 1) & b) << ASHIFT) + ABASE; - if ((o = U.getObjectVolatile(a, j)) == null) - return true; // retry - if (o instanceof CountedCompleter) { - for (t = (CountedCompleter)o, r = t;;) { - if (r == root) { - if (base == b && - U.compareAndSwapObject(a, j, t, null)) { - U.putOrderedInt(this, QBASE, b + 1); - t.doExec(); + final boolean tryRemoveAndExec(ForkJoinTask task) { + if (task != null && task.status >= 0) { + int b, s, d, al; ForkJoinTask[] a; + while ((d = (b = base) - (s = top)) < 0 && + (a = array) != null && (al = a.length) > 0) { + for (;;) { // traverse from s to b + int index = --s & (al - 1); + long offset = (index << ASHIFT) + ABASE; + ForkJoinTask t = (ForkJoinTask) + U.getObjectVolatile(a, offset); + if (t == null) + break; // restart + else if (t == task) { + boolean removed = false; + if (s + 1 == top) { // pop + if (U.compareAndSwapObject(a, offset, t, null)) { + top = s; + removed = true; + } + } + else if (base == b) // replace with proxy + removed = U.compareAndSwapObject(a, offset, t, + new EmptyTask()); + if (removed) { + ForkJoinTask ps = currentSteal; + (currentSteal = task).doExec(); + currentSteal = ps; + } + break; + } + else if (t.status < 0 && s + 1 == top) { + if (U.compareAndSwapObject(a, offset, t, null)) { + top = s; } - return true; + break; // was cancelled + } + else if (++d == 0) { + if (base != b) // rescan + break; + return false; } - else if ((r = r.completer) == null) - break; // not part of root computation } + if (task.status < 0) + return false; } } - return false; + return true; } /** - * Tries to pop and execute the given task or any other task - * in its CountedCompleter computation. + * Pops task if in the same CC computation as the given task, + * in either shared or owned mode. Used only by helpComplete. */ - final boolean externalPopAndExecCC(CountedCompleter root) { - ForkJoinTask[] a; int s; Object o; CountedCompleter t, r; - if (base - (s = top) < 0 && (a = array) != null) { - long j = (((a.length - 1) & (s - 1)) << ASHIFT) + ABASE; - if ((o = U.getObject(a, j)) instanceof CountedCompleter) { - for (t = (CountedCompleter)o, r = t;;) { - if (r == root) { - if (U.compareAndSwapInt(this, QLOCK, 0, 1)) { - if (top == s && array == a && - U.compareAndSwapObject(a, j, t, null)) { - top = s - 1; - qlock = 0; - t.doExec(); + final CountedCompleter popCC(CountedCompleter task, int mode) { + int b = base, s = top, al; ForkJoinTask[] a; + if ((a = array) != null && b != s && (al = a.length) > 0) { + int index = (al - 1) & (s - 1); + long offset = ((long)index << ASHIFT) + ABASE; + ForkJoinTask o = (ForkJoinTask) + U.getObjectVolatile(a, offset); + if (o instanceof CountedCompleter) { + CountedCompleter t = (CountedCompleter)o; + for (CountedCompleter r = t;;) { + if (r == task) { + if ((mode & IS_OWNED) == 0) { + boolean popped = false; + if (U.compareAndSwapInt(this, QLOCK, 0, 1)) { + if (top == s && array == a && + U.compareAndSwapObject(a, offset, + t, null)) { + popped = true; + top = s - 1; + } + U.putOrderedInt(this, QLOCK, 0); + if (popped) + return t; } - else - qlock = 0; } - return true; + else if (U.compareAndSwapObject(a, offset, + t, null)) { + top = s - 1; + return t; + } + break; } - else if ((r = r.completer) == null) + else if ((r = r.completer) == null) // try parent break; } } } - return false; + return null; } /** - * Internal version + * Steals and runs a task in the same CC computation as the + * given task if one exists and can be taken without + * contention. Otherwise returns a checksum/control value for + * use by method helpComplete. + * + * @return 1 if successful, 2 if retryable (lost to another + * stealer), -1 if non-empty but no matching task found, else + * the base index, forced negative. */ - final boolean internalPopAndExecCC(CountedCompleter root) { - ForkJoinTask[] a; int s; Object o; CountedCompleter t, r; - if (base - (s = top) < 0 && (a = array) != null) { - long j = (((a.length - 1) & (s - 1)) << ASHIFT) + ABASE; - if ((o = U.getObject(a, j)) instanceof CountedCompleter) { - for (t = (CountedCompleter)o, r = t;;) { - if (r == root) { - if (U.compareAndSwapObject(a, j, t, null)) { - top = s - 1; + final int pollAndExecCC(CountedCompleter task) { + ForkJoinTask[] a; + int b = base, s = top, al, h; + if ((a = array) != null && b != s && (al = a.length) > 0) { + int index = (al - 1) & b; + long offset = ((long)index << ASHIFT) + ABASE; + ForkJoinTask o = (ForkJoinTask) + U.getObjectVolatile(a, offset); + if (o == null) + h = 2; // retryable + else if (!(o instanceof CountedCompleter)) + h = -1; // unmatchable + else { + CountedCompleter t = (CountedCompleter)o; + for (CountedCompleter r = t;;) { + if (r == task) { + if (b++ == base && + U.compareAndSwapObject(a, offset, t, null)) { + base = b; t.doExec(); + h = 1; // success } - return true; + else + h = 2; // lost CAS + break; } - else if ((r = r.completer) == null) + else if ((r = r.completer) == null) { + h = -1; // unmatched break; + } } } } - return false; + else + h = b | Integer.MIN_VALUE; // to sense movement on re-poll + return h; } /** @@ -1005,34 +1305,28 @@ else if ((r = r.completer) == null) */ final boolean isApparentlyUnblocked() { Thread wt; Thread.State s; - return (eventCount >= 0 && + return (scanState >= 0 && (wt = owner) != null && (s = wt.getState()) != Thread.State.BLOCKED && s != Thread.State.WAITING && s != Thread.State.TIMED_WAITING); } - // Unsafe mechanics - private static final sun.misc.Unsafe U; - private static final long QBASE; + // Unsafe mechanics. Note that some are (and must be) the same as in FJP + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); private static final long QLOCK; private static final int ABASE; private static final int ASHIFT; static { try { - U = sun.misc.Unsafe.getUnsafe(); - Class k = WorkQueue.class; - Class ak = ForkJoinTask[].class; - QBASE = U.objectFieldOffset - (k.getDeclaredField("base")); QLOCK = U.objectFieldOffset - (k.getDeclaredField("qlock")); - ABASE = U.arrayBaseOffset(ak); - int scale = U.arrayIndexScale(ak); + (WorkQueue.class.getDeclaredField("qlock")); + ABASE = U.arrayBaseOffset(ForkJoinTask[].class); + int scale = U.arrayIndexScale(ForkJoinTask[].class); if ((scale & (scale - 1)) != 0) - throw new Error("data type scale not a power of two"); + throw new Error("array index scale not a power of two"); ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); - } catch (Exception e) { + } catch (ReflectiveOperationException e) { throw new Error(e); } } @@ -1040,15 +1334,6 @@ final boolean isApparentlyUnblocked() { // static fields (initialized in static initializer below) - /** - * Per-thread submission bookkeeping. Shared across all pools - * to reduce ThreadLocal pollution and because random motion - * to avoid contention in one pool is likely to hold for others. - * Lazily initialized on first submission (but null-checked - * in other contexts to avoid unnecessary initialization). - */ - static final ThreadLocal submitters; - /** * Creates a new ForkJoinWorkerThread. This factory is used unless * overridden in ForkJoinPool constructors. @@ -1058,9 +1343,9 @@ final boolean isApparentlyUnblocked() { /** * Permission required for callers of methods that may start or - * kill threads. + * kill threads. Also used as a static lock in tryInitialize. */ - private static final RuntimePermission modifyThreadPermission; + static final RuntimePermission modifyThreadPermission; /** * Common (static) pool. Non-null for public use unless a static @@ -1076,7 +1361,12 @@ final boolean isApparentlyUnblocked() { * common.parallelism field to be zero, but in that case still report * parallelism as 1 to reflect resulting caller-runs mechanics. */ - static final int commonParallelism; + static final int COMMON_PARALLELISM; + + /** + * Limit on spare thread construction in tryCompensate. + */ + private static final int COMMON_MAX_SPARES; /** * Sequence number for creating workerNamePrefix. @@ -1091,270 +1381,215 @@ private static final synchronized int nextPoolId() { return ++poolNumberSequence; } - // static constants + // static configuration constants /** - * Initial timeout value (in nanoseconds) for the thread + * Initial timeout value (in milliseconds) for the thread * triggering quiescence to park waiting for new work. On timeout, - * the thread will instead try to shrink the number of - * workers. The value should be large enough to avoid overly - * aggressive shrinkage during most transient stalls (long GCs - * etc). + * the thread will instead try to shrink the number of workers. + * The value should be large enough to avoid overly aggressive + * shrinkage during most transient stalls (long GCs etc). */ - private static final long IDLE_TIMEOUT = 2000L * 1000L * 1000L; // 2sec + private static final long IDLE_TIMEOUT_MS = 2000L; // 2sec /** - * Timeout value when there are more threads than parallelism level + * Tolerance for idle timeouts, to cope with timer undershoots. */ - private static final long FAST_IDLE_TIMEOUT = 200L * 1000L * 1000L; + private static final long TIMEOUT_SLOP_MS = 20L; // 20ms /** - * Tolerance for idle timeouts, to cope with timer undershoots + * The default value for COMMON_MAX_SPARES. Overridable using the + * "java.util.concurrent.ForkJoinPool.common.maximumSpares" system + * property. The default value is far in excess of normal + * requirements, but also far short of MAX_CAP and typical OS + * thread limits, so allows JVMs to catch misuse/abuse before + * running out of resources needed to do so. */ - private static final long TIMEOUT_SLOP = 2000000L; - - /** - * The maximum stolen->joining link depth allowed in method - * tryHelpStealer. Must be a power of two. Depths for legitimate - * chains are unbounded, but we use a fixed constant to avoid - * (otherwise unchecked) cycles and to bound staleness of - * traversal parameters at the expense of sometimes blocking when - * we could be helping. - */ - private static final int MAX_HELP = 64; + private static final int DEFAULT_COMMON_MAX_SPARES = 256; /** * Increment for seed generators. See class ThreadLocal for * explanation. */ - private static final int SEED_INCREMENT = 0x61c88647; + private static final int SEED_INCREMENT = 0x9e3779b9; /* - * Bits and masks for control variables - * - * Field ctl is a long packed with: - * AC: Number of active running workers minus target parallelism (16 bits) - * TC: Number of total workers minus target parallelism (16 bits) - * ST: true if pool is terminating (1 bit) - * EC: the wait count of top waiting thread (15 bits) - * ID: poolIndex of top of Treiber stack of waiters (16 bits) - * - * When convenient, we can extract the upper 32 bits of counts and - * the lower 32 bits of queue state, u = (int)(ctl >>> 32) and e = - * (int)ctl. The ec field is never accessed alone, but always - * together with id and st. The offsets of counts by the target - * parallelism and the positionings of fields makes it possible to - * perform the most common checks via sign tests of fields: When - * ac is negative, there are not enough active workers, when tc is - * negative, there are not enough total workers, and when e is - * negative, the pool is terminating. To deal with these possibly - * negative fields, we use casts in and out of "short" and/or - * signed shifts to maintain signedness. - * - * When a thread is queued (inactivated), its eventCount field is - * set negative, which is the only way to tell if a worker is - * prevented from executing tasks, even though it must continue to - * scan for them to avoid queuing races. Note however that - * eventCount updates lag releases so usage requires care. - * - * Field plock is an int packed with: - * SHUTDOWN: true if shutdown is enabled (1 bit) - * SEQ: a sequence lock, with PL_LOCK bit set if locked (30 bits) - * SIGNAL: set when threads may be waiting on the lock (1 bit) - * - * The sequence number enables simple consistency checks: - * Staleness of read-only operations on the workQueues array can - * be checked by comparing plock before vs after the reads. - */ - - // bit positions/shifts for fields + * Bits and masks for field ctl, packed with 4 16 bit subfields: + * AC: Number of active running workers minus target parallelism + * TC: Number of total workers minus target parallelism + * SS: version count and status of top waiting thread + * ID: poolIndex of top of Treiber stack of waiters + * + * When convenient, we can extract the lower 32 stack top bits + * (including version bits) as sp=(int)ctl. The offsets of counts + * by the target parallelism and the positionings of fields makes + * it possible to perform the most common checks via sign tests of + * fields: When ac is negative, there are not enough active + * workers, when tc is negative, there are not enough total + * workers. When sp is non-zero, there are waiting workers. To + * deal with possibly negative fields, we use casts in and out of + * "short" and/or signed shifts to maintain signedness. + * + * Because it occupies uppermost bits, we can add one active count + * using getAndAddLong of AC_UNIT, rather than CAS, when returning + * from a blocked join. Other updates entail multiple subfields + * and masking, requiring CAS. + */ + + // Lower and upper word masks + private static final long SP_MASK = 0xffffffffL; + private static final long UC_MASK = ~SP_MASK; + + // Active counts private static final int AC_SHIFT = 48; + private static final long AC_UNIT = 0x0001L << AC_SHIFT; + private static final long AC_MASK = 0xffffL << AC_SHIFT; + + // Total counts private static final int TC_SHIFT = 32; - private static final int ST_SHIFT = 31; - private static final int EC_SHIFT = 16; - - // bounds - private static final int SMASK = 0xffff; // short bits - private static final int MAX_CAP = 0x7fff; // max #workers - 1 - private static final int EVENMASK = 0xfffe; // even short bits - private static final int SQMASK = 0x007e; // max 64 (even) slots - private static final int SHORT_SIGN = 1 << 15; - private static final int INT_SIGN = 1 << 31; - - // masks - private static final long STOP_BIT = 0x0001L << ST_SHIFT; - private static final long AC_MASK = ((long)SMASK) << AC_SHIFT; - private static final long TC_MASK = ((long)SMASK) << TC_SHIFT; - - // units for incrementing and decrementing - private static final long TC_UNIT = 1L << TC_SHIFT; - private static final long AC_UNIT = 1L << AC_SHIFT; - - // masks and units for dealing with u = (int)(ctl >>> 32) - private static final int UAC_SHIFT = AC_SHIFT - 32; - private static final int UTC_SHIFT = TC_SHIFT - 32; - private static final int UAC_MASK = SMASK << UAC_SHIFT; - private static final int UTC_MASK = SMASK << UTC_SHIFT; - private static final int UAC_UNIT = 1 << UAC_SHIFT; - private static final int UTC_UNIT = 1 << UTC_SHIFT; - - // masks and units for dealing with e = (int)ctl - private static final int E_MASK = 0x7fffffff; // no STOP_BIT - private static final int E_SEQ = 1 << EC_SHIFT; - - // plock bits - private static final int SHUTDOWN = 1 << 31; - private static final int PL_LOCK = 2; - private static final int PL_SIGNAL = 1; - private static final int PL_SPINS = 1 << 8; - - // access mode for WorkQueue - static final int LIFO_QUEUE = 0; - static final int FIFO_QUEUE = 1; - static final int SHARED_QUEUE = -1; - - // Heuristic padding to ameliorate unfortunate memory placements - volatile long pad00, pad01, pad02, pad03, pad04, pad05, pad06; + private static final long TC_UNIT = 0x0001L << TC_SHIFT; + private static final long TC_MASK = 0xffffL << TC_SHIFT; + private static final long ADD_WORKER = 0x0001L << (TC_SHIFT + 15); // sign + + // runState bits: SHUTDOWN must be negative, others arbitrary powers of two + private static final int STARTED = 1; + private static final int STOP = 1 << 1; + private static final int TERMINATED = 1 << 2; + private static final int SHUTDOWN = 1 << 31; // Instance fields - volatile long stealCount; // collects worker counts - volatile long ctl; // main pool control - volatile int plock; // shutdown status and seqLock - volatile int indexSeed; // worker/submitter index seed - final short parallelism; // parallelism level - final short mode; // LIFO/FIFO - WorkQueue[] workQueues; // main registry + volatile long ctl; // main pool control + volatile int runState; + final int config; // parallelism, mode + AuxState auxState; // lock, steal counts + volatile WorkQueue[] workQueues; // main registry + final String workerNamePrefix; // to create worker name string final ForkJoinWorkerThreadFactory factory; - final UncaughtExceptionHandler ueh; // per-worker UEH - final String workerNamePrefix; // to create worker name string - - volatile Object pad10, pad11, pad12, pad13, pad14, pad15, pad16, pad17; - volatile Object pad18, pad19, pad1a, pad1b; - - /** - * Acquires the plock lock to protect worker array and related - * updates. This method is called only if an initial CAS on plock - * fails. This acts as a spinlock for normal cases, but falls back - * to builtin monitor to block when (rarely) needed. This would be - * a terrible idea for a highly contended lock, but works fine as - * a more conservative alternative to a pure spinlock. - */ - private int acquirePlock() { - int spins = PL_SPINS, ps, nps; - for (;;) { - if (((ps = plock) & PL_LOCK) == 0 && - U.compareAndSwapInt(this, PLOCK, ps, nps = ps + PL_LOCK)) - return nps; - else if (spins >= 0) { - if (ThreadLocalRandom.current().nextInt() >= 0) - --spins; - } - else if (U.compareAndSwapInt(this, PLOCK, ps, ps | PL_SIGNAL)) { - synchronized (this) { - if ((plock & PL_SIGNAL) != 0) { - try { - wait(); - } catch (InterruptedException ie) { - try { - Thread.currentThread().interrupt(); - } catch (SecurityException ignore) { - } - } - } - else - notifyAll(); + final UncaughtExceptionHandler ueh; // per-worker UEH + + /** + * Instantiates fields upon first submission, or upon shutdown if + * no submissions. If checkTermination true, also responds to + * termination by external calls submitting tasks. + */ + private void tryInitialize(boolean checkTermination) { + if (runState == 0) { // bootstrap by locking static field + int p = config & SMASK; + int n = (p > 1) ? p - 1 : 1; // ensure at least 2 slots + n |= n >>> 1; // create workQueues array with size a power of two + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + n = ((n + 1) << 1) & SMASK; + AuxState aux = new AuxState(); + WorkQueue[] ws = new WorkQueue[n]; + synchronized (modifyThreadPermission) { // double-check + if (runState == 0) { + workQueues = ws; + auxState = aux; + runState = STARTED; } } } + if (checkTermination && runState < 0) { + tryTerminate(false, false); // help terminate + throw new RejectedExecutionException(); + } } + // Creating, registering and deregistering workers + /** - * Unlocks and signals any thread waiting for plock. Called only - * when CAS of seq value for unlock fails. + * Tries to construct and start one worker. Assumes that total + * count has already been incremented as a reservation. Invokes + * deregisterWorker on any failure. + * + * @param isSpare true if this is a spare thread + * @return true if successful */ - private void releasePlock(int ps) { - plock = ps; - synchronized (this) { notifyAll(); } + private boolean createWorker(boolean isSpare) { + ForkJoinWorkerThreadFactory fac = factory; + Throwable ex = null; + ForkJoinWorkerThread wt = null; + WorkQueue q; + try { + if (fac != null && (wt = fac.newThread(this)) != null) { + if (isSpare && (q = wt.workQueue) != null) + q.config |= SPARE_WORKER; + wt.start(); + return true; + } + } catch (Throwable rex) { + ex = rex; + } + deregisterWorker(wt, ex); + return false; } /** - * Tries to create and start one worker if fewer than target - * parallelism level exist. Adjusts counts etc on failure. + * Tries to add one worker, incrementing ctl counts before doing + * so, relying on createWorker to back out on failure. + * + * @param c incoming ctl value, with total count negative and no + * idle workers. On CAS failure, c is refreshed and retried if + * this holds (otherwise, a new worker is not needed). */ - private void tryAddWorker() { - long c; int u, e; - while ((u = (int)((c = ctl) >>> 32)) < 0 && - (u & SHORT_SIGN) != 0 && (e = (int)c) >= 0) { - long nc = ((long)(((u + UTC_UNIT) & UTC_MASK) | - ((u + UAC_UNIT) & UAC_MASK)) << 32) | (long)e; - if (U.compareAndSwapLong(this, CTL, c, nc)) { - ForkJoinWorkerThreadFactory fac; - Throwable ex = null; - ForkJoinWorkerThread wt = null; - try { - if ((fac = factory) != null && - (wt = fac.newThread(this)) != null) { - wt.start(); - break; - } - } catch (Throwable rex) { - ex = rex; - } - deregisterWorker(wt, ex); + private void tryAddWorker(long c) { + do { + long nc = ((AC_MASK & (c + AC_UNIT)) | + (TC_MASK & (c + TC_UNIT))); + if (ctl == c && U.compareAndSwapLong(this, CTL, c, nc)) { + createWorker(false); break; } - } + } while (((c = ctl) & ADD_WORKER) != 0L && (int)c == 0); } - // Registering and deregistering workers - /** - * Callback from ForkJoinWorkerThread to establish and record its - * WorkQueue. To avoid scanning bias due to packing entries in - * front of the workQueues array, we treat the array as a simple - * power-of-two hash table using per-thread seed as hash, - * expanding as needed. + * Callback from ForkJoinWorkerThread constructor to establish and + * record its WorkQueue. * * @param wt the worker thread * @return the worker's queue */ final WorkQueue registerWorker(ForkJoinWorkerThread wt) { - UncaughtExceptionHandler handler; WorkQueue[] ws; int s, ps; - wt.setDaemon(true); + UncaughtExceptionHandler handler; + AuxState aux; + wt.setDaemon(true); // configure thread if ((handler = ueh) != null) wt.setUncaughtExceptionHandler(handler); - do {} while (!U.compareAndSwapInt(this, INDEXSEED, s = indexSeed, - s += SEED_INCREMENT) || - s == 0); // skip 0 - WorkQueue w = new WorkQueue(this, wt, mode, s); - if (((ps = plock) & PL_LOCK) != 0 || - !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) - ps = acquirePlock(); - int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN); - try { - if ((ws = workQueues) != null) { // skip if shutting down - int n = ws.length, m = n - 1; - int r = (s << 1) | 1; // use odd-numbered indices - if (ws[r &= m] != null) { // collision - int probes = 0; // step by approx half size - int step = (n <= 4) ? 2 : ((n >>> 1) & EVENMASK) + 2; - while (ws[r = (r + step) & m] != null) { - if (++probes >= n) { - workQueues = ws = Arrays.copyOf(ws, n <<= 1); - m = n - 1; - probes = 0; + WorkQueue w = new WorkQueue(this, wt); + int i = 0; // assign a pool index + int mode = config & MODE_MASK; + if ((aux = auxState) != null) { + aux.lock(); + try { + int s = (int)(aux.indexSeed += SEED_INCREMENT), n, m; + WorkQueue[] ws = workQueues; + if (ws != null && (n = ws.length) > 0) { + i = (m = n - 1) & ((s << 1) | 1); // odd-numbered indices + if (ws[i] != null) { // collision + int probes = 0; // step by approx half n + int step = (n <= 4) ? 2 : ((n >>> 1) & EVENMASK) + 2; + while (ws[i = (i + step) & m] != null) { + if (++probes >= n) { + workQueues = ws = Arrays.copyOf(ws, n <<= 1); + m = n - 1; + probes = 0; + } } } + w.hint = s; // use as random seed + w.config = i | mode; + w.scanState = i | (s & 0x7fff0000); // random seq bits + ws[i] = w; } - w.poolIndex = (short)r; - w.eventCount = r; // volatile write orders - ws[r] = w; + } finally { + aux.unlock(); } - } finally { - if (!U.compareAndSwapInt(this, PLOCK, ps, nps)) - releasePlock(nps); } - wt.setName(workerNamePrefix.concat(Integer.toString(w.poolIndex >>> 1))); + wt.setName(workerNamePrefix.concat(Integer.toString(i >>> 1))); return w; } @@ -1370,672 +1605,628 @@ final WorkQueue registerWorker(ForkJoinWorkerThread wt) { final void deregisterWorker(ForkJoinWorkerThread wt, Throwable ex) { WorkQueue w = null; if (wt != null && (w = wt.workQueue) != null) { - int ps; long sc; - w.qlock = -1; // ensure set - do {} while (!U.compareAndSwapLong(this, STEALCOUNT, - sc = stealCount, - sc + w.nsteals)); - if (((ps = plock) & PL_LOCK) != 0 || - !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) - ps = acquirePlock(); - int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN); - try { - int idx = w.poolIndex; - WorkQueue[] ws = workQueues; - if (ws != null && idx >= 0 && idx < ws.length && ws[idx] == w) - ws[idx] = null; - } finally { - if (!U.compareAndSwapInt(this, PLOCK, ps, nps)) - releasePlock(nps); + AuxState aux; WorkQueue[] ws; // remove index from array + int idx = w.config & SMASK; + int ns = w.nsteals; + if ((aux = auxState) != null) { + aux.lock(); + try { + if ((ws = workQueues) != null && ws.length > idx && + ws[idx] == w) + ws[idx] = null; + aux.stealCount += ns; + } finally { + aux.unlock(); + } } } - - long c; // adjust ctl counts - do {} while (!U.compareAndSwapLong - (this, CTL, c = ctl, (((c - AC_UNIT) & AC_MASK) | - ((c - TC_UNIT) & TC_MASK) | - (c & ~(AC_MASK|TC_MASK))))); - - if (!tryTerminate(false, false) && w != null && w.array != null) { - w.cancelAll(); // cancel remaining tasks - WorkQueue[] ws; WorkQueue v; Thread p; int u, i, e; - while ((u = (int)((c = ctl) >>> 32)) < 0 && (e = (int)c) >= 0) { - if (e > 0) { // activate or create replacement - if ((ws = workQueues) == null || - (i = e & SMASK) >= ws.length || - (v = ws[i]) == null) - break; - long nc = (((long)(v.nextWait & E_MASK)) | - ((long)(u + UAC_UNIT) << 32)); - if (v.eventCount != (e | INT_SIGN)) - break; - if (U.compareAndSwapLong(this, CTL, c, nc)) { - v.eventCount = (e + E_SEQ) & E_MASK; - if ((p = v.parker) != null) - U.unpark(p); - break; - } - } - else { - if ((short)u < 0) - tryAddWorker(); + if (w == null || (w.config & UNREGISTERED) == 0) { // else pre-adjusted + long c; // decrement counts + do {} while (!U.compareAndSwapLong + (this, CTL, c = ctl, ((AC_MASK & (c - AC_UNIT)) | + (TC_MASK & (c - TC_UNIT)) | + (SP_MASK & c)))); + } + if (w != null) { + w.currentSteal = null; + w.qlock = -1; // ensure set + w.cancelAll(); // cancel remaining tasks + } + while (tryTerminate(false, false) >= 0) { // possibly replace + WorkQueue[] ws; int wl, sp; long c; + if (w == null || w.array == null || + (ws = workQueues) == null || (wl = ws.length) <= 0) + break; + else if ((sp = (int)(c = ctl)) != 0) { // wake up replacement + if (tryRelease(c, ws[(wl - 1) & sp], AC_UNIT)) break; - } } + else if (ex != null && (c & ADD_WORKER) != 0L) { + tryAddWorker(c); // create replacement + break; + } + else // don't need replacement + break; } - if (ex == null) // help clean refs on way out + if (ex == null) // help clean on way out ForkJoinTask.helpExpungeStaleExceptions(); - else // rethrow + else // rethrow ForkJoinTask.rethrow(ex); } - // Submissions + // Signalling /** - * Per-thread records for threads that submit to pools. Currently - * holds only pseudo-random seed / index that is used to choose - * submission queues in method externalPush. In the future, this may - * also incorporate a means to implement different task rejection - * and resubmission policies. - * - * Seeds for submitters and workers/workQueues work in basically - * the same way but are initialized and updated using slightly - * different mechanics. Both are initialized using the same - * approach as in class ThreadLocal, where successive values are - * unlikely to collide with previous values. Seeds are then - * randomly modified upon collisions using xorshifts, which - * requires a non-zero seed. - */ - static final class Submitter { - int seed; - Submitter(int s) { seed = s; } - } - - /** - * Unless shutting down, adds the given task to a submission queue - * at submitter's current queue index (modulo submission - * range). Only the most common path is directly handled in this - * method. All others are relayed to fullExternalPush. - * - * @param task the task. Caller must ensure non-null. + * Tries to create or activate a worker if too few are active. */ - final void externalPush(ForkJoinTask task) { - Submitter z = submitters.get(); - WorkQueue q; int r, m, s, n, am; ForkJoinTask[] a; - int ps = plock; - WorkQueue[] ws = workQueues; - if (z != null && ps > 0 && ws != null && (m = (ws.length - 1)) >= 0 && - (q = ws[m & (r = z.seed) & SQMASK]) != null && r != 0 && - U.compareAndSwapInt(q, QLOCK, 0, 1)) { // lock - if ((a = q.array) != null && - (am = a.length - 1) > (n = (s = q.top) - q.base)) { - int j = ((am & s) << ASHIFT) + ABASE; - U.putOrderedObject(a, j, task); - q.top = s + 1; // push on to deque - q.qlock = 0; - if (n <= 1) - signalWork(ws, q); - return; - } - q.qlock = 0; - } - fullExternalPush(task); - } - - /** - * Full version of externalPush. This method is called, among - * other times, upon the first submission of the first task to the - * pool, so must perform secondary initialization. It also - * detects first submission by an external thread by looking up - * its ThreadLocal, and creates a new shared queue if the one at - * index if empty or contended. The plock lock body must be - * exception-free (so no try/finally) so we optimistically - * allocate new queues outside the lock and throw them away if - * (very rarely) not needed. - * - * Secondary initialization occurs when plock is zero, to create - * workQueue array and set plock to a valid value. This lock body - * must also be exception-free. Because the plock seq value can - * eventually wrap around zero, this method harmlessly fails to - * reinitialize if workQueues exists, while still advancing plock. - */ - private void fullExternalPush(ForkJoinTask task) { - int r = 0; // random index seed - for (Submitter z = submitters.get();;) { - WorkQueue[] ws; WorkQueue q; int ps, m, k; - if (z == null) { - if (U.compareAndSwapInt(this, INDEXSEED, r = indexSeed, - r += SEED_INCREMENT) && r != 0) - submitters.set(z = new Submitter(r)); - } - else if (r == 0) { // move to a different index - r = z.seed; - r ^= r << 13; // same xorshift as WorkQueues - r ^= r >>> 17; - z.seed = r ^= (r << 5); - } - if ((ps = plock) < 0) - throw new RejectedExecutionException(); - else if (ps == 0 || (ws = workQueues) == null || - (m = ws.length - 1) < 0) { // initialize workQueues - int p = parallelism; // find power of two table size - int n = (p > 1) ? p - 1 : 1; // ensure at least 2 slots - n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; - n |= n >>> 8; n |= n >>> 16; n = (n + 1) << 1; - WorkQueue[] nws = ((ws = workQueues) == null || ws.length == 0 ? - new WorkQueue[n] : null); - if (((ps = plock) & PL_LOCK) != 0 || - !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) - ps = acquirePlock(); - if (((ws = workQueues) == null || ws.length == 0) && nws != null) - workQueues = nws; - int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN); - if (!U.compareAndSwapInt(this, PLOCK, ps, nps)) - releasePlock(nps); - } - else if ((q = ws[k = r & m & SQMASK]) != null) { - if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) { - ForkJoinTask[] a = q.array; - int s = q.top; - boolean submitted = false; - try { // locked version of push - if ((a != null && a.length > s + 1 - q.base) || - (a = q.growArray()) != null) { // must presize - int j = (((a.length - 1) & s) << ASHIFT) + ABASE; - U.putOrderedObject(a, j, task); - q.top = s + 1; - submitted = true; - } - } finally { - q.qlock = 0; // unlock - } - if (submitted) { - signalWork(ws, q); - return; - } + final void signalWork() { + for (;;) { + long c; int sp, i; WorkQueue v; WorkQueue[] ws; + if ((c = ctl) >= 0L) // enough workers + break; + else if ((sp = (int)c) == 0) { // no idle workers + if ((c & ADD_WORKER) != 0L) // too few workers + tryAddWorker(c); + break; + } + else if ((ws = workQueues) == null) + break; // unstarted/terminated + else if (ws.length <= (i = sp & SMASK)) + break; // terminated + else if ((v = ws[i]) == null) + break; // terminating + else { + int ns = sp & ~UNSIGNALLED; + int vs = v.scanState; + long nc = (v.stackPred & SP_MASK) | (UC_MASK & (c + AC_UNIT)); + if (sp == vs && U.compareAndSwapLong(this, CTL, c, nc)) { + v.scanState = ns; + LockSupport.unpark(v.parker); + break; } - r = 0; // move on failure - } - else if (((ps = plock) & PL_LOCK) == 0) { // create new queue - q = new WorkQueue(this, null, SHARED_QUEUE, r); - q.poolIndex = (short)k; - if (((ps = plock) & PL_LOCK) != 0 || - !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) - ps = acquirePlock(); - if ((ws = workQueues) != null && k < ws.length && ws[k] == null) - ws[k] = q; - int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN); - if (!U.compareAndSwapInt(this, PLOCK, ps, nps)) - releasePlock(nps); } - else - r = 0; } } - // Maintaining ctl counts - /** - * Increments active count; mainly called upon return from blocking. + * Signals and releases worker v if it is top of idle worker + * stack. This performs a one-shot version of signalWork only if + * there is (apparently) at least one idle worker. + * + * @param c incoming ctl value + * @param v if non-null, a worker + * @param inc the increment to active count (zero when compensating) + * @return true if successful */ - final void incrementActiveCount() { - long c; - do {} while (!U.compareAndSwapLong - (this, CTL, c = ctl, ((c & ~AC_MASK) | - ((c & AC_MASK) + AC_UNIT)))); + private boolean tryRelease(long c, WorkQueue v, long inc) { + int sp = (int)c, ns = sp & ~UNSIGNALLED; + if (v != null) { + int vs = v.scanState; + long nc = (v.stackPred & SP_MASK) | (UC_MASK & (c + inc)); + if (sp == vs && U.compareAndSwapLong(this, CTL, c, nc)) { + v.scanState = ns; + LockSupport.unpark(v.parker); + return true; + } + } + return false; } /** - * Tries to create or activate a worker if too few are active. + * With approx probability of a missed signal, tries (once) to + * reactivate worker w (or some other worker), failing if stale or + * known to be already active. * - * @param ws the worker array to use to find signallees - * @param q if non-null, the queue holding tasks to be processed + * @param w the worker + * @param ws the workQueue array to use + * @param r random seed */ - final void signalWork(WorkQueue[] ws, WorkQueue q) { - for (;;) { - long c; int e, u, i; WorkQueue w; Thread p; - if ((u = (int)((c = ctl) >>> 32)) >= 0) - break; - if ((e = (int)c) <= 0) { - if ((short)u < 0) - tryAddWorker(); - break; - } - if (ws == null || ws.length <= (i = e & SMASK) || - (w = ws[i]) == null) - break; - long nc = (((long)(w.nextWait & E_MASK)) | - ((long)(u + UAC_UNIT)) << 32); - int ne = (e + E_SEQ) & E_MASK; - if (w.eventCount == (e | INT_SIGN) && + private void tryReactivate(WorkQueue w, WorkQueue[] ws, int r) { + long c; int sp, wl; WorkQueue v; + if ((sp = (int)(c = ctl)) != 0 && w != null && + ws != null && (wl = ws.length) > 0 && + ((sp ^ r) & SS_SEQ) == 0 && + (v = ws[(wl - 1) & sp]) != null) { + long nc = (v.stackPred & SP_MASK) | (UC_MASK & (c + AC_UNIT)); + int ns = sp & ~UNSIGNALLED; + if (w.scanState < 0 && + v.scanState == sp && U.compareAndSwapLong(this, CTL, c, nc)) { - w.eventCount = ne; - if ((p = w.parker) != null) - U.unpark(p); - break; + v.scanState = ns; + LockSupport.unpark(v.parker); } - if (q != null && q.base >= q.top) - break; } } - // Scanning for tasks - /** - * Top-level runloop for workers, called by ForkJoinWorkerThread.run. + * If worker w exists and is active, enqueues and sets status to inactive. + * + * @param w the worker + * @param ss current (non-negative) scanState */ - final void runWorker(WorkQueue w) { - w.growArray(); // allocate queue - for (int r = w.hint; scan(w, r) == 0; ) { - r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift + private void inactivate(WorkQueue w, int ss) { + int ns = (ss + SS_SEQ) | UNSIGNALLED; + long lc = ns & SP_MASK, nc, c; + if (w != null) { + w.scanState = ns; + do { + nc = lc | (UC_MASK & ((c = ctl) - AC_UNIT)); + w.stackPred = (int)c; + } while (!U.compareAndSwapLong(this, CTL, c, nc)); } } /** - * Scans for and, if found, runs one task, else possibly - * inactivates the worker. This method operates on single reads of - * volatile state and is designed to be re-invoked continuously, - * in part because it returns upon detecting inconsistencies, - * contention, or state changes that indicate possible success on - * re-invocation. + * Possibly blocks worker w waiting for signal, or returns + * negative status if the worker should terminate. May return + * without status change if multiple stale unparks and/or + * interrupts occur. * - * The scan searches for tasks across queues starting at a random - * index, checking each at least twice. The scan terminates upon - * either finding a non-empty queue, or completing the sweep. If - * the worker is not inactivated, it takes and runs a task from - * this queue. Otherwise, if not activated, it tries to activate - * itself or some other worker by signalling. On failure to find a - * task, returns (for retry) if pool state may have changed during - * an empty scan, or tries to inactivate if active, else possibly - * blocks or terminates via method awaitWork. + * @param w the calling worker + * @return negative if w should terminate + */ + private int awaitWork(WorkQueue w) { + int stat = 0; + if (w != null && w.scanState < 0) { + long c = ctl; + if ((int)(c >> AC_SHIFT) + (config & SMASK) <= 0) + stat = timedAwaitWork(w, c); // possibly quiescent + else if ((runState & STOP) != 0) + stat = w.qlock = -1; // pool terminating + else if (w.scanState < 0) { + w.parker = Thread.currentThread(); + if (w.scanState < 0) // recheck after write + LockSupport.park(this); + w.parker = null; + if ((runState & STOP) != 0) + stat = w.qlock = -1; // recheck + else if (w.scanState < 0) + Thread.interrupted(); // clear status + } + } + return stat; + } + + /** + * Possibly triggers shutdown and tries (once) to block worker + * when pool is (or may be) quiescent. Waits up to a duration + * determined by number of workers. On timeout, if ctl has not + * changed, terminates the worker, which will in turn wake up + * another worker to possibly repeat this process. * - * @param w the worker (via its WorkQueue) - * @param r a random seed - * @return worker qlock status if would have waited, else 0 - */ - private final int scan(WorkQueue w, int r) { - WorkQueue[] ws; int m; - long c = ctl; // for consistency check - if ((ws = workQueues) != null && (m = ws.length - 1) >= 0 && w != null) { - for (int j = m + m + 1, ec = w.eventCount;;) { - WorkQueue q; int b, e; ForkJoinTask[] a; ForkJoinTask t; - if ((q = ws[(r - j) & m]) != null && - (b = q.base) - q.top < 0 && (a = q.array) != null) { - long i = (((a.length - 1) & b) << ASHIFT) + ABASE; - if ((t = ((ForkJoinTask) - U.getObjectVolatile(a, i))) != null) { - if (ec < 0) - helpRelease(c, ws, w, q, b); - else if (q.base == b && - U.compareAndSwapObject(a, i, t, null)) { - U.putOrderedInt(q, QBASE, b + 1); - if ((b + 1) - q.top < 0) - signalWork(ws, q); - w.runTask(t); - } + * @param w the calling worker + * @return negative if w should terminate + */ + private int timedAwaitWork(WorkQueue w, long c) { + int stat = 0; + int scale = 1 - (short)(c >>> TC_SHIFT); + long deadline = (((scale <= 0) ? 1 : scale) * IDLE_TIMEOUT_MS + + System.currentTimeMillis()); + if ((runState >= 0 || (stat = tryTerminate(false, false)) > 0) && + w != null && w.scanState < 0) { + int ss; AuxState aux; + w.parker = Thread.currentThread(); + if (w.scanState < 0) + LockSupport.parkUntil(this, deadline); + w.parker = null; + if ((runState & STOP) != 0) + stat = w.qlock = -1; // pool terminating + else if ((ss = w.scanState) < 0 && !Thread.interrupted() && + (int)c == ss && (aux = auxState) != null && ctl == c && + deadline - System.currentTimeMillis() <= TIMEOUT_SLOP_MS) { + aux.lock(); + try { // pre-deregister + WorkQueue[] ws; + int cfg = w.config, idx = cfg & SMASK; + long nc = ((UC_MASK & (c - TC_UNIT)) | + (SP_MASK & w.stackPred)); + if ((runState & STOP) == 0 && + (ws = workQueues) != null && + idx < ws.length && idx >= 0 && ws[idx] == w && + U.compareAndSwapLong(this, CTL, c, nc)) { + ws[idx] = null; + w.config = cfg | UNREGISTERED; + stat = w.qlock = -1; } - break; + } finally { + aux.unlock(); + } + } + } + return stat; + } + + /** + * If the given worker is a spare with no queued tasks, and there + * are enough existing workers, drops it from ctl counts and sets + * its state to terminated. + * + * @param w the calling worker -- must be a spare + * @return true if dropped (in which case it must not process more tasks) + */ + private boolean tryDropSpare(WorkQueue w) { + if (w != null && w.isEmpty()) { // no local tasks + long c; int sp, wl; WorkQueue[] ws; WorkQueue v; + while ((short)((c = ctl) >> TC_SHIFT) > 0 && + ((sp = (int)c) != 0 || (int)(c >> AC_SHIFT) > 0) && + (ws = workQueues) != null && (wl = ws.length) > 0) { + boolean dropped, canDrop; + if (sp == 0) { // no queued workers + long nc = ((AC_MASK & (c - AC_UNIT)) | + (TC_MASK & (c - TC_UNIT)) | (SP_MASK & c)); + dropped = U.compareAndSwapLong(this, CTL, c, nc); } - else if (--j < 0) { - if ((ec | (e = (int)c)) < 0) // inactive or terminating - return awaitWork(w, c, ec); - else if (ctl == c) { // try to inactivate and enqueue - long nc = (long)ec | ((c - AC_UNIT) & (AC_MASK|TC_MASK)); - w.nextWait = e; - w.eventCount = ec | INT_SIGN; - if (!U.compareAndSwapLong(this, CTL, c, nc)) - w.eventCount = ec; // back out + else if ( + (v = ws[(wl - 1) & sp]) == null || v.scanState != sp) + dropped = false; // stale; retry + else { + long nc = v.stackPred & SP_MASK; + if (w == v || w.scanState >= 0) { + canDrop = true; // w unqueued or topmost + nc |= ((AC_MASK & c) | // ensure replacement + (TC_MASK & (c - TC_UNIT))); } - break; + else { // w may be queued + canDrop = false; // help uncover + nc |= ((AC_MASK & (c + AC_UNIT)) | + (TC_MASK & c)); + } + if (U.compareAndSwapLong(this, CTL, c, nc)) { + v.scanState = sp & ~UNSIGNALLED; + LockSupport.unpark(v.parker); + dropped = canDrop; + } + else + dropped = false; + } + if (dropped) { // pre-deregister + int cfg = w.config, idx = cfg & SMASK; + if (idx >= 0 && idx < ws.length && ws[idx] == w) + ws[idx] = null; + w.config = cfg | UNREGISTERED; + w.qlock = -1; + return true; } } } - return 0; + return false; } /** - * A continuation of scan(), possibly blocking or terminating - * worker w. Returns without blocking if pool state has apparently - * changed since last invocation. Also, if inactivating w has - * caused the pool to become quiescent, checks for pool - * termination, and, so long as this is not the only worker, waits - * for event for up to a given duration. On timeout, if ctl has - * not changed, terminates the worker, which will in turn wake up - * another worker to possibly repeat this process. - * - * @param w the calling worker - * @param c the ctl value on entry to scan - * @param ec the worker's eventCount on entry to scan - */ - private final int awaitWork(WorkQueue w, long c, int ec) { - int stat, ns; long parkTime, deadline; - if ((stat = w.qlock) >= 0 && w.eventCount == ec && ctl == c && - !Thread.interrupted()) { - int e = (int)c; - int u = (int)(c >>> 32); - int d = (u >> UAC_SHIFT) + parallelism; // active count - - if (e < 0 || (d <= 0 && tryTerminate(false, false))) - stat = w.qlock = -1; // pool is terminating - else if ((ns = w.nsteals) != 0) { // collect steals and retry - long sc; - w.nsteals = 0; - do {} while (!U.compareAndSwapLong(this, STEALCOUNT, - sc = stealCount, sc + ns)); + * Top-level runloop for workers, called by ForkJoinWorkerThread.run. + */ + final void runWorker(WorkQueue w) { + w.growArray(); // allocate queue + int bound = (w.config & SPARE_WORKER) != 0 ? 0 : POLL_LIMIT; + long seed = w.hint * 0xdaba0b6eb09322e3L; // initial random seed + if ((runState & STOP) == 0) { + for (long r = (seed == 0L) ? 1L : seed;;) { // ensure nonzero + if (bound == 0 && tryDropSpare(w)) + break; + // high bits of prev seed for step; current low bits for idx + int step = (int)(r >>> 48) | 1; + r ^= r >>> 12; r ^= r << 25; r ^= r >>> 27; // xorshift + if (scan(w, bound, step, (int)r) < 0 && awaitWork(w) < 0) + break; } - else { - long pc = ((d > 0 || ec != (e | INT_SIGN)) ? 0L : - ((long)(w.nextWait & E_MASK)) | // ctl to restore - ((long)(u + UAC_UNIT)) << 32); - if (pc != 0L) { // timed wait if last waiter - int dc = -(short)(c >>> TC_SHIFT); - parkTime = (dc < 0 ? FAST_IDLE_TIMEOUT: - (dc + 1) * IDLE_TIMEOUT); - deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP; + } + } + + // Scanning for tasks + + /** + * Repeatedly scans for and tries to steal and execute (via + * workQueue.runTask) a queued task. Each scan traverses queues in + * pseudorandom permutation. Upon finding a non-empty queue, makes + * at most the given bound attempts to re-poll (fewer if + * contended) on the same queue before returning (impossible + * scanState value) 0 to restart scan. Else returns after at least + * 1 and at most 32 full scans. + * + * @param w the worker (via its WorkQueue) + * @param bound repoll bound as bitmask (0 if spare) + * @param step (circular) index increment per iteration (must be odd) + * @param r a random seed for origin index + * @return negative if should await signal + */ + private int scan(WorkQueue w, int bound, int step, int r) { + int stat = 0, wl; WorkQueue[] ws; + if ((ws = workQueues) != null && w != null && (wl = ws.length) > 0) { + for (int m = wl - 1, + origin = m & r, idx = origin, + npolls = 0, + ss = w.scanState;;) { // negative if inactive + WorkQueue q; ForkJoinTask[] a; int b, al; + if ((q = ws[idx]) != null && (b = q.base) - q.top < 0 && + (a = q.array) != null && (al = a.length) > 0) { + int index = (al - 1) & b; + long offset = ((long)index << ASHIFT) + ABASE; + ForkJoinTask t = (ForkJoinTask) + U.getObjectVolatile(a, offset); + if (t == null) + break; // empty or busy + else if (b++ != q.base) + break; // busy + else if (ss < 0) { + tryReactivate(w, ws, r); + break; // retry upon rescan + } + else if (!U.compareAndSwapObject(a, offset, t, null)) + break; // contended + else { + q.base = b; + w.currentSteal = t; + if (b != q.top) // propagate signal + signalWork(); + w.runTask(t); + if (++npolls > bound) + break; + } } - else - parkTime = deadline = 0L; - if (w.eventCount == ec && ctl == c) { - Thread wt = Thread.currentThread(); - U.putObject(wt, PARKBLOCKER, this); - w.parker = wt; // emulate LockSupport.park - if (w.eventCount == ec && ctl == c) - U.park(false, parkTime); // must recheck before park - w.parker = null; - U.putObject(wt, PARKBLOCKER, null); - if (parkTime != 0L && ctl == c && - deadline - System.nanoTime() <= 0L && - U.compareAndSwapLong(this, CTL, c, pc)) - stat = w.qlock = -1; // shrink pool + else if (npolls != 0) // rescan + break; + else if ((idx = (idx + step) & m) == origin) { + if (ss < 0) { // await signal + stat = ss; + break; + } + else if (r >= 0) { + inactivate(w, ss); + break; + } + else + r <<= 1; // at most 31 rescans } } } return stat; } + // Joining tasks + /** - * Possibly releases (signals) a worker. Called only from scan() - * when a worker with apparently inactive status finds a non-empty - * queue. This requires revalidating all of the associated state - * from caller. + * Tries to steal and run tasks within the target's computation. + * Uses a variant of the top-level algorithm, restricted to tasks + * with the given task as ancestor: It prefers taking and running + * eligible tasks popped from the worker's own queue (via + * popCC). Otherwise it scans others, randomly moving on + * contention or execution, deciding to give up based on a + * checksum (via return codes from pollAndExecCC). The maxTasks + * argument supports external usages; internal calls use zero, + * allowing unbounded steps (external calls trap non-positive + * values). + * + * @param w caller + * @param maxTasks if non-zero, the maximum number of other tasks to run + * @return task status on exit */ - private final void helpRelease(long c, WorkQueue[] ws, WorkQueue w, - WorkQueue q, int b) { - WorkQueue v; int e, i; Thread p; - if (w != null && w.eventCount < 0 && (e = (int)c) > 0 && - ws != null && ws.length > (i = e & SMASK) && - (v = ws[i]) != null && ctl == c) { - long nc = (((long)(v.nextWait & E_MASK)) | - ((long)((int)(c >>> 32) + UAC_UNIT)) << 32); - int ne = (e + E_SEQ) & E_MASK; - if (q != null && q.base == b && w.eventCount < 0 && - v.eventCount == (e | INT_SIGN) && - U.compareAndSwapLong(this, CTL, c, nc)) { - v.eventCount = ne; - if ((p = v.parker) != null) - U.unpark(p); + final int helpComplete(WorkQueue w, CountedCompleter task, + int maxTasks) { + WorkQueue[] ws; int s = 0, wl; + if ((ws = workQueues) != null && (wl = ws.length) > 1 && + task != null && w != null) { + for (int m = wl - 1, + mode = w.config, + r = ~mode, // scanning seed + origin = r & m, k = origin, // first queue to scan + step = 3, // first scan step + h = 1, // 1:ran, >1:contended, <0:hash + oldSum = 0, checkSum = 0;;) { + CountedCompleter p; WorkQueue q; int i; + if ((s = task.status) < 0) + break; + if (h == 1 && (p = w.popCC(task, mode)) != null) { + p.doExec(); // run local task + if (maxTasks != 0 && --maxTasks == 0) + break; + origin = k; // reset + oldSum = checkSum = 0; + } + else { // poll other worker queues + if ((i = k | 1) < 0 || i > m || (q = ws[i]) == null) + h = 0; + else if ((h = q.pollAndExecCC(task)) < 0) + checkSum += h; + if (h > 0) { + if (h == 1 && maxTasks != 0 && --maxTasks == 0) + break; + step = (r >>> 16) | 3; + r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift + k = origin = r & m; // move and restart + oldSum = checkSum = 0; + } + else if ((k = (k + step) & m) == origin) { + if (oldSum == (oldSum = checkSum)) + break; + checkSum = 0; + } + } } } + return s; } /** * Tries to locate and execute tasks for a stealer of the given - * task, or in turn one of its stealers, Traces currentSteal -> + * task, or in turn one of its stealers. Traces currentSteal -> * currentJoin links looking for a thread working on a descendant * of the given task and with a non-empty queue to steal back and * execute tasks from. The first call to this method upon a * waiting join will often entail scanning/search, (which is OK * because the joiner has nothing better to do), but this method - * leaves hints in workers to speed up subsequent calls. The - * implementation is very branchy to cope with potential - * inconsistencies or loops encountering chains that are stale, - * unknown, or so long that they are likely cyclic. + * leaves hints in workers to speed up subsequent calls. * - * @param joiner the joining worker + * @param w caller * @param task the task to join - * @return 0 if no progress can be made, negative if task - * known complete, else positive - */ - private int tryHelpStealer(WorkQueue joiner, ForkJoinTask task) { - int stat = 0, steps = 0; // bound to avoid cycles - if (task != null && joiner != null && - joiner.base - joiner.top >= 0) { // hoist checks - restart: for (;;) { - ForkJoinTask subtask = task; // current target - for (WorkQueue j = joiner, v;;) { // v is stealer of subtask - WorkQueue[] ws; int m, s, h; - if ((s = task.status) < 0) { - stat = s; - break restart; - } - if ((ws = workQueues) == null || (m = ws.length - 1) <= 0) - break restart; // shutting down - if ((v = ws[h = (j.hint | 1) & m]) == null || - v.currentSteal != subtask) { - for (int origin = h;;) { // find stealer - if (((h = (h + 2) & m) & 15) == 1 && - (subtask.status < 0 || j.currentJoin != subtask)) - continue restart; // occasional staleness check - if ((v = ws[h]) != null && - v.currentSteal == subtask) { - j.hint = h; // save hint + */ + private void helpStealer(WorkQueue w, ForkJoinTask task) { + if (task != null && w != null) { + ForkJoinTask ps = w.currentSteal; + WorkQueue[] ws; int wl, oldSum = 0; + outer: while (w.tryRemoveAndExec(task) && task.status >= 0 && + (ws = workQueues) != null && (wl = ws.length) > 0) { + ForkJoinTask subtask; + int m = wl - 1, checkSum = 0; // for stability check + WorkQueue j = w, v; // v is subtask stealer + descent: for (subtask = task; subtask.status >= 0; ) { + for (int h = j.hint | 1, k = 0, i;;) { + if ((v = ws[i = (h + (k << 1)) & m]) != null) { + if (v.currentSteal == subtask) { + j.hint = i; break; } - if (h == origin) - break restart; // cannot find stealer + checkSum += v.base; } + if (++k > m) // can't find stealer + break outer; } - for (;;) { // help stealer or descend to its stealer - ForkJoinTask[] a; int b; - if (subtask.status < 0) // surround probes with - continue restart; // consistency checks - if ((b = v.base) - v.top < 0 && (a = v.array) != null) { - int i = (((a.length - 1) & b) << ASHIFT) + ABASE; - ForkJoinTask t = - (ForkJoinTask)U.getObjectVolatile(a, i); - if (subtask.status < 0 || j.currentJoin != subtask || - v.currentSteal != subtask) - continue restart; // stale - stat = 1; // apparent progress - if (v.base == b) { - if (t == null) - break restart; - if (U.compareAndSwapObject(a, i, t, null)) { - U.putOrderedInt(v, QBASE, b + 1); - ForkJoinTask ps = joiner.currentSteal; - int jt = joiner.top; - do { - joiner.currentSteal = t; - t.doExec(); // clear local tasks too - } while (task.status >= 0 && - joiner.top != jt && - (t = joiner.pop()) != null); - joiner.currentSteal = ps; - break restart; + + for (;;) { // help v or descend + ForkJoinTask[] a; int b, al; + if (subtask.status < 0) // too late to help + break descent; + checkSum += (b = v.base); + ForkJoinTask next = v.currentJoin; + ForkJoinTask t = null; + if ((a = v.array) != null && (al = a.length) > 0) { + int index = (al - 1) & b; + long offset = ((long)index << ASHIFT) + ABASE; + t = (ForkJoinTask) + U.getObjectVolatile(a, offset); + if (t != null && b++ == v.base) { + if (j.currentJoin != subtask || + v.currentSteal != subtask || + subtask.status < 0) + break descent; // stale + if (U.compareAndSwapObject(a, offset, t, null)) { + v.base = b; + w.currentSteal = t; + for (int top = w.top;;) { + t.doExec(); // help + w.currentSteal = ps; + if (task.status < 0) + break outer; + if (w.top == top) + break; // run local tasks + if ((t = w.pop()) == null) + break descent; + w.currentSteal = t; + } } } } - else { // empty -- try to descend - ForkJoinTask next = v.currentJoin; - if (subtask.status < 0 || j.currentJoin != subtask || - v.currentSteal != subtask) - continue restart; // stale - else if (next == null || ++steps == MAX_HELP) - break restart; // dead-end or maybe cyclic - else { - subtask = next; - j = v; - break; + if (t == null && b == v.base && b - v.top >= 0) { + if ((subtask = next) == null) { // try to descend + if (next == v.currentJoin && + oldSum == (oldSum = checkSum)) + break outer; + break descent; } + j = v; + break; } } } } } - return stat; - } - - /** - * Analog of tryHelpStealer for CountedCompleters. Tries to steal - * and run tasks within the target's computation. - * - * @param task the task to join - */ - private int helpComplete(WorkQueue joiner, CountedCompleter task) { - WorkQueue[] ws; int m; - int s = 0; - if ((ws = workQueues) != null && (m = ws.length - 1) >= 0 && - joiner != null && task != null) { - int j = joiner.poolIndex; - int scans = m + m + 1; - long c = 0L; // for stability check - for (int k = scans; ; j += 2) { - WorkQueue q; - if ((s = task.status) < 0) - break; - else if (joiner.internalPopAndExecCC(task)) - k = scans; - else if ((s = task.status) < 0) - break; - else if ((q = ws[j & m]) != null && q.pollAndExecCC(task)) - k = scans; - else if (--k < 0) { - if (c == (c = ctl)) - break; - k = scans; - } - } - } - return s; } /** * Tries to decrement active count (sometimes implicitly) and * possibly release or create a compensating worker in preparation - * for blocking. Fails on contention or termination. Otherwise, - * adds a new thread if no idle workers are available and pool - * may become starved. + * for blocking. Returns false (retryable by caller), on + * contention, detected staleness, instability, or termination. * - * @param c the assumed ctl value + * @param w caller */ - final boolean tryCompensate(long c) { + private boolean tryCompensate(WorkQueue w) { + boolean canBlock; int wl; + long c = ctl; WorkQueue[] ws = workQueues; - int pc = parallelism, e = (int)c, m, tc; - if (ws != null && (m = ws.length - 1) >= 0 && e >= 0 && ctl == c) { - WorkQueue w = ws[e & m]; - if (e != 0 && w != null) { - Thread p; - long nc = ((long)(w.nextWait & E_MASK) | - (c & (AC_MASK|TC_MASK))); - int ne = (e + E_SEQ) & E_MASK; - if (w.eventCount == (e | INT_SIGN) && - U.compareAndSwapLong(this, CTL, c, nc)) { - w.eventCount = ne; - if ((p = w.parker) != null) - U.unpark(p); - return true; // replace with idle worker + int pc = config & SMASK; + int ac = pc + (int)(c >> AC_SHIFT); + int tc = pc + (short)(c >> TC_SHIFT); + if (w == null || w.qlock < 0 || pc == 0 || // terminating or disabled + ws == null || (wl = ws.length) <= 0) + canBlock = false; + else { + int m = wl - 1, sp; + boolean busy = true; // validate ac + for (int i = 0; i <= m; ++i) { + int k; WorkQueue v; + if ((k = (i << 1) | 1) <= m && k >= 0 && (v = ws[k]) != null && + v.scanState >= 0 && v.currentSteal == null) { + busy = false; + break; } } - else if ((tc = (short)(c >>> TC_SHIFT)) >= 0 && - (int)(c >> AC_SHIFT) + pc > 1) { - long nc = ((c - AC_UNIT) & AC_MASK) | (c & ~AC_MASK); - if (U.compareAndSwapLong(this, CTL, c, nc)) - return true; // no compensation - } - else if (tc + pc < MAX_CAP) { - long nc = ((c + TC_UNIT) & TC_MASK) | (c & ~TC_MASK); - if (U.compareAndSwapLong(this, CTL, c, nc)) { - ForkJoinWorkerThreadFactory fac; - Throwable ex = null; - ForkJoinWorkerThread wt = null; - try { - if ((fac = factory) != null && - (wt = fac.newThread(this)) != null) { - wt.start(); - return true; - } - } catch (Throwable rex) { - ex = rex; - } - deregisterWorker(wt, ex); // clean up and return false - } + if (!busy || ctl != c) + canBlock = false; // unstable or stale + else if ((sp = (int)c) != 0) // release idle worker + canBlock = tryRelease(c, ws[m & sp], 0L); + else if (tc >= pc && ac > 1 && w.isEmpty()) { + long nc = ((AC_MASK & (c - AC_UNIT)) | + (~AC_MASK & c)); // uncompensated + canBlock = U.compareAndSwapLong(this, CTL, c, nc); + } + else if (tc >= MAX_CAP || + (this == common && tc >= pc + COMMON_MAX_SPARES)) + throw new RejectedExecutionException( + "Thread limit exceeded replacing blocked worker"); + else { // similar to tryAddWorker + boolean isSpare = (tc >= pc); + long nc = (AC_MASK & c) | (TC_MASK & (c + TC_UNIT)); + canBlock = (U.compareAndSwapLong(this, CTL, c, nc) && + createWorker(isSpare)); // throws on exception } } - return false; + return canBlock; } /** - * Helps and/or blocks until the given task is done. + * Helps and/or blocks until the given task is done or timeout. * - * @param joiner the joining worker + * @param w caller * @param task the task + * @param deadline for timed waits, if nonzero * @return task status on exit */ - final int awaitJoin(WorkQueue joiner, ForkJoinTask task) { + final int awaitJoin(WorkQueue w, ForkJoinTask task, long deadline) { int s = 0; - if (task != null && (s = task.status) >= 0 && joiner != null) { - ForkJoinTask prevJoin = joiner.currentJoin; - joiner.currentJoin = task; - do {} while (joiner.tryRemoveAndExec(task) && // process local tasks - (s = task.status) >= 0); - if (s >= 0 && (task instanceof CountedCompleter)) - s = helpComplete(joiner, (CountedCompleter)task); - long cc = 0; // for stability checks - while (s >= 0 && (s = task.status) >= 0) { - if ((s = tryHelpStealer(joiner, task)) == 0 && - (s = task.status) >= 0) { - if (!tryCompensate(cc)) - cc = ctl; - else { - if (task.trySetSignal() && (s = task.status) >= 0) { - synchronized (task) { - if (task.status >= 0) { - try { // see ForkJoinTask - task.wait(); // for explanation - } catch (InterruptedException ie) { - } - } - else - task.notifyAll(); - } - } - long c; // reactivate - do {} while (!U.compareAndSwapLong - (this, CTL, c = ctl, - ((c & ~AC_MASK) | - ((c & AC_MASK) + AC_UNIT)))); + if (w != null) { + ForkJoinTask prevJoin = w.currentJoin; + if (task != null && (s = task.status) >= 0) { + w.currentJoin = task; + CountedCompleter cc = (task instanceof CountedCompleter) ? + (CountedCompleter)task : null; + for (;;) { + if (cc != null) + helpComplete(w, cc, 0); + else + helpStealer(w, task); + if ((s = task.status) < 0) + break; + long ms, ns; + if (deadline == 0L) + ms = 0L; + else if ((ns = deadline - System.nanoTime()) <= 0L) + break; + else if ((ms = TimeUnit.NANOSECONDS.toMillis(ns)) <= 0L) + ms = 1L; + if (tryCompensate(w)) { + task.internalWait(ms); + U.getAndAddLong(this, CTL, AC_UNIT); } + if ((s = task.status) < 0) + break; } + w.currentJoin = prevJoin; } - joiner.currentJoin = prevJoin; } return s; } - /** - * Stripped-down variant of awaitJoin used by timed joins. Tries - * to help join only while there is continuous progress. (Caller - * will then enter a timed wait.) - * - * @param joiner the joining worker - * @param task the task - */ - final void helpJoinOnce(WorkQueue joiner, ForkJoinTask task) { - int s; - if (joiner != null && task != null && (s = task.status) >= 0) { - ForkJoinTask prevJoin = joiner.currentJoin; - joiner.currentJoin = task; - do {} while (joiner.tryRemoveAndExec(task) && // process local tasks - (s = task.status) >= 0); - if (s >= 0) { - if (task instanceof CountedCompleter) - helpComplete(joiner, (CountedCompleter)task); - do {} while (task.status >= 0 && - tryHelpStealer(joiner, task) > 0); - } - joiner.currentJoin = prevJoin; - } - } + // Specialized scanning /** * Returns a (probably) non-empty steal queue, if one is found @@ -2043,19 +2234,25 @@ final void helpJoinOnce(WorkQueue joiner, ForkJoinTask task) { * caller if, by the time it tries to use the queue, it is empty. */ private WorkQueue findNonEmptyStealQueue() { - int r = ThreadLocalRandom.current().nextInt(); - for (;;) { - int ps = plock, m; WorkQueue[] ws; WorkQueue q; - if ((ws = workQueues) != null && (m = ws.length - 1) >= 0) { - for (int j = (m + 1) << 2; j >= 0; --j) { - if ((q = ws[(((r - j) << 1) | 1) & m]) != null && - q.base - q.top < 0) + WorkQueue[] ws; int wl; // one-shot version of scan loop + int r = ThreadLocalRandom.nextSecondarySeed(); + if ((ws = workQueues) != null && (wl = ws.length) > 0) { + int m = wl - 1, origin = r & m; + for (int k = origin, oldSum = 0, checkSum = 0;;) { + WorkQueue q; int b; + if ((q = ws[k]) != null) { + if ((b = q.base) - q.top < 0) return q; + checkSum += b; + } + if ((k = (k + 1) & m) == origin) { + if (oldSum == (oldSum = checkSum)) + break; + checkSum = 0; } } - if (plock == ps) - return null; } + return null; } /** @@ -2065,35 +2262,33 @@ private WorkQueue findNonEmptyStealQueue() { * find tasks either. */ final void helpQuiescePool(WorkQueue w) { - ForkJoinTask ps = w.currentSteal; + ForkJoinTask ps = w.currentSteal; // save context + int wc = w.config; for (boolean active = true;;) { - long c; WorkQueue q; ForkJoinTask t; int b; - while ((t = w.nextLocalTask()) != null) - t.doExec(); - if ((q = findNonEmptyStealQueue()) != null) { + long c; WorkQueue q; ForkJoinTask t; + if (wc >= 0 && (t = w.pop()) != null) { // run locals if LIFO + (w.currentSteal = t).doExec(); + w.currentSteal = ps; + } + else if ((q = findNonEmptyStealQueue()) != null) { if (!active) { // re-establish active count active = true; - do {} while (!U.compareAndSwapLong - (this, CTL, c = ctl, - ((c & ~AC_MASK) | - ((c & AC_MASK) + AC_UNIT)))); + U.getAndAddLong(this, CTL, AC_UNIT); } - if ((b = q.base) - q.top < 0 && (t = q.pollAt(b)) != null) { + if ((t = q.pollAt(q.base)) != null) { (w.currentSteal = t).doExec(); w.currentSteal = ps; + if (++w.nsteals < 0) + w.transferStealCount(this); } } else if (active) { // decrement active count without queuing - long nc = ((c = ctl) & ~AC_MASK) | ((c & AC_MASK) - AC_UNIT); - if ((int)(nc >> AC_SHIFT) + parallelism == 0) - break; // bypass decrement-then-increment + long nc = (AC_MASK & ((c = ctl) - AC_UNIT)) | (~AC_MASK & c); if (U.compareAndSwapLong(this, CTL, c, nc)) active = false; } - else if ((int)((c = ctl) >> AC_SHIFT) + parallelism <= 0 && - U.compareAndSwapLong - (this, CTL, c, ((c & ~AC_MASK) | - ((c & AC_MASK) + AC_UNIT)))) + else if ((int)((c = ctl) >> AC_SHIFT) + (config & SMASK) <= 0 && + U.compareAndSwapLong(this, CTL, c, c + AC_UNIT)) break; } } @@ -2105,12 +2300,12 @@ else if ((int)((c = ctl) >> AC_SHIFT) + parallelism <= 0 && */ final ForkJoinTask nextTaskFor(WorkQueue w) { for (ForkJoinTask t;;) { - WorkQueue q; int b; + WorkQueue q; if ((t = w.nextLocalTask()) != null) return t; if ((q = findNonEmptyStealQueue()) == null) return null; - if ((b = q.base) - q.top < 0 && (t = q.pollAt(b)) != null) + if ((t = q.pollAt(q.base)) != null) return t; } } @@ -2118,7 +2313,7 @@ final ForkJoinTask nextTaskFor(WorkQueue w) { /** * Returns a cheap heuristic guide for task partitioning when * programmers, frameworks, tools, or languages have little or no - * idea about task granularity. In essence by offering this + * idea about task granularity. In essence, by offering this * method, we ask users only about tradeoffs in overhead vs * expected throughput and its variance, rather than how finely to * partition tasks. @@ -2156,15 +2351,11 @@ final ForkJoinTask nextTaskFor(WorkQueue w) { * many of these by further considering the number of "idle" * threads, that are known to have zero queued tasks, so * compensate by a factor of (#idle/#active) threads. - * - * Note: The approximation of #busy workers as #active workers is - * not very good under current signalling scheme, and should be - * improved. */ static int getSurplusQueuedTaskCount() { Thread t; ForkJoinWorkerThread wt; ForkJoinPool pool; WorkQueue q; - if (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)) { - int p = (pool = (wt = (ForkJoinWorkerThread)t).pool).parallelism; + if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) { + int p = (pool = (wt = (ForkJoinWorkerThread)t).pool).config & SMASK; int n = (q = wt.workQueue).top - q.base; int a = (int)(pool.ctl >> AC_SHIFT) + p; return n - (a > (p >>>= 1) ? 0 : @@ -2179,170 +2370,203 @@ static int getSurplusQueuedTaskCount() { // Termination /** - * Possibly initiates and/or completes termination. The caller - * triggering termination runs three passes through workQueues: - * (0) Setting termination status, followed by wakeups of queued - * workers; (1) cancelling all tasks; (2) interrupting lagging - * threads (likely in external tasks, but possibly also blocked in - * joins). Each pass repeats previous steps because of potential - * lagging thread creation. + * Possibly initiates and/or completes termination. * * @param now if true, unconditionally terminate, else only * if no work and no active workers - * @param enable if true, enable shutdown when next possible - * @return true if now terminating or terminated + * @param enable if true, terminate when next possible + * @return -1: terminating/terminated, 0: retry if internal caller, else 1 */ - private boolean tryTerminate(boolean now, boolean enable) { - int ps; - if (this == common) // cannot shut down - return false; - if ((ps = plock) >= 0) { // enable by setting plock - if (!enable) - return false; - if ((ps & PL_LOCK) != 0 || - !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) - ps = acquirePlock(); - int nps = ((ps + PL_LOCK) & ~SHUTDOWN) | SHUTDOWN; - if (!U.compareAndSwapInt(this, PLOCK, ps, nps)) - releasePlock(nps); - } - for (long c;;) { - if (((c = ctl) & STOP_BIT) != 0) { // already terminating - if ((short)(c >>> TC_SHIFT) + parallelism <= 0) { - synchronized (this) { - notifyAll(); // signal when 0 workers - } - } - return true; - } - if (!now) { // check if idle & no tasks - WorkQueue[] ws; WorkQueue w; - if ((int)(c >> AC_SHIFT) + parallelism > 0) - return false; - if ((ws = workQueues) != null) { - for (int i = 0; i < ws.length; ++i) { - if ((w = ws[i]) != null && - (!w.isEmpty() || - ((i & 1) != 0 && w.eventCount >= 0))) { - signalWork(ws, w); - return false; + private int tryTerminate(boolean now, boolean enable) { + int rs; // 3 phases: try to set SHUTDOWN, then STOP, then TERMINATED + + while ((rs = runState) >= 0) { + if (!enable || this == common) // cannot shutdown + return 1; + else if (rs == 0) + tryInitialize(false); // ensure initialized + else + U.compareAndSwapInt(this, RUNSTATE, rs, rs | SHUTDOWN); + } + + if ((rs & STOP) == 0) { // try to initiate termination + if (!now) { // check quiescence + for (long oldSum = 0L;;) { // repeat until stable + WorkQueue[] ws; WorkQueue w; int b; + long checkSum = ctl; + if ((int)(checkSum >> AC_SHIFT) + (config & SMASK) > 0) + return 0; // still active workers + if ((ws = workQueues) != null) { + for (int i = 0; i < ws.length; ++i) { + if ((w = ws[i]) != null) { + checkSum += (b = w.base); + if (w.currentSteal != null || b != w.top) + return 0; // retry if internal caller + } } } + if (oldSum == (oldSum = checkSum)) + break; } } - if (U.compareAndSwapLong(this, CTL, c, c | STOP_BIT)) { - for (int pass = 0; pass < 3; ++pass) { - WorkQueue[] ws; WorkQueue w; Thread wt; - if ((ws = workQueues) != null) { - int n = ws.length; - for (int i = 0; i < n; ++i) { - if ((w = ws[i]) != null) { - w.qlock = -1; - if (pass > 0) { - w.cancelAll(); - if (pass > 1 && (wt = w.owner) != null) { - if (!wt.isInterrupted()) { - try { - wt.interrupt(); - } catch (Throwable ignore) { - } - } - U.unpark(wt); - } + do {} while (!U.compareAndSwapInt(this, RUNSTATE, + rs = runState, rs | STOP)); + } + + for (long oldSum = 0L;;) { // repeat until stable + WorkQueue[] ws; WorkQueue w; ForkJoinWorkerThread wt; + long checkSum = ctl; + if ((ws = workQueues) != null) { // help terminate others + for (int i = 0; i < ws.length; ++i) { + if ((w = ws[i]) != null) { + w.cancelAll(); // clear queues + checkSum += w.base; + if (w.qlock >= 0) { + w.qlock = -1; // racy set OK + if ((wt = w.owner) != null) { + try { // unblock join or park + wt.interrupt(); + } catch (Throwable ignore) { } } } - // Wake up workers parked on event queue - int i, e; long cc; Thread p; - while ((e = (int)(cc = ctl) & E_MASK) != 0 && - (i = e & SMASK) < n && i >= 0 && - (w = ws[i]) != null) { - long nc = ((long)(w.nextWait & E_MASK) | - ((cc + AC_UNIT) & AC_MASK) | - (cc & (TC_MASK|STOP_BIT))); - if (w.eventCount == (e | INT_SIGN) && - U.compareAndSwapLong(this, CTL, cc, nc)) { - w.eventCount = (e + E_SEQ) & E_MASK; - w.qlock = -1; - if ((p = w.parker) != null) - U.unpark(p); - } - } } } } + if (oldSum == (oldSum = checkSum)) + break; } - } - // external operations on common pool + if ((short)(ctl >>> TC_SHIFT) + (config & SMASK) <= 0) { + runState = (STARTED | SHUTDOWN | STOP | TERMINATED); // final write + synchronized (this) { + notifyAll(); // for awaitTermination + } + } - /** - * Returns common pool queue for a thread that has submitted at - * least one task. - */ - static WorkQueue commonSubmitterQueue() { - Submitter z; ForkJoinPool p; WorkQueue[] ws; int m, r; - return ((z = submitters.get()) != null && - (p = common) != null && - (ws = p.workQueues) != null && - (m = ws.length - 1) >= 0) ? - ws[m & z.seed & SQMASK] : null; + return -1; } + // External operations + /** - * Tries to pop the given task from submitter's queue in common pool. + * Constructs and tries to install a new external queue, + * failing if the workQueues array already has a queue at + * the given index. + * + * @param index the index of the new queue */ - final boolean tryExternalUnpush(ForkJoinTask task) { - WorkQueue joiner; ForkJoinTask[] a; int m, s; - Submitter z = submitters.get(); - WorkQueue[] ws = workQueues; - boolean popped = false; - if (z != null && ws != null && (m = ws.length - 1) >= 0 && - (joiner = ws[z.seed & m & SQMASK]) != null && - joiner.base != (s = joiner.top) && - (a = joiner.array) != null) { - long j = (((a.length - 1) & (s - 1)) << ASHIFT) + ABASE; - if (U.getObject(a, j) == task && - U.compareAndSwapInt(joiner, QLOCK, 0, 1)) { - if (joiner.top == s && joiner.array == a && - U.compareAndSwapObject(a, j, task, null)) { - joiner.top = s - 1; - popped = true; + private void tryCreateExternalQueue(int index) { + AuxState aux; + if ((aux = auxState) != null && index >= 0) { + WorkQueue q = new WorkQueue(this, null); + q.config = index; + q.scanState = ~UNSIGNALLED; + q.qlock = 1; // lock queue + boolean installed = false; + aux.lock(); + try { // lock pool to install + WorkQueue[] ws; + if ((ws = workQueues) != null && index < ws.length && + ws[index] == null) { + ws[index] = q; // else throw away + installed = true; + } + } finally { + aux.unlock(); + } + if (installed) { + try { + q.growArray(); + } finally { + q.qlock = 0; } - joiner.qlock = 0; } } - return popped; } - final int externalHelpComplete(CountedCompleter task) { - WorkQueue joiner; int m, j; - Submitter z = submitters.get(); - WorkQueue[] ws = workQueues; - int s = 0; - if (z != null && ws != null && (m = ws.length - 1) >= 0 && - (joiner = ws[(j = z.seed) & m & SQMASK]) != null && task != null) { - int scans = m + m + 1; - long c = 0L; // for stability check - j |= 1; // poll odd queues - for (int k = scans; ; j += 2) { - WorkQueue q; - if ((s = task.status) < 0) - break; - else if (joiner.externalPopAndExecCC(task)) - k = scans; - else if ((s = task.status) < 0) - break; - else if ((q = ws[j & m]) != null && q.pollAndExecCC(task)) - k = scans; - else if (--k < 0) { - if (c == (c = ctl)) - break; - k = scans; - } + /** + * Adds the given task to a submission queue at submitter's + * current queue. Also performs secondary initialization upon the + * first submission of the first task to the pool, and detects + * first submission by an external thread and creates a new shared + * queue if the one at index if empty or contended. + * + * @param task the task. Caller must ensure non-null. + */ + final void externalPush(ForkJoinTask task) { + int r; // initialize caller's probe + if ((r = ThreadLocalRandom.getProbe()) == 0) { + ThreadLocalRandom.localInit(); + r = ThreadLocalRandom.getProbe(); + } + for (;;) { + WorkQueue q; int wl, k, stat; + int rs = runState; + WorkQueue[] ws = workQueues; + if (rs <= 0 || ws == null || (wl = ws.length) <= 0) + tryInitialize(true); + else if ((q = ws[k = (wl - 1) & r & SQMASK]) == null) + tryCreateExternalQueue(k); + else if ((stat = q.sharedPush(task)) < 0) + break; + else if (stat == 0) { + signalWork(); + break; } + else // move if busy + r = ThreadLocalRandom.advanceProbe(r); } - return s; + } + + /** + * Pushes a possibly-external submission. + */ + private ForkJoinTask externalSubmit(ForkJoinTask task) { + Thread t; ForkJoinWorkerThread w; WorkQueue q; + if (task == null) + throw new NullPointerException(); + if (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) && + (w = (ForkJoinWorkerThread)t).pool == this && + (q = w.workQueue) != null) + q.push(task); + else + externalPush(task); + return task; + } + + /** + * Returns common pool queue for an external thread. + */ + static WorkQueue commonSubmitterQueue() { + ForkJoinPool p = common; + int r = ThreadLocalRandom.getProbe(); + WorkQueue[] ws; int wl; + return (p != null && (ws = p.workQueues) != null && + (wl = ws.length) > 0) ? + ws[(wl - 1) & r & SQMASK] : null; + } + + /** + * Performs tryUnpush for an external submitter. + */ + final boolean tryExternalUnpush(ForkJoinTask task) { + int r = ThreadLocalRandom.getProbe(); + WorkQueue[] ws; WorkQueue w; int wl; + return ((ws = workQueues) != null && + (wl = ws.length) > 0 && + (w = ws[(wl - 1) & r & SQMASK]) != null && + w.trySharedUnpush(task)); + } + + /** + * Performs helpComplete for an external submitter. + */ + final int externalHelpComplete(CountedCompleter task, int maxTasks) { + WorkQueue[] ws; int wl; + int r = ThreadLocalRandom.getProbe(); + return ((ws = workQueues) != null && (wl = ws.length) > 0) ? + helpComplete(ws[(wl - 1) & r & SQMASK], task, maxTasks) : 0; } // Exported methods @@ -2354,6 +2578,11 @@ else if (--k < 0) { * java.lang.Runtime#availableProcessors}, using the {@linkplain * #defaultForkJoinWorkerThreadFactory default thread factory}, * no UncaughtExceptionHandler, and non-async LIFO processing mode. + * + * @throws SecurityException if a security manager exists and + * the caller is not permitted to modify threads + * because it does not hold {@link + * java.lang.RuntimePermission}{@code ("modifyThread")} */ public ForkJoinPool() { this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()), @@ -2369,6 +2598,10 @@ public ForkJoinPool() { * @param parallelism the parallelism level * @throws IllegalArgumentException if parallelism less than or * equal to zero, or greater than implementation limit + * @throws SecurityException if a security manager exists and + * the caller is not permitted to modify threads + * because it does not hold {@link + * java.lang.RuntimePermission}{@code ("modifyThread")} */ public ForkJoinPool(int parallelism) { this(parallelism, defaultForkJoinWorkerThreadFactory, null, false); @@ -2393,6 +2626,10 @@ public ForkJoinPool(int parallelism) { * @throws IllegalArgumentException if parallelism less than or * equal to zero, or greater than implementation limit * @throws NullPointerException if the factory is null + * @throws SecurityException if a security manager exists and + * the caller is not permitted to modify threads + * because it does not hold {@link + * java.lang.RuntimePermission}{@code ("modifyThread")} */ public ForkJoinPool(int parallelism, ForkJoinWorkerThreadFactory factory, @@ -2401,7 +2638,7 @@ public ForkJoinPool(int parallelism, this(checkParallelism(parallelism), checkFactory(factory), handler, - (asyncMode ? FIFO_QUEUE : LIFO_QUEUE), + asyncMode ? FIFO_QUEUE : LIFO_QUEUE, "ForkJoinPool-" + nextPoolId() + "-worker-"); checkPermission(); } @@ -2432,8 +2669,7 @@ private ForkJoinPool(int parallelism, this.workerNamePrefix = workerNamePrefix; this.factory = factory; this.ueh = handler; - this.mode = (short)mode; - this.parallelism = (short)parallelism; + this.config = (parallelism & SMASK) | mode; long np = (long)(-parallelism); // offset ctl counts this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK); } @@ -2450,7 +2686,6 @@ private ForkJoinPool(int parallelism, * * @return the common pool instance * @since 1.8 - * @hide */ public static ForkJoinPool commonPool() { // assert common != null : "static init error"; @@ -2479,7 +2714,7 @@ public static ForkJoinPool commonPool() { public T invoke(ForkJoinTask task) { if (task == null) throw new NullPointerException(); - externalPush(task); + externalSubmit(task); return task.join(); } @@ -2492,9 +2727,7 @@ public T invoke(ForkJoinTask task) { * scheduled for execution */ public void execute(ForkJoinTask task) { - if (task == null) - throw new NullPointerException(); - externalPush(task); + externalSubmit(task); } // AbstractExecutorService methods @@ -2512,7 +2745,7 @@ public void execute(Runnable task) { job = (ForkJoinTask) task; else job = new ForkJoinTask.RunnableExecuteAction(task); - externalPush(job); + externalSubmit(job); } /** @@ -2526,10 +2759,7 @@ public void execute(Runnable task) { * scheduled for execution */ public ForkJoinTask submit(ForkJoinTask task) { - if (task == null) - throw new NullPointerException(); - externalPush(task); - return task; + return externalSubmit(task); } /** @@ -2538,9 +2768,7 @@ public ForkJoinTask submit(ForkJoinTask task) { * scheduled for execution */ public ForkJoinTask submit(Callable task) { - ForkJoinTask job = new ForkJoinTask.AdaptedCallable(task); - externalPush(job); - return job; + return externalSubmit(new ForkJoinTask.AdaptedCallable(task)); } /** @@ -2549,9 +2777,7 @@ public ForkJoinTask submit(Callable task) { * scheduled for execution */ public ForkJoinTask submit(Runnable task, T result) { - ForkJoinTask job = new ForkJoinTask.AdaptedRunnable(task, result); - externalPush(job); - return job; + return externalSubmit(new ForkJoinTask.AdaptedRunnable(task, result)); } /** @@ -2567,8 +2793,7 @@ public ForkJoinTask submit(Runnable task) { job = (ForkJoinTask) task; else job = new ForkJoinTask.AdaptedRunnableAction(task); - externalPush(job); - return job; + return externalSubmit(job); } /** @@ -2579,23 +2804,21 @@ public List> invokeAll(Collection> tasks) { // In previous versions of this class, this method constructed // a task to run ForkJoinTask.invokeAll, but now external // invocation of multiple tasks is at least as efficient. - ArrayList> futures = new ArrayList>(tasks.size()); + ArrayList> futures = new ArrayList<>(tasks.size()); - boolean done = false; try { for (Callable t : tasks) { ForkJoinTask f = new ForkJoinTask.AdaptedCallable(t); futures.add(f); - externalPush(f); + externalSubmit(f); } for (int i = 0, size = futures.size(); i < size; i++) ((ForkJoinTask)futures.get(i)).quietlyJoin(); - done = true; return futures; - } finally { - if (!done) - for (int i = 0, size = futures.size(); i < size; i++) - futures.get(i).cancel(false); + } catch (Throwable t) { + for (int i = 0, size = futures.size(); i < size; i++) + futures.get(i).cancel(false); + throw t; } } @@ -2625,7 +2848,7 @@ public UncaughtExceptionHandler getUncaughtExceptionHandler() { */ public int getParallelism() { int par; - return ((par = parallelism) > 0) ? par : 1; + return ((par = config & SMASK) > 0) ? par : 1; } /** @@ -2633,10 +2856,9 @@ public int getParallelism() { * * @return the targeted parallelism level of the common pool * @since 1.8 - * @hide */ public static int getCommonPoolParallelism() { - return commonParallelism; + return COMMON_PARALLELISM; } /** @@ -2648,7 +2870,7 @@ public static int getCommonPoolParallelism() { * @return the number of worker threads */ public int getPoolSize() { - return parallelism + (short)(ctl >>> TC_SHIFT); + return (config & SMASK) + (short)(ctl >>> TC_SHIFT); } /** @@ -2658,7 +2880,7 @@ public int getPoolSize() { * @return {@code true} if this pool uses async mode */ public boolean getAsyncMode() { - return mode == FIFO_QUEUE; + return (config & FIFO_QUEUE) != 0; } /** @@ -2689,7 +2911,7 @@ public int getRunningThreadCount() { * @return the number of active threads */ public int getActiveThreadCount() { - int r = parallelism + (int)(ctl >> AC_SHIFT); + int r = (config & SMASK) + (int)(ctl >> AC_SHIFT); return (r <= 0) ? 0 : r; // suppress momentarily negative values } @@ -2705,7 +2927,7 @@ public int getActiveThreadCount() { * @return {@code true} if all threads are currently idle */ public boolean isQuiescent() { - return parallelism + (int)(ctl >> AC_SHIFT) <= 0; + return (config & SMASK) + (int)(ctl >> AC_SHIFT) <= 0; } /** @@ -2720,7 +2942,8 @@ public boolean isQuiescent() { * @return the number of steals */ public long getStealCount() { - long count = stealCount; + AuxState sc = auxState; + long count = (sc == null) ? 0L : sc.stealCount; WorkQueue[] ws; WorkQueue w; if ((ws = workQueues) != null) { for (int i = 1; i < ws.length; i += 2) { @@ -2797,10 +3020,11 @@ public boolean hasQueuedSubmissions() { * @return the next submission, or {@code null} if none */ protected ForkJoinTask pollSubmission() { - WorkQueue[] ws; WorkQueue w; ForkJoinTask t; - if ((ws = workQueues) != null) { - for (int i = 0; i < ws.length; i += 2) { - if ((w = ws[i]) != null && (t = w.poll()) != null) + WorkQueue[] ws; int wl; WorkQueue w; ForkJoinTask t; + int r = ThreadLocalRandom.nextSecondarySeed(); + if ((ws = workQueues) != null && (wl = ws.length) > 0) { + for (int m = wl - 1, i = 0; i < wl; ++i) { + if ((w = ws[(i << 1) & m]) != null && (t = w.poll()) != null) return t; } } @@ -2850,7 +3074,8 @@ protected int drainTasksTo(Collection> c) { public String toString() { // Use a single pass through workQueues to collect counts long qt = 0L, qs = 0L; int rc = 0; - long st = stealCount; + AuxState sc = auxState; + long st = (sc == null) ? 0L : sc.stealCount; long c = ctl; WorkQueue[] ws; WorkQueue w; if ((ws = workQueues) != null) { @@ -2868,16 +3093,16 @@ public String toString() { } } } - int pc = parallelism; + int pc = (config & SMASK); int tc = pc + (short)(c >>> TC_SHIFT); int ac = pc + (int)(c >> AC_SHIFT); if (ac < 0) // ignore transient negative ac = 0; - String level; - if ((c & STOP_BIT) != 0) - level = (tc == 0) ? "Terminated" : "Terminating"; - else - level = plock < 0 ? "Shutting down" : "Running"; + int rs = runState; + String level = ((rs & TERMINATED) != 0 ? "Terminated" : + (rs & STOP) != 0 ? "Terminating" : + (rs & SHUTDOWN) != 0 ? "Shutting down" : + "Running"); return super.toString() + "[" + level + ", parallelism = " + pc + @@ -2894,10 +3119,15 @@ public String toString() { * Possibly initiates an orderly shutdown in which previously * submitted tasks are executed, but no new tasks will be * accepted. Invocation has no effect on execution state if this - * is the {@code commonPool()}, and no additional effect if + * is the {@link #commonPool()}, and no additional effect if * already shut down. Tasks that are in the process of being * submitted concurrently during the course of this method may or * may not be rejected. + * + * @throws SecurityException if a security manager exists and + * the caller is not permitted to modify threads + * because it does not hold {@link + * java.lang.RuntimePermission}{@code ("modifyThread")} */ public void shutdown() { checkPermission(); @@ -2907,7 +3137,7 @@ public void shutdown() { /** * Possibly attempts to cancel and/or stop all tasks, and reject * all subsequently submitted tasks. Invocation has no effect on - * execution state if this is the {@code commonPool()}, and no + * execution state if this is the {@link #commonPool()}, and no * additional effect if already shut down. Otherwise, tasks that * are in the process of being submitted or executed concurrently * during the course of this method may or may not be @@ -2917,6 +3147,10 @@ public void shutdown() { * (unlike the case for some other Executors). * * @return an empty list + * @throws SecurityException if a security manager exists and + * the caller is not permitted to modify threads + * because it does not hold {@link + * java.lang.RuntimePermission}{@code ("modifyThread")} */ public List shutdownNow() { checkPermission(); @@ -2930,9 +3164,7 @@ public List shutdownNow() { * @return {@code true} if all tasks have completed following shut down */ public boolean isTerminated() { - long c = ctl; - return ((c & STOP_BIT) != 0L && - (short)(c >>> TC_SHIFT) + parallelism <= 0); + return (runState & TERMINATED) != 0; } /** @@ -2949,9 +3181,8 @@ public boolean isTerminated() { * @return {@code true} if terminating but not yet terminated */ public boolean isTerminating() { - long c = ctl; - return ((c & STOP_BIT) != 0L && - (short)(c >>> TC_SHIFT) + parallelism > 0); + int rs = runState; + return (rs & STOP) != 0 && (rs & TERMINATED) == 0; } /** @@ -2960,14 +3191,14 @@ public boolean isTerminating() { * @return {@code true} if this pool has been shut down */ public boolean isShutdown() { - return plock < 0; + return (runState & SHUTDOWN) != 0; } /** * Blocks until all tasks have completed execution after a * shutdown request, or the timeout occurs, or the current thread - * is interrupted, whichever happens first. Because the {@code - * commonPool()} never terminates until program shutdown, when + * is interrupted, whichever happens first. Because the {@link + * #commonPool()} never terminates until program shutdown, when * applied to the common pool, this method is equivalent to {@link * #awaitQuiescence(long, TimeUnit)} but always returns {@code false}. * @@ -3026,19 +3257,20 @@ public boolean awaitQuiescence(long timeout, TimeUnit unit) { } long startTime = System.nanoTime(); WorkQueue[] ws; - int r = 0, m; + int r = 0, wl; boolean found = true; while (!isQuiescent() && (ws = workQueues) != null && - (m = ws.length - 1) >= 0) { + (wl = ws.length) > 0) { if (!found) { if ((System.nanoTime() - startTime) > nanos) return false; Thread.yield(); // cannot block } found = false; - for (int j = (m + 1) << 2; j >= 0; --j) { - ForkJoinTask t; WorkQueue q; int b; - if ((q = ws[r++ & m]) != null && (b = q.base) - q.top < 0) { + for (int m = wl - 1, j = (m + 1) << 2; j >= 0; --j) { + ForkJoinTask t; WorkQueue q; int b, k; + if ((k = r++ & m) <= m && k >= 0 && (q = ws[k]) != null && + (b = q.base) - q.top < 0) { found = true; if ((t = q.pollAt(b)) != null) t.doExec(); @@ -3051,7 +3283,7 @@ public boolean awaitQuiescence(long timeout, TimeUnit unit) { /** * Waits and/or attempts to assist performing tasks indefinitely - * until the {@code commonPool()} {@link #isQuiescent}. + * until the {@link #commonPool()} {@link #isQuiescent}. */ static void quiesceCommonPool() { common.awaitQuiescence(Long.MAX_VALUE, TimeUnit.NANOSECONDS); @@ -3062,8 +3294,8 @@ static void quiesceCommonPool() { * in {@link ForkJoinPool}s. * *

    A {@code ManagedBlocker} provides two methods. Method - * {@code isReleasable} must return {@code true} if blocking is - * not necessary. Method {@code block} blocks the current thread + * {@link #isReleasable} must return {@code true} if blocking is + * not necessary. Method {@link #block} blocks the current thread * if necessary (perhaps internally invoking {@code isReleasable} * before actually blocking). These actions are performed by any * thread invoking {@link ForkJoinPool#managedBlock(ManagedBlocker)}. @@ -3077,7 +3309,7 @@ static void quiesceCommonPool() { * *

    For example, here is a ManagedBlocker based on a * ReentrantLock: - *

     {@code
    +     * 
     {@code
          * class ManagedLocker implements ManagedBlocker {
          *   final ReentrantLock lock;
          *   boolean hasLock = false;
    @@ -3094,7 +3326,7 @@ static void quiesceCommonPool() {
          *
          * 

    Here is a class that possibly blocks waiting for an * item on a given queue: - *

     {@code
    +     * 
     {@code
          * class QueueTaker implements ManagedBlocker {
          *   final BlockingQueue queue;
          *   volatile E item = null;
    @@ -3132,37 +3364,46 @@ public static interface ManagedBlocker {
         }
     
         /**
    -     * Blocks in accord with the given blocker.  If the current thread
    -     * is a {@link ForkJoinWorkerThread}, this method possibly
    -     * arranges for a spare thread to be activated if necessary to
    -     * ensure sufficient parallelism while the current thread is blocked.
    +     * Runs the given possibly blocking task.  When {@linkplain
    +     * ForkJoinTask#inForkJoinPool() running in a ForkJoinPool}, this
    +     * method possibly arranges for a spare thread to be activated if
    +     * necessary to ensure sufficient parallelism while the current
    +     * thread is blocked in {@link ManagedBlocker#block blocker.block()}.
    +     *
    +     * 

    This method repeatedly calls {@code blocker.isReleasable()} and + * {@code blocker.block()} until either method returns {@code true}. + * Every call to {@code blocker.block()} is preceded by a call to + * {@code blocker.isReleasable()} that returned {@code false}. * - *

    If the caller is not a {@link ForkJoinTask}, this method is + *

    If not running in a ForkJoinPool, this method is * behaviorally equivalent to - *

     {@code
    +     * 
     {@code
          * while (!blocker.isReleasable())
          *   if (blocker.block())
    -     *     return;
    -     * }
    + * break;}
    * - * If the caller is a {@code ForkJoinTask}, then the pool may - * first be expanded to ensure parallelism, and later adjusted. + * If running in a ForkJoinPool, the pool may first be expanded to + * ensure sufficient parallelism available during the call to + * {@code blocker.block()}. * - * @param blocker the blocker - * @throws InterruptedException if blocker.block did so + * @param blocker the blocker task + * @throws InterruptedException if {@code blocker.block()} did so */ public static void managedBlock(ManagedBlocker blocker) throws InterruptedException { + ForkJoinPool p; + ForkJoinWorkerThread wt; Thread t = Thread.currentThread(); - if (t instanceof ForkJoinWorkerThread) { - ForkJoinPool p = ((ForkJoinWorkerThread)t).pool; + if ((t instanceof ForkJoinWorkerThread) && + (p = (wt = (ForkJoinWorkerThread)t).pool) != null) { + WorkQueue w = wt.workQueue; while (!blocker.isReleasable()) { - if (p.tryCompensate(p.ctl)) { + if (p.tryCompensate(w)) { try { do {} while (!blocker.isReleasable() && !blocker.block()); } finally { - p.incrementActiveCount(); + U.getAndAddLong(p, CTL, AC_UNIT); } break; } @@ -3187,49 +3428,40 @@ protected RunnableFuture newTaskFor(Callable callable) { } // Unsafe mechanics - private static final sun.misc.Unsafe U; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); private static final long CTL; - private static final long PARKBLOCKER; + private static final long RUNSTATE; private static final int ABASE; private static final int ASHIFT; - private static final long STEALCOUNT; - private static final long PLOCK; - private static final long INDEXSEED; - private static final long QBASE; - private static final long QLOCK; static { - // initialize field offsets for CAS etc try { - U = sun.misc.Unsafe.getUnsafe(); - Class k = ForkJoinPool.class; CTL = U.objectFieldOffset - (k.getDeclaredField("ctl")); - STEALCOUNT = U.objectFieldOffset - (k.getDeclaredField("stealCount")); - PLOCK = U.objectFieldOffset - (k.getDeclaredField("plock")); - INDEXSEED = U.objectFieldOffset - (k.getDeclaredField("indexSeed")); - Class tk = Thread.class; - PARKBLOCKER = U.objectFieldOffset - (tk.getDeclaredField("parkBlocker")); - Class wk = WorkQueue.class; - QBASE = U.objectFieldOffset - (wk.getDeclaredField("base")); - QLOCK = U.objectFieldOffset - (wk.getDeclaredField("qlock")); - Class ak = ForkJoinTask[].class; - ABASE = U.arrayBaseOffset(ak); - int scale = U.arrayIndexScale(ak); + (ForkJoinPool.class.getDeclaredField("ctl")); + RUNSTATE = U.objectFieldOffset + (ForkJoinPool.class.getDeclaredField("runState")); + ABASE = U.arrayBaseOffset(ForkJoinTask[].class); + int scale = U.arrayIndexScale(ForkJoinTask[].class); if ((scale & (scale - 1)) != 0) - throw new Error("data type scale not a power of two"); + throw new Error("array index scale not a power of two"); ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); - } catch (Exception e) { + } catch (ReflectiveOperationException e) { throw new Error(e); } - submitters = new ThreadLocal(); + // Reduce the risk of rare disastrous classloading in first call to + // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773 + Class ensureLoaded = LockSupport.class; + + int commonMaxSpares = DEFAULT_COMMON_MAX_SPARES; + try { + String p = System.getProperty + ("java.util.concurrent.ForkJoinPool.common.maximumSpares"); + if (p != null) + commonMaxSpares = Integer.parseInt(p); + } catch (Exception ignore) {} + COMMON_MAX_SPARES = commonMaxSpares; + defaultForkJoinWorkerThreadFactory = new DefaultForkJoinWorkerThreadFactory(); modifyThreadPermission = new RuntimePermission("modifyThread"); @@ -3237,18 +3469,18 @@ protected RunnableFuture newTaskFor(Callable callable) { common = java.security.AccessController.doPrivileged (new java.security.PrivilegedAction() { public ForkJoinPool run() { return makeCommonPool(); }}); - int par = common.parallelism; // report 1 even if threads disabled - commonParallelism = par > 0 ? par : 1; + + // report 1 even if threads disabled + COMMON_PARALLELISM = Math.max(common.config & SMASK, 1); } /** * Creates and returns the common pool, respecting user settings * specified via system properties. */ - private static ForkJoinPool makeCommonPool() { + static ForkJoinPool makeCommonPool() { int parallelism = -1; - ForkJoinWorkerThreadFactory factory - = defaultForkJoinWorkerThreadFactory; + ForkJoinWorkerThreadFactory factory = null; UncaughtExceptionHandler handler = null; try { // ignore exceptions in accessing/parsing properties String pp = System.getProperty @@ -3267,14 +3499,52 @@ private static ForkJoinPool makeCommonPool() { getSystemClassLoader().loadClass(hp).newInstance()); } catch (Exception ignore) { } - + if (factory == null) { + if (System.getSecurityManager() == null) + factory = defaultForkJoinWorkerThreadFactory; + else // use security-managed default + factory = new InnocuousForkJoinWorkerThreadFactory(); + } if (parallelism < 0 && // default 1 less than #cores - (parallelism = Runtime.getRuntime().availableProcessors() - 1) < 0) - parallelism = 0; + (parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0) + parallelism = 1; if (parallelism > MAX_CAP) parallelism = MAX_CAP; return new ForkJoinPool(parallelism, factory, handler, LIFO_QUEUE, "ForkJoinPool.commonPool-worker-"); } + /** + * Factory for innocuous worker threads. + */ + private static final class InnocuousForkJoinWorkerThreadFactory + implements ForkJoinWorkerThreadFactory { + + /** + * An ACC to restrict permissions for the factory itself. + * The constructed workers have no permissions set. + */ + private static final AccessControlContext innocuousAcc; + static { + Permissions innocuousPerms = new Permissions(); + innocuousPerms.add(modifyThreadPermission); + innocuousPerms.add(new RuntimePermission( + "enableContextClassLoaderOverride")); + innocuousPerms.add(new RuntimePermission( + "modifyThreadGroup")); + innocuousAcc = new AccessControlContext(new ProtectionDomain[] { + new ProtectionDomain(null, innocuousPerms) + }); + } + + public final ForkJoinWorkerThread newThread(ForkJoinPool pool) { + return java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public ForkJoinWorkerThread run() { + return new ForkJoinWorkerThread. + InnocuousForkJoinWorkerThread(pool); + }}, innocuousAcc); + } + } + } diff --git a/luni/src/main/java/java/util/concurrent/ForkJoinTask.java b/luni/src/main/java/java/util/concurrent/ForkJoinTask.java index 3a1a381a3..a8d97dbbc 100644 --- a/luni/src/main/java/java/util/concurrent/ForkJoinTask.java +++ b/luni/src/main/java/java/util/concurrent/ForkJoinTask.java @@ -7,13 +7,17 @@ package java.util.concurrent; import java.io.Serializable; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.lang.reflect.Constructor; import java.util.Collection; import java.util.List; import java.util.RandomAccess; -import java.lang.ref.WeakReference; -import java.lang.ref.ReferenceQueue; import java.util.concurrent.locks.ReentrantLock; -import java.lang.reflect.Constructor; + +// BEGIN android-note +// removed java 9 code +// END android-note /** * Abstract base class for tasks that run within a {@link ForkJoinPool}. @@ -24,8 +28,8 @@ * *

    A "main" {@code ForkJoinTask} begins execution when it is * explicitly submitted to a {@link ForkJoinPool}, or, if not already - * engaged in a ForkJoin computation, commenced in the {@code - * ForkJoinPool.commonPool()} via {@link #fork}, {@link #invoke}, or + * engaged in a ForkJoin computation, commenced in the {@link + * ForkJoinPool#commonPool()} via {@link #fork}, {@link #invoke}, or * related methods. Once started, it will usually in turn start other * subtasks. As indicated by the name of this class, many programs * using {@code ForkJoinTask} employ only methods {@link #fork} and @@ -66,9 +70,10 @@ * but doing do requires three further considerations: (1) Completion * of few if any other tasks should be dependent on a task * that blocks on external synchronization or I/O. Event-style async - * tasks that are never joined often fall into this category. - * (2) To minimize resource impact, tasks should be small; ideally - * performing only the (possibly) blocking action. (3) Unless the {@link + * tasks that are never joined (for example, those subclassing {@link + * CountedCompleter}) often fall into this category. (2) To minimize + * resource impact, tasks should be small; ideally performing only the + * (possibly) blocking action. (3) Unless the {@link * ForkJoinPool.ManagedBlocker} API is used, or the number of possibly * blocked tasks is known to be less than the pool's {@link * ForkJoinPool#getParallelism} level, the pool cannot guarantee that @@ -111,11 +116,13 @@ *

    The ForkJoinTask class is not usually directly subclassed. * Instead, you subclass one of the abstract classes that support a * particular style of fork/join processing, typically {@link - * RecursiveAction} for most computations that do not return results - * and {@link RecursiveTask} for those that do. Normally, a concrete - * ForkJoinTask subclass declares fields comprising its parameters, - * established in a constructor, and then defines a {@code compute} - * method that somehow uses the control methods supplied by this base class. + * RecursiveAction} for most computations that do not return results, + * {@link RecursiveTask} for those that do, and {@link + * CountedCompleter} for those in which completed actions trigger + * other actions. Normally, a concrete ForkJoinTask subclass declares + * fields comprising its parameters, established in a constructor, and + * then defines a {@code compute} method that somehow uses the control + * methods supplied by this base class. * *

    Method {@link #join} and its variants are appropriate for use * only when completion dependencies are acyclic; that is, the @@ -127,9 +134,9 @@ * may be of use in constructing custom subclasses for problems that * are not statically structured as DAGs. To support such usages, a * ForkJoinTask may be atomically tagged with a {@code short} - * value using {@code setForkJoinTaskTag} or {@code - * compareAndSetForkJoinTaskTag} and checked using {@code - * getForkJoinTaskTag}. The ForkJoinTask implementation does not use + * value using {@link #setForkJoinTaskTag} or {@link + * #compareAndSetForkJoinTaskTag} and checked using {@link + * #getForkJoinTaskTag}. The ForkJoinTask implementation does not use * these {@code protected} methods or tags for any purpose, but they * may be of use in the construction of specialized subclasses. For * example, parallel graph traversals can use the supplied methods to @@ -169,8 +176,6 @@ * @since 1.7 * @author Doug Lea */ -// android-note: Removed references to hidden apis commonPool, CountedCompleter -// etc. public abstract class ForkJoinTask implements Future, Serializable { /* @@ -259,15 +264,22 @@ final int doExec() { } /** - * Tries to set SIGNAL status unless already completed. Used by - * ForkJoinPool. Other variants are directly incorporated into - * externalAwaitDone etc. + * If not done, sets SIGNAL status and performs Object.wait(timeout). + * This task may or may not be done on exit. Ignores interrupts. * - * @return true if successful + * @param timeout using Object.wait conventions. */ - final boolean trySetSignal() { - int s = status; - return s >= 0 && U.compareAndSwapInt(this, STATUS, s, s | SIGNAL); + final void internalWait(long timeout) { + int s; + if ((s = status) >= 0 && // force completer to issue notify + U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) { + synchronized (this) { + if (status >= 0) + try { wait(timeout); } catch (InterruptedException ie) { } + else + notifyAll(); + } + } } /** @@ -275,35 +287,29 @@ final boolean trySetSignal() { * @return status upon completion */ private int externalAwaitDone() { - int s; - ForkJoinPool cp = ForkJoinPool.common; - if ((s = status) >= 0) { - if (cp != null) { - if (this instanceof CountedCompleter) - s = cp.externalHelpComplete((CountedCompleter)this); - else if (cp.tryExternalUnpush(this)) - s = doExec(); - } - if (s >= 0 && (s = status) >= 0) { - boolean interrupted = false; - do { - if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) { - synchronized (this) { - if (status >= 0) { - try { - wait(); - } catch (InterruptedException ie) { - interrupted = true; - } + int s = ((this instanceof CountedCompleter) ? // try helping + ForkJoinPool.common.externalHelpComplete( + (CountedCompleter)this, 0) : + ForkJoinPool.common.tryExternalUnpush(this) ? doExec() : 0); + if (s >= 0 && (s = status) >= 0) { + boolean interrupted = false; + do { + if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) { + synchronized (this) { + if (status >= 0) { + try { + wait(0L); + } catch (InterruptedException ie) { + interrupted = true; } - else - notifyAll(); } + else + notifyAll(); } - } while ((s = status) >= 0); - if (interrupted) - Thread.currentThread().interrupt(); - } + } + } while ((s = status) >= 0); + if (interrupted) + Thread.currentThread().interrupt(); } return s; } @@ -313,22 +319,22 @@ else if (cp.tryExternalUnpush(this)) */ private int externalInterruptibleAwaitDone() throws InterruptedException { int s; - ForkJoinPool cp = ForkJoinPool.common; if (Thread.interrupted()) throw new InterruptedException(); - if ((s = status) >= 0 && cp != null) { - if (this instanceof CountedCompleter) - cp.externalHelpComplete((CountedCompleter)this); - else if (cp.tryExternalUnpush(this)) - doExec(); - } - while ((s = status) >= 0) { - if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) { - synchronized (this) { - if (status >= 0) - wait(); - else - notifyAll(); + if ((s = status) >= 0 && + (s = ((this instanceof CountedCompleter) ? + ForkJoinPool.common.externalHelpComplete( + (CountedCompleter)this, 0) : + ForkJoinPool.common.tryExternalUnpush(this) ? doExec() : + 0)) >= 0) { + while ((s = status) >= 0) { + if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) { + synchronized (this) { + if (status >= 0) + wait(0L); + else + notifyAll(); + } } } } @@ -348,7 +354,7 @@ private int doJoin() { ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ? (w = (wt = (ForkJoinWorkerThread)t).workQueue). tryUnpush(this) && (s = doExec()) < 0 ? s : - wt.pool.awaitJoin(w, this) : + wt.pool.awaitJoin(w, this, 0L) : externalAwaitDone(); } @@ -361,7 +367,8 @@ private int doInvoke() { int s; Thread t; ForkJoinWorkerThread wt; return (s = doExec()) < 0 ? s : ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ? - (wt = (ForkJoinWorkerThread)t).pool.awaitJoin(wt.workQueue, this) : + (wt = (ForkJoinWorkerThread)t).pool. + awaitJoin(wt.workQueue, this, 0L) : externalAwaitDone(); } @@ -402,7 +409,8 @@ static final class ExceptionNode extends WeakReference> { ExceptionNode next; final long thrower; // use id not ref to avoid weak cycles final int hashCode; // store task hashCode before weak ref disappears - ExceptionNode(ForkJoinTask task, Throwable ex, ExceptionNode next) { + ExceptionNode(ForkJoinTask task, Throwable ex, ExceptionNode next, + ReferenceQueue exceptionTableRefQueue) { super(task, exceptionTableRefQueue); this.ex = ex; this.next = next; @@ -428,7 +436,8 @@ final int recordExceptionalCompletion(Throwable ex) { int i = h & (t.length - 1); for (ExceptionNode e = t[i]; ; e = e.next) { if (e == null) { - t[i] = new ExceptionNode(this, ex, t[i]); + t[i] = new ExceptionNode(this, ex, t[i], + exceptionTableRefQueue); break; } if (e.get() == this) // already present @@ -507,22 +516,20 @@ private void clearExceptionalCompletion() { } /** - * Returns a rethrowable exception for the given task, if - * available. To provide accurate stack traces, if the exception - * was not thrown by the current thread, we try to create a new - * exception of the same type as the one thrown, but with the - * recorded exception as its cause. If there is no such - * constructor, we instead try to use a no-arg constructor, - * followed by initCause, to the same effect. If none of these - * apply, or any fail due to other exceptions, we return the - * recorded exception, which is still correct, although it may - * contain a misleading stack trace. + * Returns a rethrowable exception for this task, if available. + * To provide accurate stack traces, if the exception was not + * thrown by the current thread, we try to create a new exception + * of the same type as the one thrown, but with the recorded + * exception as its cause. If there is no such constructor, we + * instead try to use a no-arg constructor, followed by initCause, + * to the same effect. If none of these apply, or any fail due to + * other exceptions, we return the recorded exception, which is + * still correct, although it may contain a misleading stack + * trace. * * @return the exception, or null if none */ private Throwable getThrowableException() { - if ((status & DONE_MASK) != EXCEPTIONAL) - return null; int h = System.identityHashCode(this); ExceptionNode e; final ReentrantLock lock = exceptionTableLock; @@ -539,21 +546,19 @@ private Throwable getThrowableException() { Throwable ex; if (e == null || (ex = e.ex) == null) return null; - if (false && e.thrower != Thread.currentThread().getId()) { - Class ec = ex.getClass(); + if (e.thrower != Thread.currentThread().getId()) { try { Constructor noArgCtor = null; - Constructor[] cs = ec.getConstructors();// public ctors only - for (int i = 0; i < cs.length; ++i) { - Constructor c = cs[i]; + // public ctors only + for (Constructor c : ex.getClass().getConstructors()) { Class[] ps = c.getParameterTypes(); if (ps.length == 0) noArgCtor = c; else if (ps.length == 1 && ps[0] == Throwable.class) - return (Throwable)(c.newInstance(ex)); + return (Throwable)c.newInstance(ex); } if (noArgCtor != null) { - Throwable wx = (Throwable)(noArgCtor.newInstance()); + Throwable wx = (Throwable)noArgCtor.newInstance(); wx.initCause(ex); return wx; } @@ -564,7 +569,7 @@ else if (ps.length == 1 && ps[0] == Throwable.class) } /** - * Poll stale refs and remove them. Call only while holding lock. + * Polls stale refs and removes them. Call only while holding lock. */ private static void expungeStaleExceptions() { for (Object x; (x = exceptionTableRefQueue.poll()) != null;) { @@ -591,7 +596,7 @@ private static void expungeStaleExceptions() { } /** - * If lock is available, poll stale refs and remove them. + * If lock is available, polls stale refs and removes them. * Called from ForkJoinPool when pools become quiescent. */ static final void helpExpungeStaleExceptions() { @@ -606,21 +611,23 @@ static final void helpExpungeStaleExceptions() { } /** - * A version of "sneaky throw" to relay exceptions + * A version of "sneaky throw" to relay exceptions. */ static void rethrow(Throwable ex) { - if (ex != null) - ForkJoinTask.uncheckedThrow(ex); + ForkJoinTask.uncheckedThrow(ex); } /** * The sneaky part of sneaky throw, relying on generics * limitations to evade compiler complaints about rethrowing - * unchecked exceptions + * unchecked exceptions. */ @SuppressWarnings("unchecked") static - void uncheckedThrow(Throwable t) throws T { - throw (T)t; // rely on vacuous cast + void uncheckedThrow(Throwable t) throws T { + if (t != null) + throw (T)t; // rely on vacuous cast + else + throw new Error("Unknown Exception"); } /** @@ -637,8 +644,8 @@ private void reportException(int s) { /** * Arranges to asynchronously execute this task in the pool the - * current task is running in, if applicable, or using the {@code - * ForkJoinPool.commonPool()} if not {@link #inForkJoinPool}. While + * current task is running in, if applicable, or using the {@link + * ForkJoinPool#commonPool()} if not {@link #inForkJoinPool}. While * it is not necessarily enforced, it is a usage error to fork a * task more than once unless it has completed and been * reinitialized. Subsequent modifications to the state of this @@ -936,7 +943,6 @@ public void complete(V value) { * invocations of {@code join} and related operations. * * @since 1.8 - * @hide */ public final void quietlyComplete() { setCompletion(NORMAL); @@ -956,11 +962,10 @@ public final void quietlyComplete() { public final V get() throws InterruptedException, ExecutionException { int s = (Thread.currentThread() instanceof ForkJoinWorkerThread) ? doJoin() : externalInterruptibleAwaitDone(); - Throwable ex; if ((s &= DONE_MASK) == CANCELLED) throw new CancellationException(); - if (s == EXCEPTIONAL && (ex = getThrowableException()) != null) - throw new ExecutionException(ex); + if (s == EXCEPTIONAL) + throw new ExecutionException(getThrowableException()); return getRawResult(); } @@ -980,75 +985,46 @@ public final V get() throws InterruptedException, ExecutionException { */ public final V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + int s; + long nanos = unit.toNanos(timeout); if (Thread.interrupted()) throw new InterruptedException(); - // Messy in part because we measure in nanosecs, but wait in millisecs - int s; long ms; - long ns = unit.toNanos(timeout); - ForkJoinPool cp; - if ((s = status) >= 0 && ns > 0L) { - long deadline = System.nanoTime() + ns; - ForkJoinPool p = null; - ForkJoinPool.WorkQueue w = null; + if ((s = status) >= 0 && nanos > 0L) { + long d = System.nanoTime() + nanos; + long deadline = (d == 0L) ? 1L : d; // avoid 0 Thread t = Thread.currentThread(); if (t instanceof ForkJoinWorkerThread) { ForkJoinWorkerThread wt = (ForkJoinWorkerThread)t; - p = wt.pool; - w = wt.workQueue; - p.helpJoinOnce(w, this); // no retries on failure - } - else if ((cp = ForkJoinPool.common) != null) { - if (this instanceof CountedCompleter) - cp.externalHelpComplete((CountedCompleter)this); - else if (cp.tryExternalUnpush(this)) - doExec(); + s = wt.pool.awaitJoin(wt.workQueue, this, deadline); } - boolean canBlock = false; - boolean interrupted = false; - try { - while ((s = status) >= 0) { - if (w != null && w.qlock < 0) - cancelIgnoringExceptions(this); - else if (!canBlock) { - if (p == null || p.tryCompensate(p.ctl)) - canBlock = true; - } - else { - if ((ms = TimeUnit.NANOSECONDS.toMillis(ns)) > 0L && - U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) { - synchronized (this) { - if (status >= 0) { - try { - wait(ms); - } catch (InterruptedException ie) { - if (p == null) - interrupted = true; - } - } - else - notifyAll(); - } + else if ((s = ((this instanceof CountedCompleter) ? + ForkJoinPool.common.externalHelpComplete( + (CountedCompleter)this, 0) : + ForkJoinPool.common.tryExternalUnpush(this) ? + doExec() : 0)) >= 0) { + long ns, ms; // measure in nanosecs, but wait in millisecs + while ((s = status) >= 0 && + (ns = deadline - System.nanoTime()) > 0L) { + if ((ms = TimeUnit.NANOSECONDS.toMillis(ns)) > 0L && + U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) { + synchronized (this) { + if (status >= 0) + wait(ms); // OK to throw InterruptedException + else + notifyAll(); } - if ((s = status) < 0 || interrupted || - (ns = deadline - System.nanoTime()) <= 0L) - break; } } - } finally { - if (p != null && canBlock) - p.incrementActiveCount(); } - if (interrupted) - throw new InterruptedException(); } + if (s >= 0) + s = status; if ((s &= DONE_MASK) != NORMAL) { - Throwable ex; if (s == CANCELLED) throw new CancellationException(); if (s != EXCEPTIONAL) throw new TimeoutException(); - if ((ex = getThrowableException()) != null) - throw new ExecutionException(ex); + throw new ExecutionException(getThrowableException()); } return getRawResult(); } @@ -1074,10 +1050,10 @@ public final void quietlyInvoke() { /** * Possibly executes tasks until the pool hosting the current task - * {@link ForkJoinPool#isQuiescent is quiescent}. This method may - * be of use in designs in which many tasks are forked, but none - * are explicitly joined, instead executing them until all are - * processed. + * {@linkplain ForkJoinPool#isQuiescent is quiescent}. This + * method may be of use in designs in which many tasks are forked, + * but none are explicitly joined, instead executing them until + * all are processed. */ public static void helpQuiesce() { Thread t; @@ -1113,10 +1089,12 @@ public void reinitialize() { } /** - * Returns the pool hosting the current task execution, or null - * if this task is executing outside of any ForkJoinPool. + * Returns the pool hosting the current thread, or {@code null} + * if the current thread is executing outside of any ForkJoinPool. + * + *

    This method returns {@code null} if and only if {@link + * #inForkJoinPool} returns {@code false}. * - * @see #inForkJoinPool * @return the pool, or {@code null} if none */ public static ForkJoinPool getPool() { @@ -1283,6 +1261,25 @@ protected static ForkJoinTask pollTask() { null; } + /** + * If the current thread is operating in a ForkJoinPool, + * unschedules and returns, without executing, a task externally + * submitted to the pool, if one is available. Availability may be + * transient, so a {@code null} result does not necessarily imply + * quiescence of the pool. This method is designed primarily to + * support extensions, and is unlikely to be useful otherwise. + * + * @return a task, or {@code null} if none are available + * @since 9 + * @hide + */ + // android-changed - hidden + protected static ForkJoinTask pollSubmission() { + Thread t; + return ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ? + ((ForkJoinWorkerThread)t).pool.pollSubmission() : null; + } + // tag operations /** @@ -1290,24 +1287,22 @@ protected static ForkJoinTask pollTask() { * * @return the tag for this task * @since 1.8 - * @hide */ public final short getForkJoinTaskTag() { return (short)status; } /** - * Atomically sets the tag value for this task. + * Atomically sets the tag value for this task and returns the old value. * - * @param tag the tag value + * @param newValue the new tag value * @return the previous value of the tag * @since 1.8 - * @hide */ - public final short setForkJoinTaskTag(short tag) { + public final short setForkJoinTaskTag(short newValue) { for (int s;;) { if (U.compareAndSwapInt(this, STATUS, s = status, - (s & ~SMASK) | (tag & SMASK))) + (s & ~SMASK) | (newValue & SMASK))) return (short)s; } } @@ -1320,25 +1315,24 @@ public final short setForkJoinTaskTag(short tag) { * before processing, otherwise exiting because the node has * already been visited. * - * @param e the expected tag value - * @param tag the new tag value + * @param expect the expected tag value + * @param update the new tag value * @return {@code true} if successful; i.e., the current value was - * equal to e and is now tag. + * equal to {@code expect} and was changed to {@code update}. * @since 1.8 - * @hide */ - public final boolean compareAndSetForkJoinTaskTag(short e, short tag) { + public final boolean compareAndSetForkJoinTaskTag(short expect, short update) { for (int s;;) { - if ((short)(s = status) != e) + if ((short)(s = status) != expect) return false; if (U.compareAndSwapInt(this, STATUS, s, - (s & ~SMASK) | (tag & SMASK))) + (s & ~SMASK) | (update & SMASK))) return true; } } /** - * Adaptor for Runnables. This implements RunnableFuture + * Adapter for Runnables. This implements RunnableFuture * to be compliant with AbstractExecutorService constraints * when used in ForkJoinPool. */ @@ -1359,7 +1353,7 @@ static final class AdaptedRunnable extends ForkJoinTask } /** - * Adaptor for Runnables without results + * Adapter for Runnables without results. */ static final class AdaptedRunnableAction extends ForkJoinTask implements RunnableFuture { @@ -1376,7 +1370,7 @@ public final void setRawResult(Void v) { } } /** - * Adaptor for Runnables in which failure forces worker exception + * Adapter for Runnables in which failure forces worker exception. */ static final class RunnableExecuteAction extends ForkJoinTask { final Runnable runnable; @@ -1394,7 +1388,7 @@ void internalPropagateException(Throwable ex) { } /** - * Adaptor for Callables + * Adapter for Callables. */ static final class AdaptedCallable extends ForkJoinTask implements RunnableFuture { @@ -1467,6 +1461,8 @@ public static ForkJoinTask adapt(Callable callable) { /** * Saves this task to a stream (that is, serializes it). * + * @param s the stream + * @throws java.io.IOException if an I/O error occurs * @serialData the current run status and the exception thrown * during execution, or {@code null} if none */ @@ -1478,6 +1474,10 @@ private void writeObject(java.io.ObjectOutputStream s) /** * Reconstitutes this task from a stream (that is, deserializes it). + * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws java.io.IOException if an I/O error occurs */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { @@ -1488,7 +1488,7 @@ private void readObject(java.io.ObjectInputStream s) } // Unsafe mechanics - private static final sun.misc.Unsafe U; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); private static final long STATUS; static { @@ -1496,12 +1496,11 @@ private void readObject(java.io.ObjectInputStream s) exceptionTableRefQueue = new ReferenceQueue(); exceptionTable = new ExceptionNode[EXCEPTION_MAP_CAPACITY]; try { - U = sun.misc.Unsafe.getUnsafe(); - Class k = ForkJoinTask.class; STATUS = U.objectFieldOffset - (k.getDeclaredField("status")); - } catch (Exception e) { + (ForkJoinTask.class.getDeclaredField("status")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } + } diff --git a/luni/src/main/java/java/util/concurrent/ForkJoinWorkerThread.java b/luni/src/main/java/java/util/concurrent/ForkJoinWorkerThread.java index ae2870055..664d56ec2 100644 --- a/luni/src/main/java/java/util/concurrent/ForkJoinWorkerThread.java +++ b/luni/src/main/java/java/util/concurrent/ForkJoinWorkerThread.java @@ -6,6 +6,9 @@ package java.util.concurrent; +import java.security.AccessControlContext; +import java.security.ProtectionDomain; + /** * A thread managed by a {@link ForkJoinPool}, which executes * {@link ForkJoinTask}s. @@ -32,6 +35,10 @@ public class ForkJoinWorkerThread extends Thread { * completes. This leads to a visibility race, that is tolerated * by requiring that the workQueue field is only accessed by the * owning thread. + * + * Support for (non-public) subclass InnocuousForkJoinWorkerThread + * requires that we break quite a lot of encapsulation (via Unsafe) + * both here and in the subclass to access and set Thread fields. */ final ForkJoinPool pool; // the pool this thread works in @@ -50,6 +57,18 @@ protected ForkJoinWorkerThread(ForkJoinPool pool) { this.workQueue = pool.registerWorker(this); } + /** + * Version for InnocuousForkJoinWorkerThread. + */ + ForkJoinWorkerThread(ForkJoinPool pool, ThreadGroup threadGroup, + AccessControlContext acc) { + super(threadGroup, null, "aForkJoinWorkerThread"); + U.putOrderedObject(this, INHERITEDACCESSCONTROLCONTEXT, acc); + eraseThreadLocals(); // clear before registering + this.pool = pool; + this.workQueue = pool.registerWorker(this); + } + /** * Returns the pool hosting this thread. * @@ -70,7 +89,7 @@ public ForkJoinPool getPool() { * @return the index number */ public int getPoolIndex() { - return workQueue.poolIndex >>> 1; // ignore odd/even tag bit + return workQueue.getPoolIndex(); } /** @@ -102,21 +121,124 @@ protected void onTermination(Throwable exception) { * {@link ForkJoinTask}s. */ public void run() { - Throwable exception = null; - try { - onStart(); - pool.runWorker(workQueue); - } catch (Throwable ex) { - exception = ex; - } finally { + if (workQueue.array == null) { // only run once + Throwable exception = null; try { - onTermination(exception); + onStart(); + pool.runWorker(workQueue); } catch (Throwable ex) { - if (exception == null) - exception = ex; + exception = ex; } finally { - pool.deregisterWorker(this, exception); + try { + onTermination(exception); + } catch (Throwable ex) { + if (exception == null) + exception = ex; + } finally { + pool.deregisterWorker(this, exception); + } + } + } + } + + /** + * Erases ThreadLocals by nulling out Thread maps. + */ + final void eraseThreadLocals() { + U.putObject(this, THREADLOCALS, null); + U.putObject(this, INHERITABLETHREADLOCALS, null); + } + + /** + * Non-public hook method for InnocuousForkJoinWorkerThread. + */ + void afterTopLevelExec() { + } + + // Set up to allow setting thread fields in constructor + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long THREADLOCALS; + private static final long INHERITABLETHREADLOCALS; + private static final long INHERITEDACCESSCONTROLCONTEXT; + static { + try { + THREADLOCALS = U.objectFieldOffset + (Thread.class.getDeclaredField("threadLocals")); + INHERITABLETHREADLOCALS = U.objectFieldOffset + (Thread.class.getDeclaredField("inheritableThreadLocals")); + INHERITEDACCESSCONTROLCONTEXT = U.objectFieldOffset + (Thread.class.getDeclaredField("inheritedAccessControlContext")); + } catch (ReflectiveOperationException e) { + throw new Error(e); + } + } + + /** + * A worker thread that has no permissions, is not a member of any + * user-defined ThreadGroup, and erases all ThreadLocals after + * running each top-level task. + */ + static final class InnocuousForkJoinWorkerThread extends ForkJoinWorkerThread { + /** The ThreadGroup for all InnocuousForkJoinWorkerThreads */ + private static final ThreadGroup innocuousThreadGroup = + createThreadGroup(); + + /** An AccessControlContext supporting no privileges */ + private static final AccessControlContext INNOCUOUS_ACC = + new AccessControlContext( + new ProtectionDomain[] { + new ProtectionDomain(null, null) + }); + + InnocuousForkJoinWorkerThread(ForkJoinPool pool) { + super(pool, innocuousThreadGroup, INNOCUOUS_ACC); + } + + @Override // to erase ThreadLocals + void afterTopLevelExec() { + eraseThreadLocals(); + } + + @Override // to always report system loader + public ClassLoader getContextClassLoader() { + return ClassLoader.getSystemClassLoader(); + } + + @Override // to silently fail + public void setUncaughtExceptionHandler(UncaughtExceptionHandler x) { } + + @Override // paranoically + public void setContextClassLoader(ClassLoader cl) { + throw new SecurityException("setContextClassLoader"); + } + + /** + * Returns a new group with the system ThreadGroup (the + * topmost, parent-less group) as parent. Uses Unsafe to + * traverse Thread.group and ThreadGroup.parent fields. + */ + private static ThreadGroup createThreadGroup() { + try { + sun.misc.Unsafe u = sun.misc.Unsafe.getUnsafe(); + long tg = u.objectFieldOffset + (Thread.class.getDeclaredField("group")); + long gp = u.objectFieldOffset + (ThreadGroup.class.getDeclaredField("parent")); + ThreadGroup group = (ThreadGroup) + u.getObject(Thread.currentThread(), tg); + while (group != null) { + ThreadGroup parent = (ThreadGroup)u.getObject(group, gp); + if (parent == null) + return new ThreadGroup(group, + "InnocuousForkJoinWorkerThreadGroup"); + group = parent; + } + } catch (ReflectiveOperationException e) { + throw new Error(e); } + // fall through if null as cannot-happen safeguard + throw new Error("Cannot create ThreadGroup"); } } + } diff --git a/luni/src/main/java/java/util/concurrent/Future.java b/luni/src/main/java/java/util/concurrent/Future.java index 32e814517..28d2de55a 100644 --- a/luni/src/main/java/java/util/concurrent/Future.java +++ b/luni/src/main/java/java/util/concurrent/Future.java @@ -23,8 +23,9 @@ * *

    * Sample Usage (Note that the following classes are all - * made-up.)

    - *

     {@code
    + * made-up.)
    + *
    + * 
     {@code
      * interface ArchiveSearcher { String search(String target); }
      * class App {
      *   ExecutorService executor = ...
    @@ -46,9 +47,9 @@
      * The {@link FutureTask} class is an implementation of {@code Future} that
      * implements {@code Runnable}, and so may be executed by an {@code Executor}.
      * For example, the above construction with {@code submit} could be replaced by:
    - *  
     {@code
    + * 
     {@code
      * FutureTask future =
    - *   new FutureTask(new Callable() {
    + *   new FutureTask<>(new Callable() {
      *     public String call() {
      *       return searcher.search(target);
      *   }});
    diff --git a/luni/src/main/java/java/util/concurrent/FutureTask.java b/luni/src/main/java/java/util/concurrent/FutureTask.java
    index 5e24fc8f1..ed42817ee 100644
    --- a/luni/src/main/java/java/util/concurrent/FutureTask.java
    +++ b/luni/src/main/java/java/util/concurrent/FutureTask.java
    @@ -367,7 +367,7 @@ private int awaitDone(boolean timed, long nanos)
             throws InterruptedException {
             // The code below is very delicate, to achieve these goals:
             // - call nanoTime exactly once for each call to park
    -        // - if nanos <= 0, return promptly without allocation or nanoTime
    +        // - if nanos <= 0L, return promptly without allocation or nanoTime
             // - if nanos == Long.MIN_VALUE, don't underflow
             // - if nanos == Long.MAX_VALUE, and nanoTime is non-monotonic
             //   and we suffer a spurious wakeup, we will do no worse than
    @@ -467,7 +467,7 @@ else if (!U.compareAndSwapObject(this, WAITERS, q, s))
                     (FutureTask.class.getDeclaredField("runner"));
                 WAITERS = U.objectFieldOffset
                     (FutureTask.class.getDeclaredField("waiters"));
    -        } catch (Exception e) {
    +        } catch (ReflectiveOperationException e) {
                 throw new Error(e);
             }
     
    diff --git a/luni/src/main/java/java/util/concurrent/Helpers.java b/luni/src/main/java/java/util/concurrent/Helpers.java
    new file mode 100644
    index 000000000..9051e2f66
    --- /dev/null
    +++ b/luni/src/main/java/java/util/concurrent/Helpers.java
    @@ -0,0 +1,89 @@
    +/*
    + * Written by Martin Buchholz with assistance from members of JCP
    + * JSR-166 Expert Group and released to the public domain, as
    + * explained at http://creativecommons.org/publicdomain/zero/1.0/
    + */
    +
    +package java.util.concurrent;
    +
    +import java.util.Collection;
    +
    +/** Shared implementation code for java.util.concurrent. */
    +class Helpers {
    +    private Helpers() {}                // non-instantiable
    +
    +    /**
    +     * An implementation of Collection.toString() suitable for classes
    +     * with locks.  Instead of holding a lock for the entire duration of
    +     * toString(), or acquiring a lock for each call to Iterator.next(),
    +     * we hold the lock only during the call to toArray() (less
    +     * disruptive to other threads accessing the collection) and follows
    +     * the maxim "Never call foreign code while holding a lock".
    +     */
    +    static String collectionToString(Collection c) {
    +        final Object[] a = c.toArray();
    +        final int size = a.length;
    +        if (size == 0)
    +            return "[]";
    +        int charLength = 0;
    +
    +        // Replace every array element with its string representation
    +        for (int i = 0; i < size; i++) {
    +            Object e = a[i];
    +            // Extreme compatibility with AbstractCollection.toString()
    +            String s = (e == c) ? "(this Collection)" : objectToString(e);
    +            a[i] = s;
    +            charLength += s.length();
    +        }
    +
    +        return toString(a, size, charLength);
    +    }
    +
    +    /**
    +     * Like Arrays.toString(), but caller guarantees that size > 0,
    +     * each element with index 0 <= i < size is a non-null String,
    +     * and charLength is the sum of the lengths of the input Strings.
    +     */
    +    static String toString(Object[] a, int size, int charLength) {
    +        // assert a != null;
    +        // assert size > 0;
    +
    +        // Copy each string into a perfectly sized char[]
    +        // Length of [ , , , ] == 2 * size
    +        final char[] chars = new char[charLength + 2 * size];
    +        chars[0] = '[';
    +        int j = 1;
    +        for (int i = 0; i < size; i++) {
    +            if (i > 0) {
    +                chars[j++] = ',';
    +                chars[j++] = ' ';
    +            }
    +            String s = (String) a[i];
    +            int len = s.length();
    +            s.getChars(0, len, chars, j);
    +            j += len;
    +        }
    +        chars[j] = ']';
    +        // assert j == chars.length - 1;
    +        return new String(chars);
    +    }
    +
    +    /** Optimized form of: key + "=" + val */
    +    static String mapEntryToString(Object key, Object val) {
    +        final String k, v;
    +        final int klen, vlen;
    +        final char[] chars =
    +            new char[(klen = (k = objectToString(key)).length()) +
    +                     (vlen = (v = objectToString(val)).length()) + 1];
    +        k.getChars(0, klen, chars, 0);
    +        chars[klen] = '=';
    +        v.getChars(0, vlen, chars, klen + 1);
    +        return new String(chars);
    +    }
    +
    +    private static String objectToString(Object x) {
    +        // Extreme compatibility with StringBuilder.append(null)
    +        String s;
    +        return (x == null || (s = x.toString()) == null) ? "null" : s;
    +    }
    +}
    diff --git a/luni/src/main/java/java/util/concurrent/LinkedBlockingDeque.java b/luni/src/main/java/java/util/concurrent/LinkedBlockingDeque.java
    index 64b0bf164..b1d196d03 100644
    --- a/luni/src/main/java/java/util/concurrent/LinkedBlockingDeque.java
    +++ b/luni/src/main/java/java/util/concurrent/LinkedBlockingDeque.java
    @@ -10,8 +10,11 @@
     import java.util.Collection;
     import java.util.Iterator;
     import java.util.NoSuchElementException;
    +import java.util.Spliterator;
    +import java.util.Spliterators;
     import java.util.concurrent.locks.Condition;
     import java.util.concurrent.locks.ReentrantLock;
    +import java.util.function.Consumer;
     
     // BEGIN android-note
     // removed link to collections framework docs
    @@ -40,7 +43,7 @@
      *
      * @since 1.6
      * @author  Doug Lea
    - * @param  the type of elements held in this collection
    + * @param  the type of elements held in this deque
      */
     public class LinkedBlockingDeque
         extends AbstractQueue
    @@ -286,8 +289,8 @@ void unlink(Node x) {
         // BlockingDeque methods
     
         /**
    -     * @throws IllegalStateException {@inheritDoc}
    -     * @throws NullPointerException  {@inheritDoc}
    +     * @throws IllegalStateException if this deque is full
    +     * @throws NullPointerException {@inheritDoc}
          */
         public void addFirst(E e) {
             if (!offerFirst(e))
    @@ -295,7 +298,7 @@ public void addFirst(E e) {
         }
     
         /**
    -     * @throws IllegalStateException {@inheritDoc}
    +     * @throws IllegalStateException if this deque is full
          * @throws NullPointerException  {@inheritDoc}
          */
         public void addLast(E e) {
    @@ -380,7 +383,7 @@ public boolean offerFirst(E e, long timeout, TimeUnit unit)
             lock.lockInterruptibly();
             try {
                 while (!linkFirst(node)) {
    -                if (nanos <= 0)
    +                if (nanos <= 0L)
                         return false;
                     nanos = notFull.awaitNanos(nanos);
                 }
    @@ -403,7 +406,7 @@ public boolean offerLast(E e, long timeout, TimeUnit unit)
             lock.lockInterruptibly();
             try {
                 while (!linkLast(node)) {
    -                if (nanos <= 0)
    +                if (nanos <= 0L)
                         return false;
                     nanos = notFull.awaitNanos(nanos);
                 }
    @@ -485,7 +488,7 @@ public E pollFirst(long timeout, TimeUnit unit)
             try {
                 E x;
                 while ( (x = unlinkFirst()) == null) {
    -                if (nanos <= 0)
    +                if (nanos <= 0L)
                         return null;
                     nanos = notEmpty.awaitNanos(nanos);
                 }
    @@ -503,7 +506,7 @@ public E pollLast(long timeout, TimeUnit unit)
             try {
                 E x;
                 while ( (x = unlinkLast()) == null) {
    -                if (nanos <= 0)
    +                if (nanos <= 0L)
                         return null;
                     nanos = notEmpty.awaitNanos(nanos);
                 }
    @@ -594,8 +597,7 @@ public boolean removeLastOccurrence(Object o) {
          *
          * 

    This method is equivalent to {@link #addLast}. * - * @throws IllegalStateException if the element cannot be added at this - * time due to capacity restrictions + * @throws IllegalStateException if this deque is full * @throws NullPointerException if the specified element is null */ public boolean add(E e) { @@ -732,8 +734,8 @@ public int drainTo(Collection c, int maxElements) { // Stack methods /** - * @throws IllegalStateException {@inheritDoc} - * @throws NullPointerException {@inheritDoc} + * @throws IllegalStateException if this deque is full + * @throws NullPointerException {@inheritDoc} */ public void push(E e) { addFirst(e); @@ -823,7 +825,7 @@ public boolean contains(Object o) { // * @throws ClassCastException {@inheritDoc} // * @throws NullPointerException {@inheritDoc} // * @throws IllegalArgumentException {@inheritDoc} -// * @throws IllegalStateException {@inheritDoc} +// * @throws IllegalStateException if this deque is full // * @see #add(Object) // */ // public boolean addAll(Collection c) { @@ -893,7 +895,7 @@ public Object[] toArray() { * The following code can be used to dump the deque into a newly * allocated array of {@code String}: * - *

     {@code String[] y = x.toArray(new String[0]);}
    + *
     {@code String[] y = x.toArray(new String[0]);}
    * * Note that {@code toArray(new Object[0])} is identical in function to * {@code toArray()}. @@ -928,26 +930,7 @@ public T[] toArray(T[] a) { } public String toString() { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - Node p = first; - if (p == null) - return "[]"; - - StringBuilder sb = new StringBuilder(); - sb.append('['); - for (;;) { - E e = p.item; - sb.append(e == this ? "(this Collection)" : e); - p = p.next; - if (p == null) - return sb.append(']').toString(); - sb.append(',').append(' '); - } - } finally { - lock.unlock(); - } + return Helpers.collectionToString(this); } /** @@ -977,12 +960,8 @@ public void clear() { * Returns an iterator over the elements in this deque in proper sequence. * The elements will be returned in order from first (head) to last (tail). * - *

    The returned iterator is a "weakly consistent" iterator that - * will never throw {@link java.util.ConcurrentModificationException - * ConcurrentModificationException}, and guarantees to traverse - * elements as they existed upon construction of the iterator, and - * may (but is not guaranteed to) reflect any modifications - * subsequent to construction. + *

    The returned iterator is + * weakly consistent. * * @return an iterator over the elements in this deque in proper sequence */ @@ -995,12 +974,8 @@ public Iterator iterator() { * sequential order. The elements will be returned in order from * last (tail) to first (head). * - *

    The returned iterator is a "weakly consistent" iterator that - * will never throw {@link java.util.ConcurrentModificationException - * ConcurrentModificationException}, and guarantees to traverse - * elements as they existed upon construction of the iterator, and - * may (but is not guaranteed to) reflect any modifications - * subsequent to construction. + *

    The returned iterator is + * weakly consistent. * * @return an iterator over the elements in this deque in reverse order */ @@ -1009,11 +984,11 @@ public Iterator descendingIterator() { } /** - * Base class for Iterators for LinkedBlockingDeque + * Base class for LinkedBlockingDeque iterators. */ private abstract class AbstractItr implements Iterator { /** - * The next node to return in next() + * The next node to return in next(). */ Node next; @@ -1122,9 +1097,149 @@ private class DescendingItr extends AbstractItr { Node nextNode(Node n) { return n.prev; } } + /** A customized variant of Spliterators.IteratorSpliterator */ + static final class LBDSpliterator implements Spliterator { + static final int MAX_BATCH = 1 << 25; // max batch array size; + final LinkedBlockingDeque queue; + Node current; // current node; null until initialized + int batch; // batch size for splits + boolean exhausted; // true when no more nodes + long est; // size estimate + LBDSpliterator(LinkedBlockingDeque queue) { + this.queue = queue; + this.est = queue.size(); + } + + public long estimateSize() { return est; } + + public Spliterator trySplit() { + Node h; + final LinkedBlockingDeque q = this.queue; + int b = batch; + int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1; + if (!exhausted && + ((h = current) != null || (h = q.first) != null) && + h.next != null) { + Object[] a = new Object[n]; + final ReentrantLock lock = q.lock; + int i = 0; + Node p = current; + lock.lock(); + try { + if (p != null || (p = q.first) != null) { + do { + if ((a[i] = p.item) != null) + ++i; + } while ((p = p.next) != null && i < n); + } + } finally { + lock.unlock(); + } + if ((current = p) == null) { + est = 0L; + exhausted = true; + } + else if ((est -= i) < 0L) + est = 0L; + if (i > 0) { + batch = i; + return Spliterators.spliterator + (a, 0, i, (Spliterator.ORDERED | + Spliterator.NONNULL | + Spliterator.CONCURRENT)); + } + } + return null; + } + + public void forEachRemaining(Consumer action) { + if (action == null) throw new NullPointerException(); + final LinkedBlockingDeque q = this.queue; + final ReentrantLock lock = q.lock; + if (!exhausted) { + exhausted = true; + Node p = current; + do { + E e = null; + lock.lock(); + try { + if (p == null) + p = q.first; + while (p != null) { + e = p.item; + p = p.next; + if (e != null) + break; + } + } finally { + lock.unlock(); + } + if (e != null) + action.accept(e); + } while (p != null); + } + } + + public boolean tryAdvance(Consumer action) { + if (action == null) throw new NullPointerException(); + final LinkedBlockingDeque q = this.queue; + final ReentrantLock lock = q.lock; + if (!exhausted) { + E e = null; + lock.lock(); + try { + if (current == null) + current = q.first; + while (current != null) { + e = current.item; + current = current.next; + if (e != null) + break; + } + } finally { + lock.unlock(); + } + if (current == null) + exhausted = true; + if (e != null) { + action.accept(e); + return true; + } + } + return false; + } + + public int characteristics() { + return Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT; + } + } + + /** + * Returns a {@link Spliterator} over the elements in this deque. + * + *

    The returned spliterator is + * weakly consistent. + * + *

    The {@code Spliterator} reports {@link Spliterator#CONCURRENT}, + * {@link Spliterator#ORDERED}, and {@link Spliterator#NONNULL}. + * + * @implNote + * The {@code Spliterator} implements {@code trySplit} to permit limited + * parallelism. + * + * @return a {@code Spliterator} over the elements in this deque + * @since 1.8 + */ + public Spliterator spliterator() { + return new LBDSpliterator(this); + } + /** * Saves this deque to a stream (that is, serializes it). * + * @param s the stream + * @throws java.io.IOException if an I/O error occurs * @serialData The capacity (int), followed by elements (each an * {@code Object}) in the proper order, followed by a null */ @@ -1147,6 +1262,10 @@ private void writeObject(java.io.ObjectOutputStream s) /** * Reconstitutes this deque from a stream (that is, deserializes it). + * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws java.io.IOException if an I/O error occurs */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { diff --git a/luni/src/main/java/java/util/concurrent/LinkedBlockingQueue.java b/luni/src/main/java/java/util/concurrent/LinkedBlockingQueue.java index 071982849..86ed04aa9 100644 --- a/luni/src/main/java/java/util/concurrent/LinkedBlockingQueue.java +++ b/luni/src/main/java/java/util/concurrent/LinkedBlockingQueue.java @@ -6,13 +6,16 @@ package java.util.concurrent; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.ReentrantLock; import java.util.AbstractQueue; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; // BEGIN android-note // removed link to collections framework docs @@ -43,7 +46,7 @@ * * @since 1.5 * @author Doug Lea - * @param the type of elements held in this collection + * @param the type of elements held in this queue */ public class LinkedBlockingQueue extends AbstractQueue implements BlockingQueue, java.io.Serializable { @@ -85,7 +88,7 @@ public class LinkedBlockingQueue extends AbstractQueue */ /** - * Linked list node class + * Linked list node class. */ static class Node { E item; @@ -348,7 +351,7 @@ public boolean offer(E e, long timeout, TimeUnit unit) putLock.lockInterruptibly(); try { while (count.get() == capacity) { - if (nanos <= 0) + if (nanos <= 0L) return false; nanos = notFull.awaitNanos(nanos); } @@ -430,7 +433,7 @@ public E poll(long timeout, TimeUnit unit) throws InterruptedException { takeLock.lockInterruptibly(); try { while (count.get() == 0) { - if (nanos <= 0) + if (nanos <= 0L) return null; nanos = notEmpty.awaitNanos(nanos); } @@ -475,11 +478,7 @@ public E peek() { final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { - Node first = head.next; - if (first == null) - return null; - else - return first.item; + return (count.get() > 0) ? head.next.item : null; } finally { takeLock.unlock(); } @@ -598,7 +597,7 @@ public Object[] toArray() { * The following code can be used to dump the queue into a newly * allocated array of {@code String}: * - *

     {@code String[] y = x.toArray(new String[0]);}
    + *
     {@code String[] y = x.toArray(new String[0]);}
    * * Note that {@code toArray(new Object[0])} is identical in function to * {@code toArray()}. @@ -633,25 +632,7 @@ public T[] toArray(T[] a) { } public String toString() { - fullyLock(); - try { - Node p = head.next; - if (p == null) - return "[]"; - - StringBuilder sb = new StringBuilder(); - sb.append('['); - for (;;) { - E e = p.item; - sb.append(e == this ? "(this Collection)" : e); - p = p.next; - if (p == null) - return sb.append(']').toString(); - sb.append(',').append(' '); - } - } finally { - fullyUnlock(); - } + return Helpers.collectionToString(this); } /** @@ -734,12 +715,8 @@ public int drainTo(Collection c, int maxElements) { * Returns an iterator over the elements in this queue in proper sequence. * The elements will be returned in order from first (head) to last (tail). * - *

    The returned iterator is a "weakly consistent" iterator that - * will never throw {@link java.util.ConcurrentModificationException - * ConcurrentModificationException}, and guarantees to traverse - * elements as they existed upon construction of the iterator, and - * may (but is not guaranteed to) reflect any modifications - * subsequent to construction. + *

    The returned iterator is + * weakly consistent. * * @return an iterator over the elements in this queue in proper sequence */ @@ -773,34 +750,26 @@ public boolean hasNext() { return current != null; } - /** - * Returns the next live successor of p, or null if no such. - * - * Unlike other traversal methods, iterators need to handle both: - * - dequeued nodes (p.next == p) - * - (possibly multiple) interior removed nodes (p.item == null) - */ - private Node nextNode(Node p) { - for (;;) { - Node s = p.next; - if (s == p) - return head.next; - if (s == null || s.item != null) - return s; - p = s; - } - } - public E next() { fullyLock(); try { if (current == null) throw new NoSuchElementException(); - E x = currentElement; lastRet = current; - current = nextNode(current); - currentElement = (current == null) ? null : current.item; - return x; + E item = null; + // Unlike other traversal methods, iterators must handle both: + // - dequeued nodes (p.next == p) + // - (possibly multiple) interior removed nodes (p.item == null) + for (Node p = current, q;; p = q) { + if ((q = p.next) == p) + q = head.next; + if (q == null || (item = q.item) != null) { + current = q; + E x = currentElement; + currentElement = item; + return x; + } + } } finally { fullyUnlock(); } @@ -827,9 +796,146 @@ public void remove() { } } + /** A customized variant of Spliterators.IteratorSpliterator */ + static final class LBQSpliterator implements Spliterator { + static final int MAX_BATCH = 1 << 25; // max batch array size; + final LinkedBlockingQueue queue; + Node current; // current node; null until initialized + int batch; // batch size for splits + boolean exhausted; // true when no more nodes + long est; // size estimate + LBQSpliterator(LinkedBlockingQueue queue) { + this.queue = queue; + this.est = queue.size(); + } + + public long estimateSize() { return est; } + + public Spliterator trySplit() { + Node h; + final LinkedBlockingQueue q = this.queue; + int b = batch; + int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1; + if (!exhausted && + ((h = current) != null || (h = q.head.next) != null) && + h.next != null) { + Object[] a = new Object[n]; + int i = 0; + Node p = current; + q.fullyLock(); + try { + if (p != null || (p = q.head.next) != null) { + do { + if ((a[i] = p.item) != null) + ++i; + } while ((p = p.next) != null && i < n); + } + } finally { + q.fullyUnlock(); + } + if ((current = p) == null) { + est = 0L; + exhausted = true; + } + else if ((est -= i) < 0L) + est = 0L; + if (i > 0) { + batch = i; + return Spliterators.spliterator + (a, 0, i, (Spliterator.ORDERED | + Spliterator.NONNULL | + Spliterator.CONCURRENT)); + } + } + return null; + } + + public void forEachRemaining(Consumer action) { + if (action == null) throw new NullPointerException(); + final LinkedBlockingQueue q = this.queue; + if (!exhausted) { + exhausted = true; + Node p = current; + do { + E e = null; + q.fullyLock(); + try { + if (p == null) + p = q.head.next; + while (p != null) { + e = p.item; + p = p.next; + if (e != null) + break; + } + } finally { + q.fullyUnlock(); + } + if (e != null) + action.accept(e); + } while (p != null); + } + } + + public boolean tryAdvance(Consumer action) { + if (action == null) throw new NullPointerException(); + final LinkedBlockingQueue q = this.queue; + if (!exhausted) { + E e = null; + q.fullyLock(); + try { + if (current == null) + current = q.head.next; + while (current != null) { + e = current.item; + current = current.next; + if (e != null) + break; + } + } finally { + q.fullyUnlock(); + } + if (current == null) + exhausted = true; + if (e != null) { + action.accept(e); + return true; + } + } + return false; + } + + public int characteristics() { + return Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT; + } + } + + /** + * Returns a {@link Spliterator} over the elements in this queue. + * + *

    The returned spliterator is + * weakly consistent. + * + *

    The {@code Spliterator} reports {@link Spliterator#CONCURRENT}, + * {@link Spliterator#ORDERED}, and {@link Spliterator#NONNULL}. + * + * @implNote + * The {@code Spliterator} implements {@code trySplit} to permit limited + * parallelism. + * + * @return a {@code Spliterator} over the elements in this queue + * @since 1.8 + */ + public Spliterator spliterator() { + return new LBQSpliterator(this); + } + /** * Saves this queue to a stream (that is, serializes it). * + * @param s the stream + * @throws java.io.IOException if an I/O error occurs * @serialData The capacity is emitted (int), followed by all of * its elements (each an {@code Object}) in the proper order, * followed by a null @@ -855,6 +961,10 @@ private void writeObject(java.io.ObjectOutputStream s) /** * Reconstitutes this queue from a stream (that is, deserializes it). + * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws java.io.IOException if an I/O error occurs */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { diff --git a/luni/src/main/java/java/util/concurrent/LinkedTransferQueue.java b/luni/src/main/java/java/util/concurrent/LinkedTransferQueue.java index db484202f..185ebfded 100644 --- a/luni/src/main/java/java/util/concurrent/LinkedTransferQueue.java +++ b/luni/src/main/java/java/util/concurrent/LinkedTransferQueue.java @@ -7,11 +7,15 @@ package java.util.concurrent; import java.util.AbstractQueue; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Queue; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.concurrent.locks.LockSupport; +import java.util.function.Consumer; // BEGIN android-note // removed link to collections framework docs @@ -50,7 +54,7 @@ * * @since 1.7 * @author Doug Lea - * @param the type of elements held in this collection + * @param the type of elements held in this queue */ public class LinkedTransferQueue extends AbstractQueue implements TransferQueue, java.io.Serializable { @@ -182,7 +186,7 @@ public class LinkedTransferQueue extends AbstractQueue * of costly-to-reclaim garbage caused by the sequential "next" * links of nodes starting at old forgotten head nodes: As first * described in detail by Boehm - * (http://portal.acm.org/citation.cfm?doid=503272.503282) if a GC + * (http://portal.acm.org/citation.cfm?doid=503272.503282), if a GC * delays noticing that any arbitrarily old node has become * garbage, all newer dead nodes will also be unreclaimed. * (Similar issues arise in non-GC environments.) To cope with @@ -423,12 +427,12 @@ static final class Node { // CAS methods for fields final boolean casNext(Node cmp, Node val) { - return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); + return U.compareAndSwapObject(this, NEXT, cmp, val); } final boolean casItem(Object cmp, Object val) { // assert cmp == null || cmp.getClass() != Node.class; - return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val); + return U.compareAndSwapObject(this, ITEM, cmp, val); } /** @@ -436,7 +440,7 @@ final boolean casItem(Object cmp, Object val) { * only be seen after publication via casNext. */ Node(Object item, boolean isData) { - UNSAFE.putObject(this, itemOffset, item); // relaxed write + U.putObject(this, ITEM, item); // relaxed write this.isData = isData; } @@ -445,7 +449,7 @@ final boolean casItem(Object cmp, Object val) { * only after CASing head field, so uses relaxed write. */ final void forgetNext() { - UNSAFE.putObject(this, nextOffset, this); + U.putObject(this, NEXT, this); } /** @@ -458,8 +462,8 @@ final void forgetNext() { * else we don't care). */ final void forgetContents() { - UNSAFE.putObject(this, itemOffset, this); - UNSAFE.putObject(this, waiterOffset, null); + U.putObject(this, ITEM, this); + U.putObject(this, WAITER, null); } /** @@ -505,21 +509,19 @@ final boolean tryMatchData() { private static final long serialVersionUID = -3375979862319811754L; // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long itemOffset; - private static final long nextOffset; - private static final long waiterOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long ITEM; + private static final long NEXT; + private static final long WAITER; static { try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = Node.class; - itemOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("item")); - nextOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("next")); - waiterOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("waiter")); - } catch (Exception e) { + ITEM = U.objectFieldOffset + (Node.class.getDeclaredField("item")); + NEXT = U.objectFieldOffset + (Node.class.getDeclaredField("next")); + WAITER = U.objectFieldOffset + (Node.class.getDeclaredField("waiter")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } @@ -536,15 +538,15 @@ final boolean tryMatchData() { // CAS methods for fields private boolean casTail(Node cmp, Node val) { - return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val); + return U.compareAndSwapObject(this, TAIL, cmp, val); } private boolean casHead(Node cmp, Node val) { - return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val); + return U.compareAndSwapObject(this, HEAD, cmp, val); } private boolean casSweepVotes(int cmp, int val) { - return UNSAFE.compareAndSwapInt(this, sweepVotesOffset, cmp, val); + return U.compareAndSwapInt(this, SWEEPVOTES, cmp, val); } /* @@ -555,12 +557,6 @@ private boolean casSweepVotes(int cmp, int val) { private static final int SYNC = 2; // for transfer, take private static final int TIMED = 3; // for timed poll, tryTransfer - @SuppressWarnings("unchecked") - static E cast(Object item) { - // assert item == null || item.getClass() != Node.class; - return (E) item; - } - /** * Implements all queuing methods. See above for explanation. * @@ -597,7 +593,8 @@ private E xfer(E e, boolean haveData, int how, long nanos) { break; // unless slack < 2 } LockSupport.unpark(p.waiter); - return LinkedTransferQueue.cast(item); + @SuppressWarnings("unchecked") E itemE = (E) item; + return itemE; } } Node n = p.next; @@ -675,15 +672,15 @@ private E awaitMatch(Node s, Node pred, E e, boolean timed, long nanos) { if (item != e) { // matched // assert item != s; s.forgetContents(); // avoid garbage - return LinkedTransferQueue.cast(item); + @SuppressWarnings("unchecked") E itemE = (E) item; + return itemE; } - if ((w.isInterrupted() || (timed && nanos <= 0)) && - s.casItem(e, s)) { // cancel - unsplice(pred, s); - return e; + else if (w.isInterrupted() || (timed && nanos <= 0L)) { + unsplice(pred, s); // try to unlink and cancel + if (s.casItem(e, s)) // return normally if lost CAS + return e; } - - if (spins < 0) { // establish spins at/near front + else if (spins < 0) { // establish spins at/near front if ((spins = spinsFor(pred, s.isData)) > 0) randomYields = ThreadLocalRandom.current(); } @@ -735,32 +732,25 @@ final Node succ(Node p) { } /** - * Returns the first unmatched node of the given mode, or null if - * none. Used by methods isEmpty, hasWaitingConsumer. + * Returns the first unmatched data node, or null if none. + * Callers must recheck if the returned node's item field is null + * or self-linked before using. */ - private Node firstOfMode(boolean isData) { - for (Node p = head; p != null; p = succ(p)) { - if (!p.isMatched()) - return (p.isData == isData) ? p : null; - } - return null; - } - - /** - * Returns the item in the first unmatched node with isData; or - * null if none. Used by peek. - */ - private E firstDataItem() { - for (Node p = head; p != null; p = succ(p)) { - Object item = p.item; - if (p.isData) { - if (item != null && item != p) - return LinkedTransferQueue.cast(item); + final Node firstDataNode() { + restartFromHead: for (;;) { + for (Node p = head; p != null;) { + Object item = p.item; + if (p.isData) { + if (item != null && item != p) + return p; + } + else if (item == null) + break; + if (p == (p = p.next)) + continue restartFromHead; } - else if (item == null) - return null; + return null; } - return null; } /** @@ -768,23 +758,140 @@ else if (item == null) * Used by methods size and getWaitingConsumerCount. */ private int countOfMode(boolean data) { - int count = 0; - for (Node p = head; p != null; ) { - if (!p.isMatched()) { - if (p.isData != data) - return 0; - if (++count == Integer.MAX_VALUE) // saturated + restartFromHead: for (;;) { + int count = 0; + for (Node p = head; p != null;) { + if (!p.isMatched()) { + if (p.isData != data) + return 0; + if (++count == Integer.MAX_VALUE) + break; // @see Collection.size() + } + if (p == (p = p.next)) + continue restartFromHead; + } + return count; + } + } + + public String toString() { + String[] a = null; + restartFromHead: for (;;) { + int charLength = 0; + int size = 0; + for (Node p = head; p != null;) { + Object item = p.item; + if (p.isData) { + if (item != null && item != p) { + if (a == null) + a = new String[4]; + else if (size == a.length) + a = Arrays.copyOf(a, 2 * size); + String s = item.toString(); + a[size++] = s; + charLength += s.length(); + } + } else if (item == null) break; + if (p == (p = p.next)) + continue restartFromHead; } - Node n = p.next; - if (n != p) - p = n; - else { - count = 0; - p = head; + + if (size == 0) + return "[]"; + + return Helpers.toString(a, size, charLength); + } + } + + private Object[] toArrayInternal(Object[] a) { + Object[] x = a; + restartFromHead: for (;;) { + int size = 0; + for (Node p = head; p != null;) { + Object item = p.item; + if (p.isData) { + if (item != null && item != p) { + if (x == null) + x = new Object[4]; + else if (size == x.length) + x = Arrays.copyOf(x, 2 * (size + 4)); + x[size++] = item; + } + } else if (item == null) + break; + if (p == (p = p.next)) + continue restartFromHead; } + if (x == null) + return new Object[0]; + else if (a != null && size <= a.length) { + if (a != x) + System.arraycopy(x, 0, a, 0, size); + if (size < a.length) + a[size] = null; + return a; + } + return (size == x.length) ? x : Arrays.copyOf(x, size); } - return count; + } + + /** + * Returns an array containing all of the elements in this queue, in + * proper sequence. + * + *

    The returned array will be "safe" in that no references to it are + * maintained by this queue. (In other words, this method must allocate + * a new array). The caller is thus free to modify the returned array. + * + *

    This method acts as bridge between array-based and collection-based + * APIs. + * + * @return an array containing all of the elements in this queue + */ + public Object[] toArray() { + return toArrayInternal(null); + } + + /** + * Returns an array containing all of the elements in this queue, in + * proper sequence; the runtime type of the returned array is that of + * the specified array. If the queue fits in the specified array, it + * is returned therein. Otherwise, a new array is allocated with the + * runtime type of the specified array and the size of this queue. + * + *

    If this queue fits in the specified array with room to spare + * (i.e., the array has more elements than this queue), the element in + * the array immediately following the end of the queue is set to + * {@code null}. + * + *

    Like the {@link #toArray()} method, this method acts as bridge between + * array-based and collection-based APIs. Further, this method allows + * precise control over the runtime type of the output array, and may, + * under certain circumstances, be used to save allocation costs. + * + *

    Suppose {@code x} is a queue known to contain only strings. + * The following code can be used to dump the queue into a newly + * allocated array of {@code String}: + * + *

     {@code String[] y = x.toArray(new String[0]);}
    + * + * Note that {@code toArray(new Object[0])} is identical in function to + * {@code toArray()}. + * + * @param a the array into which the elements of the queue are to + * be stored, if it is big enough; otherwise, a new array of the + * same runtime type is allocated for this purpose + * @return an array containing all of the elements in this queue + * @throws ArrayStoreException if the runtime type of the specified array + * is not a supertype of the runtime type of every element in + * this queue + * @throws NullPointerException if the specified array is null + */ + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + if (a == null) throw new NullPointerException(); + return (T[]) toArrayInternal(a); } final class Itr implements Iterator { @@ -833,7 +940,8 @@ else if (s == p) { Object item = s.item; if (s.isData) { if (item != null && item != s) { - nextItem = LinkedTransferQueue.cast(item); + @SuppressWarnings("unchecked") E itemE = (E) item; + nextItem = itemE; nextNode = s; return; } @@ -880,6 +988,111 @@ public final void remove() { } } + /** A customized variant of Spliterators.IteratorSpliterator */ + final class LTQSpliterator implements Spliterator { + static final int MAX_BATCH = 1 << 25; // max batch array size; + Node current; // current node; null until initialized + int batch; // batch size for splits + boolean exhausted; // true when no more nodes + LTQSpliterator() {} + + public Spliterator trySplit() { + Node p; + int b = batch; + int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1; + if (!exhausted && + ((p = current) != null || (p = firstDataNode()) != null) && + p.next != null) { + Object[] a = new Object[n]; + int i = 0; + do { + Object e = p.item; + if (e != p && (a[i] = e) != null) + ++i; + if (p == (p = p.next)) + p = firstDataNode(); + } while (p != null && i < n && p.isData); + if ((current = p) == null) + exhausted = true; + if (i > 0) { + batch = i; + return Spliterators.spliterator + (a, 0, i, (Spliterator.ORDERED | + Spliterator.NONNULL | + Spliterator.CONCURRENT)); + } + } + return null; + } + + @SuppressWarnings("unchecked") + public void forEachRemaining(Consumer action) { + Node p; + if (action == null) throw new NullPointerException(); + if (!exhausted && + ((p = current) != null || (p = firstDataNode()) != null)) { + exhausted = true; + do { + Object e = p.item; + if (e != null && e != p) + action.accept((E)e); + if (p == (p = p.next)) + p = firstDataNode(); + } while (p != null && p.isData); + } + } + + @SuppressWarnings("unchecked") + public boolean tryAdvance(Consumer action) { + Node p; + if (action == null) throw new NullPointerException(); + if (!exhausted && + ((p = current) != null || (p = firstDataNode()) != null)) { + Object e; + do { + if ((e = p.item) == p) + e = null; + if (p == (p = p.next)) + p = firstDataNode(); + } while (e == null && p != null && p.isData); + if ((current = p) == null) + exhausted = true; + if (e != null) { + action.accept((E)e); + return true; + } + } + return false; + } + + public long estimateSize() { return Long.MAX_VALUE; } + + public int characteristics() { + return Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT; + } + } + + /** + * Returns a {@link Spliterator} over the elements in this queue. + * + *

    The returned spliterator is + * weakly consistent. + * + *

    The {@code Spliterator} reports {@link Spliterator#CONCURRENT}, + * {@link Spliterator#ORDERED}, and {@link Spliterator#NONNULL}. + * + * @implNote + * The {@code Spliterator} implements {@code trySplit} to permit limited + * parallelism. + * + * @return a {@code Spliterator} over the elements in this queue + * @since 1.8 + */ + public Spliterator spliterator() { + return new LTQSpliterator(); + } + /* -------------- Removal methods -------------- */ /** @@ -891,7 +1104,7 @@ public final void remove() { * @param s the node to be unspliced */ final void unsplice(Node pred, Node s) { - s.forgetContents(); // forget unneeded fields + s.waiter = null; // disable signals /* * See above for rationale. Briefly: if pred still points to * s, try to unlink s. If s cannot be unlinked, because it is @@ -1159,12 +1372,8 @@ public int drainTo(Collection c, int maxElements) { * Returns an iterator over the elements in this queue in proper sequence. * The elements will be returned in order from first (head) to last (tail). * - *

    The returned iterator is a "weakly consistent" iterator that - * will never throw {@link java.util.ConcurrentModificationException - * ConcurrentModificationException}, and guarantees to traverse - * elements as they existed upon construction of the iterator, and - * may (but is not guaranteed to) reflect any modifications - * subsequent to construction. + *

    The returned iterator is + * weakly consistent. * * @return an iterator over the elements in this queue in proper sequence */ @@ -1173,7 +1382,22 @@ public Iterator iterator() { } public E peek() { - return firstDataItem(); + restartFromHead: for (;;) { + for (Node p = head; p != null;) { + Object item = p.item; + if (p.isData) { + if (item != null && item != p) { + @SuppressWarnings("unchecked") E e = (E) item; + return e; + } + } + else if (item == null) + break; + if (p == (p = p.next)) + continue restartFromHead; + } + return null; + } } /** @@ -1182,15 +1406,24 @@ public E peek() { * @return {@code true} if this queue contains no elements */ public boolean isEmpty() { - for (Node p = head; p != null; p = succ(p)) { - if (!p.isMatched()) - return !p.isData; - } - return true; + return firstDataNode() == null; } public boolean hasWaitingConsumer() { - return firstOfMode(false) != null; + restartFromHead: for (;;) { + for (Node p = head; p != null;) { + Object item = p.item; + if (p.isData) { + if (item != null && item != p) + break; + } + else if (item == null) + return true; + if (p == (p = p.next)) + continue restartFromHead; + } + return false; + } } /** @@ -1237,15 +1470,16 @@ public boolean remove(Object o) { * @return {@code true} if this queue contains the specified element */ public boolean contains(Object o) { - if (o == null) return false; - for (Node p = head; p != null; p = succ(p)) { - Object item = p.item; - if (p.isData) { - if (item != null && item != p && o.equals(item)) - return true; + if (o != null) { + for (Node p = head; p != null; p = succ(p)) { + Object item = p.item; + if (p.isData) { + if (item != null && item != p && o.equals(item)) + return true; + } + else if (item == null) + break; } - else if (item == null) - break; } return false; } @@ -1265,6 +1499,8 @@ public int remainingCapacity() { /** * Saves this queue to a stream (that is, serializes it). * + * @param s the stream + * @throws java.io.IOException if an I/O error occurs * @serialData All of the elements (each an {@code E}) in * the proper order, followed by a null */ @@ -1279,6 +1515,10 @@ private void writeObject(java.io.ObjectOutputStream s) /** * Reconstitutes this queue from a stream (that is, deserializes it). + * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws java.io.IOException if an I/O error occurs */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { @@ -1295,21 +1535,19 @@ private void readObject(java.io.ObjectInputStream s) // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long headOffset; - private static final long tailOffset; - private static final long sweepVotesOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long HEAD; + private static final long TAIL; + private static final long SWEEPVOTES; static { try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = LinkedTransferQueue.class; - headOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("head")); - tailOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("tail")); - sweepVotesOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("sweepVotes")); - } catch (Exception e) { + HEAD = U.objectFieldOffset + (LinkedTransferQueue.class.getDeclaredField("head")); + TAIL = U.objectFieldOffset + (LinkedTransferQueue.class.getDeclaredField("tail")); + SWEEPVOTES = U.objectFieldOffset + (LinkedTransferQueue.class.getDeclaredField("sweepVotes")); + } catch (ReflectiveOperationException e) { throw new Error(e); } diff --git a/luni/src/main/java/java/util/concurrent/Phaser.java b/luni/src/main/java/java/util/concurrent/Phaser.java index c5faf164c..9b2a7a1a2 100644 --- a/luni/src/main/java/java/util/concurrent/Phaser.java +++ b/luni/src/main/java/java/util/concurrent/Phaser.java @@ -42,7 +42,7 @@ * *

      * - *
    • Arrival. Methods {@link #arrive} and + *
    • Arrival. Methods {@link #arrive} and * {@link #arriveAndDeregister} record arrival. These methods * do not block, but return an associated arrival phase * number; that is, the phase number of the phaser to which @@ -55,7 +55,7 @@ * flexible than, providing a barrier action to a {@code * CyclicBarrier}. * - *
    • Waiting. Method {@link #awaitAdvance} requires an + *
    • Waiting. Method {@link #awaitAdvance} requires an * argument indicating an arrival phase number, and returns when * the phaser advances to (or is already at) a different phase. * Unlike similar constructions using {@code CyclicBarrier}, @@ -66,9 +66,10 @@ * state of the phaser. If necessary, you can perform any * associated recovery within handlers of those exceptions, * often after invoking {@code forceTermination}. Phasers may - * also be used by tasks executing in a {@link ForkJoinPool}, - * which will ensure sufficient parallelism to execute tasks - * when others are blocked waiting for a phase to advance. + * also be used by tasks executing in a {@link ForkJoinPool}. + * Progress is ensured if the pool's parallelismLevel can + * accommodate the maximum number of simultaneously blocked + * parties. * *
    * @@ -124,7 +125,7 @@ * The typical idiom is for the method setting this up to first * register, then start the actions, then deregister, as in: * - *
     {@code
    + * 
     {@code
      * void runTasks(List tasks) {
      *   final Phaser phaser = new Phaser(1); // "1" to register self
      *   // create and start threads
    @@ -145,7 +146,7 @@
      * 

    One way to cause a set of threads to repeatedly perform actions * for a given number of iterations is to override {@code onAdvance}: * - *

     {@code
    + * 
     {@code
      * void startTasks(List tasks, final int iterations) {
      *   final Phaser phaser = new Phaser() {
      *     protected boolean onAdvance(int phase, int registeredParties) {
    @@ -169,7 +170,7 @@
      *
      * If the main task must later await termination, it
      * may re-register and then execute a similar loop:
    - *  
     {@code
    + * 
     {@code
      *   // ...
      *   phaser.register();
      *   while (!phaser.isTerminated())
    @@ -179,7 +180,7 @@
      * in contexts where you are sure that the phase will never wrap around
      * {@code Integer.MAX_VALUE}. For example:
      *
    - *  
     {@code
    + * 
     {@code
      * void awaitPhase(Phaser phaser, int phase) {
      *   int p = phaser.register(); // assumes caller not already registered
      *   while (p < phase) {
    @@ -199,7 +200,7 @@
      * new Phaser())}, these tasks could then be started, for example by
      * submitting to a pool:
      *
    - *  
     {@code
    + * 
     {@code
      * void build(Task[] tasks, int lo, int hi, Phaser ph) {
      *   if (hi - lo > TASKS_PER_PHASER) {
      *     for (int i = lo; i < hi; i += TASKS_PER_PHASER) {
    @@ -300,7 +301,7 @@ private static int arrivedOf(long s) {
         }
     
         /**
    -     * The parent of this phaser, or null if none
    +     * The parent of this phaser, or null if none.
          */
         private final Phaser parent;
     
    @@ -358,7 +359,7 @@ private int doArrive(int adjust) {
                 int unarrived = (counts == EMPTY) ? 0 : (counts & UNARRIVED_MASK);
                 if (unarrived <= 0)
                     throw new IllegalStateException(badArrive(s));
    -            if (UNSAFE.compareAndSwapLong(this, stateOffset, s, s-=adjust)) {
    +            if (U.compareAndSwapLong(this, STATE, s, s-=adjust)) {
                     if (unarrived == 1) {
                         long n = s & PARTIES_MASK;  // base of next state
                         int nextUnarrived = (int)n >>> PARTIES_SHIFT;
    @@ -371,13 +372,12 @@ else if (nextUnarrived == 0)
                                 n |= nextUnarrived;
                             int nextPhase = (phase + 1) & MAX_PHASE;
                             n |= (long)nextPhase << PHASE_SHIFT;
    -                        UNSAFE.compareAndSwapLong(this, stateOffset, s, n);
    +                        U.compareAndSwapLong(this, STATE, s, n);
                             releaseWaiters(phase);
                         }
                         else if (nextUnarrived == 0) { // propagate deregistration
                             phase = parent.doArrive(ONE_DEREGISTER);
    -                        UNSAFE.compareAndSwapLong(this, stateOffset,
    -                                                  s, s | EMPTY);
    +                        U.compareAndSwapLong(this, STATE, s, s | EMPTY);
                         }
                         else
                             phase = parent.doArrive(ONE_ARRIVAL);
    @@ -388,7 +388,7 @@ else if (nextUnarrived == 0) { // propagate deregistration
         }
     
         /**
    -     * Implementation of register, bulkRegister
    +     * Implementation of register, bulkRegister.
          *
          * @param registrations number to add to both parties and
          * unarrived fields. Must be greater than zero.
    @@ -412,14 +412,13 @@ private int doRegister(int registrations) {
                     if (parent == null || reconcileState() == s) {
                         if (unarrived == 0)             // wait out advance
                             root.internalAwaitAdvance(phase, null);
    -                    else if (UNSAFE.compareAndSwapLong(this, stateOffset,
    -                                                       s, s + adjust))
    +                    else if (U.compareAndSwapLong(this, STATE, s, s + adjust))
                             break;
                     }
                 }
                 else if (parent == null) {              // 1st root registration
                     long next = ((long)phase << PHASE_SHIFT) | adjust;
    -                if (UNSAFE.compareAndSwapLong(this, stateOffset, s, next))
    +                if (U.compareAndSwapLong(this, STATE, s, next))
                         break;
                 }
                 else {
    @@ -431,8 +430,8 @@ else if (parent == null) {              // 1st root registration
                             // finish registration whenever parent registration
                             // succeeded, even when racing with termination,
                             // since these are part of the same "transaction".
    -                        while (!UNSAFE.compareAndSwapLong
    -                               (this, stateOffset, s,
    +                        while (!U.compareAndSwapLong
    +                               (this, STATE, s,
                                     ((long)phase << PHASE_SHIFT) | adjust)) {
                                 s = state;
                                 phase = (int)(root.state >>> PHASE_SHIFT);
    @@ -463,8 +462,8 @@ private long reconcileState() {
                 // CAS to root phase with current parties, tripping unarrived
                 while ((phase = (int)(root.state >>> PHASE_SHIFT)) !=
                        (int)(s >>> PHASE_SHIFT) &&
    -                   !UNSAFE.compareAndSwapLong
    -                   (this, stateOffset, s,
    +                   !U.compareAndSwapLong
    +                   (this, STATE, s,
                         s = (((long)phase << PHASE_SHIFT) |
                              ((phase < 0) ? (s & COUNTS_MASK) :
                               (((p = (int)s >>> PARTIES_SHIFT) == 0) ? EMPTY :
    @@ -653,8 +652,7 @@ public int arriveAndAwaitAdvance() {
                 int unarrived = (counts == EMPTY) ? 0 : (counts & UNARRIVED_MASK);
                 if (unarrived <= 0)
                     throw new IllegalStateException(badArrive(s));
    -            if (UNSAFE.compareAndSwapLong(this, stateOffset, s,
    -                                          s -= ONE_ARRIVAL)) {
    +            if (U.compareAndSwapLong(this, STATE, s, s -= ONE_ARRIVAL)) {
                     if (unarrived > 1)
                         return root.internalAwaitAdvance(phase, null);
                     if (root != this)
    @@ -669,7 +667,7 @@ else if (nextUnarrived == 0)
                         n |= nextUnarrived;
                     int nextPhase = (phase + 1) & MAX_PHASE;
                     n |= (long)nextPhase << PHASE_SHIFT;
    -                if (!UNSAFE.compareAndSwapLong(this, stateOffset, s, n))
    +                if (!U.compareAndSwapLong(this, STATE, s, n))
                         return (int)(state >>> PHASE_SHIFT); // terminated
                     releaseWaiters(phase);
                     return nextPhase;
    @@ -785,8 +783,7 @@ public void forceTermination() {
             final Phaser root = this.root;
             long s;
             while ((s = root.state) >= 0) {
    -            if (UNSAFE.compareAndSwapLong(root, stateOffset,
    -                                          s, s | TERMINATION_BIT)) {
    +            if (U.compareAndSwapLong(root, STATE, s, s | TERMINATION_BIT)) {
                     // signal all threads
                     releaseWaiters(0); // Waiters on evenQ
                     releaseWaiters(1); // Waiters on oddQ
    @@ -925,7 +922,7 @@ public String toString() {
         }
     
         /**
    -     * Implementation of toString and string-based error messages
    +     * Implementation of toString and string-based error messages.
          */
         private String stateToString(long s) {
             return super.toString() +
    @@ -1034,7 +1031,7 @@ else if (!queued) {           // push onto queue
                 else {
                     try {
                         ForkJoinPool.managedBlock(node);
    -                } catch (InterruptedException ie) {
    +                } catch (InterruptedException cantHappen) {
                         node.wasInterrupted = true;
                     }
                 }
    @@ -1053,7 +1050,7 @@ else if (!queued) {           // push onto queue
         }
     
         /**
    -     * Wait nodes for Treiber stack representing wait queue
    +     * Wait nodes for Treiber stack representing wait queue.
          */
         static final class QNode implements ForkJoinPool.ManagedBlocker {
             final Phaser phaser;
    @@ -1090,40 +1087,34 @@ public boolean isReleasable() {
                     thread = null;
                     return true;
                 }
    -            if (timed) {
    -                if (nanos > 0L) {
    -                    nanos = deadline - System.nanoTime();
    -                }
    -                if (nanos <= 0L) {
    -                    thread = null;
    -                    return true;
    -                }
    +            if (timed &&
    +                (nanos <= 0L || (nanos = deadline - System.nanoTime()) <= 0L)) {
    +                thread = null;
    +                return true;
                 }
                 return false;
             }
     
             public boolean block() {
    -            if (isReleasable())
    -                return true;
    -            else if (!timed)
    -                LockSupport.park(this);
    -            else if (nanos > 0L)
    -                LockSupport.parkNanos(this, nanos);
    -            return isReleasable();
    +            while (!isReleasable()) {
    +                if (timed)
    +                    LockSupport.parkNanos(this, nanos);
    +                else
    +                    LockSupport.park(this);
    +            }
    +            return true;
             }
         }
     
         // Unsafe mechanics
     
    -    private static final sun.misc.Unsafe UNSAFE;
    -    private static final long stateOffset;
    +    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
    +    private static final long STATE;
         static {
             try {
    -            UNSAFE = sun.misc.Unsafe.getUnsafe();
    -            Class k = Phaser.class;
    -            stateOffset = UNSAFE.objectFieldOffset
    -                (k.getDeclaredField("state"));
    -        } catch (Exception e) {
    +            STATE = U.objectFieldOffset
    +                (Phaser.class.getDeclaredField("state"));
    +        } catch (ReflectiveOperationException e) {
                 throw new Error(e);
             }
     
    diff --git a/luni/src/main/java/java/util/concurrent/PriorityBlockingQueue.java b/luni/src/main/java/java/util/concurrent/PriorityBlockingQueue.java
    index 40b351013..2044406ac 100644
    --- a/luni/src/main/java/java/util/concurrent/PriorityBlockingQueue.java
    +++ b/luni/src/main/java/java/util/concurrent/PriorityBlockingQueue.java
    @@ -6,9 +6,19 @@
     
     package java.util.concurrent;
     
    +import java.util.AbstractQueue;
    +import java.util.Arrays;
    +import java.util.Collection;
    +import java.util.Comparator;
    +import java.util.Iterator;
    +import java.util.NoSuchElementException;
    +import java.util.PriorityQueue;
    +import java.util.Queue;
    +import java.util.SortedSet;
    +import java.util.Spliterator;
     import java.util.concurrent.locks.Condition;
     import java.util.concurrent.locks.ReentrantLock;
    -import java.util.*;
    +import java.util.function.Consumer;
     
     // BEGIN android-note
     // removed link to collections framework docs
    @@ -43,7 +53,7 @@
      * tie-breaking to comparable elements. To use it, you would insert a
      * {@code new FIFOEntry(anEntry)} instead of a plain entry object.
      *
    - *  
     {@code
    + * 
     {@code
      * class FIFOEntry>
      *     implements Comparable> {
      *   static final AtomicLong seq = new AtomicLong(0);
    @@ -64,7 +74,7 @@
      *
      * @since 1.5
      * @author Doug Lea
    - * @param  the type of elements held in this collection
    + * @param  the type of elements held in this queue
      */
     @SuppressWarnings("unchecked")
     public class PriorityBlockingQueue extends AbstractQueue
    @@ -122,12 +132,12 @@ public class PriorityBlockingQueue extends AbstractQueue
         private transient Comparator comparator;
     
         /**
    -     * Lock used for all public operations
    +     * Lock used for all public operations.
          */
         private final ReentrantLock lock;
     
         /**
    -     * Condition for blocking when empty
    +     * Condition for blocking when empty.
          */
         private final Condition notEmpty;
     
    @@ -250,8 +260,7 @@ private void tryGrow(Object[] array, int oldCap) {
             lock.unlock(); // must release and then re-acquire main lock
             Object[] newArray = null;
             if (allocationSpinLock == 0 &&
    -            UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
    -                                     0, 1)) {
    +            U.compareAndSwapInt(this, ALLOCATIONSPINLOCK, 0, 1)) {
                 try {
                     int newCap = oldCap + ((oldCap < 64) ?
                                            (oldCap + 2) : // grow faster if small
    @@ -633,7 +642,7 @@ public boolean remove(Object o) {
         }
     
         /**
    -     * Identity-based version for use in Itr.remove
    +     * Identity-based version for use in Itr.remove.
          */
         void removeEQ(Object o) {
             final ReentrantLock lock = this.lock;
    @@ -669,48 +678,8 @@ public boolean contains(Object o) {
             }
         }
     
    -    /**
    -     * Returns an array containing all of the elements in this queue.
    -     * The returned array elements are in no particular order.
    -     *
    -     * 

    The returned array will be "safe" in that no references to it are - * maintained by this queue. (In other words, this method must allocate - * a new array). The caller is thus free to modify the returned array. - * - *

    This method acts as bridge between array-based and collection-based - * APIs. - * - * @return an array containing all of the elements in this queue - */ - public Object[] toArray() { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - return Arrays.copyOf(queue, size); - } finally { - lock.unlock(); - } - } - public String toString() { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - int n = size; - if (n == 0) - return "[]"; - StringBuilder sb = new StringBuilder(); - sb.append('['); - for (int i = 0; i < n; ++i) { - Object e = queue[i]; - sb.append(e == this ? "(this Collection)" : e); - if (i != n - 1) - sb.append(',').append(' '); - } - return sb.append(']').toString(); - } finally { - lock.unlock(); - } + return Helpers.collectionToString(this); } /** @@ -768,6 +737,29 @@ public void clear() { } } + /** + * Returns an array containing all of the elements in this queue. + * The returned array elements are in no particular order. + * + *

    The returned array will be "safe" in that no references to it are + * maintained by this queue. (In other words, this method must allocate + * a new array). The caller is thus free to modify the returned array. + * + *

    This method acts as bridge between array-based and collection-based + * APIs. + * + * @return an array containing all of the elements in this queue + */ + public Object[] toArray() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return Arrays.copyOf(queue, size); + } finally { + lock.unlock(); + } + } + /** * Returns an array containing all of the elements in this queue; the * runtime type of the returned array is that of the specified array. @@ -790,7 +782,7 @@ public void clear() { * The following code can be used to dump the queue into a newly * allocated array of {@code String}: * - *

     {@code String[] y = x.toArray(new String[0]);}
    + *
     {@code String[] y = x.toArray(new String[0]);}
    * * Note that {@code toArray(new Object[0])} is identical in function to * {@code toArray()}. @@ -825,12 +817,8 @@ public T[] toArray(T[] a) { * Returns an iterator over the elements in this queue. The * iterator does not return the elements in any particular order. * - *

    The returned iterator is a "weakly consistent" iterator that - * will never throw {@link java.util.ConcurrentModificationException - * ConcurrentModificationException}, and guarantees to traverse - * elements as they existed upon construction of the iterator, and - * may (but is not guaranteed to) reflect any modifications - * subsequent to construction. + *

    The returned iterator is + * weakly consistent. * * @return an iterator over the elements in this queue */ @@ -876,6 +864,9 @@ public void remove() { * For compatibility with previous version of this class, elements * are first copied to a java.util.PriorityQueue, which is then * serialized. + * + * @param s the stream + * @throws java.io.IOException if an I/O error occurs */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { @@ -893,6 +884,10 @@ private void writeObject(java.io.ObjectOutputStream s) /** * Reconstitutes this queue from a stream (that is, deserializes it). + * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws java.io.IOException if an I/O error occurs */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { @@ -906,16 +901,93 @@ private void readObject(java.io.ObjectInputStream s) } } + // Similar to Collections.ArraySnapshotSpliterator but avoids + // commitment to toArray until needed + static final class PBQSpliterator implements Spliterator { + final PriorityBlockingQueue queue; + Object[] array; + int index; + int fence; + + PBQSpliterator(PriorityBlockingQueue queue, Object[] array, + int index, int fence) { + this.queue = queue; + this.array = array; + this.index = index; + this.fence = fence; + } + + final int getFence() { + int hi; + if ((hi = fence) < 0) + hi = fence = (array = queue.toArray()).length; + return hi; + } + + public PBQSpliterator trySplit() { + int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; + return (lo >= mid) ? null : + new PBQSpliterator(queue, array, lo, index = mid); + } + + @SuppressWarnings("unchecked") + public void forEachRemaining(Consumer action) { + Object[] a; int i, hi; // hoist accesses and checks from loop + if (action == null) + throw new NullPointerException(); + if ((a = array) == null) + fence = (a = queue.toArray()).length; + if ((hi = fence) <= a.length && + (i = index) >= 0 && i < (index = hi)) { + do { action.accept((E)a[i]); } while (++i < hi); + } + } + + public boolean tryAdvance(Consumer action) { + if (action == null) + throw new NullPointerException(); + if (getFence() > index && index >= 0) { + @SuppressWarnings("unchecked") E e = (E) array[index++]; + action.accept(e); + return true; + } + return false; + } + + public long estimateSize() { return (long)(getFence() - index); } + + public int characteristics() { + return Spliterator.NONNULL | Spliterator.SIZED | Spliterator.SUBSIZED; + } + } + + /** + * Returns a {@link Spliterator} over the elements in this queue. + * + *

    The returned spliterator is + * weakly consistent. + * + *

    The {@code Spliterator} reports {@link Spliterator#SIZED} and + * {@link Spliterator#NONNULL}. + * + * @implNote + * The {@code Spliterator} additionally reports {@link Spliterator#SUBSIZED}. + * + * @return a {@code Spliterator} over the elements in this queue + * @since 1.8 + */ + public Spliterator spliterator() { + return new PBQSpliterator(this, null, 0, -1); + } + // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long allocationSpinLockOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long ALLOCATIONSPINLOCK; static { try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = PriorityBlockingQueue.class; - allocationSpinLockOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("allocationSpinLock")); - } catch (Exception e) { + ALLOCATIONSPINLOCK = U.objectFieldOffset + (PriorityBlockingQueue.class.getDeclaredField("allocationSpinLock")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } diff --git a/luni/src/main/java/java/util/concurrent/RecursiveAction.java b/luni/src/main/java/java/util/concurrent/RecursiveAction.java index e3a634077..8631a895f 100644 --- a/luni/src/main/java/java/util/concurrent/RecursiveAction.java +++ b/luni/src/main/java/java/util/concurrent/RecursiveAction.java @@ -16,7 +16,7 @@ *

    Sample Usages. Here is a simple but complete ForkJoin * sort that sorts a given {@code long[]} array: * - *

     {@code
    + * 
     {@code
      * static class SortTask extends RecursiveAction {
      *   final long[] array; final int lo, hi;
      *   SortTask(long[] array, int lo, int hi) {
    @@ -50,7 +50,7 @@
      * SortTask(anArray)} and invoking it in a ForkJoinPool.  As a more
      * concrete simple example, the following task increments each element
      * of an array:
    - *  
     {@code
    + * 
     {@code
      * class IncrementTask extends RecursiveAction {
      *   final long[] array; final int lo, hi;
      *   IncrementTask(long[] array, int lo, int hi) {
    @@ -81,7 +81,7 @@
      * performing leaf actions on unstolen tasks rather than further
      * subdividing.
      *
    - *  
     {@code
    + * 
     {@code
      * double sumOfSquares(ForkJoinPool pool, double[] array) {
      *   int n = array.length;
      *   Applyer a = new Applyer(array, 0, n, null);
    diff --git a/luni/src/main/java/java/util/concurrent/RecursiveTask.java b/luni/src/main/java/java/util/concurrent/RecursiveTask.java
    index d201bd6df..5cba1dac3 100644
    --- a/luni/src/main/java/java/util/concurrent/RecursiveTask.java
    +++ b/luni/src/main/java/java/util/concurrent/RecursiveTask.java
    @@ -11,11 +11,11 @@
      *
      * 

    For a classic example, here is a task computing Fibonacci numbers: * - *

     {@code
    + * 
     {@code
      * class Fibonacci extends RecursiveTask {
      *   final int n;
      *   Fibonacci(int n) { this.n = n; }
    - *   Integer compute() {
    + *   protected Integer compute() {
      *     if (n <= 1)
      *       return n;
      *     Fibonacci f1 = new Fibonacci(n - 1);
    diff --git a/luni/src/main/java/java/util/concurrent/RunnableScheduledFuture.java b/luni/src/main/java/java/util/concurrent/RunnableScheduledFuture.java
    index 712523329..604f180bc 100644
    --- a/luni/src/main/java/java/util/concurrent/RunnableScheduledFuture.java
    +++ b/luni/src/main/java/java/util/concurrent/RunnableScheduledFuture.java
    @@ -19,11 +19,11 @@
     public interface RunnableScheduledFuture extends RunnableFuture, ScheduledFuture {
     
         /**
    -     * Returns true if this is a periodic task. A periodic task may
    +     * Returns {@code true} if this task is periodic. A periodic task may
          * re-run according to some schedule. A non-periodic task can be
          * run only once.
          *
    -     * @return true if this task is periodic
    +     * @return {@code true} if this task is periodic
          */
         boolean isPeriodic();
     }
    diff --git a/luni/src/main/java/java/util/concurrent/ScheduledExecutorService.java b/luni/src/main/java/java/util/concurrent/ScheduledExecutorService.java
    index c07a5b994..eae47b30f 100644
    --- a/luni/src/main/java/java/util/concurrent/ScheduledExecutorService.java
    +++ b/luni/src/main/java/java/util/concurrent/ScheduledExecutorService.java
    @@ -41,7 +41,7 @@
      * Here is a class with a method that sets up a ScheduledExecutorService
      * to beep every ten seconds for an hour:
      *
    - *  
     {@code
    + * 
     {@code
      * import static java.util.concurrent.TimeUnit.*;
      * class BeeperControl {
      *   private final ScheduledExecutorService scheduler =
    @@ -100,23 +100,37 @@ public  ScheduledFuture schedule(Callable callable,
         /**
          * Creates and executes a periodic action that becomes enabled first
          * after the given initial delay, and subsequently with the given
    -     * period; that is executions will commence after
    -     * {@code initialDelay} then {@code initialDelay+period}, then
    +     * period; that is, executions will commence after
    +     * {@code initialDelay}, then {@code initialDelay + period}, then
          * {@code initialDelay + 2 * period}, and so on.
    -     * If any execution of the task
    -     * encounters an exception, subsequent executions are suppressed.
    -     * Otherwise, the task will only terminate via cancellation or
    -     * termination of the executor.  If any execution of this task
    -     * takes longer than its period, then subsequent executions
    -     * may start late, but will not concurrently execute.
    +     *
    +     * 

    The sequence of task executions continues indefinitely until + * one of the following exceptional completions occur: + *

      + *
    • The task is {@linkplain Future#cancel explicitly cancelled} + * via the returned future. + *
    • The executor terminates, also resulting in task cancellation. + *
    • An execution of the task throws an exception. In this case + * calling {@link Future#get() get} on the returned future will + * throw {@link ExecutionException}. + *
    + * Subsequent executions are suppressed. Subsequent calls to + * {@link Future#isDone isDone()} on the returned future will + * return {@code true}. + * + *

    If any execution of this task takes longer than its period, then + * subsequent executions may start late, but will not concurrently + * execute. * * @param command the task to execute * @param initialDelay the time to delay first execution * @param period the period between successive executions * @param unit the time unit of the initialDelay and period parameters * @return a ScheduledFuture representing pending completion of - * the task, and whose {@code get()} method will throw an - * exception upon cancellation + * the series of repeated tasks. The future's {@link + * Future#get() get()} method will never return normally, + * and will throw an exception upon task cancellation or + * abnormal termination of a task execution. * @throws RejectedExecutionException if the task cannot be * scheduled for execution * @throws NullPointerException if command is null @@ -131,10 +145,21 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, * Creates and executes a periodic action that becomes enabled first * after the given initial delay, and subsequently with the * given delay between the termination of one execution and the - * commencement of the next. If any execution of the task - * encounters an exception, subsequent executions are suppressed. - * Otherwise, the task will only terminate via cancellation or - * termination of the executor. + * commencement of the next. + * + *

    The sequence of task executions continues indefinitely until + * one of the following exceptional completions occur: + *

      + *
    • The task is {@linkplain Future#cancel explicitly cancelled} + * via the returned future. + *
    • The executor terminates, also resulting in task cancellation. + *
    • An execution of the task throws an exception. In this case + * calling {@link Future#get() get} on the returned future will + * throw {@link ExecutionException}. + *
    + * Subsequent executions are suppressed. Subsequent calls to + * {@link Future#isDone isDone()} on the returned future will + * return {@code true}. * * @param command the task to execute * @param initialDelay the time to delay first execution @@ -142,8 +167,10 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, * execution and the commencement of the next * @param unit the time unit of the initialDelay and delay parameters * @return a ScheduledFuture representing pending completion of - * the task, and whose {@code get()} method will throw an - * exception upon cancellation + * the series of repeated tasks. The future's {@link + * Future#get() get()} method will never return normally, + * and will throw an exception upon task cancellation or + * abnormal termination of a task execution. * @throws RejectedExecutionException if the task cannot be * scheduled for execution * @throws NullPointerException if command is null diff --git a/luni/src/main/java/java/util/concurrent/ScheduledThreadPoolExecutor.java b/luni/src/main/java/java/util/concurrent/ScheduledThreadPoolExecutor.java index 821342d7c..f9a2000f4 100644 --- a/luni/src/main/java/java/util/concurrent/ScheduledThreadPoolExecutor.java +++ b/luni/src/main/java/java/util/concurrent/ScheduledThreadPoolExecutor.java @@ -6,12 +6,18 @@ package java.util.concurrent; -import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import java.util.AbstractQueue; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; -import java.util.*; // BEGIN android-note // omit class-level docs on setRemoveOnCancelPolicy() @@ -32,17 +38,17 @@ * submission. * *

    When a submitted task is cancelled before it is run, execution - * is suppressed. By default, such a cancelled task is not - * automatically removed from the work queue until its delay - * elapses. While this enables further inspection and monitoring, it - * may also cause unbounded retention of cancelled tasks. + * is suppressed. By default, such a cancelled task is not + * automatically removed from the work queue until its delay elapses. + * While this enables further inspection and monitoring, it may also + * cause unbounded retention of cancelled tasks. * *

    Successive executions of a periodic task scheduled via - * {@link #scheduleAtFixedRate} or - * {@link #scheduleWithFixedDelay} do not overlap. While different - * executions may be performed by different threads, the effects of - * prior executions happen-before + * {@link #scheduleAtFixedRate scheduleAtFixedRate} or + * {@link #scheduleWithFixedDelay scheduleWithFixedDelay} + * do not overlap. While different executions may be performed by + * different threads, the effects of prior executions + * happen-before * those of subsequent ones. * *

    While this class inherits from {@link ThreadPoolExecutor}, a few @@ -72,7 +78,7 @@ * {@link FutureTask}. However, this may be modified or replaced using * subclasses of the form: * - *

     {@code
    + * 
     {@code
      * public class CustomScheduledExecutor extends ScheduledThreadPoolExecutor {
      *
      *   static class CustomTask implements RunnableScheduledFuture { ... }
    @@ -99,11 +105,10 @@ public class ScheduledThreadPoolExecutor
         /*
          * This class specializes ThreadPoolExecutor implementation by
          *
    -     * 1. Using a custom task type, ScheduledFutureTask for
    -     *    tasks, even those that don't require scheduling (i.e.,
    -     *    those submitted using ExecutorService execute, not
    -     *    ScheduledExecutorService methods) which are treated as
    -     *    delayed tasks with a delay of zero.
    +     * 1. Using a custom task type ScheduledFutureTask, even for tasks
    +     *    that don't require scheduling because they are submitted
    +     *    using ExecutorService rather than ScheduledExecutorService
    +     *    methods, which are treated as tasks with a delay of zero.
          *
          * 2. Using a custom queue (DelayedWorkQueue), a variant of
          *    unbounded DelayQueue. The lack of capacity constraint and
    @@ -136,7 +141,7 @@ public class ScheduledThreadPoolExecutor
         /**
          * True if ScheduledFutureTask.cancel should remove from queue.
          */
    -    private volatile boolean removeOnCancel = false;
    +    volatile boolean removeOnCancel;
     
         /**
          * Sequence number to break scheduling ties, and in turn to
    @@ -144,24 +149,17 @@ public class ScheduledThreadPoolExecutor
          */
         private static final AtomicLong sequencer = new AtomicLong();
     
    -    /**
    -     * Returns current nanosecond time.
    -     */
    -    final long now() {
    -        return System.nanoTime();
    -    }
    -
         private class ScheduledFutureTask
                 extends FutureTask implements RunnableScheduledFuture {
     
             /** Sequence number to break ties FIFO */
             private final long sequenceNumber;
     
    -        /** The time the task is enabled to execute in nanoTime units */
    -        private long time;
    +        /** The nanoTime-based time when the task is enabled to execute. */
    +        private volatile long time;
     
             /**
    -         * Period in nanoseconds for repeating tasks.
    +         * Period for repeating tasks, in nanoseconds.
              * A positive value indicates fixed-rate execution.
              * A negative value indicates fixed-delay execution.
              * A value of 0 indicates a non-repeating (one-shot) task.
    @@ -179,11 +177,12 @@ private class ScheduledFutureTask
             /**
              * Creates a one-shot action with given nanoTime-based trigger time.
              */
    -        ScheduledFutureTask(Runnable r, V result, long triggerTime) {
    +        ScheduledFutureTask(Runnable r, V result, long triggerTime,
    +                            long sequenceNumber) {
                 super(r, result);
                 this.time = triggerTime;
                 this.period = 0;
    -            this.sequenceNumber = sequencer.getAndIncrement();
    +            this.sequenceNumber = sequenceNumber;
             }
     
             /**
    @@ -191,25 +190,26 @@ private class ScheduledFutureTask
              * trigger time and period.
              */
             ScheduledFutureTask(Runnable r, V result, long triggerTime,
    -                            long period) {
    +                            long period, long sequenceNumber) {
                 super(r, result);
                 this.time = triggerTime;
                 this.period = period;
    -            this.sequenceNumber = sequencer.getAndIncrement();
    +            this.sequenceNumber = sequenceNumber;
             }
     
             /**
              * Creates a one-shot action with given nanoTime-based trigger time.
              */
    -        ScheduledFutureTask(Callable callable, long triggerTime) {
    +        ScheduledFutureTask(Callable callable, long triggerTime,
    +                            long sequenceNumber) {
                 super(callable);
                 this.time = triggerTime;
                 this.period = 0;
    -            this.sequenceNumber = sequencer.getAndIncrement();
    +            this.sequenceNumber = sequenceNumber;
             }
     
             public long getDelay(TimeUnit unit) {
    -            return unit.convert(time - now(), NANOSECONDS);
    +            return unit.convert(time - System.nanoTime(), NANOSECONDS);
             }
     
             public int compareTo(Delayed other) {
    @@ -252,6 +252,9 @@ private void setNextRunTime() {
             }
     
             public boolean cancel(boolean mayInterruptIfRunning) {
    +            // The racy read of heapIndex below is benign:
    +            // if heapIndex < 0, then OOTA guarantees that we have surely
    +            // been removed; else we recheck under lock in remove()
                 boolean cancelled = super.cancel(mayInterruptIfRunning);
                 if (cancelled && removeOnCancel && heapIndex >= 0)
                     remove(this);
    @@ -266,8 +269,8 @@ public void run() {
                 if (!canRunInCurrentRunState(periodic))
                     cancel(false);
                 else if (!periodic)
    -                ScheduledFutureTask.super.run();
    -            else if (ScheduledFutureTask.super.runAndReset()) {
    +                super.run();
    +            else if (super.runAndReset()) {
                     setNextRunTime();
                     reExecutePeriodic(outerTask);
                 }
    @@ -493,7 +496,7 @@ private long triggerTime(long delay, TimeUnit unit) {
          * Returns the nanoTime-based trigger time of a delayed action.
          */
         long triggerTime(long delay) {
    -        return now() +
    +        return System.nanoTime() +
                 ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
         }
     
    @@ -525,7 +528,8 @@ public ScheduledFuture schedule(Runnable command,
                 throw new NullPointerException();
             RunnableScheduledFuture t = decorateTask(command,
                 new ScheduledFutureTask(command, null,
    -                                          triggerTime(delay, unit)));
    +                                          triggerTime(delay, unit),
    +                                          sequencer.getAndIncrement()));
             delayedExecute(t);
             return t;
         }
    @@ -541,7 +545,8 @@ public  ScheduledFuture schedule(Callable callable,
                 throw new NullPointerException();
             RunnableScheduledFuture t = decorateTask(callable,
                 new ScheduledFutureTask(callable,
    -                                       triggerTime(delay, unit)));
    +                                       triggerTime(delay, unit),
    +                                       sequencer.getAndIncrement()));
             delayedExecute(t);
             return t;
         }
    @@ -557,13 +562,14 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command,
                                                       TimeUnit unit) {
             if (command == null || unit == null)
                 throw new NullPointerException();
    -        if (period <= 0)
    +        if (period <= 0L)
                 throw new IllegalArgumentException();
             ScheduledFutureTask sft =
                 new ScheduledFutureTask(command,
                                               null,
                                               triggerTime(initialDelay, unit),
    -                                          unit.toNanos(period));
    +                                          unit.toNanos(period),
    +                                          sequencer.getAndIncrement());
             RunnableScheduledFuture t = decorateTask(command, sft);
             sft.outerTask = t;
             delayedExecute(t);
    @@ -581,13 +587,14 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command,
                                                          TimeUnit unit) {
             if (command == null || unit == null)
                 throw new NullPointerException();
    -        if (delay <= 0)
    +        if (delay <= 0L)
                 throw new IllegalArgumentException();
             ScheduledFutureTask sft =
                 new ScheduledFutureTask(command,
                                               null,
                                               triggerTime(initialDelay, unit),
    -                                          unit.toNanos(-delay));
    +                                          -unit.toNanos(delay),
    +                                          sequencer.getAndIncrement());
             RunnableScheduledFuture t = decorateTask(command, sft);
             sft.outerTask = t;
             delayedExecute(t);
    @@ -759,7 +766,8 @@ public void shutdown() {
         /**
          * Attempts to stop all actively executing tasks, halts the
          * processing of waiting tasks, and returns a list of the tasks
    -     * that were awaiting execution.
    +     * that were awaiting execution. These tasks are drained (removed)
    +     * from the task queue upon return from this method.
          *
          * 

    This method does not wait for actively executing tasks to * terminate. Use {@link #awaitTermination awaitTermination} to @@ -767,7 +775,7 @@ public void shutdown() { * *

    There are no guarantees beyond best-effort attempts to stop * processing actively executing tasks. This implementation - * cancels tasks via {@link Thread#interrupt}, so any task that + * interrupts tasks via {@link Thread#interrupt}; any task that * fails to respond to interrupts may never terminate. * * @return list of tasks that never commenced execution. @@ -775,8 +783,8 @@ public void shutdown() { * For tasks submitted via one of the {@code schedule} * methods, the element will be identical to the returned * {@code ScheduledFuture}. For tasks submitted using - * {@link #execute}, the element will be a zero-delay {@code - * ScheduledFuture}. + * {@link #execute execute}, the element will be a + * zero-delay {@code ScheduledFuture}. */ // android-note: Removed "throws SecurityException" doc. public List shutdownNow() { @@ -784,12 +792,16 @@ public List shutdownNow() { } /** - * Returns the task queue used by this executor. - * Each element of this list is a {@link ScheduledFuture}. + * Returns the task queue used by this executor. Access to the + * task queue is intended primarily for debugging and monitoring. + * This queue may be in active use. Retrieving the task queue + * does not prevent queued tasks from executing. + * + *

    Each element of this queue is a {@link ScheduledFuture}. * For tasks submitted via one of the {@code schedule} methods, the * element will be identical to the returned {@code ScheduledFuture}. - * For tasks submitted using {@link #execute}, the element will be a - * zero-delay {@code ScheduledFuture}. + * For tasks submitted using {@link #execute execute}, the element + * will be a zero-delay {@code ScheduledFuture}. * *

    Iteration over this queue is not guaranteed to traverse * tasks in the order in which they will execute. @@ -1061,10 +1073,9 @@ public RunnableScheduledFuture poll() { lock.lock(); try { RunnableScheduledFuture first = queue[0]; - if (first == null || first.getDelay(NANOSECONDS) > 0) - return null; - else - return finishPoll(first); + return (first == null || first.getDelay(NANOSECONDS) > 0) + ? null + : finishPoll(first); } finally { lock.unlock(); } @@ -1080,7 +1091,7 @@ public RunnableScheduledFuture take() throws InterruptedException { available.await(); else { long delay = first.getDelay(NANOSECONDS); - if (delay <= 0) + if (delay <= 0L) return finishPoll(first); first = null; // don't retain ref while waiting if (leader != null) @@ -1113,15 +1124,15 @@ public RunnableScheduledFuture poll(long timeout, TimeUnit unit) for (;;) { RunnableScheduledFuture first = queue[0]; if (first == null) { - if (nanos <= 0) + if (nanos <= 0L) return null; else nanos = available.awaitNanos(nanos); } else { long delay = first.getDelay(NANOSECONDS); - if (delay <= 0) + if (delay <= 0L) return finishPoll(first); - if (nanos <= 0) + if (nanos <= 0L) return null; first = null; // don't retain ref while waiting if (nanos < delay || leader != null) @@ -1253,8 +1264,8 @@ public Iterator iterator() { */ private class Itr implements Iterator { final RunnableScheduledFuture[] array; - int cursor = 0; // index of next element to return - int lastRet = -1; // index of last element, or -1 if no such + int cursor; // index of next element to return; initially 0 + int lastRet = -1; // index of last element returned; -1 if no such Itr(RunnableScheduledFuture[] array) { this.array = array; diff --git a/luni/src/main/java/java/util/concurrent/Semaphore.java b/luni/src/main/java/java/util/concurrent/Semaphore.java index b4b7edd66..856b02bd3 100644 --- a/luni/src/main/java/java/util/concurrent/Semaphore.java +++ b/luni/src/main/java/java/util/concurrent/Semaphore.java @@ -20,7 +20,7 @@ *

    Semaphores are often used to restrict the number of threads than can * access some (physical or logical) resource. For example, here is * a class that uses a semaphore to control access to a pool of items: - *

     {@code
    + * 
     {@code
      * class Pool {
      *   private static final int MAX_AVAILABLE = 100;
      *   private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
    @@ -113,8 +113,13 @@
      *
      * 

    This class also provides convenience methods to {@link * #acquire(int) acquire} and {@link #release(int) release} multiple - * permits at a time. Beware of the increased risk of indefinite - * postponement when these methods are used without fairness set true. + * permits at a time. These methods are generally more efficient and + * effective than loops. However, they do not establish any preference + * order. For example, if thread A invokes {@code s.acquire(3}) and + * thread B invokes {@code s.acquire(2)}, and two permits become + * available, then there is no guarantee that thread B will obtain + * them unless its acquire came first and Semaphore {@code s} is in + * fair mode. * *

    Memory consistency effects: Actions in a thread prior to calling * a "release" method such as {@code release()} @@ -405,14 +410,16 @@ public void release() { * *

    Acquires the given number of permits, if they are available, * and returns immediately, reducing the number of available permits - * by the given amount. + * by the given amount. This method has the same effect as the + * loop {@code for (int i = 0; i < permits; ++i) acquire();} except + * that it atomically acquires the permits all at once: * *

    If insufficient permits are available then the current thread becomes * disabled for thread scheduling purposes and lies dormant until * one of two things happens: *

      *
    • Some other thread invokes one of the {@link #release() release} - * methods for this semaphore, the current thread is next to be assigned + * methods for this semaphore and the current thread is next to be assigned * permits and the number of available permits satisfies this request; or *
    • Some other thread {@linkplain Thread#interrupt interrupts} * the current thread. @@ -445,12 +452,14 @@ public void acquire(int permits) throws InterruptedException { * *

      Acquires the given number of permits, if they are available, * and returns immediately, reducing the number of available permits - * by the given amount. + * by the given amount. This method has the same effect as the + * loop {@code for (int i = 0; i < permits; ++i) acquireUninterruptibly();} + * except that it atomically acquires the permits all at once: * *

      If insufficient permits are available then the current thread becomes * disabled for thread scheduling purposes and lies dormant until * some other thread invokes one of the {@link #release() release} - * methods for this semaphore, the current thread is next to be assigned + * methods for this semaphore and the current thread is next to be assigned * permits and the number of available permits satisfies this request. * *

      If the current thread is {@linkplain Thread#interrupt interrupted} @@ -512,7 +521,7 @@ public boolean tryAcquire(int permits) { * purposes and lies dormant until one of three things happens: *

        *
      • Some other thread invokes one of the {@link #release() release} - * methods for this semaphore, the current thread is next to be assigned + * methods for this semaphore and the current thread is next to be assigned * permits and the number of available permits satisfies this request; or *
      • Some other thread {@linkplain Thread#interrupt interrupts} * the current thread; or @@ -559,7 +568,7 @@ public boolean tryAcquire(int permits, long timeout, TimeUnit unit) * *

        Releases the given number of permits, increasing the number of * available permits by that amount. - * If any threads are trying to acquire permits, then one + * If any threads are trying to acquire permits, then one thread * is selected and given the permits that were just released. * If the number of available permits satisfies that thread's request * then that thread is (re)enabled for thread scheduling purposes; @@ -643,7 +652,7 @@ public final boolean hasQueuedThreads() { * Returns an estimate of the number of threads waiting to acquire. * The value is only an estimate because the number of threads may * change dynamically while this method traverses internal data - * structures. This method is designed for use in monitoring of the + * structures. This method is designed for use in monitoring * system state, not for synchronization control. * * @return the estimated number of threads waiting for this lock diff --git a/luni/src/main/java/java/util/concurrent/SynchronousQueue.java b/luni/src/main/java/java/util/concurrent/SynchronousQueue.java index 69452af7b..a46f67205 100644 --- a/luni/src/main/java/java/util/concurrent/SynchronousQueue.java +++ b/luni/src/main/java/java/util/concurrent/SynchronousQueue.java @@ -7,9 +7,14 @@ package java.util.concurrent; +import java.util.AbstractQueue; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.ReentrantLock; -import java.util.*; // BEGIN android-note // removed link to collections framework docs @@ -49,7 +54,7 @@ * * @since 1.5 * @author Doug Lea and Bill Scherer and Michael Scott - * @param the type of elements held in this collection + * @param the type of elements held in this queue */ public class SynchronousQueue extends AbstractQueue implements BlockingQueue, java.io.Serializable { @@ -152,9 +157,6 @@ abstract static class Transferer { abstract E transfer(E e, boolean timed, long nanos); } - /** The number of CPUs, for spin control */ - static final int NCPUS = Runtime.getRuntime().availableProcessors(); - /** * The number of times to spin before blocking in timed waits. * The value is empirically derived -- it works well across a @@ -162,20 +164,21 @@ abstract static class Transferer { * seems not to vary with number of CPUs (beyond 2) so is just * a constant. */ - static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32; + static final int MAX_TIMED_SPINS = + (Runtime.getRuntime().availableProcessors() < 2) ? 0 : 32; /** * The number of times to spin before blocking in untimed waits. * This is greater than timed value because untimed waits spin * faster since they don't need to check times on each spin. */ - static final int maxUntimedSpins = maxTimedSpins * 16; + static final int MAX_UNTIMED_SPINS = MAX_TIMED_SPINS * 16; /** * The number of nanoseconds for which it is faster to spin * rather than to use timed park. A rough estimate suffices. */ - static final long spinForTimeoutThreshold = 1000L; + static final long SPIN_FOR_TIMEOUT_THRESHOLD = 1000L; /** Dual stack */ static final class TransferStack extends Transferer { @@ -215,7 +218,7 @@ static final class SNode { boolean casNext(SNode cmp, SNode val) { return cmp == next && - UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); + U.compareAndSwapObject(this, NEXT, cmp, val); } /** @@ -228,7 +231,7 @@ boolean casNext(SNode cmp, SNode val) { */ boolean tryMatch(SNode s) { if (match == null && - UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) { + U.compareAndSwapObject(this, MATCH, null, s)) { Thread w = waiter; if (w != null) { // waiters need at most one unpark waiter = null; @@ -243,7 +246,7 @@ boolean tryMatch(SNode s) { * Tries to cancel a wait by matching node to itself. */ void tryCancel() { - UNSAFE.compareAndSwapObject(this, matchOffset, null, this); + U.compareAndSwapObject(this, MATCH, null, this); } boolean isCancelled() { @@ -251,19 +254,17 @@ boolean isCancelled() { } // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long matchOffset; - private static final long nextOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long MATCH; + private static final long NEXT; static { try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = SNode.class; - matchOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("match")); - nextOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("next")); - } catch (Exception e) { + MATCH = U.objectFieldOffset + (SNode.class.getDeclaredField("match")); + NEXT = U.objectFieldOffset + (SNode.class.getDeclaredField("next")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } @@ -274,7 +275,7 @@ boolean isCancelled() { boolean casHead(SNode h, SNode nh) { return h == head && - UNSAFE.compareAndSwapObject(this, headOffset, h, nh); + U.compareAndSwapObject(this, HEAD, h, nh); } /** @@ -323,7 +324,7 @@ E transfer(E e, boolean timed, long nanos) { for (;;) { SNode h = head; if (h == null || h.mode == mode) { // empty or same-mode - if (timed && nanos <= 0) { // can't wait + if (timed && nanos <= 0L) { // can't wait if (h != null && h.isCancelled()) casHead(h, h.next); // pop cancelled node else @@ -405,8 +406,9 @@ SNode awaitFulfill(SNode s, boolean timed, long nanos) { */ final long deadline = timed ? System.nanoTime() + nanos : 0L; Thread w = Thread.currentThread(); - int spins = (shouldSpin(s) ? - (timed ? maxTimedSpins : maxUntimedSpins) : 0); + int spins = shouldSpin(s) + ? (timed ? MAX_TIMED_SPINS : MAX_UNTIMED_SPINS) + : 0; for (;;) { if (w.isInterrupted()) s.tryCancel(); @@ -421,12 +423,12 @@ SNode awaitFulfill(SNode s, boolean timed, long nanos) { } } if (spins > 0) - spins = shouldSpin(s) ? (spins-1) : 0; + spins = shouldSpin(s) ? (spins - 1) : 0; else if (s.waiter == null) s.waiter = w; // establish waiter so can park next iter else if (!timed) LockSupport.park(this); - else if (nanos > spinForTimeoutThreshold) + else if (nanos > SPIN_FOR_TIMEOUT_THRESHOLD) LockSupport.parkNanos(this, nanos); } } @@ -478,15 +480,13 @@ void clean(SNode s) { } // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long headOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long HEAD; static { try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = TransferStack.class; - headOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("head")); - } catch (Exception e) { + HEAD = U.objectFieldOffset + (TransferStack.class.getDeclaredField("head")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } @@ -517,19 +517,19 @@ static final class QNode { boolean casNext(QNode cmp, QNode val) { return next == cmp && - UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); + U.compareAndSwapObject(this, NEXT, cmp, val); } boolean casItem(Object cmp, Object val) { return item == cmp && - UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val); + U.compareAndSwapObject(this, ITEM, cmp, val); } /** * Tries to cancel by CAS'ing ref to this as item. */ void tryCancel(Object cmp) { - UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this); + U.compareAndSwapObject(this, ITEM, cmp, this); } boolean isCancelled() { @@ -546,19 +546,17 @@ boolean isOffList() { } // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long itemOffset; - private static final long nextOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long ITEM; + private static final long NEXT; static { try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = QNode.class; - itemOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("item")); - nextOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("next")); - } catch (Exception e) { + ITEM = U.objectFieldOffset + (QNode.class.getDeclaredField("item")); + NEXT = U.objectFieldOffset + (QNode.class.getDeclaredField("next")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } @@ -587,7 +585,7 @@ boolean isOffList() { */ void advanceHead(QNode h, QNode nh) { if (h == head && - UNSAFE.compareAndSwapObject(this, headOffset, h, nh)) + U.compareAndSwapObject(this, HEAD, h, nh)) h.next = h; // forget old next } @@ -596,7 +594,7 @@ void advanceHead(QNode h, QNode nh) { */ void advanceTail(QNode t, QNode nt) { if (tail == t) - UNSAFE.compareAndSwapObject(this, tailOffset, t, nt); + U.compareAndSwapObject(this, TAIL, t, nt); } /** @@ -604,7 +602,7 @@ void advanceTail(QNode t, QNode nt) { */ boolean casCleanMe(QNode cmp, QNode val) { return cleanMe == cmp && - UNSAFE.compareAndSwapObject(this, cleanMeOffset, cmp, val); + U.compareAndSwapObject(this, CLEANME, cmp, val); } /** @@ -654,7 +652,7 @@ E transfer(E e, boolean timed, long nanos) { advanceTail(t, tn); continue; } - if (timed && nanos <= 0) // can't wait + if (timed && nanos <= 0L) // can't wait return null; if (s == null) s = new QNode(e, isData); @@ -709,8 +707,9 @@ Object awaitFulfill(QNode s, E e, boolean timed, long nanos) { /* Same idea as TransferStack.awaitFulfill */ final long deadline = timed ? System.nanoTime() + nanos : 0L; Thread w = Thread.currentThread(); - int spins = ((head.next == s) ? - (timed ? maxTimedSpins : maxUntimedSpins) : 0); + int spins = (head.next == s) + ? (timed ? MAX_TIMED_SPINS : MAX_UNTIMED_SPINS) + : 0; for (;;) { if (w.isInterrupted()) s.tryCancel(e); @@ -730,7 +729,7 @@ else if (s.waiter == null) s.waiter = w; else if (!timed) LockSupport.park(this); - else if (nanos > spinForTimeoutThreshold) + else if (nanos > SPIN_FOR_TIMEOUT_THRESHOLD) LockSupport.parkNanos(this, nanos); } } @@ -789,21 +788,19 @@ void clean(QNode pred, QNode s) { } } - private static final sun.misc.Unsafe UNSAFE; - private static final long headOffset; - private static final long tailOffset; - private static final long cleanMeOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long HEAD; + private static final long TAIL; + private static final long CLEANME; static { try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class k = TransferQueue.class; - headOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("head")); - tailOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("tail")); - cleanMeOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("cleanMe")); - } catch (Exception e) { + HEAD = U.objectFieldOffset + (TransferQueue.class.getDeclaredField("head")); + TAIL = U.objectFieldOffset + (TransferQueue.class.getDeclaredField("tail")); + CLEANME = U.objectFieldOffset + (TransferQueue.class.getDeclaredField("cleanMe")); + } catch (ReflectiveOperationException e) { throw new Error(e); } } @@ -1034,19 +1031,19 @@ public E peek() { * * @return an empty iterator */ - @SuppressWarnings("unchecked") public Iterator iterator() { - return (Iterator) EmptyIterator.EMPTY_ITERATOR; + return Collections.emptyIterator(); } - // Replicated from a previous version of Collections - private static class EmptyIterator implements Iterator { - static final EmptyIterator EMPTY_ITERATOR - = new EmptyIterator(); - - public boolean hasNext() { return false; } - public E next() { throw new NoSuchElementException(); } - public void remove() { throw new IllegalStateException(); } + /** + * Returns an empty spliterator in which calls to + * {@link java.util.Spliterator#trySplit()} always return {@code null}. + * + * @return an empty spliterator + * @since 1.8 + */ + public Spliterator spliterator() { + return Spliterators.emptySpliterator(); } /** @@ -1071,6 +1068,14 @@ public T[] toArray(T[] a) { return a; } + /** + * Always returns {@code "[]"}. + * @return {@code "[]"} + */ + public String toString() { + return "[]"; + } + /** * @throws UnsupportedOperationException {@inheritDoc} * @throws ClassCastException {@inheritDoc} @@ -1131,6 +1136,8 @@ static class FifoWaitQueue extends WaitQueue { /** * Saves this queue to a stream (that is, serializes it). + * @param s the stream + * @throws java.io.IOException if an I/O error occurs */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { @@ -1150,6 +1157,10 @@ private void writeObject(java.io.ObjectOutputStream s) /** * Reconstitutes this queue from a stream (that is, deserializes it). + * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws java.io.IOException if an I/O error occurs */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { @@ -1160,19 +1171,6 @@ private void readObject(java.io.ObjectInputStream s) transferer = new TransferStack(); } - // Unsafe mechanics - static long objectFieldOffset(sun.misc.Unsafe UNSAFE, - String field, Class klazz) { - try { - return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field)); - } catch (NoSuchFieldException e) { - // Convert Exception to corresponding Error - NoSuchFieldError error = new NoSuchFieldError(field); - error.initCause(e); - throw error; - } - } - static { // Reduce the risk of rare disastrous classloading in first call to // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773 diff --git a/luni/src/main/java/java/util/concurrent/ThreadFactory.java b/luni/src/main/java/java/util/concurrent/ThreadFactory.java index d1a4eb66a..fdedea34b 100644 --- a/luni/src/main/java/java/util/concurrent/ThreadFactory.java +++ b/luni/src/main/java/java/util/concurrent/ThreadFactory.java @@ -13,7 +13,7 @@ * *

        * The simplest implementation of this interface is just: - *

         {@code
        + * 
         {@code
          * class SimpleThreadFactory implements ThreadFactory {
          *   public Thread newThread(Runnable r) {
          *     return new Thread(r);
        diff --git a/luni/src/main/java/java/util/concurrent/ThreadLocalRandom.java b/luni/src/main/java/java/util/concurrent/ThreadLocalRandom.java
        index b007af2f7..8c65bdebb 100644
        --- a/luni/src/main/java/java/util/concurrent/ThreadLocalRandom.java
        +++ b/luni/src/main/java/java/util/concurrent/ThreadLocalRandom.java
        @@ -6,7 +6,19 @@
         
         package java.util.concurrent;
         
        +import java.io.ObjectStreamField;
         import java.util.Random;
        +import java.util.Spliterator;
        +import java.util.concurrent.atomic.AtomicInteger;
        +import java.util.concurrent.atomic.AtomicLong;
        +import java.util.function.DoubleConsumer;
        +import java.util.function.IntConsumer;
        +import java.util.function.LongConsumer;
        +// TODO(streams):
        +// import java.util.stream.DoubleStream;
        +// import java.util.stream.IntStream;
        +// import java.util.stream.LongStream;
        +// import java.util.stream.StreamSupport;
         
         /**
          * A random number generator isolated to the current thread.  Like the
        @@ -29,50 +41,98 @@
          * 

        This class also provides additional commonly used bounded random * generation methods. * + *

        Instances of {@code ThreadLocalRandom} are not cryptographically + * secure. Consider instead using {@link java.security.SecureRandom} + * in security-sensitive applications. Additionally, + * default-constructed instances do not use a cryptographically random + * seed unless the {@linkplain System#getProperty system property} + * {@code java.util.secureRandomSeed} is set to {@code true}. + * * @since 1.7 * @author Doug Lea */ public class ThreadLocalRandom extends Random { - // same constants as Random, but must be redeclared because private - private static final long multiplier = 0x5DEECE66DL; - private static final long addend = 0xBL; - private static final long mask = (1L << 48) - 1; - - /** - * The random seed. We can't use super.seed. + /* + * This class implements the java.util.Random API (and subclasses + * Random) using a single static instance that accesses random + * number state held in class Thread (primarily, field + * threadLocalRandomSeed). In doing so, it also provides a home + * for managing package-private utilities that rely on exactly the + * same state as needed to maintain the ThreadLocalRandom + * instances. We leverage the need for an initialization flag + * field to also use it as a "probe" -- a self-adjusting thread + * hash used for contention avoidance, as well as a secondary + * simpler (xorShift) random seed that is conservatively used to + * avoid otherwise surprising users by hijacking the + * ThreadLocalRandom sequence. The dual use is a marriage of + * convenience, but is a simple and efficient way of reducing + * application-level overhead and footprint of most concurrent + * programs. + * + * Even though this class subclasses java.util.Random, it uses the + * same basic algorithm as java.util.SplittableRandom. (See its + * internal documentation for explanations, which are not repeated + * here.) Because ThreadLocalRandoms are not splittable + * though, we use only a single 64bit gamma. + * + * Because this class is in a different package than class Thread, + * field access methods use Unsafe to bypass access control rules. + * To conform to the requirements of the Random superclass + * constructor, the common static ThreadLocalRandom maintains an + * "initialized" field for the sake of rejecting user calls to + * setSeed while still allowing a call from constructor. Note + * that serialization is completely unnecessary because there is + * only a static singleton. But we generate a serial form + * containing "rnd" and "initialized" fields to ensure + * compatibility across versions. + * + * Implementations of non-core methods are mostly the same as in + * SplittableRandom, that were in part derived from a previous + * version of this class. + * + * The nextLocalGaussian ThreadLocal supports the very rarely used + * nextGaussian method by providing a holder for the second of a + * pair of them. As is true for the base class version of this + * method, this time/space tradeoff is probably never worthwhile, + * but we provide identical statistical properties. */ - private long rnd; - /** - * Initialization flag to permit calls to setSeed to succeed only - * while executing the Random constructor. We can't allow others - * since it would cause setting seed in one part of a program to - * unintentionally impact other usages by the thread. - */ - boolean initialized; + private static long mix64(long z) { + z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL; + z = (z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L; + return z ^ (z >>> 33); + } - // Padding to help avoid memory contention among seed updates in - // different TLRs in the common case that they are located near - // each other. - private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; + private static int mix32(long z) { + z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL; + return (int)(((z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L) >>> 32); + } /** - * The actual ThreadLocal + * Field used only during singleton initialization. + * True when constructor completes. */ - private static final ThreadLocal localRandom = - new ThreadLocal() { - protected ThreadLocalRandom initialValue() { - return new ThreadLocalRandom(); - } - }; + boolean initialized; + /** Constructor used only for static singleton */ + private ThreadLocalRandom() { + initialized = true; // false during super() call + } /** - * Constructor called only by localRandom.initialValue. + * Initialize Thread fields for the current thread. Called only + * when Thread.threadLocalRandomProbe is zero, indicating that a + * thread local seed value needs to be generated. Note that even + * though the initialization is purely thread-local, we need to + * rely on (static) atomic generators to initialize the values. */ - ThreadLocalRandom() { - super(); - initialized = true; + static final void localInit() { + int p = probeGenerator.addAndGet(PROBE_INCREMENT); + int probe = (p == 0) ? 1 : p; // skip 0 + long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT)); + Thread t = Thread.currentThread(); + U.putLong(t, SEED, seed); + U.putInt(t, PROBE, probe); } /** @@ -81,7 +141,9 @@ protected ThreadLocalRandom initialValue() { * @return the current thread's {@code ThreadLocalRandom} */ public static ThreadLocalRandom current() { - return localRandom.get(); + if (U.getInt(Thread.currentThread(), PROBE) == 0) + localInit(); + return instance; } /** @@ -91,107 +153,894 @@ public static ThreadLocalRandom current() { * @throws UnsupportedOperationException always */ public void setSeed(long seed) { + // only allow call from super() constructor if (initialized) throw new UnsupportedOperationException(); - rnd = (seed ^ multiplier) & mask; } + final long nextSeed() { + Thread t; long r; // read and update per-thread seed + U.putLong(t = Thread.currentThread(), SEED, + r = U.getLong(t, SEED) + GAMMA); + return r; + } + + // We must define this, but never use it. protected int next(int bits) { - rnd = (rnd * multiplier + addend) & mask; - return (int) (rnd >>> (48-bits)); + return (int)(mix64(nextSeed()) >>> (64 - bits)); + } + + /** + * The form of nextLong used by LongStream Spliterators. If + * origin is greater than bound, acts as unbounded form of + * nextLong, else as bounded form. + * + * @param origin the least value, unless greater than bound + * @param bound the upper bound (exclusive), must not equal origin + * @return a pseudorandom value + */ + final long internalNextLong(long origin, long bound) { + long r = mix64(nextSeed()); + if (origin < bound) { + long n = bound - origin, m = n - 1; + if ((n & m) == 0L) // power of two + r = (r & m) + origin; + else if (n > 0L) { // reject over-represented candidates + for (long u = r >>> 1; // ensure nonnegative + u + m - (r = u % n) < 0L; // rejection check + u = mix64(nextSeed()) >>> 1) // retry + ; + r += origin; + } + else { // range not representable as long + while (r < origin || r >= bound) + r = mix64(nextSeed()); + } + } + return r; + } + + /** + * The form of nextInt used by IntStream Spliterators. + * Exactly the same as long version, except for types. + * + * @param origin the least value, unless greater than bound + * @param bound the upper bound (exclusive), must not equal origin + * @return a pseudorandom value + */ + final int internalNextInt(int origin, int bound) { + int r = mix32(nextSeed()); + if (origin < bound) { + int n = bound - origin, m = n - 1; + if ((n & m) == 0) + r = (r & m) + origin; + else if (n > 0) { + for (int u = r >>> 1; + u + m - (r = u % n) < 0; + u = mix32(nextSeed()) >>> 1) + ; + r += origin; + } + else { + while (r < origin || r >= bound) + r = mix32(nextSeed()); + } + } + return r; + } + + /** + * The form of nextDouble used by DoubleStream Spliterators. + * + * @param origin the least value, unless greater than bound + * @param bound the upper bound (exclusive), must not equal origin + * @return a pseudorandom value + */ + final double internalNextDouble(double origin, double bound) { + double r = (nextLong() >>> 11) * DOUBLE_UNIT; + if (origin < bound) { + r = r * (bound - origin) + origin; + if (r >= bound) // correct for rounding + r = Double.longBitsToDouble(Double.doubleToLongBits(bound) - 1); + } + return r; + } + + /** + * Returns a pseudorandom {@code int} value. + * + * @return a pseudorandom {@code int} value + */ + public int nextInt() { + return mix32(nextSeed()); + } + + /** + * Returns a pseudorandom {@code int} value between zero (inclusive) + * and the specified bound (exclusive). + * + * @param bound the upper bound (exclusive). Must be positive. + * @return a pseudorandom {@code int} value between zero + * (inclusive) and the bound (exclusive) + * @throws IllegalArgumentException if {@code bound} is not positive + */ + public int nextInt(int bound) { + if (bound <= 0) + throw new IllegalArgumentException(BAD_BOUND); + int r = mix32(nextSeed()); + int m = bound - 1; + if ((bound & m) == 0) // power of two + r &= m; + else { // reject over-represented candidates + for (int u = r >>> 1; + u + m - (r = u % bound) < 0; + u = mix32(nextSeed()) >>> 1) + ; + } + return r; } /** - * Returns a pseudorandom, uniformly distributed value between the - * given least value (inclusive) and bound (exclusive). + * Returns a pseudorandom {@code int} value between the specified + * origin (inclusive) and the specified bound (exclusive). * - * @param least the least value returned + * @param origin the least value returned * @param bound the upper bound (exclusive) - * @return the next value - * @throws IllegalArgumentException if least greater than or equal - * to bound + * @return a pseudorandom {@code int} value between the origin + * (inclusive) and the bound (exclusive) + * @throws IllegalArgumentException if {@code origin} is greater than + * or equal to {@code bound} + */ + public int nextInt(int origin, int bound) { + if (origin >= bound) + throw new IllegalArgumentException(BAD_RANGE); + return internalNextInt(origin, bound); + } + + /** + * Returns a pseudorandom {@code long} value. + * + * @return a pseudorandom {@code long} value */ - public int nextInt(int least, int bound) { - if (least >= bound) - throw new IllegalArgumentException(); - return nextInt(bound - least) + least; + public long nextLong() { + return mix64(nextSeed()); } /** - * Returns a pseudorandom, uniformly distributed value - * between 0 (inclusive) and the specified value (exclusive). + * Returns a pseudorandom {@code long} value between zero (inclusive) + * and the specified bound (exclusive). * - * @param n the bound on the random number to be returned. Must be - * positive. - * @return the next value - * @throws IllegalArgumentException if n is not positive + * @param bound the upper bound (exclusive). Must be positive. + * @return a pseudorandom {@code long} value between zero + * (inclusive) and the bound (exclusive) + * @throws IllegalArgumentException if {@code bound} is not positive */ - public long nextLong(long n) { - if (n <= 0) - throw new IllegalArgumentException("n must be positive"); - // Divide n by two until small enough for nextInt. On each - // iteration (at most 31 of them but usually much less), - // randomly choose both whether to include high bit in result - // (offset) and whether to continue with the lower vs upper - // half (which makes a difference only if odd). - long offset = 0; - while (n >= Integer.MAX_VALUE) { - int bits = next(2); - long half = n >>> 1; - long nextn = ((bits & 2) == 0) ? half : n - half; - if ((bits & 1) == 0) - offset += n - nextn; - n = nextn; + public long nextLong(long bound) { + if (bound <= 0) + throw new IllegalArgumentException(BAD_BOUND); + long r = mix64(nextSeed()); + long m = bound - 1; + if ((bound & m) == 0L) // power of two + r &= m; + else { // reject over-represented candidates + for (long u = r >>> 1; + u + m - (r = u % bound) < 0L; + u = mix64(nextSeed()) >>> 1) + ; } - return offset + nextInt((int) n); + return r; } /** - * Returns a pseudorandom, uniformly distributed value between the - * given least value (inclusive) and bound (exclusive). + * Returns a pseudorandom {@code long} value between the specified + * origin (inclusive) and the specified bound (exclusive). * - * @param least the least value returned + * @param origin the least value returned * @param bound the upper bound (exclusive) - * @return the next value - * @throws IllegalArgumentException if least greater than or equal - * to bound + * @return a pseudorandom {@code long} value between the origin + * (inclusive) and the bound (exclusive) + * @throws IllegalArgumentException if {@code origin} is greater than + * or equal to {@code bound} */ - public long nextLong(long least, long bound) { - if (least >= bound) - throw new IllegalArgumentException(); - return nextLong(bound - least) + least; + public long nextLong(long origin, long bound) { + if (origin >= bound) + throw new IllegalArgumentException(BAD_RANGE); + return internalNextLong(origin, bound); } /** - * Returns a pseudorandom, uniformly distributed {@code double} value - * between 0 (inclusive) and the specified value (exclusive). + * Returns a pseudorandom {@code double} value between zero + * (inclusive) and one (exclusive). * - * @param n the bound on the random number to be returned. Must be - * positive. - * @return the next value - * @throws IllegalArgumentException if n is not positive + * @return a pseudorandom {@code double} value between zero + * (inclusive) and one (exclusive) */ - public double nextDouble(double n) { - if (!(n > 0)) - throw new IllegalArgumentException("n must be positive"); - return nextDouble() * n; + public double nextDouble() { + return (mix64(nextSeed()) >>> 11) * DOUBLE_UNIT; } /** - * Returns a pseudorandom, uniformly distributed value between the - * given least value (inclusive) and bound (exclusive). + * Returns a pseudorandom {@code double} value between 0.0 + * (inclusive) and the specified bound (exclusive). * - * @param least the least value returned + * @param bound the upper bound (exclusive). Must be positive. + * @return a pseudorandom {@code double} value between zero + * (inclusive) and the bound (exclusive) + * @throws IllegalArgumentException if {@code bound} is not positive + */ + public double nextDouble(double bound) { + if (!(bound > 0.0)) + throw new IllegalArgumentException(BAD_BOUND); + double result = (mix64(nextSeed()) >>> 11) * DOUBLE_UNIT * bound; + return (result < bound) ? result : // correct for rounding + Double.longBitsToDouble(Double.doubleToLongBits(bound) - 1); + } + + /** + * Returns a pseudorandom {@code double} value between the specified + * origin (inclusive) and bound (exclusive). + * + * @param origin the least value returned * @param bound the upper bound (exclusive) - * @return the next value - * @throws IllegalArgumentException if least greater than or equal - * to bound + * @return a pseudorandom {@code double} value between the origin + * (inclusive) and the bound (exclusive) + * @throws IllegalArgumentException if {@code origin} is greater than + * or equal to {@code bound} */ - public double nextDouble(double least, double bound) { - if (least >= bound) - throw new IllegalArgumentException(); - return nextDouble() * (bound - least) + least; + public double nextDouble(double origin, double bound) { + if (!(origin < bound)) + throw new IllegalArgumentException(BAD_RANGE); + return internalNextDouble(origin, bound); + } + + /** + * Returns a pseudorandom {@code boolean} value. + * + * @return a pseudorandom {@code boolean} value + */ + public boolean nextBoolean() { + return mix32(nextSeed()) < 0; + } + + /** + * Returns a pseudorandom {@code float} value between zero + * (inclusive) and one (exclusive). + * + * @return a pseudorandom {@code float} value between zero + * (inclusive) and one (exclusive) + */ + public float nextFloat() { + return (mix32(nextSeed()) >>> 8) * FLOAT_UNIT; + } + + public double nextGaussian() { + // Use nextLocalGaussian instead of nextGaussian field + Double d = nextLocalGaussian.get(); + if (d != null) { + nextLocalGaussian.set(null); + return d.doubleValue(); + } + double v1, v2, s; + do { + v1 = 2 * nextDouble() - 1; // between -1 and 1 + v2 = 2 * nextDouble() - 1; // between -1 and 1 + s = v1 * v1 + v2 * v2; + } while (s >= 1 || s == 0); + double multiplier = StrictMath.sqrt(-2 * StrictMath.log(s)/s); + nextLocalGaussian.set(new Double(v2 * multiplier)); + return v1 * multiplier; } + // stream methods, coded in a way intended to better isolate for + // maintenance purposes the small differences across forms. + + // TODO(streams): + // /** + // * Returns a stream producing the given {@code streamSize} number of + // * pseudorandom {@code int} values. + // * + // * @param streamSize the number of values to generate + // * @return a stream of pseudorandom {@code int} values + // * @throws IllegalArgumentException if {@code streamSize} is + // * less than zero + // * @since 1.8 + // */ + // public IntStream ints(long streamSize) { + // if (streamSize < 0L) + // throw new IllegalArgumentException(BAD_SIZE); + // return StreamSupport.intStream + // (new RandomIntsSpliterator + // (0L, streamSize, Integer.MAX_VALUE, 0), + // false); + // } + + // /** + // * Returns an effectively unlimited stream of pseudorandom {@code int} + // * values. + // * + // * @implNote This method is implemented to be equivalent to {@code + // * ints(Long.MAX_VALUE)}. + // * + // * @return a stream of pseudorandom {@code int} values + // * @since 1.8 + // */ + // public IntStream ints() { + // return StreamSupport.intStream + // (new RandomIntsSpliterator + // (0L, Long.MAX_VALUE, Integer.MAX_VALUE, 0), + // false); + // } + + // /** + // * Returns a stream producing the given {@code streamSize} number + // * of pseudorandom {@code int} values, each conforming to the given + // * origin (inclusive) and bound (exclusive). + // * + // * @param streamSize the number of values to generate + // * @param randomNumberOrigin the origin (inclusive) of each random value + // * @param randomNumberBound the bound (exclusive) of each random value + // * @return a stream of pseudorandom {@code int} values, + // * each with the given origin (inclusive) and bound (exclusive) + // * @throws IllegalArgumentException if {@code streamSize} is + // * less than zero, or {@code randomNumberOrigin} + // * is greater than or equal to {@code randomNumberBound} + // * @since 1.8 + // */ + // public IntStream ints(long streamSize, int randomNumberOrigin, + // int randomNumberBound) { + // if (streamSize < 0L) + // throw new IllegalArgumentException(BAD_SIZE); + // if (randomNumberOrigin >= randomNumberBound) + // throw new IllegalArgumentException(BAD_RANGE); + // return StreamSupport.intStream + // (new RandomIntsSpliterator + // (0L, streamSize, randomNumberOrigin, randomNumberBound), + // false); + // } + + // /** + // * Returns an effectively unlimited stream of pseudorandom {@code + // * int} values, each conforming to the given origin (inclusive) and bound + // * (exclusive). + // * + // * @implNote This method is implemented to be equivalent to {@code + // * ints(Long.MAX_VALUE, randomNumberOrigin, randomNumberBound)}. + // * + // * @param randomNumberOrigin the origin (inclusive) of each random value + // * @param randomNumberBound the bound (exclusive) of each random value + // * @return a stream of pseudorandom {@code int} values, + // * each with the given origin (inclusive) and bound (exclusive) + // * @throws IllegalArgumentException if {@code randomNumberOrigin} + // * is greater than or equal to {@code randomNumberBound} + // * @since 1.8 + // */ + // public IntStream ints(int randomNumberOrigin, int randomNumberBound) { + // if (randomNumberOrigin >= randomNumberBound) + // throw new IllegalArgumentException(BAD_RANGE); + // return StreamSupport.intStream + // (new RandomIntsSpliterator + // (0L, Long.MAX_VALUE, randomNumberOrigin, randomNumberBound), + // false); + // } + + // /** + // * Returns a stream producing the given {@code streamSize} number of + // * pseudorandom {@code long} values. + // * + // * @param streamSize the number of values to generate + // * @return a stream of pseudorandom {@code long} values + // * @throws IllegalArgumentException if {@code streamSize} is + // * less than zero + // * @since 1.8 + // */ + // public LongStream longs(long streamSize) { + // if (streamSize < 0L) + // throw new IllegalArgumentException(BAD_SIZE); + // return StreamSupport.longStream + // (new RandomLongsSpliterator + // (0L, streamSize, Long.MAX_VALUE, 0L), + // false); + // } + + // /** + // * Returns an effectively unlimited stream of pseudorandom {@code long} + // * values. + // * + // * @implNote This method is implemented to be equivalent to {@code + // * longs(Long.MAX_VALUE)}. + // * + // * @return a stream of pseudorandom {@code long} values + // * @since 1.8 + // */ + // public LongStream longs() { + // return StreamSupport.longStream + // (new RandomLongsSpliterator + // (0L, Long.MAX_VALUE, Long.MAX_VALUE, 0L), + // false); + // } + + // /** + // * Returns a stream producing the given {@code streamSize} number of + // * pseudorandom {@code long}, each conforming to the given origin + // * (inclusive) and bound (exclusive). + // * + // * @param streamSize the number of values to generate + // * @param randomNumberOrigin the origin (inclusive) of each random value + // * @param randomNumberBound the bound (exclusive) of each random value + // * @return a stream of pseudorandom {@code long} values, + // * each with the given origin (inclusive) and bound (exclusive) + // * @throws IllegalArgumentException if {@code streamSize} is + // * less than zero, or {@code randomNumberOrigin} + // * is greater than or equal to {@code randomNumberBound} + // * @since 1.8 + // */ + // public LongStream longs(long streamSize, long randomNumberOrigin, + // long randomNumberBound) { + // if (streamSize < 0L) + // throw new IllegalArgumentException(BAD_SIZE); + // if (randomNumberOrigin >= randomNumberBound) + // throw new IllegalArgumentException(BAD_RANGE); + // return StreamSupport.longStream + // (new RandomLongsSpliterator + // (0L, streamSize, randomNumberOrigin, randomNumberBound), + // false); + // } + + // /** + // * Returns an effectively unlimited stream of pseudorandom {@code + // * long} values, each conforming to the given origin (inclusive) and bound + // * (exclusive). + // * + // * @implNote This method is implemented to be equivalent to {@code + // * longs(Long.MAX_VALUE, randomNumberOrigin, randomNumberBound)}. + // * + // * @param randomNumberOrigin the origin (inclusive) of each random value + // * @param randomNumberBound the bound (exclusive) of each random value + // * @return a stream of pseudorandom {@code long} values, + // * each with the given origin (inclusive) and bound (exclusive) + // * @throws IllegalArgumentException if {@code randomNumberOrigin} + // * is greater than or equal to {@code randomNumberBound} + // * @since 1.8 + // */ + // public LongStream longs(long randomNumberOrigin, long randomNumberBound) { + // if (randomNumberOrigin >= randomNumberBound) + // throw new IllegalArgumentException(BAD_RANGE); + // return StreamSupport.longStream + // (new RandomLongsSpliterator + // (0L, Long.MAX_VALUE, randomNumberOrigin, randomNumberBound), + // false); + // } + + // /** + // * Returns a stream producing the given {@code streamSize} number of + // * pseudorandom {@code double} values, each between zero + // * (inclusive) and one (exclusive). + // * + // * @param streamSize the number of values to generate + // * @return a stream of {@code double} values + // * @throws IllegalArgumentException if {@code streamSize} is + // * less than zero + // * @since 1.8 + // */ + // public DoubleStream doubles(long streamSize) { + // if (streamSize < 0L) + // throw new IllegalArgumentException(BAD_SIZE); + // return StreamSupport.doubleStream + // (new RandomDoublesSpliterator + // (0L, streamSize, Double.MAX_VALUE, 0.0), + // false); + // } + + // /** + // * Returns an effectively unlimited stream of pseudorandom {@code + // * double} values, each between zero (inclusive) and one + // * (exclusive). + // * + // * @implNote This method is implemented to be equivalent to {@code + // * doubles(Long.MAX_VALUE)}. + // * + // * @return a stream of pseudorandom {@code double} values + // * @since 1.8 + // */ + // public DoubleStream doubles() { + // return StreamSupport.doubleStream + // (new RandomDoublesSpliterator + // (0L, Long.MAX_VALUE, Double.MAX_VALUE, 0.0), + // false); + // } + + // /** + // * Returns a stream producing the given {@code streamSize} number of + // * pseudorandom {@code double} values, each conforming to the given origin + // * (inclusive) and bound (exclusive). + // * + // * @param streamSize the number of values to generate + // * @param randomNumberOrigin the origin (inclusive) of each random value + // * @param randomNumberBound the bound (exclusive) of each random value + // * @return a stream of pseudorandom {@code double} values, + // * each with the given origin (inclusive) and bound (exclusive) + // * @throws IllegalArgumentException if {@code streamSize} is + // * less than zero + // * @throws IllegalArgumentException if {@code randomNumberOrigin} + // * is greater than or equal to {@code randomNumberBound} + // * @since 1.8 + // */ + // public DoubleStream doubles(long streamSize, double randomNumberOrigin, + // double randomNumberBound) { + // if (streamSize < 0L) + // throw new IllegalArgumentException(BAD_SIZE); + // if (!(randomNumberOrigin < randomNumberBound)) + // throw new IllegalArgumentException(BAD_RANGE); + // return StreamSupport.doubleStream + // (new RandomDoublesSpliterator + // (0L, streamSize, randomNumberOrigin, randomNumberBound), + // false); + // } + + // /** + // * Returns an effectively unlimited stream of pseudorandom {@code + // * double} values, each conforming to the given origin (inclusive) and bound + // * (exclusive). + // * + // * @implNote This method is implemented to be equivalent to {@code + // * doubles(Long.MAX_VALUE, randomNumberOrigin, randomNumberBound)}. + // * + // * @param randomNumberOrigin the origin (inclusive) of each random value + // * @param randomNumberBound the bound (exclusive) of each random value + // * @return a stream of pseudorandom {@code double} values, + // * each with the given origin (inclusive) and bound (exclusive) + // * @throws IllegalArgumentException if {@code randomNumberOrigin} + // * is greater than or equal to {@code randomNumberBound} + // * @since 1.8 + // */ + // public DoubleStream doubles(double randomNumberOrigin, double randomNumberBound) { + // if (!(randomNumberOrigin < randomNumberBound)) + // throw new IllegalArgumentException(BAD_RANGE); + // return StreamSupport.doubleStream + // (new RandomDoublesSpliterator + // (0L, Long.MAX_VALUE, randomNumberOrigin, randomNumberBound), + // false); + // } + + /** + * Spliterator for int streams. We multiplex the four int + * versions into one class by treating a bound less than origin as + * unbounded, and also by treating "infinite" as equivalent to + * Long.MAX_VALUE. For splits, it uses the standard divide-by-two + * approach. The long and double versions of this class are + * identical except for types. + */ + private static final class RandomIntsSpliterator + implements Spliterator.OfInt { + long index; + final long fence; + final int origin; + final int bound; + RandomIntsSpliterator(long index, long fence, + int origin, int bound) { + this.index = index; this.fence = fence; + this.origin = origin; this.bound = bound; + } + + public RandomIntsSpliterator trySplit() { + long i = index, m = (i + fence) >>> 1; + return (m <= i) ? null : + new RandomIntsSpliterator(i, index = m, origin, bound); + } + + public long estimateSize() { + return fence - index; + } + + public int characteristics() { + return (Spliterator.SIZED | Spliterator.SUBSIZED | + Spliterator.NONNULL | Spliterator.IMMUTABLE); + } + + public boolean tryAdvance(IntConsumer consumer) { + if (consumer == null) throw new NullPointerException(); + long i = index, f = fence; + if (i < f) { + consumer.accept(ThreadLocalRandom.current().internalNextInt(origin, bound)); + index = i + 1; + return true; + } + return false; + } + + public void forEachRemaining(IntConsumer consumer) { + if (consumer == null) throw new NullPointerException(); + long i = index, f = fence; + if (i < f) { + index = f; + int o = origin, b = bound; + ThreadLocalRandom rng = ThreadLocalRandom.current(); + do { + consumer.accept(rng.internalNextInt(o, b)); + } while (++i < f); + } + } + } + + /** + * Spliterator for long streams. + */ + private static final class RandomLongsSpliterator + implements Spliterator.OfLong { + long index; + final long fence; + final long origin; + final long bound; + RandomLongsSpliterator(long index, long fence, + long origin, long bound) { + this.index = index; this.fence = fence; + this.origin = origin; this.bound = bound; + } + + public RandomLongsSpliterator trySplit() { + long i = index, m = (i + fence) >>> 1; + return (m <= i) ? null : + new RandomLongsSpliterator(i, index = m, origin, bound); + } + + public long estimateSize() { + return fence - index; + } + + public int characteristics() { + return (Spliterator.SIZED | Spliterator.SUBSIZED | + Spliterator.NONNULL | Spliterator.IMMUTABLE); + } + + public boolean tryAdvance(LongConsumer consumer) { + if (consumer == null) throw new NullPointerException(); + long i = index, f = fence; + if (i < f) { + consumer.accept(ThreadLocalRandom.current().internalNextLong(origin, bound)); + index = i + 1; + return true; + } + return false; + } + + public void forEachRemaining(LongConsumer consumer) { + if (consumer == null) throw new NullPointerException(); + long i = index, f = fence; + if (i < f) { + index = f; + long o = origin, b = bound; + ThreadLocalRandom rng = ThreadLocalRandom.current(); + do { + consumer.accept(rng.internalNextLong(o, b)); + } while (++i < f); + } + } + + } + + /** + * Spliterator for double streams. + */ + private static final class RandomDoublesSpliterator + implements Spliterator.OfDouble { + long index; + final long fence; + final double origin; + final double bound; + RandomDoublesSpliterator(long index, long fence, + double origin, double bound) { + this.index = index; this.fence = fence; + this.origin = origin; this.bound = bound; + } + + public RandomDoublesSpliterator trySplit() { + long i = index, m = (i + fence) >>> 1; + return (m <= i) ? null : + new RandomDoublesSpliterator(i, index = m, origin, bound); + } + + public long estimateSize() { + return fence - index; + } + + public int characteristics() { + return (Spliterator.SIZED | Spliterator.SUBSIZED | + Spliterator.NONNULL | Spliterator.IMMUTABLE); + } + + public boolean tryAdvance(DoubleConsumer consumer) { + if (consumer == null) throw new NullPointerException(); + long i = index, f = fence; + if (i < f) { + consumer.accept(ThreadLocalRandom.current().internalNextDouble(origin, bound)); + index = i + 1; + return true; + } + return false; + } + + public void forEachRemaining(DoubleConsumer consumer) { + if (consumer == null) throw new NullPointerException(); + long i = index, f = fence; + if (i < f) { + index = f; + double o = origin, b = bound; + ThreadLocalRandom rng = ThreadLocalRandom.current(); + do { + consumer.accept(rng.internalNextDouble(o, b)); + } while (++i < f); + } + } + } + + + // Within-package utilities + + /* + * Descriptions of the usages of the methods below can be found in + * the classes that use them. Briefly, a thread's "probe" value is + * a non-zero hash code that (probably) does not collide with + * other existing threads with respect to any power of two + * collision space. When it does collide, it is pseudo-randomly + * adjusted (using a Marsaglia XorShift). The nextSecondarySeed + * method is used in the same contexts as ThreadLocalRandom, but + * only for transient usages such as random adaptive spin/block + * sequences for which a cheap RNG suffices and for which it could + * in principle disrupt user-visible statistical properties of the + * main ThreadLocalRandom if we were to use it. + * + * Note: Because of package-protection issues, versions of some + * these methods also appear in some subpackage classes. + */ + + /** + * Returns the probe value for the current thread without forcing + * initialization. Note that invoking ThreadLocalRandom.current() + * can be used to force initialization on zero return. + */ + static final int getProbe() { + return U.getInt(Thread.currentThread(), PROBE); + } + + /** + * Pseudo-randomly advances and records the given probe value for the + * given thread. + */ + static final int advanceProbe(int probe) { + probe ^= probe << 13; // xorshift + probe ^= probe >>> 17; + probe ^= probe << 5; + U.putInt(Thread.currentThread(), PROBE, probe); + return probe; + } + + /** + * Returns the pseudo-randomly initialized or updated secondary seed. + */ + static final int nextSecondarySeed() { + int r; + Thread t = Thread.currentThread(); + if ((r = U.getInt(t, SECONDARY)) != 0) { + r ^= r << 13; // xorshift + r ^= r >>> 17; + r ^= r << 5; + } + else if ((r = mix32(seeder.getAndAdd(SEEDER_INCREMENT))) == 0) + r = 1; // avoid zero + U.putInt(t, SECONDARY, r); + return r; + } + + // Serialization support + private static final long serialVersionUID = -5851777807851030925L; + + /** + * @serialField rnd long + * seed for random computations + * @serialField initialized boolean + * always true + */ + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("rnd", long.class), + new ObjectStreamField("initialized", boolean.class), + }; + + /** + * Saves the {@code ThreadLocalRandom} to a stream (that is, serializes it). + * @param s the stream + * @throws java.io.IOException if an I/O error occurs + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + + java.io.ObjectOutputStream.PutField fields = s.putFields(); + fields.put("rnd", U.getLong(Thread.currentThread(), SEED)); + fields.put("initialized", true); + s.writeFields(); + } + + /** + * Returns the {@link #current() current} thread's {@code ThreadLocalRandom}. + * @return the {@link #current() current} thread's {@code ThreadLocalRandom} + */ + private Object readResolve() { + return current(); + } + + // Static initialization + + /** + * The seed increment. + */ + private static final long GAMMA = 0x9e3779b97f4a7c15L; + + /** + * The increment for generating probe values. + */ + private static final int PROBE_INCREMENT = 0x9e3779b9; + + /** + * The increment of seeder per new instance. + */ + private static final long SEEDER_INCREMENT = 0xbb67ae8584caa73bL; + + // Constants from SplittableRandom + private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53) + private static final float FLOAT_UNIT = 0x1.0p-24f; // 1.0f / (1 << 24) + + // IllegalArgumentException messages + static final String BAD_BOUND = "bound must be positive"; + static final String BAD_RANGE = "bound must be greater than origin"; + static final String BAD_SIZE = "size must be non-negative"; + + // Unsafe mechanics + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long SEED; + private static final long PROBE; + private static final long SECONDARY; + static { + try { + SEED = U.objectFieldOffset + (Thread.class.getDeclaredField("threadLocalRandomSeed")); + PROBE = U.objectFieldOffset + (Thread.class.getDeclaredField("threadLocalRandomProbe")); + SECONDARY = U.objectFieldOffset + (Thread.class.getDeclaredField("threadLocalRandomSecondarySeed")); + } catch (ReflectiveOperationException e) { + throw new Error(e); + } + } + + /** Rarely-used holder for the second of a pair of Gaussians */ + private static final ThreadLocal nextLocalGaussian = + new ThreadLocal<>(); + + /** Generates per-thread initialization/probe field */ + private static final AtomicInteger probeGenerator = new AtomicInteger(); + + /** The common ThreadLocalRandom */ + static final ThreadLocalRandom instance = new ThreadLocalRandom(); + + /** + * The next seed for default constructors. + */ + private static final AtomicLong seeder + = new AtomicLong(mix64(System.currentTimeMillis()) ^ + mix64(System.nanoTime())); + + // at end of to survive static initialization circularity + static { + if (java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Boolean run() { + return Boolean.getBoolean("java.util.secureRandomSeed"); + }})) { + byte[] seedBytes = java.security.SecureRandom.getSeed(8); + long s = (long)seedBytes[0] & 0xffL; + for (int i = 1; i < 8; ++i) + s = (s << 8) | ((long)seedBytes[i] & 0xffL); + seeder.set(s); + } + } } diff --git a/luni/src/main/java/java/util/concurrent/ThreadPoolExecutor.java b/luni/src/main/java/java/util/concurrent/ThreadPoolExecutor.java index c484920c3..e601b6ab8 100644 --- a/luni/src/main/java/java/util/concurrent/ThreadPoolExecutor.java +++ b/luni/src/main/java/java/util/concurrent/ThreadPoolExecutor.java @@ -6,11 +6,15 @@ package java.util.concurrent; +import java.util.ArrayList; +import java.util.ConcurrentModificationException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.AbstractQueuedSynchronizer; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.*; // BEGIN android-note // removed security manager docs @@ -45,7 +49,8 @@ * *

        Core and maximum pool sizes
        * - *
        A {@code ThreadPoolExecutor} will automatically adjust the + *
        + * A {@code ThreadPoolExecutor} will automatically adjust the * pool size (see {@link #getPoolSize}) * according to the bounds set by * corePoolSize (see {@link #getCorePoolSize}) and @@ -67,7 +72,8 @@ * *
        On-demand construction
        * - *
        By default, even core threads are initially created and + *
        + * By default, even core threads are initially created and * started only when new tasks arrive, but this can be overridden * dynamically using method {@link #prestartCoreThread} or {@link * #prestartAllCoreThreads}. You probably want to prestart threads if @@ -75,7 +81,8 @@ * *
        Creating new threads
        * - *
        New threads are created using a {@link ThreadFactory}. If not + *
        + * New threads are created using a {@link ThreadFactory}. If not * otherwise specified, a {@link Executors#defaultThreadFactory} is * used, that creates threads to all be in the same {@link * ThreadGroup} and with the same {@code NORM_PRIORITY} priority and @@ -83,11 +90,17 @@ * alter the thread's name, thread group, priority, daemon status, * etc. If a {@code ThreadFactory} fails to create a thread when asked * by returning null from {@code newThread}, the executor will - * continue, but might not be able to execute any tasks.
        + * continue, but might not be able to execute any tasks. Threads + * should possess the "modifyThread" {@code RuntimePermission}. If + * worker threads or other threads using the pool do not possess this + * permission, service may be degraded: configuration changes may not + * take effect in a timely manner, and a shutdown pool may remain in a + * state in which termination is possible but not completed. * *
        Keep-alive times
        * - *
        If the pool currently has more than corePoolSize threads, + *
        + * If the pool currently has more than corePoolSize threads, * excess threads will be terminated if they have been idle for more * than the keepAliveTime (see {@link #getKeepAliveTime(TimeUnit)}). * This provides a means of reducing resource consumption when the @@ -97,36 +110,37 @@ * TimeUnit)}. Using a value of {@code Long.MAX_VALUE} {@link * TimeUnit#NANOSECONDS} effectively disables idle threads from ever * terminating prior to shut down. By default, the keep-alive policy - * applies only when there are more than corePoolSize threads. But + * applies only when there are more than corePoolSize threads, but * method {@link #allowCoreThreadTimeOut(boolean)} can be used to * apply this time-out policy to core threads as well, so long as the * keepAliveTime value is non-zero.
        * *
        Queuing
        * - *
        Any {@link BlockingQueue} may be used to transfer and hold + *
        + * Any {@link BlockingQueue} may be used to transfer and hold * submitted tasks. The use of this queue interacts with pool sizing: * *
          * - *
        • If fewer than corePoolSize threads are running, the Executor + *
        • If fewer than corePoolSize threads are running, the Executor * always prefers adding a new thread - * rather than queuing.
        • + * rather than queuing. * - *
        • If corePoolSize or more threads are running, the Executor + *
        • If corePoolSize or more threads are running, the Executor * always prefers queuing a request rather than adding a new - * thread.
        • + * thread. * - *
        • If a request cannot be queued, a new thread is created unless + *
        • If a request cannot be queued, a new thread is created unless * this would exceed maximumPoolSize, in which case, the task will be - * rejected.
        • + * rejected. * *
        * * There are three general strategies for queuing: *
          * - *
        1. Direct handoffs. A good default choice for a work + *
        2. Direct handoffs. A good default choice for a work * queue is a {@link SynchronousQueue} that hands off tasks to threads * without otherwise holding them. Here, an attempt to queue a task * will fail if no threads are immediately available to run it, so a @@ -135,7 +149,7 @@ * Direct handoffs generally require unbounded maximumPoolSizes to * avoid rejection of new submitted tasks. This in turn admits the * possibility of unbounded thread growth when commands continue to - * arrive on average faster than they can be processed.
        3. + * arrive on average faster than they can be processed. * *
        4. Unbounded queues. Using an unbounded queue (for * example a {@link LinkedBlockingQueue} without a predefined @@ -148,7 +162,7 @@ * While this style of queuing can be useful in smoothing out * transient bursts of requests, it admits the possibility of * unbounded work queue growth when commands continue to arrive on - * average faster than they can be processed.
        5. + * average faster than they can be processed. * *
        6. Bounded queues. A bounded queue (for example, an * {@link ArrayBlockingQueue}) helps prevent resource exhaustion when @@ -161,7 +175,7 @@ * time for more threads than you otherwise allow. Use of small queues * generally requires larger pool sizes, which keeps CPUs busier but * may encounter unacceptable scheduling overhead, which also - * decreases throughput.
        7. + * decreases throughput. * *
        * @@ -169,7 +183,8 @@ * *
        Rejected tasks
        * - *
        New tasks submitted in method {@link #execute(Runnable)} will be + *
        + * New tasks submitted in method {@link #execute(Runnable)} will be * rejected when the Executor has been shut down, and also when * the Executor uses finite bounds for both maximum threads and work queue * capacity, and is saturated. In either case, the {@code execute} method @@ -180,22 +195,22 @@ * *
          * - *
        1. In the default {@link ThreadPoolExecutor.AbortPolicy}, the + *
        2. In the default {@link ThreadPoolExecutor.AbortPolicy}, the * handler throws a runtime {@link RejectedExecutionException} upon - * rejection.
        3. + * rejection. * - *
        4. In {@link ThreadPoolExecutor.CallerRunsPolicy}, the thread + *
        5. In {@link ThreadPoolExecutor.CallerRunsPolicy}, the thread * that invokes {@code execute} itself runs the task. This provides a * simple feedback control mechanism that will slow down the rate that - * new tasks are submitted.
        6. + * new tasks are submitted. * - *
        7. In {@link ThreadPoolExecutor.DiscardPolicy}, a task that - * cannot be executed is simply dropped.
        8. + *
        9. In {@link ThreadPoolExecutor.DiscardPolicy}, a task that + * cannot be executed is simply dropped. * *
        10. In {@link ThreadPoolExecutor.DiscardOldestPolicy}, if the * executor is not shut down, the task at the head of the work queue * is dropped, and then execution is retried (which can fail again, - * causing this to be repeated.)
        11. + * causing this to be repeated.) * *
        * @@ -206,7 +221,8 @@ * *
        Hook methods
        * - *
        This class provides {@code protected} overridable + *
        + * This class provides {@code protected} overridable * {@link #beforeExecute(Thread, Runnable)} and * {@link #afterExecute(Runnable, Throwable)} methods that are called * before and after execution of each task. These can be used to @@ -216,12 +232,14 @@ * any special processing that needs to be done once the Executor has * fully terminated. * - *

        If hook or callback methods throw exceptions, internal worker - * threads may in turn fail and abruptly terminate.

        + *

        If hook, callback, or BlockingQueue methods throw exceptions, + * internal worker threads may in turn fail, abruptly terminate, and + * possibly be replaced. * *

        Queue maintenance
        * - *
        Method {@link #getQueue()} allows access to the work queue + *
        + * Method {@link #getQueue()} allows access to the work queue * for purposes of monitoring and debugging. Use of this method for * any other purpose is strongly discouraged. Two supplied methods, * {@link #remove(Runnable)} and {@link #purge} are available to @@ -230,7 +248,8 @@ * *
        Finalization
        * - *
        A pool that is no longer referenced in a program AND + *
        + * A pool that is no longer referenced in a program AND * has no remaining threads will be {@code shutdown} automatically. If * you would like to ensure that unreferenced pools are reclaimed even * if users forget to call {@link #shutdown}, then you must arrange @@ -244,7 +263,7 @@ * override one or more of the protected hook methods. For example, * here is a subclass that adds a simple pause/resume feature: * - *
         {@code
        + * 
         {@code
          * class PausableThreadPoolExecutor extends ThreadPoolExecutor {
          *   private boolean isPaused;
          *   private ReentrantLock pauseLock = new ReentrantLock();
        @@ -433,10 +452,10 @@ private void decrementWorkerCount() {
              * Set containing all worker threads in pool. Accessed only when
              * holding mainLock.
              */
        -    private final HashSet workers = new HashSet();
        +    private final HashSet workers = new HashSet<>();
         
             /**
        -     * Wait condition to support awaitTermination
        +     * Wait condition to support awaitTermination.
              */
             private final Condition termination = mainLock.newCondition();
         
        @@ -512,7 +531,7 @@ private void decrementWorkerCount() {
             private volatile int maximumPoolSize;
         
             /**
        -     * The default rejected execution handler
        +     * The default rejected execution handler.
              */
             private static final RejectedExecutionHandler defaultHandler =
                 new AbortPolicy();
        @@ -639,6 +658,7 @@ void interruptIfStarted() {
              *        (but not TIDYING or TERMINATED -- use tryTerminate for that)
              */
             private void advanceRunState(int targetState) {
        +        // assert targetState == SHUTDOWN || targetState == STOP;
                 for (;;) {
                     int c = ctl.get();
                     if (runStateAtLeast(c, targetState) ||
        @@ -821,7 +841,7 @@ final boolean isRunningOrShutdown(boolean shutdownOK) {
              */
             private List drainQueue() {
                 BlockingQueue q = workQueue;
        -        ArrayList taskList = new ArrayList();
        +        ArrayList taskList = new ArrayList<>();
                 q.drainTo(taskList);
                 if (!q.isEmpty()) {
                     for (Runnable r : q.toArray(new Runnable[0])) {
        @@ -1376,7 +1396,7 @@ public void shutdown() {
              *
              * 

        There are no guarantees beyond best-effort attempts to stop * processing actively executing tasks. This implementation - * cancels tasks via {@link Thread#interrupt}, so any task that + * interrupts tasks via {@link Thread#interrupt}; any task that * fails to respond to interrupts may never terminate. */ // android-note: Removed @throws SecurityException @@ -1426,13 +1446,12 @@ public boolean awaitTermination(long timeout, TimeUnit unit) final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { - for (;;) { - if (runStateAtLeast(ctl.get(), TERMINATED)) - return true; - if (nanos <= 0) + while (!runStateAtLeast(ctl.get(), TERMINATED)) { + if (nanos <= 0L) return false; nanos = termination.awaitNanos(nanos); } + return true; } finally { mainLock.unlock(); } @@ -1501,10 +1520,12 @@ public RejectedExecutionHandler getRejectedExecutionHandler() { * * @param corePoolSize the new core size * @throws IllegalArgumentException if {@code corePoolSize < 0} + * or {@code corePoolSize} is greater than the {@linkplain + * #getMaximumPoolSize() maximum pool size} * @see #getCorePoolSize */ public void setCorePoolSize(int corePoolSize) { - if (corePoolSize < 0) + if (corePoolSize < 0 || maximumPoolSize < corePoolSize) throw new IllegalArgumentException(); int delta = corePoolSize - this.corePoolSize; this.corePoolSize = corePoolSize; @@ -1647,11 +1668,13 @@ public int getMaximumPoolSize() { } /** - * Sets the time limit for which threads may remain idle before - * being terminated. If there are more than the core number of - * threads currently in the pool, after waiting this amount of - * time without processing a task, excess threads will be - * terminated. This overrides any value set in the constructor. + * Sets the thread keep-alive time, which is the amount of time + * that threads may remain idle before being terminated. + * Threads that wait this amount of time without processing a + * task will be terminated if there are more than the core + * number of threads currently in the pool, or if this pool + * {@linkplain #allowsCoreThreadTimeOut() allows core thread timeout}. + * This overrides any value set in the constructor. * * @param time the time to wait. A time value of zero will cause * excess threads to terminate immediately after executing tasks. @@ -1674,8 +1697,11 @@ public void setKeepAliveTime(long time, TimeUnit unit) { /** * Returns the thread keep-alive time, which is the amount of time - * that threads in excess of the core pool size may remain - * idle before being terminated. + * that threads may remain idle before being terminated. + * Threads that wait this amount of time without processing a + * task will be terminated if there are more than the core + * number of threads currently in the pool, or if this pool + * {@linkplain #allowsCoreThreadTimeOut() allows core thread timeout}. * * @param unit the desired time unit of the result * @return the time limit @@ -1706,8 +1732,8 @@ public BlockingQueue getQueue() { * *

        This method may be useful as one part of a cancellation * scheme. It may fail to remove tasks that have been converted - * into other forms before being placed on the internal queue. For - * example, a task entered using {@code submit} might be + * into other forms before being placed on the internal queue. + * For example, a task entered using {@code submit} might be * converted into a form that maintains {@code Future} status. * However, in such cases, method {@link #purge} may be used to * remove those Futures that have been cancelled. @@ -1879,11 +1905,12 @@ public String toString() { mainLock.unlock(); } int c = ctl.get(); - String rs = (runStateLessThan(c, SHUTDOWN) ? "Running" : - (runStateAtLeast(c, TERMINATED) ? "Terminated" : - "Shutting down")); + String runState = + runStateLessThan(c, SHUTDOWN) ? "Running" : + runStateAtLeast(c, TERMINATED) ? "Terminated" : + "Shutting down"; return super.toString() + - "[" + rs + + "[" + runState + ", pool size = " + nworkers + ", active threads = " + nactive + ", queued tasks = " + workQueue.size() + @@ -1930,20 +1957,23 @@ protected void beforeExecute(Thread t, Runnable r) { } * as in this sample subclass that prints either the direct cause * or the underlying exception if a task has been aborted: * - *

         {@code
        +     * 
         {@code
              * class ExtendedExecutor extends ThreadPoolExecutor {
              *   // ...
              *   protected void afterExecute(Runnable r, Throwable t) {
              *     super.afterExecute(r, t);
        -     *     if (t == null && r instanceof Future) {
        +     *     if (t == null
        +     *         && r instanceof Future
        +     *         && ((Future)r).isDone()) {
              *       try {
              *         Object result = ((Future) r).get();
              *       } catch (CancellationException ce) {
        -     *           t = ce;
        +     *         t = ce;
              *       } catch (ExecutionException ee) {
        -     *           t = ee.getCause();
        +     *         t = ee.getCause();
              *       } catch (InterruptedException ie) {
        -     *           Thread.currentThread().interrupt(); // ignore/reset
        +     *         // ignore/reset
        +     *         Thread.currentThread().interrupt();
              *       }
              *     }
              *     if (t != null)
        diff --git a/luni/src/main/java/java/util/concurrent/TimeUnit.java b/luni/src/main/java/java/util/concurrent/TimeUnit.java
        index 8e6a5f710..fff02d8c9 100644
        --- a/luni/src/main/java/java/util/concurrent/TimeUnit.java
        +++ b/luni/src/main/java/java/util/concurrent/TimeUnit.java
        @@ -6,6 +6,12 @@
         
         package java.util.concurrent;
         
        +import java.util.Objects;
        +
        +// BEGIN android-note
        +// removed java 9 ChronoUnit related code
        +// END android-note
        +
         /**
          * A {@code TimeUnit} represents time durations at a given unit of
          * granularity and provides utility methods to convert across units,
        @@ -23,12 +29,12 @@
          * the following code will timeout in 50 milliseconds if the {@link
          * java.util.concurrent.locks.Lock lock} is not available:
          *
        - *  
         {@code
        + * 
         {@code
          * Lock lock = ...;
          * if (lock.tryLock(50L, TimeUnit.MILLISECONDS)) ...}
        * * while this code will timeout in 50 seconds: - *
         {@code
        + * 
         {@code
          * Lock lock = ...;
          * if (lock.tryLock(50L, TimeUnit.SECONDS)) ...}
        * @@ -41,7 +47,7 @@ */ public enum TimeUnit { /** - * Time unit representing one thousandth of a microsecond + * Time unit representing one thousandth of a microsecond. */ NANOSECONDS { public long toNanos(long d) { return d; } @@ -56,7 +62,7 @@ public enum TimeUnit { }, /** - * Time unit representing one thousandth of a millisecond + * Time unit representing one thousandth of a millisecond. */ MICROSECONDS { public long toNanos(long d) { return x(d, C1/C0, MAX/(C1/C0)); } @@ -71,7 +77,7 @@ public enum TimeUnit { }, /** - * Time unit representing one thousandth of a second + * Time unit representing one thousandth of a second. */ MILLISECONDS { public long toNanos(long d) { return x(d, C2/C0, MAX/(C2/C0)); } @@ -86,7 +92,7 @@ public enum TimeUnit { }, /** - * Time unit representing one second + * Time unit representing one second. */ SECONDS { public long toNanos(long d) { return x(d, C3/C0, MAX/(C3/C0)); } @@ -101,7 +107,7 @@ public enum TimeUnit { }, /** - * Time unit representing sixty seconds + * Time unit representing sixty seconds. * @since 1.6 */ MINUTES { @@ -117,7 +123,7 @@ public enum TimeUnit { }, /** - * Time unit representing sixty minutes + * Time unit representing sixty minutes. * @since 1.6 */ HOURS { @@ -133,7 +139,7 @@ public enum TimeUnit { }, /** - * Time unit representing twenty four hours + * Time unit representing twenty four hours. * @since 1.6 */ DAYS { @@ -164,7 +170,7 @@ public enum TimeUnit { * This has a short name to make above code more readable. */ static long x(long d, long m, long over) { - if (d > over) return Long.MAX_VALUE; + if (d > +over) return Long.MAX_VALUE; if (d < -over) return Long.MIN_VALUE; return d * m; } @@ -300,7 +306,7 @@ public long toDays(long duration) { * method (see {@link BlockingQueue#poll BlockingQueue.poll}) * using: * - *
         {@code
        +     * 
         {@code
              * public synchronized Object poll(long timeout, TimeUnit unit)
              *     throws InterruptedException {
              *   while (empty) {
        @@ -360,5 +366,4 @@ public void sleep(long timeout) throws InterruptedException {
                     Thread.sleep(ms, ns);
                 }
             }
        -
         }
        diff --git a/luni/src/main/java/java/util/concurrent/TransferQueue.java b/luni/src/main/java/java/util/concurrent/TransferQueue.java
        index 4c2be6f35..d4166b5b5 100644
        --- a/luni/src/main/java/java/util/concurrent/TransferQueue.java
        +++ b/luni/src/main/java/java/util/concurrent/TransferQueue.java
        @@ -34,7 +34,7 @@
          *
          * @since 1.7
          * @author Doug Lea
        - * @param  the type of elements held in this collection
        + * @param  the type of elements held in this queue
          */
         public interface TransferQueue extends BlockingQueue {
             /**
        diff --git a/luni/src/main/java/java/util/concurrent/atomic/AtomicBoolean.java b/luni/src/main/java/java/util/concurrent/atomic/AtomicBoolean.java
        index f51e6af97..01e4b072d 100644
        --- a/luni/src/main/java/java/util/concurrent/atomic/AtomicBoolean.java
        +++ b/luni/src/main/java/java/util/concurrent/atomic/AtomicBoolean.java
        @@ -6,8 +6,6 @@
         
         package java.util.concurrent.atomic;
         
        -import sun.misc.Unsafe;
        -
         /**
          * A {@code boolean} value that may be updated atomically. See the
          * {@link java.util.concurrent.atomic} package specification for
        @@ -21,15 +19,17 @@
          */
         public class AtomicBoolean implements java.io.Serializable {
             private static final long serialVersionUID = 4654671469794556979L;
        -    // setup to use Unsafe.compareAndSwapInt for updates
        -    private static final Unsafe unsafe = Unsafe.getUnsafe();
        -    private static final long valueOffset;
        +
        +    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
        +    private static final long VALUE;
         
             static {
                 try {
        -            valueOffset = unsafe.objectFieldOffset
        +            VALUE = U.objectFieldOffset
                         (AtomicBoolean.class.getDeclaredField("value"));
        -        } catch (Exception ex) { throw new Error(ex); }
        +        } catch (ReflectiveOperationException e) {
        +            throw new Error(e);
        +        }
             }
         
             private volatile int value;
        @@ -64,13 +64,13 @@ public final boolean get() {
              *
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful. False return indicates that
        +     * @return {@code true} if successful. False return indicates that
              * the actual value was not equal to the expected value.
              */
             public final boolean compareAndSet(boolean expect, boolean update) {
        -        int e = expect ? 1 : 0;
        -        int u = update ? 1 : 0;
        -        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
        +        return U.compareAndSwapInt(this, VALUE,
        +                                   (expect ? 1 : 0),
        +                                   (update ? 1 : 0));
             }
         
             /**
        @@ -83,12 +83,12 @@ public final boolean compareAndSet(boolean expect, boolean update) {
              *
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public boolean weakCompareAndSet(boolean expect, boolean update) {
        -        int e = expect ? 1 : 0;
        -        int u = update ? 1 : 0;
        -        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
        +        return U.compareAndSwapInt(this, VALUE,
        +                                   (expect ? 1 : 0),
        +                                   (update ? 1 : 0));
             }
         
             /**
        @@ -107,8 +107,7 @@ public final void set(boolean newValue) {
              * @since 1.6
              */
             public final void lazySet(boolean newValue) {
        -        int v = newValue ? 1 : 0;
        -        unsafe.putOrderedInt(this, valueOffset, v);
        +        U.putOrderedInt(this, VALUE, (newValue ? 1 : 0));
             }
         
             /**
        @@ -118,11 +117,11 @@ public final void lazySet(boolean newValue) {
              * @return the previous value
              */
             public final boolean getAndSet(boolean newValue) {
        -        for (;;) {
        -            boolean current = get();
        -            if (compareAndSet(current, newValue))
        -                return current;
        -        }
        +        boolean prev;
        +        do {
        +            prev = get();
        +        } while (!compareAndSet(prev, newValue));
        +        return prev;
             }
         
             /**
        diff --git a/luni/src/main/java/java/util/concurrent/atomic/AtomicInteger.java b/luni/src/main/java/java/util/concurrent/atomic/AtomicInteger.java
        index 8a152985d..849fd8aba 100644
        --- a/luni/src/main/java/java/util/concurrent/atomic/AtomicInteger.java
        +++ b/luni/src/main/java/java/util/concurrent/atomic/AtomicInteger.java
        @@ -6,7 +6,8 @@
         
         package java.util.concurrent.atomic;
         
        -import sun.misc.Unsafe;
        +import java.util.function.IntBinaryOperator;
        +import java.util.function.IntUnaryOperator;
         
         /**
          * An {@code int} value that may be updated atomically.  See the
        @@ -20,19 +21,20 @@
          *
          * @since 1.5
          * @author Doug Lea
        -*/
        + */
         public class AtomicInteger extends Number implements java.io.Serializable {
             private static final long serialVersionUID = 6214790243416807050L;
         
        -    // setup to use Unsafe.compareAndSwapInt for updates
        -    private static final Unsafe unsafe = Unsafe.getUnsafe();
        -    private static final long valueOffset;
        +    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
        +    private static final long VALUE;
         
             static {
                 try {
        -            valueOffset = unsafe.objectFieldOffset
        +            VALUE = U.objectFieldOffset
                         (AtomicInteger.class.getDeclaredField("value"));
        -        } catch (Exception ex) { throw new Error(ex); }
        +        } catch (ReflectiveOperationException e) {
        +            throw new Error(e);
        +        }
             }
         
             private volatile int value;
        @@ -77,7 +79,7 @@ public final void set(int newValue) {
              * @since 1.6
              */
             public final void lazySet(int newValue) {
        -        unsafe.putOrderedInt(this, valueOffset, newValue);
        +        U.putOrderedInt(this, VALUE, newValue);
             }
         
             /**
        @@ -87,11 +89,7 @@ public final void lazySet(int newValue) {
              * @return the previous value
              */
             public final int getAndSet(int newValue) {
        -        for (;;) {
        -            int current = get();
        -            if (compareAndSet(current, newValue))
        -                return current;
        -        }
        +        return U.getAndSetInt(this, VALUE, newValue);
             }
         
             /**
        @@ -100,11 +98,11 @@ public final int getAndSet(int newValue) {
              *
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful. False return indicates that
        +     * @return {@code true} if successful. False return indicates that
              * the actual value was not equal to the expected value.
              */
             public final boolean compareAndSet(int expect, int update) {
        -        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        +        return U.compareAndSwapInt(this, VALUE, expect, update);
             }
         
             /**
        @@ -117,10 +115,10 @@ public final boolean compareAndSet(int expect, int update) {
              *
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public final boolean weakCompareAndSet(int expect, int update) {
        -        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        +        return U.compareAndSwapInt(this, VALUE, expect, update);
             }
         
             /**
        @@ -129,12 +127,7 @@ public final boolean weakCompareAndSet(int expect, int update) {
              * @return the previous value
              */
             public final int getAndIncrement() {
        -        for (;;) {
        -            int current = get();
        -            int next = current + 1;
        -            if (compareAndSet(current, next))
        -                return current;
        -        }
        +        return U.getAndAddInt(this, VALUE, 1);
             }
         
             /**
        @@ -143,12 +136,7 @@ public final int getAndIncrement() {
              * @return the previous value
              */
             public final int getAndDecrement() {
        -        for (;;) {
        -            int current = get();
        -            int next = current - 1;
        -            if (compareAndSet(current, next))
        -                return current;
        -        }
        +        return U.getAndAddInt(this, VALUE, -1);
             }
         
             /**
        @@ -158,12 +146,7 @@ public final int getAndDecrement() {
              * @return the previous value
              */
             public final int getAndAdd(int delta) {
        -        for (;;) {
        -            int current = get();
        -            int next = current + delta;
        -            if (compareAndSet(current, next))
        -                return current;
        -        }
        +        return U.getAndAddInt(this, VALUE, delta);
             }
         
             /**
        @@ -172,12 +155,7 @@ public final int getAndAdd(int delta) {
              * @return the updated value
              */
             public final int incrementAndGet() {
        -        for (;;) {
        -            int current = get();
        -            int next = current + 1;
        -            if (compareAndSet(current, next))
        -                return next;
        -        }
        +        return U.getAndAddInt(this, VALUE, 1) + 1;
             }
         
             /**
        @@ -186,12 +164,7 @@ public final int incrementAndGet() {
              * @return the updated value
              */
             public final int decrementAndGet() {
        -        for (;;) {
        -            int current = get();
        -            int next = current - 1;
        -            if (compareAndSet(current, next))
        -                return next;
        -        }
        +        return U.getAndAddInt(this, VALUE, -1) - 1;
             }
         
             /**
        @@ -201,12 +174,93 @@ public final int decrementAndGet() {
              * @return the updated value
              */
             public final int addAndGet(int delta) {
        -        for (;;) {
        -            int current = get();
        -            int next = current + delta;
        -            if (compareAndSet(current, next))
        -                return next;
        -        }
        +        return U.getAndAddInt(this, VALUE, delta) + delta;
        +    }
        +
        +    /**
        +     * Atomically updates the current value with the results of
        +     * applying the given function, returning the previous value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.
        +     *
        +     * @param updateFunction a side-effect-free function
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final int getAndUpdate(IntUnaryOperator updateFunction) {
        +        int prev, next;
        +        do {
        +            prev = get();
        +            next = updateFunction.applyAsInt(prev);
        +        } while (!compareAndSet(prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the current value with the results of
        +     * applying the given function, returning the updated value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.
        +     *
        +     * @param updateFunction a side-effect-free function
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final int updateAndGet(IntUnaryOperator updateFunction) {
        +        int prev, next;
        +        do {
        +            prev = get();
        +            next = updateFunction.applyAsInt(prev);
        +        } while (!compareAndSet(prev, next));
        +        return next;
        +    }
        +
        +    /**
        +     * Atomically updates the current value with the results of
        +     * applying the given function to the current and given values,
        +     * returning the previous value. The function should be
        +     * side-effect-free, since it may be re-applied when attempted
        +     * updates fail due to contention among threads.  The function
        +     * is applied with the current value as its first argument,
        +     * and the given update as the second argument.
        +     *
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final int getAndAccumulate(int x,
        +                                      IntBinaryOperator accumulatorFunction) {
        +        int prev, next;
        +        do {
        +            prev = get();
        +            next = accumulatorFunction.applyAsInt(prev, x);
        +        } while (!compareAndSet(prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the current value with the results of
        +     * applying the given function to the current and given values,
        +     * returning the updated value. The function should be
        +     * side-effect-free, since it may be re-applied when attempted
        +     * updates fail due to contention among threads.  The function
        +     * is applied with the current value as its first argument,
        +     * and the given update as the second argument.
        +     *
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final int accumulateAndGet(int x,
        +                                      IntBinaryOperator accumulatorFunction) {
        +        int prev, next;
        +        do {
        +            prev = get();
        +            next = accumulatorFunction.applyAsInt(prev, x);
        +        } while (!compareAndSet(prev, next));
        +        return next;
             }
         
             /**
        @@ -219,6 +273,7 @@ public String toString() {
         
             /**
              * Returns the value of this {@code AtomicInteger} as an {@code int}.
        +     * Equivalent to {@link #get()}.
              */
             public int intValue() {
                 return get();
        @@ -227,6 +282,7 @@ public int intValue() {
             /**
              * Returns the value of this {@code AtomicInteger} as a {@code long}
              * after a widening primitive conversion.
        +     * @jls 5.1.2 Widening Primitive Conversions
              */
             public long longValue() {
                 return (long)get();
        @@ -235,6 +291,7 @@ public long longValue() {
             /**
              * Returns the value of this {@code AtomicInteger} as a {@code float}
              * after a widening primitive conversion.
        +     * @jls 5.1.2 Widening Primitive Conversions
              */
             public float floatValue() {
                 return (float)get();
        @@ -243,6 +300,7 @@ public float floatValue() {
             /**
              * Returns the value of this {@code AtomicInteger} as a {@code double}
              * after a widening primitive conversion.
        +     * @jls 5.1.2 Widening Primitive Conversions
              */
             public double doubleValue() {
                 return (double)get();
        diff --git a/luni/src/main/java/java/util/concurrent/atomic/AtomicIntegerArray.java b/luni/src/main/java/java/util/concurrent/atomic/AtomicIntegerArray.java
        index fd492d146..7597e53a9 100644
        --- a/luni/src/main/java/java/util/concurrent/atomic/AtomicIntegerArray.java
        +++ b/luni/src/main/java/java/util/concurrent/atomic/AtomicIntegerArray.java
        @@ -6,7 +6,8 @@
         
         package java.util.concurrent.atomic;
         
        -import sun.misc.Unsafe;
        +import java.util.function.IntBinaryOperator;
        +import java.util.function.IntUnaryOperator;
         
         /**
          * An {@code int} array in which elements may be updated atomically.
        @@ -19,16 +20,17 @@
         public class AtomicIntegerArray implements java.io.Serializable {
             private static final long serialVersionUID = 2862133569453604235L;
         
        -    private static final Unsafe unsafe = Unsafe.getUnsafe();
        -    private static final int base = unsafe.arrayBaseOffset(int[].class);
        -    private static final int shift;
        +    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
        +    private static final int ABASE;
        +    private static final int ASHIFT;
             private final int[] array;
         
             static {
        -        int scale = unsafe.arrayIndexScale(int[].class);
        +        ABASE = U.arrayBaseOffset(int[].class);
        +        int scale = U.arrayIndexScale(int[].class);
                 if ((scale & (scale - 1)) != 0)
        -            throw new Error("data type scale not a power of two");
        -        shift = 31 - Integer.numberOfLeadingZeros(scale);
        +            throw new Error("array index scale not a power of two");
        +        ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
             }
         
             private long checkedByteOffset(int i) {
        @@ -39,7 +41,7 @@ private long checkedByteOffset(int i) {
             }
         
             private static long byteOffset(int i) {
        -        return ((long) i << shift) + base;
        +        return ((long) i << ASHIFT) + ABASE;
             }
         
             /**
        @@ -84,7 +86,7 @@ public final int get(int i) {
             }
         
             private int getRaw(long offset) {
        -        return unsafe.getIntVolatile(array, offset);
        +        return U.getIntVolatile(array, offset);
             }
         
             /**
        @@ -94,7 +96,7 @@ private int getRaw(long offset) {
              * @param newValue the new value
              */
             public final void set(int i, int newValue) {
        -        unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
        +        U.putIntVolatile(array, checkedByteOffset(i), newValue);
             }
         
             /**
        @@ -105,7 +107,7 @@ public final void set(int i, int newValue) {
              * @since 1.6
              */
             public final void lazySet(int i, int newValue) {
        -        unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);
        +        U.putOrderedInt(array, checkedByteOffset(i), newValue);
             }
         
             /**
        @@ -117,12 +119,7 @@ public final void lazySet(int i, int newValue) {
              * @return the previous value
              */
             public final int getAndSet(int i, int newValue) {
        -        long offset = checkedByteOffset(i);
        -        while (true) {
        -            int current = getRaw(offset);
        -            if (compareAndSetRaw(offset, current, newValue))
        -                return current;
        -        }
        +        return U.getAndSetInt(array, checkedByteOffset(i), newValue);
             }
         
             /**
        @@ -132,7 +129,7 @@ public final int getAndSet(int i, int newValue) {
              * @param i the index
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful. False return indicates that
        +     * @return {@code true} if successful. False return indicates that
              * the actual value was not equal to the expected value.
              */
             public final boolean compareAndSet(int i, int expect, int update) {
        @@ -140,7 +137,7 @@ public final boolean compareAndSet(int i, int expect, int update) {
             }
         
             private boolean compareAndSetRaw(long offset, int expect, int update) {
        -        return unsafe.compareAndSwapInt(array, offset, expect, update);
        +        return U.compareAndSwapInt(array, offset, expect, update);
             }
         
             /**
        @@ -154,7 +151,7 @@ private boolean compareAndSetRaw(long offset, int expect, int update) {
              * @param i the index
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public final boolean weakCompareAndSet(int i, int expect, int update) {
                 return compareAndSet(i, expect, update);
        @@ -188,12 +185,7 @@ public final int getAndDecrement(int i) {
              * @return the previous value
              */
             public final int getAndAdd(int i, int delta) {
        -        long offset = checkedByteOffset(i);
        -        while (true) {
        -            int current = getRaw(offset);
        -            if (compareAndSetRaw(offset, current, current + delta))
        -                return current;
        -        }
        +        return U.getAndAddInt(array, checkedByteOffset(i), delta);
             }
         
             /**
        @@ -203,7 +195,7 @@ public final int getAndAdd(int i, int delta) {
              * @return the updated value
              */
             public final int incrementAndGet(int i) {
        -        return addAndGet(i, 1);
        +        return getAndAdd(i, 1) + 1;
             }
         
             /**
        @@ -213,7 +205,7 @@ public final int incrementAndGet(int i) {
              * @return the updated value
              */
             public final int decrementAndGet(int i) {
        -        return addAndGet(i, -1);
        +        return getAndAdd(i, -1) - 1;
             }
         
             /**
        @@ -224,13 +216,101 @@ public final int decrementAndGet(int i) {
              * @return the updated value
              */
             public final int addAndGet(int i, int delta) {
        +        return getAndAdd(i, delta) + delta;
        +    }
        +
        +    /**
        +     * Atomically updates the element at index {@code i} with the results
        +     * of applying the given function, returning the previous value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.
        +     *
        +     * @param i the index
        +     * @param updateFunction a side-effect-free function
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final int getAndUpdate(int i, IntUnaryOperator updateFunction) {
                 long offset = checkedByteOffset(i);
        -        while (true) {
        -            int current = getRaw(offset);
        -            int next = current + delta;
        -            if (compareAndSetRaw(offset, current, next))
        -                return next;
        -        }
        +        int prev, next;
        +        do {
        +            prev = getRaw(offset);
        +            next = updateFunction.applyAsInt(prev);
        +        } while (!compareAndSetRaw(offset, prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the element at index {@code i} with the results
        +     * of applying the given function, returning the updated value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.
        +     *
        +     * @param i the index
        +     * @param updateFunction a side-effect-free function
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final int updateAndGet(int i, IntUnaryOperator updateFunction) {
        +        long offset = checkedByteOffset(i);
        +        int prev, next;
        +        do {
        +            prev = getRaw(offset);
        +            next = updateFunction.applyAsInt(prev);
        +        } while (!compareAndSetRaw(offset, prev, next));
        +        return next;
        +    }
        +
        +    /**
        +     * Atomically updates the element at index {@code i} with the
        +     * results of applying the given function to the current and
        +     * given values, returning the previous value. The function should
        +     * be side-effect-free, since it may be re-applied when attempted
        +     * updates fail due to contention among threads.  The function is
        +     * applied with the current value at index {@code i} as its first
        +     * argument, and the given update as the second argument.
        +     *
        +     * @param i the index
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final int getAndAccumulate(int i, int x,
        +                                      IntBinaryOperator accumulatorFunction) {
        +        long offset = checkedByteOffset(i);
        +        int prev, next;
        +        do {
        +            prev = getRaw(offset);
        +            next = accumulatorFunction.applyAsInt(prev, x);
        +        } while (!compareAndSetRaw(offset, prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the element at index {@code i} with the
        +     * results of applying the given function to the current and
        +     * given values, returning the updated value. The function should
        +     * be side-effect-free, since it may be re-applied when attempted
        +     * updates fail due to contention among threads.  The function is
        +     * applied with the current value at index {@code i} as its first
        +     * argument, and the given update as the second argument.
        +     *
        +     * @param i the index
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final int accumulateAndGet(int i, int x,
        +                                      IntBinaryOperator accumulatorFunction) {
        +        long offset = checkedByteOffset(i);
        +        int prev, next;
        +        do {
        +            prev = getRaw(offset);
        +            next = accumulatorFunction.applyAsInt(prev, x);
        +        } while (!compareAndSetRaw(offset, prev, next));
        +        return next;
             }
         
             /**
        diff --git a/luni/src/main/java/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java b/luni/src/main/java/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java
        index 15e8840bc..9c5549170 100644
        --- a/luni/src/main/java/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java
        +++ b/luni/src/main/java/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java
        @@ -7,12 +7,15 @@
         package java.util.concurrent.atomic;
         
         import dalvik.system.VMStack; // android-added
        -import sun.misc.Unsafe;
         import java.lang.reflect.Field;
         import java.lang.reflect.Modifier;
         import java.security.AccessController;
        -import java.security.PrivilegedExceptionAction;
         import java.security.PrivilegedActionException;
        +import java.security.PrivilegedExceptionAction;
        +import java.util.function.IntBinaryOperator;
        +import java.util.function.IntUnaryOperator;
        +import sun.reflect.CallerSensitive;
        +import sun.reflect.Reflection;
         
         /**
          * A reflection-based utility that enables atomic updates to
        @@ -49,8 +52,11 @@ public abstract class AtomicIntegerFieldUpdater {
              * or the field is inaccessible to the caller according to Java language
              * access control
              */
        -    public static  AtomicIntegerFieldUpdater newUpdater(Class tclass, String fieldName) {
        -        return new AtomicIntegerFieldUpdaterImpl(tclass, fieldName);
        +    @CallerSensitive
        +    public static  AtomicIntegerFieldUpdater newUpdater(Class tclass,
        +                                                              String fieldName) {
        +        return new AtomicIntegerFieldUpdaterImpl
        +            (tclass, fieldName, VMStack.getStackClass1()); // android-changed
             }
         
             /**
        @@ -69,7 +75,7 @@ protected AtomicIntegerFieldUpdater() {
              * @param obj An object whose field to conditionally set
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful
        +     * @return {@code true} if successful
              * @throws ClassCastException if {@code obj} is not an instance
              * of the class possessing the field established in the constructor
              */
        @@ -89,7 +95,7 @@ protected AtomicIntegerFieldUpdater() {
              * @param obj An object whose field to conditionally set
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful
        +     * @return {@code true} if successful
              * @throws ClassCastException if {@code obj} is not an instance
              * of the class possessing the field established in the constructor
              */
        @@ -133,11 +139,11 @@ protected AtomicIntegerFieldUpdater() {
              * @return the previous value
              */
             public int getAndSet(T obj, int newValue) {
        -        for (;;) {
        -            int current = get(obj);
        -            if (compareAndSet(obj, current, newValue))
        -                return current;
        -        }
        +        int prev;
        +        do {
        +            prev = get(obj);
        +        } while (!compareAndSet(obj, prev, newValue));
        +        return prev;
             }
         
             /**
        @@ -148,12 +154,12 @@ public int getAndSet(T obj, int newValue) {
              * @return the previous value
              */
             public int getAndIncrement(T obj) {
        -        for (;;) {
        -            int current = get(obj);
        -            int next = current + 1;
        -            if (compareAndSet(obj, current, next))
        -                return current;
        -        }
        +        int prev, next;
        +        do {
        +            prev = get(obj);
        +            next = prev + 1;
        +        } while (!compareAndSet(obj, prev, next));
        +        return prev;
             }
         
             /**
        @@ -164,12 +170,12 @@ public int getAndIncrement(T obj) {
              * @return the previous value
              */
             public int getAndDecrement(T obj) {
        -        for (;;) {
        -            int current = get(obj);
        -            int next = current - 1;
        -            if (compareAndSet(obj, current, next))
        -                return current;
        -        }
        +        int prev, next;
        +        do {
        +            prev = get(obj);
        +            next = prev - 1;
        +        } while (!compareAndSet(obj, prev, next));
        +        return prev;
             }
         
             /**
        @@ -181,12 +187,12 @@ public int getAndDecrement(T obj) {
              * @return the previous value
              */
             public int getAndAdd(T obj, int delta) {
        -        for (;;) {
        -            int current = get(obj);
        -            int next = current + delta;
        -            if (compareAndSet(obj, current, next))
        -                return current;
        -        }
        +        int prev, next;
        +        do {
        +            prev = get(obj);
        +            next = prev + delta;
        +        } while (!compareAndSet(obj, prev, next));
        +        return prev;
             }
         
             /**
        @@ -197,12 +203,12 @@ public int getAndAdd(T obj, int delta) {
              * @return the updated value
              */
             public int incrementAndGet(T obj) {
        -        for (;;) {
        -            int current = get(obj);
        -            int next = current + 1;
        -            if (compareAndSet(obj, current, next))
        -                return next;
        -        }
        +        int prev, next;
        +        do {
        +            prev = get(obj);
        +            next = prev + 1;
        +        } while (!compareAndSet(obj, prev, next));
        +        return next;
             }
         
             /**
        @@ -213,12 +219,12 @@ public int incrementAndGet(T obj) {
              * @return the updated value
              */
             public int decrementAndGet(T obj) {
        -        for (;;) {
        -            int current = get(obj);
        -            int next = current - 1;
        -            if (compareAndSet(obj, current, next))
        -                return next;
        -        }
        +        int prev, next;
        +        do {
        +            prev = get(obj);
        +            next = prev - 1;
        +        } while (!compareAndSet(obj, prev, next));
        +        return next;
             }
         
             /**
        @@ -230,31 +236,131 @@ public int decrementAndGet(T obj) {
              * @return the updated value
              */
             public int addAndGet(T obj, int delta) {
        -        for (;;) {
        -            int current = get(obj);
        -            int next = current + delta;
        -            if (compareAndSet(obj, current, next))
        -                return next;
        -        }
        +        int prev, next;
        +        do {
        +            prev = get(obj);
        +            next = prev + delta;
        +        } while (!compareAndSet(obj, prev, next));
        +        return next;
             }
         
             /**
        -     * Standard hotspot implementation using intrinsics
        +     * Atomically updates the field of the given object managed by this updater
        +     * with the results of applying the given function, returning the previous
        +     * value. The function should be side-effect-free, since it may be
        +     * re-applied when attempted updates fail due to contention among threads.
        +     *
        +     * @param obj An object whose field to get and set
        +     * @param updateFunction a side-effect-free function
        +     * @return the previous value
        +     * @since 1.8
              */
        -    private static class AtomicIntegerFieldUpdaterImpl extends AtomicIntegerFieldUpdater {
        -        private static final Unsafe unsafe = Unsafe.getUnsafe();
        +    public final int getAndUpdate(T obj, IntUnaryOperator updateFunction) {
        +        int prev, next;
        +        do {
        +            prev = get(obj);
        +            next = updateFunction.applyAsInt(prev);
        +        } while (!compareAndSet(obj, prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the field of the given object managed by this updater
        +     * with the results of applying the given function, returning the updated
        +     * value. The function should be side-effect-free, since it may be
        +     * re-applied when attempted updates fail due to contention among threads.
        +     *
        +     * @param obj An object whose field to get and set
        +     * @param updateFunction a side-effect-free function
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final int updateAndGet(T obj, IntUnaryOperator updateFunction) {
        +        int prev, next;
        +        do {
        +            prev = get(obj);
        +            next = updateFunction.applyAsInt(prev);
        +        } while (!compareAndSet(obj, prev, next));
        +        return next;
        +    }
        +
        +    /**
        +     * Atomically updates the field of the given object managed by this
        +     * updater with the results of applying the given function to the
        +     * current and given values, returning the previous value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.  The
        +     * function is applied with the current value as its first argument,
        +     * and the given update as the second argument.
        +     *
        +     * @param obj An object whose field to get and set
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final int getAndAccumulate(T obj, int x,
        +                                      IntBinaryOperator accumulatorFunction) {
        +        int prev, next;
        +        do {
        +            prev = get(obj);
        +            next = accumulatorFunction.applyAsInt(prev, x);
        +        } while (!compareAndSet(obj, prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the field of the given object managed by this
        +     * updater with the results of applying the given function to the
        +     * current and given values, returning the updated value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.  The
        +     * function is applied with the current value as its first argument,
        +     * and the given update as the second argument.
        +     *
        +     * @param obj An object whose field to get and set
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final int accumulateAndGet(T obj, int x,
        +                                      IntBinaryOperator accumulatorFunction) {
        +        int prev, next;
        +        do {
        +            prev = get(obj);
        +            next = accumulatorFunction.applyAsInt(prev, x);
        +        } while (!compareAndSet(obj, prev, next));
        +        return next;
        +    }
        +
        +    /**
        +     * Standard hotspot implementation using intrinsics.
        +     */
        +    private static final class AtomicIntegerFieldUpdaterImpl
        +        extends AtomicIntegerFieldUpdater {
        +        private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
                 private final long offset;
        -        private final Class tclass;
        +        /**
        +         * if field is protected, the subclass constructing updater, else
        +         * the same as tclass
        +         */
                 private final Class cclass;
        +        /** class holding the field */
        +        private final Class tclass;
         
        -        AtomicIntegerFieldUpdaterImpl(final Class tclass, final String fieldName) {
        +        AtomicIntegerFieldUpdaterImpl(final Class tclass,
        +                                      final String fieldName,
        +                                      final Class caller) {
                     final Field field;
        -            final Class caller;
                     final int modifiers;
                     try {
        -                field = tclass.getDeclaredField(fieldName); // android-changed
        -                caller = VMStack.getStackClass2(); // android-changed
        -
        +                field = AccessController.doPrivileged(
        +                    new PrivilegedExceptionAction() {
        +                        public Field run() throws NoSuchFieldException {
        +                            return tclass.getDeclaredField(fieldName);
        +                        }
        +                    });
                         modifiers = field.getModifiers();
                         // BEGIN android-removed
                         // sun.reflect.misc.ReflectUtil.ensureMemberAccess(
        @@ -263,7 +369,7 @@ private static class AtomicIntegerFieldUpdaterImpl extends AtomicIntegerField
                         // ClassLoader ccl = caller.getClassLoader();
                         // if ((ccl != null) && (ccl != cl) &&
                         //     ((cl == null) || !isAncestor(cl, ccl))) {
        -                //   sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
        +                //     sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
                         // }
                         // END android-removed
                     // BEGIN android-removed
        @@ -274,17 +380,15 @@ private static class AtomicIntegerFieldUpdaterImpl extends AtomicIntegerField
                         throw new RuntimeException(ex);
                     }
         
        -            Class fieldt = field.getType();
        -            if (fieldt != int.class)
        +            if (field.getType() != int.class)
                         throw new IllegalArgumentException("Must be integer type");
         
                     if (!Modifier.isVolatile(modifiers))
                         throw new IllegalArgumentException("Must be volatile type");
         
        -            this.cclass = (Modifier.isProtected(modifiers) &&
        -                           caller != tclass) ? caller : null;
        +            this.cclass = (Modifier.isProtected(modifiers)) ? caller : tclass;
                     this.tclass = tclass;
        -            offset = unsafe.objectFieldOffset(field);
        +            this.offset = U.objectFieldOffset(field);
                 }
         
                 // BEGIN android-removed
        @@ -293,7 +397,7 @@ private static class AtomicIntegerFieldUpdaterImpl extends AtomicIntegerField
                 //  * classloader's delegation chain.
                 //  * Equivalent to the inaccessible: first.isAncestor(second).
                 //  */
        -        //  private static boolean isAncestor(ClassLoader first, ClassLoader second) {
        +        // private static boolean isAncestor(ClassLoader first, ClassLoader second) {
                 //     ClassLoader acl = first;
                 //     do {
                 //         acl = acl.getParent();
        @@ -304,51 +408,88 @@ private static class AtomicIntegerFieldUpdaterImpl extends AtomicIntegerField
                 //     return false;
                 // }
                 // END android-removed
        -        private void fullCheck(T obj) {
        -            if (!tclass.isInstance(obj))
        +
        +        /**
        +         * Checks that target argument is instance of cclass.  On
        +         * failure, throws cause.
        +         */
        +        private final void accessCheck(T obj) {
        +            if (!cclass.isInstance(obj))
        +                throwAccessCheckException(obj);
        +        }
        +
        +        /**
        +         * Throws access exception if accessCheck failed due to
        +         * protected access, else ClassCastException.
        +         */
        +        private final void throwAccessCheckException(T obj) {
        +            if (cclass == tclass)
                         throw new ClassCastException();
        -            if (cclass != null)
        -                ensureProtectedAccess(obj);
        +            else
        +                throw new RuntimeException(
        +                    new IllegalAccessException(
        +                        "Class " +
        +                        cclass.getName() +
        +                        " can not access a protected member of class " +
        +                        tclass.getName() +
        +                        " using an instance of " +
        +                        obj.getClass().getName()));
                 }
         
        -        public boolean compareAndSet(T obj, int expect, int update) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
        -            return unsafe.compareAndSwapInt(obj, offset, expect, update);
        +        public final boolean compareAndSet(T obj, int expect, int update) {
        +            accessCheck(obj);
        +            return U.compareAndSwapInt(obj, offset, expect, update);
                 }
         
        -        public boolean weakCompareAndSet(T obj, int expect, int update) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
        -            return unsafe.compareAndSwapInt(obj, offset, expect, update);
        +        public final boolean weakCompareAndSet(T obj, int expect, int update) {
        +            accessCheck(obj);
        +            return U.compareAndSwapInt(obj, offset, expect, update);
                 }
         
        -        public void set(T obj, int newValue) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
        -            unsafe.putIntVolatile(obj, offset, newValue);
        +        public final void set(T obj, int newValue) {
        +            accessCheck(obj);
        +            U.putIntVolatile(obj, offset, newValue);
                 }
         
        -        public void lazySet(T obj, int newValue) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
        -            unsafe.putOrderedInt(obj, offset, newValue);
        +        public final void lazySet(T obj, int newValue) {
        +            accessCheck(obj);
        +            U.putOrderedInt(obj, offset, newValue);
                 }
         
                 public final int get(T obj) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
        -            return unsafe.getIntVolatile(obj, offset);
        +            accessCheck(obj);
        +            return U.getIntVolatile(obj, offset);
                 }
         
        -        private void ensureProtectedAccess(T obj) {
        -            if (cclass.isInstance(obj)) {
        -                return;
        -            }
        -            throw new RuntimeException(
        -                new IllegalAccessException("Class " +
        -                    cclass.getName() +
        -                    " can not access a protected member of class " +
        -                    tclass.getName() +
        -                    " using an instance of " +
        -                    obj.getClass().getName()
        -                )
        -            );
        +        public final int getAndSet(T obj, int newValue) {
        +            accessCheck(obj);
        +            return U.getAndSetInt(obj, offset, newValue);
        +        }
        +
        +        public final int getAndAdd(T obj, int delta) {
        +            accessCheck(obj);
        +            return U.getAndAddInt(obj, offset, delta);
        +        }
        +
        +        public final int getAndIncrement(T obj) {
        +            return getAndAdd(obj, 1);
        +        }
        +
        +        public final int getAndDecrement(T obj) {
        +            return getAndAdd(obj, -1);
                 }
        +
        +        public final int incrementAndGet(T obj) {
        +            return getAndAdd(obj, 1) + 1;
        +        }
        +
        +        public final int decrementAndGet(T obj) {
        +            return getAndAdd(obj, -1) - 1;
        +        }
        +
        +        public final int addAndGet(T obj, int delta) {
        +            return getAndAdd(obj, delta) + delta;
        +        }
        +
             }
         }
        diff --git a/luni/src/main/java/java/util/concurrent/atomic/AtomicLong.java b/luni/src/main/java/java/util/concurrent/atomic/AtomicLong.java
        index ab2961a99..fdb5f553b 100644
        --- a/luni/src/main/java/java/util/concurrent/atomic/AtomicLong.java
        +++ b/luni/src/main/java/java/util/concurrent/atomic/AtomicLong.java
        @@ -6,7 +6,8 @@
         
         package java.util.concurrent.atomic;
         
        -import sun.misc.Unsafe;
        +import java.util.function.LongBinaryOperator;
        +import java.util.function.LongUnaryOperator;
         
         /**
          * A {@code long} value that may be updated atomically.  See the
        @@ -24,9 +25,8 @@
         public class AtomicLong extends Number implements java.io.Serializable {
             private static final long serialVersionUID = 1927816293512124184L;
         
        -    // setup to use Unsafe.compareAndSwapLong for updates
        -    private static final Unsafe unsafe = Unsafe.getUnsafe();
        -    private static final long valueOffset;
        +    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
        +    private static final long VALUE;
         
             /**
              * Records whether the underlying JVM supports lockless
        @@ -44,9 +44,11 @@ public class AtomicLong extends Number implements java.io.Serializable {
         
             static {
                 try {
        -            valueOffset = unsafe.objectFieldOffset
        +            VALUE = U.objectFieldOffset
                         (AtomicLong.class.getDeclaredField("value"));
        -        } catch (Exception ex) { throw new Error(ex); }
        +        } catch (ReflectiveOperationException e) {
        +            throw new Error(e);
        +        }
             }
         
             private volatile long value;
        @@ -81,7 +83,9 @@ public final long get() {
              * @param newValue the new value
              */
             public final void set(long newValue) {
        -        value = newValue;
        +        // Use putLongVolatile instead of ordinary volatile store when
        +        // using compareAndSwapLong, for sake of some 32bit systems.
        +        U.putLongVolatile(this, VALUE, newValue);
             }
         
             /**
        @@ -91,7 +95,7 @@ public final void set(long newValue) {
              * @since 1.6
              */
             public final void lazySet(long newValue) {
        -        unsafe.putOrderedLong(this, valueOffset, newValue);
        +        U.putOrderedLong(this, VALUE, newValue);
             }
         
             /**
        @@ -101,11 +105,7 @@ public final void lazySet(long newValue) {
              * @return the previous value
              */
             public final long getAndSet(long newValue) {
        -        while (true) {
        -            long current = get();
        -            if (compareAndSet(current, newValue))
        -                return current;
        -        }
        +        return U.getAndSetLong(this, VALUE, newValue);
             }
         
             /**
        @@ -114,11 +114,11 @@ public final long getAndSet(long newValue) {
              *
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful. False return indicates that
        +     * @return {@code true} if successful. False return indicates that
              * the actual value was not equal to the expected value.
              */
             public final boolean compareAndSet(long expect, long update) {
        -        return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
        +        return U.compareAndSwapLong(this, VALUE, expect, update);
             }
         
             /**
        @@ -131,10 +131,10 @@ public final boolean compareAndSet(long expect, long update) {
              *
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public final boolean weakCompareAndSet(long expect, long update) {
        -        return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
        +        return U.compareAndSwapLong(this, VALUE, expect, update);
             }
         
             /**
        @@ -143,12 +143,7 @@ public final boolean weakCompareAndSet(long expect, long update) {
              * @return the previous value
              */
             public final long getAndIncrement() {
        -        while (true) {
        -            long current = get();
        -            long next = current + 1;
        -            if (compareAndSet(current, next))
        -                return current;
        -        }
        +        return U.getAndAddLong(this, VALUE, 1L);
             }
         
             /**
        @@ -157,12 +152,7 @@ public final long getAndIncrement() {
              * @return the previous value
              */
             public final long getAndDecrement() {
        -        while (true) {
        -            long current = get();
        -            long next = current - 1;
        -            if (compareAndSet(current, next))
        -                return current;
        -        }
        +        return U.getAndAddLong(this, VALUE, -1L);
             }
         
             /**
        @@ -172,12 +162,7 @@ public final long getAndDecrement() {
              * @return the previous value
              */
             public final long getAndAdd(long delta) {
        -        while (true) {
        -            long current = get();
        -            long next = current + delta;
        -            if (compareAndSet(current, next))
        -                return current;
        -        }
        +        return U.getAndAddLong(this, VALUE, delta);
             }
         
             /**
        @@ -186,12 +171,7 @@ public final long getAndAdd(long delta) {
              * @return the updated value
              */
             public final long incrementAndGet() {
        -        for (;;) {
        -            long current = get();
        -            long next = current + 1;
        -            if (compareAndSet(current, next))
        -                return next;
        -        }
        +        return U.getAndAddLong(this, VALUE, 1L) + 1L;
             }
         
             /**
        @@ -200,12 +180,7 @@ public final long incrementAndGet() {
              * @return the updated value
              */
             public final long decrementAndGet() {
        -        for (;;) {
        -            long current = get();
        -            long next = current - 1;
        -            if (compareAndSet(current, next))
        -                return next;
        -        }
        +        return U.getAndAddLong(this, VALUE, -1L) - 1L;
             }
         
             /**
        @@ -215,12 +190,93 @@ public final long decrementAndGet() {
              * @return the updated value
              */
             public final long addAndGet(long delta) {
        -        for (;;) {
        -            long current = get();
        -            long next = current + delta;
        -            if (compareAndSet(current, next))
        -                return next;
        -        }
        +        return U.getAndAddLong(this, VALUE, delta) + delta;
        +    }
        +
        +    /**
        +     * Atomically updates the current value with the results of
        +     * applying the given function, returning the previous value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.
        +     *
        +     * @param updateFunction a side-effect-free function
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final long getAndUpdate(LongUnaryOperator updateFunction) {
        +        long prev, next;
        +        do {
        +            prev = get();
        +            next = updateFunction.applyAsLong(prev);
        +        } while (!compareAndSet(prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the current value with the results of
        +     * applying the given function, returning the updated value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.
        +     *
        +     * @param updateFunction a side-effect-free function
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final long updateAndGet(LongUnaryOperator updateFunction) {
        +        long prev, next;
        +        do {
        +            prev = get();
        +            next = updateFunction.applyAsLong(prev);
        +        } while (!compareAndSet(prev, next));
        +        return next;
        +    }
        +
        +    /**
        +     * Atomically updates the current value with the results of
        +     * applying the given function to the current and given values,
        +     * returning the previous value. The function should be
        +     * side-effect-free, since it may be re-applied when attempted
        +     * updates fail due to contention among threads.  The function
        +     * is applied with the current value as its first argument,
        +     * and the given update as the second argument.
        +     *
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final long getAndAccumulate(long x,
        +                                       LongBinaryOperator accumulatorFunction) {
        +        long prev, next;
        +        do {
        +            prev = get();
        +            next = accumulatorFunction.applyAsLong(prev, x);
        +        } while (!compareAndSet(prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the current value with the results of
        +     * applying the given function to the current and given values,
        +     * returning the updated value. The function should be
        +     * side-effect-free, since it may be re-applied when attempted
        +     * updates fail due to contention among threads.  The function
        +     * is applied with the current value as its first argument,
        +     * and the given update as the second argument.
        +     *
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final long accumulateAndGet(long x,
        +                                       LongBinaryOperator accumulatorFunction) {
        +        long prev, next;
        +        do {
        +            prev = get();
        +            next = accumulatorFunction.applyAsLong(prev, x);
        +        } while (!compareAndSet(prev, next));
        +        return next;
             }
         
             /**
        @@ -234,6 +290,7 @@ public String toString() {
             /**
              * Returns the value of this {@code AtomicLong} as an {@code int}
              * after a narrowing primitive conversion.
        +     * @jls 5.1.3 Narrowing Primitive Conversions
              */
             public int intValue() {
                 return (int)get();
        @@ -241,6 +298,7 @@ public int intValue() {
         
             /**
              * Returns the value of this {@code AtomicLong} as a {@code long}.
        +     * Equivalent to {@link #get()}.
              */
             public long longValue() {
                 return get();
        @@ -249,6 +307,7 @@ public long longValue() {
             /**
              * Returns the value of this {@code AtomicLong} as a {@code float}
              * after a widening primitive conversion.
        +     * @jls 5.1.2 Widening Primitive Conversions
              */
             public float floatValue() {
                 return (float)get();
        @@ -257,6 +316,7 @@ public float floatValue() {
             /**
              * Returns the value of this {@code AtomicLong} as a {@code double}
              * after a widening primitive conversion.
        +     * @jls 5.1.2 Widening Primitive Conversions
              */
             public double doubleValue() {
                 return (double)get();
        diff --git a/luni/src/main/java/java/util/concurrent/atomic/AtomicLongArray.java b/luni/src/main/java/java/util/concurrent/atomic/AtomicLongArray.java
        index b7f3d1e06..8da150142 100644
        --- a/luni/src/main/java/java/util/concurrent/atomic/AtomicLongArray.java
        +++ b/luni/src/main/java/java/util/concurrent/atomic/AtomicLongArray.java
        @@ -6,7 +6,8 @@
         
         package java.util.concurrent.atomic;
         
        -import sun.misc.Unsafe;
        +import java.util.function.LongBinaryOperator;
        +import java.util.function.LongUnaryOperator;
         
         /**
          * A {@code long} array in which elements may be updated atomically.
        @@ -18,16 +19,17 @@
         public class AtomicLongArray implements java.io.Serializable {
             private static final long serialVersionUID = -2308431214976778248L;
         
        -    private static final Unsafe unsafe = Unsafe.getUnsafe();
        -    private static final int base = unsafe.arrayBaseOffset(long[].class);
        -    private static final int shift;
        +    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
        +    private static final int ABASE;
        +    private static final int ASHIFT;
             private final long[] array;
         
             static {
        -        int scale = unsafe.arrayIndexScale(long[].class);
        +        ABASE = U.arrayBaseOffset(long[].class);
        +        int scale = U.arrayIndexScale(long[].class);
                 if ((scale & (scale - 1)) != 0)
        -            throw new Error("data type scale not a power of two");
        -        shift = 31 - Integer.numberOfLeadingZeros(scale);
        +            throw new Error("array index scale not a power of two");
        +        ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
             }
         
             private long checkedByteOffset(int i) {
        @@ -38,7 +40,7 @@ private long checkedByteOffset(int i) {
             }
         
             private static long byteOffset(int i) {
        -        return ((long) i << shift) + base;
        +        return ((long) i << ASHIFT) + ABASE;
             }
         
             /**
        @@ -83,7 +85,7 @@ public final long get(int i) {
             }
         
             private long getRaw(long offset) {
        -        return unsafe.getLongVolatile(array, offset);
        +        return U.getLongVolatile(array, offset);
             }
         
             /**
        @@ -93,7 +95,7 @@ private long getRaw(long offset) {
              * @param newValue the new value
              */
             public final void set(int i, long newValue) {
        -        unsafe.putLongVolatile(array, checkedByteOffset(i), newValue);
        +        U.putLongVolatile(array, checkedByteOffset(i), newValue);
             }
         
             /**
        @@ -104,7 +106,7 @@ public final void set(int i, long newValue) {
              * @since 1.6
              */
             public final void lazySet(int i, long newValue) {
        -        unsafe.putOrderedLong(array, checkedByteOffset(i), newValue);
        +        U.putOrderedLong(array, checkedByteOffset(i), newValue);
             }
         
             /**
        @@ -116,12 +118,7 @@ public final void lazySet(int i, long newValue) {
              * @return the previous value
              */
             public final long getAndSet(int i, long newValue) {
        -        long offset = checkedByteOffset(i);
        -        while (true) {
        -            long current = getRaw(offset);
        -            if (compareAndSetRaw(offset, current, newValue))
        -                return current;
        -        }
        +        return U.getAndSetLong(array, checkedByteOffset(i), newValue);
             }
         
             /**
        @@ -131,7 +128,7 @@ public final long getAndSet(int i, long newValue) {
              * @param i the index
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful. False return indicates that
        +     * @return {@code true} if successful. False return indicates that
              * the actual value was not equal to the expected value.
              */
             public final boolean compareAndSet(int i, long expect, long update) {
        @@ -139,7 +136,7 @@ public final boolean compareAndSet(int i, long expect, long update) {
             }
         
             private boolean compareAndSetRaw(long offset, long expect, long update) {
        -        return unsafe.compareAndSwapLong(array, offset, expect, update);
        +        return U.compareAndSwapLong(array, offset, expect, update);
             }
         
             /**
        @@ -153,7 +150,7 @@ private boolean compareAndSetRaw(long offset, long expect, long update) {
              * @param i the index
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public final boolean weakCompareAndSet(int i, long expect, long update) {
                 return compareAndSet(i, expect, update);
        @@ -187,12 +184,7 @@ public final long getAndDecrement(int i) {
              * @return the previous value
              */
             public final long getAndAdd(int i, long delta) {
        -        long offset = checkedByteOffset(i);
        -        while (true) {
        -            long current = getRaw(offset);
        -            if (compareAndSetRaw(offset, current, current + delta))
        -                return current;
        -        }
        +        return U.getAndAddLong(array, checkedByteOffset(i), delta);
             }
         
             /**
        @@ -202,7 +194,7 @@ public final long getAndAdd(int i, long delta) {
              * @return the updated value
              */
             public final long incrementAndGet(int i) {
        -        return addAndGet(i, 1);
        +        return getAndAdd(i, 1) + 1;
             }
         
             /**
        @@ -212,7 +204,7 @@ public final long incrementAndGet(int i) {
              * @return the updated value
              */
             public final long decrementAndGet(int i) {
        -        return addAndGet(i, -1);
        +        return getAndAdd(i, -1) - 1;
             }
         
             /**
        @@ -223,13 +215,101 @@ public final long decrementAndGet(int i) {
              * @return the updated value
              */
             public long addAndGet(int i, long delta) {
        +        return getAndAdd(i, delta) + delta;
        +    }
        +
        +    /**
        +     * Atomically updates the element at index {@code i} with the results
        +     * of applying the given function, returning the previous value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.
        +     *
        +     * @param i the index
        +     * @param updateFunction a side-effect-free function
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final long getAndUpdate(int i, LongUnaryOperator updateFunction) {
                 long offset = checkedByteOffset(i);
        -        while (true) {
        -            long current = getRaw(offset);
        -            long next = current + delta;
        -            if (compareAndSetRaw(offset, current, next))
        -                return next;
        -        }
        +        long prev, next;
        +        do {
        +            prev = getRaw(offset);
        +            next = updateFunction.applyAsLong(prev);
        +        } while (!compareAndSetRaw(offset, prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the element at index {@code i} with the results
        +     * of applying the given function, returning the updated value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.
        +     *
        +     * @param i the index
        +     * @param updateFunction a side-effect-free function
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final long updateAndGet(int i, LongUnaryOperator updateFunction) {
        +        long offset = checkedByteOffset(i);
        +        long prev, next;
        +        do {
        +            prev = getRaw(offset);
        +            next = updateFunction.applyAsLong(prev);
        +        } while (!compareAndSetRaw(offset, prev, next));
        +        return next;
        +    }
        +
        +    /**
        +     * Atomically updates the element at index {@code i} with the
        +     * results of applying the given function to the current and
        +     * given values, returning the previous value. The function should
        +     * be side-effect-free, since it may be re-applied when attempted
        +     * updates fail due to contention among threads.  The function is
        +     * applied with the current value at index {@code i} as its first
        +     * argument, and the given update as the second argument.
        +     *
        +     * @param i the index
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final long getAndAccumulate(int i, long x,
        +                                      LongBinaryOperator accumulatorFunction) {
        +        long offset = checkedByteOffset(i);
        +        long prev, next;
        +        do {
        +            prev = getRaw(offset);
        +            next = accumulatorFunction.applyAsLong(prev, x);
        +        } while (!compareAndSetRaw(offset, prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the element at index {@code i} with the
        +     * results of applying the given function to the current and
        +     * given values, returning the updated value. The function should
        +     * be side-effect-free, since it may be re-applied when attempted
        +     * updates fail due to contention among threads.  The function is
        +     * applied with the current value at index {@code i} as its first
        +     * argument, and the given update as the second argument.
        +     *
        +     * @param i the index
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final long accumulateAndGet(int i, long x,
        +                                      LongBinaryOperator accumulatorFunction) {
        +        long offset = checkedByteOffset(i);
        +        long prev, next;
        +        do {
        +            prev = getRaw(offset);
        +            next = accumulatorFunction.applyAsLong(prev, x);
        +        } while (!compareAndSetRaw(offset, prev, next));
        +        return next;
             }
         
             /**
        diff --git a/luni/src/main/java/java/util/concurrent/atomic/AtomicLongFieldUpdater.java b/luni/src/main/java/java/util/concurrent/atomic/AtomicLongFieldUpdater.java
        index 65bd45264..c1a02b57e 100644
        --- a/luni/src/main/java/java/util/concurrent/atomic/AtomicLongFieldUpdater.java
        +++ b/luni/src/main/java/java/util/concurrent/atomic/AtomicLongFieldUpdater.java
        @@ -7,9 +7,15 @@
         package java.util.concurrent.atomic;
         
         import dalvik.system.VMStack; // android-added
        -import sun.misc.Unsafe;
         import java.lang.reflect.Field;
         import java.lang.reflect.Modifier;
        +import java.security.AccessController;
        +import java.security.PrivilegedActionException;
        +import java.security.PrivilegedExceptionAction;
        +import java.util.function.LongBinaryOperator;
        +import java.util.function.LongUnaryOperator;
        +import sun.reflect.CallerSensitive;
        +import sun.reflect.Reflection;
         
         /**
          * A reflection-based utility that enables atomic updates to
        @@ -46,11 +52,14 @@ public abstract class AtomicLongFieldUpdater {
              * or the field is inaccessible to the caller according to Java language
              * access control
              */
        -    public static  AtomicLongFieldUpdater newUpdater(Class tclass, String fieldName) {
        +    @CallerSensitive
        +    public static  AtomicLongFieldUpdater newUpdater(Class tclass,
        +                                                           String fieldName) {
        +      Class caller = VMStack.getStackClass1(); // android-changed
                 if (AtomicLong.VM_SUPPORTS_LONG_CAS)
        -            return new CASUpdater(tclass, fieldName);
        +            return new CASUpdater(tclass, fieldName, caller);
                 else
        -            return new LockedUpdater(tclass, fieldName);
        +            return new LockedUpdater(tclass, fieldName, caller);
             }
         
             /**
        @@ -69,7 +78,7 @@ protected AtomicLongFieldUpdater() {
              * @param obj An object whose field to conditionally set
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful
        +     * @return {@code true} if successful
              * @throws ClassCastException if {@code obj} is not an instance
              * of the class possessing the field established in the constructor
              */
        @@ -89,7 +98,7 @@ protected AtomicLongFieldUpdater() {
              * @param obj An object whose field to conditionally set
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful
        +     * @return {@code true} if successful
              * @throws ClassCastException if {@code obj} is not an instance
              * of the class possessing the field established in the constructor
              */
        @@ -133,11 +142,11 @@ protected AtomicLongFieldUpdater() {
              * @return the previous value
              */
             public long getAndSet(T obj, long newValue) {
        -        for (;;) {
        -            long current = get(obj);
        -            if (compareAndSet(obj, current, newValue))
        -                return current;
        -        }
        +        long prev;
        +        do {
        +            prev = get(obj);
        +        } while (!compareAndSet(obj, prev, newValue));
        +        return prev;
             }
         
             /**
        @@ -148,12 +157,12 @@ public long getAndSet(T obj, long newValue) {
              * @return the previous value
              */
             public long getAndIncrement(T obj) {
        -        for (;;) {
        -            long current = get(obj);
        -            long next = current + 1;
        -            if (compareAndSet(obj, current, next))
        -                return current;
        -        }
        +        long prev, next;
        +        do {
        +            prev = get(obj);
        +            next = prev + 1;
        +        } while (!compareAndSet(obj, prev, next));
        +        return prev;
             }
         
             /**
        @@ -164,12 +173,12 @@ public long getAndIncrement(T obj) {
              * @return the previous value
              */
             public long getAndDecrement(T obj) {
        -        for (;;) {
        -            long current = get(obj);
        -            long next = current - 1;
        -            if (compareAndSet(obj, current, next))
        -                return current;
        -        }
        +        long prev, next;
        +        do {
        +            prev = get(obj);
        +            next = prev - 1;
        +        } while (!compareAndSet(obj, prev, next));
        +        return prev;
             }
         
             /**
        @@ -181,12 +190,12 @@ public long getAndDecrement(T obj) {
              * @return the previous value
              */
             public long getAndAdd(T obj, long delta) {
        -        for (;;) {
        -            long current = get(obj);
        -            long next = current + delta;
        -            if (compareAndSet(obj, current, next))
        -                return current;
        -        }
        +        long prev, next;
        +        do {
        +            prev = get(obj);
        +            next = prev + delta;
        +        } while (!compareAndSet(obj, prev, next));
        +        return prev;
             }
         
             /**
        @@ -197,12 +206,12 @@ public long getAndAdd(T obj, long delta) {
              * @return the updated value
              */
             public long incrementAndGet(T obj) {
        -        for (;;) {
        -            long current = get(obj);
        -            long next = current + 1;
        -            if (compareAndSet(obj, current, next))
        -                return next;
        -        }
        +        long prev, next;
        +        do {
        +            prev = get(obj);
        +            next = prev + 1;
        +        } while (!compareAndSet(obj, prev, next));
        +        return next;
             }
         
             /**
        @@ -213,12 +222,12 @@ public long incrementAndGet(T obj) {
              * @return the updated value
              */
             public long decrementAndGet(T obj) {
        -        for (;;) {
        -            long current = get(obj);
        -            long next = current - 1;
        -            if (compareAndSet(obj, current, next))
        -                return next;
        -        }
        +        long prev, next;
        +        do {
        +            prev = get(obj);
        +            next = prev - 1;
        +        } while (!compareAndSet(obj, prev, next));
        +        return next;
             }
         
             /**
        @@ -230,27 +239,121 @@ public long decrementAndGet(T obj) {
              * @return the updated value
              */
             public long addAndGet(T obj, long delta) {
        -        for (;;) {
        -            long current = get(obj);
        -            long next = current + delta;
        -            if (compareAndSet(obj, current, next))
        -                return next;
        -        }
        +        long prev, next;
        +        do {
        +            prev = get(obj);
        +            next = prev + delta;
        +        } while (!compareAndSet(obj, prev, next));
        +        return next;
        +    }
        +
        +    /**
        +     * Atomically updates the field of the given object managed by this updater
        +     * with the results of applying the given function, returning the previous
        +     * value. The function should be side-effect-free, since it may be
        +     * re-applied when attempted updates fail due to contention among threads.
        +     *
        +     * @param obj An object whose field to get and set
        +     * @param updateFunction a side-effect-free function
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final long getAndUpdate(T obj, LongUnaryOperator updateFunction) {
        +        long prev, next;
        +        do {
        +            prev = get(obj);
        +            next = updateFunction.applyAsLong(prev);
        +        } while (!compareAndSet(obj, prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the field of the given object managed by this updater
        +     * with the results of applying the given function, returning the updated
        +     * value. The function should be side-effect-free, since it may be
        +     * re-applied when attempted updates fail due to contention among threads.
        +     *
        +     * @param obj An object whose field to get and set
        +     * @param updateFunction a side-effect-free function
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final long updateAndGet(T obj, LongUnaryOperator updateFunction) {
        +        long prev, next;
        +        do {
        +            prev = get(obj);
        +            next = updateFunction.applyAsLong(prev);
        +        } while (!compareAndSet(obj, prev, next));
        +        return next;
        +    }
        +
        +    /**
        +     * Atomically updates the field of the given object managed by this
        +     * updater with the results of applying the given function to the
        +     * current and given values, returning the previous value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.  The
        +     * function is applied with the current value as its first argument,
        +     * and the given update as the second argument.
        +     *
        +     * @param obj An object whose field to get and set
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final long getAndAccumulate(T obj, long x,
        +                                       LongBinaryOperator accumulatorFunction) {
        +        long prev, next;
        +        do {
        +            prev = get(obj);
        +            next = accumulatorFunction.applyAsLong(prev, x);
        +        } while (!compareAndSet(obj, prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the field of the given object managed by this
        +     * updater with the results of applying the given function to the
        +     * current and given values, returning the updated value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.  The
        +     * function is applied with the current value as its first argument,
        +     * and the given update as the second argument.
        +     *
        +     * @param obj An object whose field to get and set
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final long accumulateAndGet(T obj, long x,
        +                                       LongBinaryOperator accumulatorFunction) {
        +        long prev, next;
        +        do {
        +            prev = get(obj);
        +            next = accumulatorFunction.applyAsLong(prev, x);
        +        } while (!compareAndSet(obj, prev, next));
        +        return next;
             }
         
        -    private static class CASUpdater extends AtomicLongFieldUpdater {
        -        private static final Unsafe unsafe = Unsafe.getUnsafe();
        +    private static final class CASUpdater extends AtomicLongFieldUpdater {
        +        private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
                 private final long offset;
        -        private final Class tclass;
        +        /**
        +         * if field is protected, the subclass constructing updater, else
        +         * the same as tclass
        +         */
                 private final Class cclass;
        +        /** class holding the field */
        +        private final Class tclass;
         
        -        CASUpdater(final Class tclass, final String fieldName) {
        +        CASUpdater(final Class tclass, final String fieldName,
        +                   final Class caller) {
                     final Field field;
        -            final Class caller;
                     final int modifiers;
                     try {
                         field = tclass.getDeclaredField(fieldName); // android-changed
        -                caller = VMStack.getStackClass2(); // android-changed
                         modifiers = field.getModifiers();
                         // BEGIN android-removed
                         // sun.reflect.misc.ReflectUtil.ensureMemberAccess(
        @@ -259,92 +362,128 @@ private static class CASUpdater extends AtomicLongFieldUpdater {
                         // ClassLoader ccl = caller.getClassLoader();
                         // if ((ccl != null) && (ccl != cl) &&
                         //     ((cl == null) || !isAncestor(cl, ccl))) {
        -                //   sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
        +                //     sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
                         // }
                         // END android-removed
                     // BEGIN android-removed
                     // } catch (PrivilegedActionException pae) {
        -            //    throw new RuntimeException(pae.getException());
        +            //     throw new RuntimeException(pae.getException());
                     // END android-removed
                     } catch (Exception ex) {
                         throw new RuntimeException(ex);
                     }
         
        -            Class fieldt = field.getType();
        -            if (fieldt != long.class)
        +            if (field.getType() != long.class)
                         throw new IllegalArgumentException("Must be long type");
         
                     if (!Modifier.isVolatile(modifiers))
                         throw new IllegalArgumentException("Must be volatile type");
         
        -            this.cclass = (Modifier.isProtected(modifiers) &&
        -                           caller != tclass) ? caller : null;
        +            this.cclass = (Modifier.isProtected(modifiers)) ? caller : tclass;
                     this.tclass = tclass;
        -            offset = unsafe.objectFieldOffset(field);
        +            this.offset = U.objectFieldOffset(field);
        +        }
        +
        +        /**
        +         * Checks that target argument is instance of cclass.  On
        +         * failure, throws cause.
        +         */
        +        private final void accessCheck(T obj) {
        +            if (!cclass.isInstance(obj))
        +                throwAccessCheckException(obj);
                 }
         
        -        private void fullCheck(T obj) {
        -            if (!tclass.isInstance(obj))
        +        /**
        +         * Throws access exception if accessCheck failed due to
        +         * protected access, else ClassCastException.
        +         */
        +        private final void throwAccessCheckException(T obj) {
        +            if (cclass == tclass)
                         throw new ClassCastException();
        -            if (cclass != null)
        -                ensureProtectedAccess(obj);
        +            else
        +                throw new RuntimeException(
        +                    new IllegalAccessException(
        +                        "Class " +
        +                        cclass.getName() +
        +                        " can not access a protected member of class " +
        +                        tclass.getName() +
        +                        " using an instance of " +
        +                        obj.getClass().getName()));
                 }
         
        -        public boolean compareAndSet(T obj, long expect, long update) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
        -            return unsafe.compareAndSwapLong(obj, offset, expect, update);
        +        public final boolean compareAndSet(T obj, long expect, long update) {
        +            accessCheck(obj);
        +            return U.compareAndSwapLong(obj, offset, expect, update);
                 }
         
        -        public boolean weakCompareAndSet(T obj, long expect, long update) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
        -            return unsafe.compareAndSwapLong(obj, offset, expect, update);
        +        public final boolean weakCompareAndSet(T obj, long expect, long update) {
        +            accessCheck(obj);
        +            return U.compareAndSwapLong(obj, offset, expect, update);
                 }
         
        -        public void set(T obj, long newValue) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
        -            unsafe.putLongVolatile(obj, offset, newValue);
        +        public final void set(T obj, long newValue) {
        +            accessCheck(obj);
        +            U.putLongVolatile(obj, offset, newValue);
                 }
         
        -        public void lazySet(T obj, long newValue) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
        -            unsafe.putOrderedLong(obj, offset, newValue);
        +        public final void lazySet(T obj, long newValue) {
        +            accessCheck(obj);
        +            U.putOrderedLong(obj, offset, newValue);
                 }
         
        -        public long get(T obj) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
        -            return unsafe.getLongVolatile(obj, offset);
        +        public final long get(T obj) {
        +            accessCheck(obj);
        +            return U.getLongVolatile(obj, offset);
                 }
         
        -        private void ensureProtectedAccess(T obj) {
        -            if (cclass.isInstance(obj)) {
        -                return;
        -            }
        -            throw new RuntimeException(
        -                new IllegalAccessException("Class " +
        -                    cclass.getName() +
        -                    " can not access a protected member of class " +
        -                    tclass.getName() +
        -                    " using an instance of " +
        -                    obj.getClass().getName()
        -                )
        -            );
        +        public final long getAndSet(T obj, long newValue) {
        +            accessCheck(obj);
        +            return U.getAndSetLong(obj, offset, newValue);
        +        }
        +
        +        public final long getAndAdd(T obj, long delta) {
        +            accessCheck(obj);
        +            return U.getAndAddLong(obj, offset, delta);
        +        }
        +
        +        public final long getAndIncrement(T obj) {
        +            return getAndAdd(obj, 1);
                 }
        -    }
         
        +        public final long getAndDecrement(T obj) {
        +            return getAndAdd(obj, -1);
        +        }
         
        -    private static class LockedUpdater extends AtomicLongFieldUpdater {
        -        private static final Unsafe unsafe = Unsafe.getUnsafe();
        +        public final long incrementAndGet(T obj) {
        +            return getAndAdd(obj, 1) + 1;
        +        }
        +
        +        public final long decrementAndGet(T obj) {
        +            return getAndAdd(obj, -1) - 1;
        +        }
        +
        +        public final long addAndGet(T obj, long delta) {
        +            return getAndAdd(obj, delta) + delta;
        +        }
        +    }
        +
        +    private static final class LockedUpdater extends AtomicLongFieldUpdater {
        +        private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
                 private final long offset;
        -        private final Class tclass;
        +        /**
        +         * if field is protected, the subclass constructing updater, else
        +         * the same as tclass
        +         */
                 private final Class cclass;
        +        /** class holding the field */
        +        private final Class tclass;
         
        -        LockedUpdater(final Class tclass, final String fieldName) {
        +        LockedUpdater(final Class tclass, final String fieldName,
        +                      final Class caller) {
                     Field field = null;
        -            Class caller = null;
                     int modifiers = 0;
                     try {
                         field = tclass.getDeclaredField(fieldName); // android-changed
        -                caller = VMStack.getStackClass2(); // android-changed
                         modifiers = field.getModifiers();
                         // BEGIN android-removed
                         // sun.reflect.misc.ReflectUtil.ensureMemberAccess(
        @@ -353,7 +492,7 @@ private static class LockedUpdater extends AtomicLongFieldUpdater {
                         // ClassLoader ccl = caller.getClassLoader();
                         // if ((ccl != null) && (ccl != cl) &&
                         //     ((cl == null) || !isAncestor(cl, ccl))) {
        -                //   sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
        +                //     sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
                         // }
                         // END android-removed
                     // BEGIN android-removed
        @@ -364,72 +503,75 @@ private static class LockedUpdater extends AtomicLongFieldUpdater {
                         throw new RuntimeException(ex);
                     }
         
        -            Class fieldt = field.getType();
        -            if (fieldt != long.class)
        +            if (field.getType() != long.class)
                         throw new IllegalArgumentException("Must be long type");
         
                     if (!Modifier.isVolatile(modifiers))
                         throw new IllegalArgumentException("Must be volatile type");
         
        -            this.cclass = (Modifier.isProtected(modifiers) &&
        -                           caller != tclass) ? caller : null;
        +            this.cclass = (Modifier.isProtected(modifiers)) ? caller : tclass;
                     this.tclass = tclass;
        -            offset = unsafe.objectFieldOffset(field);
        +            this.offset = U.objectFieldOffset(field);
                 }
         
        -        private void fullCheck(T obj) {
        -            if (!tclass.isInstance(obj))
        -                throw new ClassCastException();
        -            if (cclass != null)
        -                ensureProtectedAccess(obj);
        +        /**
        +         * Checks that target argument is instance of cclass.  On
        +         * failure, throws cause.
        +         */
        +        private final void accessCheck(T obj) {
        +            if (!cclass.isInstance(obj))
        +                throw accessCheckException(obj);
        +        }
        +
        +        /**
        +         * Returns access exception if accessCheck failed due to
        +         * protected access, else ClassCastException.
        +         */
        +        private final RuntimeException accessCheckException(T obj) {
        +            if (cclass == tclass)
        +                return new ClassCastException();
        +            else
        +                return new RuntimeException(
        +                    new IllegalAccessException(
        +                        "Class " +
        +                        cclass.getName() +
        +                        " can not access a protected member of class " +
        +                        tclass.getName() +
        +                        " using an instance of " +
        +                        obj.getClass().getName()));
                 }
         
        -        public boolean compareAndSet(T obj, long expect, long update) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
        +        public final boolean compareAndSet(T obj, long expect, long update) {
        +            accessCheck(obj);
                     synchronized (this) {
        -                long v = unsafe.getLong(obj, offset);
        +                long v = U.getLong(obj, offset);
                         if (v != expect)
                             return false;
        -                unsafe.putLong(obj, offset, update);
        +                U.putLong(obj, offset, update);
                         return true;
                     }
                 }
         
        -        public boolean weakCompareAndSet(T obj, long expect, long update) {
        +        public final boolean weakCompareAndSet(T obj, long expect, long update) {
                     return compareAndSet(obj, expect, update);
                 }
         
        -        public void set(T obj, long newValue) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
        +        public final void set(T obj, long newValue) {
        +            accessCheck(obj);
                     synchronized (this) {
        -                unsafe.putLong(obj, offset, newValue);
        +                U.putLong(obj, offset, newValue);
                     }
                 }
         
        -        public void lazySet(T obj, long newValue) {
        +        public final void lazySet(T obj, long newValue) {
                     set(obj, newValue);
                 }
         
        -        public long get(T obj) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
        +        public final long get(T obj) {
        +            accessCheck(obj);
                     synchronized (this) {
        -                return unsafe.getLong(obj, offset);
        -            }
        -        }
        -
        -        private void ensureProtectedAccess(T obj) {
        -            if (cclass.isInstance(obj)) {
        -                return;
        +                return U.getLong(obj, offset);
                     }
        -            throw new RuntimeException(
        -                new IllegalAccessException("Class " +
        -                    cclass.getName() +
        -                    " can not access a protected member of class " +
        -                    tclass.getName() +
        -                    " using an instance of " +
        -                    obj.getClass().getName()
        -                )
        -            );
                 }
             }
         
        @@ -439,13 +581,13 @@ private void ensureProtectedAccess(T obj) {
             //  * classloader's delegation chain.
             //  * Equivalent to the inaccessible: first.isAncestor(second).
             //  */
        -    // private static boolean isAncestor(ClassLoader first, ClassLoader second) {
        +    // static boolean isAncestor(ClassLoader first, ClassLoader second) {
             //     ClassLoader acl = first;
             //     do {
             //         acl = acl.getParent();
             //         if (second == acl) {
             //             return true;
        -    //        }
        +    //         }
             //     } while (acl != null);
             //     return false;
             // }
        diff --git a/luni/src/main/java/java/util/concurrent/atomic/AtomicMarkableReference.java b/luni/src/main/java/java/util/concurrent/atomic/AtomicMarkableReference.java
        index 18d148faf..d11be1079 100644
        --- a/luni/src/main/java/java/util/concurrent/atomic/AtomicMarkableReference.java
        +++ b/luni/src/main/java/java/util/concurrent/atomic/AtomicMarkableReference.java
        @@ -91,7 +91,7 @@ public V get(boolean[] markHolder) {
              * @param newReference the new value for the reference
              * @param expectedMark the expected value of the mark
              * @param newMark the new value for the mark
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public boolean weakCompareAndSet(V       expectedReference,
                                              V       newReference,
        @@ -111,7 +111,7 @@ public boolean weakCompareAndSet(V       expectedReference,
              * @param newReference the new value for the reference
              * @param expectedMark the expected value of the mark
              * @param newMark the new value for the mark
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public boolean compareAndSet(V       expectedReference,
                                          V       newReference,
        @@ -149,7 +149,7 @@ public void set(V newReference, boolean newMark) {
              *
              * @param expectedReference the expected value of the reference
              * @param newMark the new value for the mark
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public boolean attemptMark(V expectedReference, boolean newMark) {
                 Pair current = pair;
        @@ -161,23 +161,18 @@ public boolean attemptMark(V expectedReference, boolean newMark) {
         
             // Unsafe mechanics
         
        -    private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
        -    private static final long pairOffset =
        -        objectFieldOffset(UNSAFE, "pair", AtomicMarkableReference.class);
        -
        -    private boolean casPair(Pair cmp, Pair val) {
        -        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
        -    }
        -
        -    static long objectFieldOffset(sun.misc.Unsafe UNSAFE,
        -                                  String field, Class klazz) {
        +    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
        +    private static final long PAIR;
        +    static {
                 try {
        -            return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
        -        } catch (NoSuchFieldException e) {
        -            // Convert Exception to corresponding Error
        -            NoSuchFieldError error = new NoSuchFieldError(field);
        -            error.initCause(e);
        -            throw error;
        +            PAIR = U.objectFieldOffset
        +                (AtomicMarkableReference.class.getDeclaredField("pair"));
        +        } catch (ReflectiveOperationException e) {
        +            throw new Error(e);
                 }
             }
        +
        +    private boolean casPair(Pair cmp, Pair val) {
        +        return U.compareAndSwapObject(this, PAIR, cmp, val);
        +    }
         }
        diff --git a/luni/src/main/java/java/util/concurrent/atomic/AtomicReference.java b/luni/src/main/java/java/util/concurrent/atomic/AtomicReference.java
        index 7ea60662a..c3a5edeb1 100644
        --- a/luni/src/main/java/java/util/concurrent/atomic/AtomicReference.java
        +++ b/luni/src/main/java/java/util/concurrent/atomic/AtomicReference.java
        @@ -6,7 +6,8 @@
         
         package java.util.concurrent.atomic;
         
        -import sun.misc.Unsafe;
        +import java.util.function.BinaryOperator;
        +import java.util.function.UnaryOperator;
         
         /**
          * An object reference that may be updated atomically. See the {@link
        @@ -19,14 +20,16 @@
         public class AtomicReference implements java.io.Serializable {
             private static final long serialVersionUID = -1848883965231344442L;
         
        -    private static final Unsafe unsafe = Unsafe.getUnsafe();
        -    private static final long valueOffset;
        +    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
        +    private static final long VALUE;
         
             static {
                 try {
        -            valueOffset = unsafe.objectFieldOffset
        +            VALUE = U.objectFieldOffset
                         (AtomicReference.class.getDeclaredField("value"));
        -        } catch (Exception ex) { throw new Error(ex); }
        +        } catch (ReflectiveOperationException e) {
        +            throw new Error(e);
        +        }
             }
         
             private volatile V value;
        @@ -71,7 +74,7 @@ public final void set(V newValue) {
              * @since 1.6
              */
             public final void lazySet(V newValue) {
        -        unsafe.putOrderedObject(this, valueOffset, newValue);
        +        U.putOrderedObject(this, VALUE, newValue);
             }
         
             /**
        @@ -79,11 +82,11 @@ public final void lazySet(V newValue) {
              * if the current value {@code ==} the expected value.
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful. False return indicates that
        +     * @return {@code true} if successful. False return indicates that
              * the actual value was not equal to the expected value.
              */
             public final boolean compareAndSet(V expect, V update) {
        -        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
        +        return U.compareAndSwapObject(this, VALUE, expect, update);
             }
         
             /**
        @@ -96,10 +99,10 @@ public final boolean compareAndSet(V expect, V update) {
              *
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public final boolean weakCompareAndSet(V expect, V update) {
        -        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
        +        return U.compareAndSwapObject(this, VALUE, expect, update);
             }
         
             /**
        @@ -108,12 +111,95 @@ public final boolean weakCompareAndSet(V expect, V update) {
              * @param newValue the new value
              * @return the previous value
              */
        +    @SuppressWarnings("unchecked")
             public final V getAndSet(V newValue) {
        -        while (true) {
        -            V x = get();
        -            if (compareAndSet(x, newValue))
        -                return x;
        -        }
        +        return (V)U.getAndSetObject(this, VALUE, newValue);
        +    }
        +
        +    /**
        +     * Atomically updates the current value with the results of
        +     * applying the given function, returning the previous value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.
        +     *
        +     * @param updateFunction a side-effect-free function
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final V getAndUpdate(UnaryOperator updateFunction) {
        +        V prev, next;
        +        do {
        +            prev = get();
        +            next = updateFunction.apply(prev);
        +        } while (!compareAndSet(prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the current value with the results of
        +     * applying the given function, returning the updated value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.
        +     *
        +     * @param updateFunction a side-effect-free function
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final V updateAndGet(UnaryOperator updateFunction) {
        +        V prev, next;
        +        do {
        +            prev = get();
        +            next = updateFunction.apply(prev);
        +        } while (!compareAndSet(prev, next));
        +        return next;
        +    }
        +
        +    /**
        +     * Atomically updates the current value with the results of
        +     * applying the given function to the current and given values,
        +     * returning the previous value. The function should be
        +     * side-effect-free, since it may be re-applied when attempted
        +     * updates fail due to contention among threads.  The function
        +     * is applied with the current value as its first argument,
        +     * and the given update as the second argument.
        +     *
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final V getAndAccumulate(V x,
        +                                    BinaryOperator accumulatorFunction) {
        +        V prev, next;
        +        do {
        +            prev = get();
        +            next = accumulatorFunction.apply(prev, x);
        +        } while (!compareAndSet(prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the current value with the results of
        +     * applying the given function to the current and given values,
        +     * returning the updated value. The function should be
        +     * side-effect-free, since it may be re-applied when attempted
        +     * updates fail due to contention among threads.  The function
        +     * is applied with the current value as its first argument,
        +     * and the given update as the second argument.
        +     *
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final V accumulateAndGet(V x,
        +                                    BinaryOperator accumulatorFunction) {
        +        V prev, next;
        +        do {
        +            prev = get();
        +            next = accumulatorFunction.apply(prev, x);
        +        } while (!compareAndSet(prev, next));
        +        return next;
             }
         
             /**
        diff --git a/luni/src/main/java/java/util/concurrent/atomic/AtomicReferenceArray.java b/luni/src/main/java/java/util/concurrent/atomic/AtomicReferenceArray.java
        index 052b839f7..f5596e879 100644
        --- a/luni/src/main/java/java/util/concurrent/atomic/AtomicReferenceArray.java
        +++ b/luni/src/main/java/java/util/concurrent/atomic/AtomicReferenceArray.java
        @@ -6,9 +6,10 @@
         
         package java.util.concurrent.atomic;
         
        -import java.util.Arrays;
         import java.lang.reflect.Array;
        -import sun.misc.Unsafe;
        +import java.util.Arrays;
        +import java.util.function.BinaryOperator;
        +import java.util.function.UnaryOperator;
         
         /**
          * An array of object references in which elements may be updated
        @@ -22,23 +23,22 @@
         public class AtomicReferenceArray implements java.io.Serializable {
             private static final long serialVersionUID = -6209656149925076980L;
         
        -    private static final Unsafe unsafe;
        -    private static final int base;
        -    private static final int shift;
        -    private static final long arrayFieldOffset;
        +    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
        +    private static final long ARRAY;
        +    private static final int ABASE;
        +    private static final int ASHIFT;
             private final Object[] array; // must have exact type Object[]
         
             static {
                 try {
        -            unsafe = Unsafe.getUnsafe();
        -            arrayFieldOffset = unsafe.objectFieldOffset
        +            ARRAY = U.objectFieldOffset
                         (AtomicReferenceArray.class.getDeclaredField("array"));
        -            base = unsafe.arrayBaseOffset(Object[].class);
        -            int scale = unsafe.arrayIndexScale(Object[].class);
        +            ABASE = U.arrayBaseOffset(Object[].class);
        +            int scale = U.arrayIndexScale(Object[].class);
                     if ((scale & (scale - 1)) != 0)
        -                throw new Error("data type scale not a power of two");
        -            shift = 31 - Integer.numberOfLeadingZeros(scale);
        -        } catch (Exception e) {
        +                throw new Error("array index scale not a power of two");
        +            ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
        +        } catch (ReflectiveOperationException e) {
                     throw new Error(e);
                 }
             }
        @@ -51,7 +51,7 @@ private long checkedByteOffset(int i) {
             }
         
             private static long byteOffset(int i) {
        -        return ((long) i << shift) + base;
        +        return ((long) i << ASHIFT) + ABASE;
             }
         
             /**
        @@ -97,7 +97,7 @@ public final E get(int i) {
         
             @SuppressWarnings("unchecked")
             private E getRaw(long offset) {
        -        return (E) unsafe.getObjectVolatile(array, offset);
        +        return (E) U.getObjectVolatile(array, offset);
             }
         
             /**
        @@ -107,7 +107,7 @@ private E getRaw(long offset) {
              * @param newValue the new value
              */
             public final void set(int i, E newValue) {
        -        unsafe.putObjectVolatile(array, checkedByteOffset(i), newValue);
        +        U.putObjectVolatile(array, checkedByteOffset(i), newValue);
             }
         
             /**
        @@ -118,7 +118,7 @@ public final void set(int i, E newValue) {
              * @since 1.6
              */
             public final void lazySet(int i, E newValue) {
        -        unsafe.putOrderedObject(array, checkedByteOffset(i), newValue);
        +        U.putOrderedObject(array, checkedByteOffset(i), newValue);
             }
         
             /**
        @@ -129,13 +129,9 @@ public final void lazySet(int i, E newValue) {
              * @param newValue the new value
              * @return the previous value
              */
        +    @SuppressWarnings("unchecked")
             public final E getAndSet(int i, E newValue) {
        -        long offset = checkedByteOffset(i);
        -        while (true) {
        -            E current = getRaw(offset);
        -            if (compareAndSetRaw(offset, current, newValue))
        -                return current;
        -        }
        +        return (E)U.getAndSetObject(array, checkedByteOffset(i), newValue);
             }
         
             /**
        @@ -145,7 +141,7 @@ public final E getAndSet(int i, E newValue) {
              * @param i the index
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful. False return indicates that
        +     * @return {@code true} if successful. False return indicates that
              * the actual value was not equal to the expected value.
              */
             public final boolean compareAndSet(int i, E expect, E update) {
        @@ -153,7 +149,7 @@ public final boolean compareAndSet(int i, E expect, E update) {
             }
         
             private boolean compareAndSetRaw(long offset, E expect, E update) {
        -        return unsafe.compareAndSwapObject(array, offset, expect, update);
        +        return U.compareAndSwapObject(array, offset, expect, update);
             }
         
             /**
        @@ -167,12 +163,106 @@ private boolean compareAndSetRaw(long offset, E expect, E update) {
              * @param i the index
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public final boolean weakCompareAndSet(int i, E expect, E update) {
                 return compareAndSet(i, expect, update);
             }
         
        +    /**
        +     * Atomically updates the element at index {@code i} with the results
        +     * of applying the given function, returning the previous value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.
        +     *
        +     * @param i the index
        +     * @param updateFunction a side-effect-free function
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final E getAndUpdate(int i, UnaryOperator updateFunction) {
        +        long offset = checkedByteOffset(i);
        +        E prev, next;
        +        do {
        +            prev = getRaw(offset);
        +            next = updateFunction.apply(prev);
        +        } while (!compareAndSetRaw(offset, prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the element at index {@code i} with the results
        +     * of applying the given function, returning the updated value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.
        +     *
        +     * @param i the index
        +     * @param updateFunction a side-effect-free function
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final E updateAndGet(int i, UnaryOperator updateFunction) {
        +        long offset = checkedByteOffset(i);
        +        E prev, next;
        +        do {
        +            prev = getRaw(offset);
        +            next = updateFunction.apply(prev);
        +        } while (!compareAndSetRaw(offset, prev, next));
        +        return next;
        +    }
        +
        +    /**
        +     * Atomically updates the element at index {@code i} with the
        +     * results of applying the given function to the current and
        +     * given values, returning the previous value. The function should
        +     * be side-effect-free, since it may be re-applied when attempted
        +     * updates fail due to contention among threads.  The function is
        +     * applied with the current value at index {@code i} as its first
        +     * argument, and the given update as the second argument.
        +     *
        +     * @param i the index
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final E getAndAccumulate(int i, E x,
        +                                    BinaryOperator accumulatorFunction) {
        +        long offset = checkedByteOffset(i);
        +        E prev, next;
        +        do {
        +            prev = getRaw(offset);
        +            next = accumulatorFunction.apply(prev, x);
        +        } while (!compareAndSetRaw(offset, prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the element at index {@code i} with the
        +     * results of applying the given function to the current and
        +     * given values, returning the updated value. The function should
        +     * be side-effect-free, since it may be re-applied when attempted
        +     * updates fail due to contention among threads.  The function is
        +     * applied with the current value at index {@code i} as its first
        +     * argument, and the given update as the second argument.
        +     *
        +     * @param i the index
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final E accumulateAndGet(int i, E x,
        +                                    BinaryOperator accumulatorFunction) {
        +        long offset = checkedByteOffset(i);
        +        E prev, next;
        +        do {
        +            prev = getRaw(offset);
        +            next = accumulatorFunction.apply(prev, x);
        +        } while (!compareAndSetRaw(offset, prev, next));
        +        return next;
        +    }
        +
             /**
              * Returns the String representation of the current values of array.
              * @return the String representation of the current values of array
        @@ -194,17 +284,20 @@ public String toString() {
         
             /**
              * Reconstitutes the instance from a stream (that is, deserializes it).
        +     * @param s the stream
        +     * @throws ClassNotFoundException if the class of a serialized object
        +     *         could not be found
        +     * @throws java.io.IOException if an I/O error occurs
              */
             private void readObject(java.io.ObjectInputStream s)
        -        throws java.io.IOException, ClassNotFoundException,
        -        java.io.InvalidObjectException {
        +        throws java.io.IOException, ClassNotFoundException {
                 // Note: This must be changed if any additional fields are defined
                 Object a = s.readFields().get("array", null);
                 if (a == null || !a.getClass().isArray())
                     throw new java.io.InvalidObjectException("Not array type");
                 if (a.getClass() != Object[].class)
                     a = Arrays.copyOf((Object[])a, Array.getLength(a), Object[].class);
        -        unsafe.putObjectVolatile(this, arrayFieldOffset, a);
        +        U.putObjectVolatile(this, ARRAY, a);
             }
         
         }
        diff --git a/luni/src/main/java/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java b/luni/src/main/java/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java
        index 13ad3eb0e..1dc2eb95e 100644
        --- a/luni/src/main/java/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java
        +++ b/luni/src/main/java/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java
        @@ -7,9 +7,15 @@
         package java.util.concurrent.atomic;
         
         import dalvik.system.VMStack; // android-added
        -import sun.misc.Unsafe;
         import java.lang.reflect.Field;
         import java.lang.reflect.Modifier;
        +import java.security.AccessController;
        +import java.security.PrivilegedActionException;
        +import java.security.PrivilegedExceptionAction;
        +import java.util.function.BinaryOperator;
        +import java.util.function.UnaryOperator;
        +import sun.reflect.CallerSensitive;
        +import sun.reflect.Reflection;
         
         /**
          * A reflection-based utility that enables atomic updates to
        @@ -19,7 +25,7 @@
          * independently subject to atomic updates. For example, a tree node
          * might be declared as
          *
        - *  
         {@code
        + * 
         {@code
          * class Node {
          *   private volatile Node left, right;
          *
        @@ -60,17 +66,19 @@ public abstract class AtomicReferenceFieldUpdater {
              * @param  the type of instances of tclass
              * @param  the type of instances of vclass
              * @return the updater
        -     * @throws IllegalArgumentException if the field is not a volatile reference type
        +     * @throws ClassCastException if the field is of the wrong type
        +     * @throws IllegalArgumentException if the field is not volatile
              * @throws RuntimeException with a nested reflection-based
              * exception if the class does not hold field or is the wrong type,
              * or the field is inaccessible to the caller according to Java language
              * access control
              */
        +    @CallerSensitive
             public static  AtomicReferenceFieldUpdater newUpdater(Class tclass,
                                                                             Class vclass,
                                                                             String fieldName) {
                 return new AtomicReferenceFieldUpdaterImpl
        -            (tclass, vclass, fieldName);
        +            (tclass, vclass, fieldName, VMStack.getStackClass1()); // android-changed
             }
         
             /**
        @@ -89,7 +97,7 @@ protected AtomicReferenceFieldUpdater() {
              * @param obj An object whose field to conditionally set
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public abstract boolean compareAndSet(T obj, V expect, V update);
         
        @@ -107,7 +115,7 @@ protected AtomicReferenceFieldUpdater() {
              * @param obj An object whose field to conditionally set
              * @param expect the expected value
              * @param update the new value
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public abstract boolean weakCompareAndSet(T obj, V expect, V update);
         
        @@ -149,20 +157,116 @@ protected AtomicReferenceFieldUpdater() {
              * @return the previous value
              */
             public V getAndSet(T obj, V newValue) {
        -        for (;;) {
        -            V current = get(obj);
        -            if (compareAndSet(obj, current, newValue))
        -                return current;
        -        }
        +        V prev;
        +        do {
        +            prev = get(obj);
        +        } while (!compareAndSet(obj, prev, newValue));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the field of the given object managed by this updater
        +     * with the results of applying the given function, returning the previous
        +     * value. The function should be side-effect-free, since it may be
        +     * re-applied when attempted updates fail due to contention among threads.
        +     *
        +     * @param obj An object whose field to get and set
        +     * @param updateFunction a side-effect-free function
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final V getAndUpdate(T obj, UnaryOperator updateFunction) {
        +        V prev, next;
        +        do {
        +            prev = get(obj);
        +            next = updateFunction.apply(prev);
        +        } while (!compareAndSet(obj, prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the field of the given object managed by this updater
        +     * with the results of applying the given function, returning the updated
        +     * value. The function should be side-effect-free, since it may be
        +     * re-applied when attempted updates fail due to contention among threads.
        +     *
        +     * @param obj An object whose field to get and set
        +     * @param updateFunction a side-effect-free function
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final V updateAndGet(T obj, UnaryOperator updateFunction) {
        +        V prev, next;
        +        do {
        +            prev = get(obj);
        +            next = updateFunction.apply(prev);
        +        } while (!compareAndSet(obj, prev, next));
        +        return next;
        +    }
        +
        +    /**
        +     * Atomically updates the field of the given object managed by this
        +     * updater with the results of applying the given function to the
        +     * current and given values, returning the previous value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.  The
        +     * function is applied with the current value as its first argument,
        +     * and the given update as the second argument.
        +     *
        +     * @param obj An object whose field to get and set
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the previous value
        +     * @since 1.8
        +     */
        +    public final V getAndAccumulate(T obj, V x,
        +                                    BinaryOperator accumulatorFunction) {
        +        V prev, next;
        +        do {
        +            prev = get(obj);
        +            next = accumulatorFunction.apply(prev, x);
        +        } while (!compareAndSet(obj, prev, next));
        +        return prev;
        +    }
        +
        +    /**
        +     * Atomically updates the field of the given object managed by this
        +     * updater with the results of applying the given function to the
        +     * current and given values, returning the updated value. The
        +     * function should be side-effect-free, since it may be re-applied
        +     * when attempted updates fail due to contention among threads.  The
        +     * function is applied with the current value as its first argument,
        +     * and the given update as the second argument.
        +     *
        +     * @param obj An object whose field to get and set
        +     * @param x the update value
        +     * @param accumulatorFunction a side-effect-free function of two arguments
        +     * @return the updated value
        +     * @since 1.8
        +     */
        +    public final V accumulateAndGet(T obj, V x,
        +                                    BinaryOperator accumulatorFunction) {
        +        V prev, next;
        +        do {
        +            prev = get(obj);
        +            next = accumulatorFunction.apply(prev, x);
        +        } while (!compareAndSet(obj, prev, next));
        +        return next;
             }
         
             private static final class AtomicReferenceFieldUpdaterImpl
                 extends AtomicReferenceFieldUpdater {
        -        private static final Unsafe unsafe = Unsafe.getUnsafe();
        +        private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
                 private final long offset;
        +        /**
        +         * if field is protected, the subclass constructing updater, else
        +         * the same as tclass
        +         */
        +        private final Class cclass;
        +        /** class holding the field */
                 private final Class tclass;
        +        /** field value type */
                 private final Class vclass;
        -        private final Class cclass;
         
                 /*
                  * Internal type checks within all update methods contain
        @@ -177,26 +281,25 @@ private static final class AtomicReferenceFieldUpdaterImpl
                  */
         
                 AtomicReferenceFieldUpdaterImpl(final Class tclass,
        -                                        Class vclass,
        -                                        final String fieldName) {
        +                                        final Class vclass,
        +                                        final String fieldName,
        +                                        final Class caller) {
                     final Field field;
                     final Class fieldClass;
        -            final Class caller;
                     final int modifiers;
                     try {
                         field = tclass.getDeclaredField(fieldName); // android-changed
        -                caller = VMStack.getStackClass2(); // android-changed
                         modifiers = field.getModifiers();
        -            // BEGIN android-removed
        -            //     sun.reflect.misc.ReflectUtil.ensureMemberAccess(
        -            //         caller, tclass, null, modifiers);
        -            //     ClassLoader cl = tclass.getClassLoader();
        -            //     ClassLoader ccl = caller.getClassLoader();
        -            //     if ((ccl != null) && (ccl != cl) &&
        -            //         ((cl == null) || !isAncestor(cl, ccl))) {
        -            //       sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
        -            //     }
        -            // END android-removed
        +                // BEGIN android-removed
        +                // sun.reflect.misc.ReflectUtil.ensureMemberAccess(
        +                //     caller, tclass, null, modifiers);
        +                // ClassLoader cl = tclass.getClassLoader();
        +                // ClassLoader ccl = caller.getClassLoader();
        +                // if ((ccl != null) && (ccl != cl) &&
        +                //     ((cl == null) || !isAncestor(cl, ccl))) {
        +                //     sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
        +                // }
        +                // END android-removed
                         fieldClass = field.getType();
                     // BEGIN android-removed
                     // } catch (PrivilegedActionException pae) {
        @@ -208,18 +311,16 @@ private static final class AtomicReferenceFieldUpdaterImpl
         
                     if (vclass != fieldClass)
                         throw new ClassCastException();
        +            if (vclass.isPrimitive())
        +                throw new IllegalArgumentException("Must be reference type");
         
                     if (!Modifier.isVolatile(modifiers))
                         throw new IllegalArgumentException("Must be volatile type");
         
        -            this.cclass = (Modifier.isProtected(modifiers) &&
        -                           caller != tclass) ? caller : null;
        +            this.cclass = (Modifier.isProtected(modifiers)) ? caller : tclass;
                     this.tclass = tclass;
        -            if (vclass == Object.class)
        -                this.vclass = null;
        -            else
        -                this.vclass = vclass;
        -            offset = unsafe.objectFieldOffset(field);
        +            this.vclass = vclass;
        +            this.offset = U.objectFieldOffset(field);
                 }
         
                 // BEGIN android-removed
        @@ -228,87 +329,90 @@ private static final class AtomicReferenceFieldUpdaterImpl
                 //  * classloader's delegation chain.
                 //  * Equivalent to the inaccessible: first.isAncestor(second).
                 //  */
        -        //
                 // private static boolean isAncestor(ClassLoader first, ClassLoader second) {
                 //     ClassLoader acl = first;
                 //     do {
                 //         acl = acl.getParent();
                 //         if (second == acl) {
                 //             return true;
        -        //        }
        +        //         }
                 //     } while (acl != null);
                 //     return false;
                 // }
                 // END android-removed
         
        -        void targetCheck(T obj) {
        -            if (!tclass.isInstance(obj))
        -                throw new ClassCastException();
        -            if (cclass != null)
        -                ensureProtectedAccess(obj);
        +        /**
        +         * Checks that target argument is instance of cclass.  On
        +         * failure, throws cause.
        +         */
        +        private final void accessCheck(T obj) {
        +            if (!cclass.isInstance(obj))
        +                throwAccessCheckException(obj);
                 }
         
        -        void updateCheck(T obj, V update) {
        -            if (!tclass.isInstance(obj) ||
        -                (update != null && vclass != null && !vclass.isInstance(update)))
        +        /**
        +         * Throws access exception if accessCheck failed due to
        +         * protected access, else ClassCastException.
        +         */
        +        private final void throwAccessCheckException(T obj) {
        +            if (cclass == tclass)
                         throw new ClassCastException();
        -            if (cclass != null)
        -                ensureProtectedAccess(obj);
        +            else
        +                throw new RuntimeException(
        +                    new IllegalAccessException(
        +                        "Class " +
        +                        cclass.getName() +
        +                        " can not access a protected member of class " +
        +                        tclass.getName() +
        +                        " using an instance of " +
        +                        obj.getClass().getName()));
        +        }
        +
        +        private final void valueCheck(V v) {
        +            if (v != null && !(vclass.isInstance(v)))
        +                throwCCE();
        +        }
        +
        +        static void throwCCE() {
        +            throw new ClassCastException();
                 }
         
        -        public boolean compareAndSet(T obj, V expect, V update) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null ||
        -                (update != null && vclass != null &&
        -                 vclass != update.getClass()))
        -                updateCheck(obj, update);
        -            return unsafe.compareAndSwapObject(obj, offset, expect, update);
        +        public final boolean compareAndSet(T obj, V expect, V update) {
        +            accessCheck(obj);
        +            valueCheck(update);
        +            return U.compareAndSwapObject(obj, offset, expect, update);
                 }
         
        -        public boolean weakCompareAndSet(T obj, V expect, V update) {
        +        public final boolean weakCompareAndSet(T obj, V expect, V update) {
                     // same implementation as strong form for now
        -            if (obj == null || obj.getClass() != tclass || cclass != null ||
        -                (update != null && vclass != null &&
        -                 vclass != update.getClass()))
        -                updateCheck(obj, update);
        -            return unsafe.compareAndSwapObject(obj, offset, expect, update);
        +            accessCheck(obj);
        +            valueCheck(update);
        +            return U.compareAndSwapObject(obj, offset, expect, update);
                 }
         
        -        public void set(T obj, V newValue) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null ||
        -                (newValue != null && vclass != null &&
        -                 vclass != newValue.getClass()))
        -                updateCheck(obj, newValue);
        -            unsafe.putObjectVolatile(obj, offset, newValue);
        +        public final void set(T obj, V newValue) {
        +            accessCheck(obj);
        +            valueCheck(newValue);
        +            U.putObjectVolatile(obj, offset, newValue);
                 }
         
        -        public void lazySet(T obj, V newValue) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null ||
        -                (newValue != null && vclass != null &&
        -                 vclass != newValue.getClass()))
        -                updateCheck(obj, newValue);
        -            unsafe.putOrderedObject(obj, offset, newValue);
        +        public final void lazySet(T obj, V newValue) {
        +            accessCheck(obj);
        +            valueCheck(newValue);
        +            U.putOrderedObject(obj, offset, newValue);
                 }
         
                 @SuppressWarnings("unchecked")
        -        public V get(T obj) {
        -            if (obj == null || obj.getClass() != tclass || cclass != null)
        -                targetCheck(obj);
        -            return (V)unsafe.getObjectVolatile(obj, offset);
        +        public final V get(T obj) {
        +            accessCheck(obj);
        +            return (V)U.getObjectVolatile(obj, offset);
                 }
         
        -        private void ensureProtectedAccess(T obj) {
        -            if (cclass.isInstance(obj)) {
        -                return;
        -            }
        -            throw new RuntimeException(
        -                new IllegalAccessException("Class " +
        -                    cclass.getName() +
        -                    " can not access a protected member of class " +
        -                    tclass.getName() +
        -                    " using an instance of " +
        -                    obj.getClass().getName()
        -                )
        -            );
        +        @SuppressWarnings("unchecked")
        +        public final V getAndSet(T obj, V newValue) {
        +            accessCheck(obj);
        +            valueCheck(newValue);
        +            return (V)U.getAndSetObject(obj, offset, newValue);
                 }
             }
         }
        diff --git a/luni/src/main/java/java/util/concurrent/atomic/AtomicStampedReference.java b/luni/src/main/java/java/util/concurrent/atomic/AtomicStampedReference.java
        index 1449856ed..69fab238b 100644
        --- a/luni/src/main/java/java/util/concurrent/atomic/AtomicStampedReference.java
        +++ b/luni/src/main/java/java/util/concurrent/atomic/AtomicStampedReference.java
        @@ -91,7 +91,7 @@ public V get(int[] stampHolder) {
              * @param newReference the new value for the reference
              * @param expectedStamp the expected value of the stamp
              * @param newStamp the new value for the stamp
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public boolean weakCompareAndSet(V   expectedReference,
                                              V   newReference,
        @@ -111,7 +111,7 @@ public boolean weakCompareAndSet(V   expectedReference,
              * @param newReference the new value for the reference
              * @param expectedStamp the expected value of the stamp
              * @param newStamp the new value for the stamp
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public boolean compareAndSet(V   expectedReference,
                                          V   newReference,
        @@ -149,7 +149,7 @@ public void set(V newReference, int newStamp) {
              *
              * @param expectedReference the expected value of the reference
              * @param newStamp the new value for the stamp
        -     * @return true if successful
        +     * @return {@code true} if successful
              */
             public boolean attemptStamp(V expectedReference, int newStamp) {
                 Pair current = pair;
        @@ -161,23 +161,18 @@ public boolean attemptStamp(V expectedReference, int newStamp) {
         
             // Unsafe mechanics
         
        -    private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
        -    private static final long pairOffset =
        -        objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);
        -
        -    private boolean casPair(Pair cmp, Pair val) {
        -        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
        -    }
        -
        -    static long objectFieldOffset(sun.misc.Unsafe UNSAFE,
        -                                  String field, Class klazz) {
        +    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
        +    private static final long PAIR;
        +    static {
                 try {
        -            return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
        -        } catch (NoSuchFieldException e) {
        -            // Convert Exception to corresponding Error
        -            NoSuchFieldError error = new NoSuchFieldError(field);
        -            error.initCause(e);
        -            throw error;
        +            PAIR = U.objectFieldOffset
        +                (AtomicStampedReference.class.getDeclaredField("pair"));
        +        } catch (ReflectiveOperationException e) {
        +            throw new Error(e);
                 }
             }
        +
        +    private boolean casPair(Pair cmp, Pair val) {
        +        return U.compareAndSwapObject(this, PAIR, cmp, val);
        +    }
         }
        diff --git a/luni/src/main/java/java/util/concurrent/atomic/DoubleAccumulator.java b/luni/src/main/java/java/util/concurrent/atomic/DoubleAccumulator.java
        new file mode 100644
        index 000000000..1ea088dc7
        --- /dev/null
        +++ b/luni/src/main/java/java/util/concurrent/atomic/DoubleAccumulator.java
        @@ -0,0 +1,270 @@
        +/*
        + * Written by Doug Lea with assistance from members of JCP JSR-166
        + * Expert Group and released to the public domain, as explained at
        + * http://creativecommons.org/publicdomain/zero/1.0/
        + */
        +
        +package java.util.concurrent.atomic;
        +
        +import java.io.Serializable;
        +import java.util.function.DoubleBinaryOperator;
        +
        +/**
        + * One or more variables that together maintain a running {@code double}
        + * value updated using a supplied function.  When updates (method
        + * {@link #accumulate}) are contended across threads, the set of variables
        + * may grow dynamically to reduce contention.  Method {@link #get}
        + * (or, equivalently, {@link #doubleValue}) returns the current value
        + * across the variables maintaining updates.
        + *
        + * 

        This class is usually preferable to alternatives when multiple + * threads update a common value that is used for purposes such as + * summary statistics that are frequently updated but less frequently + * read. + * + *

        The supplied accumulator function should be side-effect-free, + * since it may be re-applied when attempted updates fail due to + * contention among threads. The function is applied with the current + * value as its first argument, and the given update as the second + * argument. For example, to maintain a running maximum value, you + * could supply {@code Double::max} along with {@code + * Double.NEGATIVE_INFINITY} as the identity. The order of + * accumulation within or across threads is not guaranteed. Thus, this + * class may not be applicable if numerical stability is required, + * especially when combining values of substantially different orders + * of magnitude. + * + *

        Class {@link DoubleAdder} provides analogs of the functionality + * of this class for the common special case of maintaining sums. The + * call {@code new DoubleAdder()} is equivalent to {@code new + * DoubleAccumulator((x, y) -> x + y, 0.0)}. + * + *

        This class extends {@link Number}, but does not define + * methods such as {@code equals}, {@code hashCode} and {@code + * compareTo} because instances are expected to be mutated, and so are + * not useful as collection keys. + * + * @since 1.8 + * @author Doug Lea + */ +public class DoubleAccumulator extends Striped64 implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + private final DoubleBinaryOperator function; + private final long identity; // use long representation + + /** + * Creates a new instance using the given accumulator function + * and identity element. + * @param accumulatorFunction a side-effect-free function of two arguments + * @param identity identity (initial value) for the accumulator function + */ + public DoubleAccumulator(DoubleBinaryOperator accumulatorFunction, + double identity) { + this.function = accumulatorFunction; + base = this.identity = Double.doubleToRawLongBits(identity); + } + + /** + * Updates with the given value. + * + * @param x the value + */ + public void accumulate(double x) { + Cell[] as; long b, v, r; int m; Cell a; + if ((as = cells) != null || + (r = Double.doubleToRawLongBits + (function.applyAsDouble + (Double.longBitsToDouble(b = base), x))) != b && !casBase(b, r)) { + boolean uncontended = true; + if (as == null || (m = as.length - 1) < 0 || + (a = as[getProbe() & m]) == null || + !(uncontended = + (r = Double.doubleToRawLongBits + (function.applyAsDouble + (Double.longBitsToDouble(v = a.value), x))) == v || + a.cas(v, r))) + doubleAccumulate(x, function, uncontended); + } + } + + /** + * Returns the current value. The returned value is NOT + * an atomic snapshot; invocation in the absence of concurrent + * updates returns an accurate result, but concurrent updates that + * occur while the value is being calculated might not be + * incorporated. + * + * @return the current value + */ + public double get() { + Cell[] as = cells; + double result = Double.longBitsToDouble(base); + if (as != null) { + for (Cell a : as) + if (a != null) + result = function.applyAsDouble + (result, Double.longBitsToDouble(a.value)); + } + return result; + } + + /** + * Resets variables maintaining updates to the identity value. + * This method may be a useful alternative to creating a new + * updater, but is only effective if there are no concurrent + * updates. Because this method is intrinsically racy, it should + * only be used when it is known that no threads are concurrently + * updating. + */ + public void reset() { + Cell[] as = cells; + base = identity; + if (as != null) { + for (Cell a : as) + if (a != null) + a.reset(identity); + } + } + + /** + * Equivalent in effect to {@link #get} followed by {@link + * #reset}. This method may apply for example during quiescent + * points between multithreaded computations. If there are + * updates concurrent with this method, the returned value is + * not guaranteed to be the final value occurring before + * the reset. + * + * @return the value before reset + */ + public double getThenReset() { + Cell[] as = cells; + double result = Double.longBitsToDouble(base); + base = identity; + if (as != null) { + for (Cell a : as) { + if (a != null) { + double v = Double.longBitsToDouble(a.value); + a.reset(identity); + result = function.applyAsDouble(result, v); + } + } + } + return result; + } + + /** + * Returns the String representation of the current value. + * @return the String representation of the current value + */ + public String toString() { + return Double.toString(get()); + } + + /** + * Equivalent to {@link #get}. + * + * @return the current value + */ + public double doubleValue() { + return get(); + } + + /** + * Returns the {@linkplain #get current value} as a {@code long} + * after a narrowing primitive conversion. + */ + public long longValue() { + return (long)get(); + } + + /** + * Returns the {@linkplain #get current value} as an {@code int} + * after a narrowing primitive conversion. + */ + public int intValue() { + return (int)get(); + } + + /** + * Returns the {@linkplain #get current value} as a {@code float} + * after a narrowing primitive conversion. + */ + public float floatValue() { + return (float)get(); + } + + /** + * Serialization proxy, used to avoid reference to the non-public + * Striped64 superclass in serialized forms. + * @serial include + */ + private static class SerializationProxy implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /** + * The current value returned by get(). + * @serial + */ + private final double value; + + /** + * The function used for updates. + * @serial + */ + private final DoubleBinaryOperator function; + + /** + * The identity value, represented as a long, as converted by + * {@link Double#doubleToRawLongBits}. The original identity + * can be recovered using {@link Double#longBitsToDouble}. + * @serial + */ + private final long identity; + + SerializationProxy(double value, + DoubleBinaryOperator function, + long identity) { + this.value = value; + this.function = function; + this.identity = identity; + } + + /** + * Returns a {@code DoubleAccumulator} object with initial state + * held by this proxy. + * + * @return a {@code DoubleAccumulator} object with initial state + * held by this proxy + */ + private Object readResolve() { + double d = Double.longBitsToDouble(identity); + DoubleAccumulator a = new DoubleAccumulator(function, d); + a.base = Double.doubleToRawLongBits(value); + return a; + } + } + + /** + * Returns a + * + * SerializationProxy + * representing the state of this instance. + * + * @return a {@link SerializationProxy} + * representing the state of this instance + */ + private Object writeReplace() { + return new SerializationProxy(get(), function, identity); + } + + /** + * @param s the stream + * @throws java.io.InvalidObjectException always + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.InvalidObjectException { + throw new java.io.InvalidObjectException("Proxy required"); + } + +} diff --git a/luni/src/main/java/java/util/concurrent/atomic/DoubleAdder.java b/luni/src/main/java/java/util/concurrent/atomic/DoubleAdder.java new file mode 100644 index 000000000..94844d50b --- /dev/null +++ b/luni/src/main/java/java/util/concurrent/atomic/DoubleAdder.java @@ -0,0 +1,237 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent.atomic; + +import java.io.Serializable; + +/** + * One or more variables that together maintain an initially zero + * {@code double} sum. When updates (method {@link #add}) are + * contended across threads, the set of variables may grow dynamically + * to reduce contention. Method {@link #sum} (or, equivalently {@link + * #doubleValue}) returns the current total combined across the + * variables maintaining the sum. The order of accumulation within or + * across threads is not guaranteed. Thus, this class may not be + * applicable if numerical stability is required, especially when + * combining values of substantially different orders of magnitude. + * + *

        This class is usually preferable to alternatives when multiple + * threads update a common value that is used for purposes such as + * summary statistics that are frequently updated but less frequently + * read. + * + *

        This class extends {@link Number}, but does not define + * methods such as {@code equals}, {@code hashCode} and {@code + * compareTo} because instances are expected to be mutated, and so are + * not useful as collection keys. + * + * @since 1.8 + * @author Doug Lea + */ +public class DoubleAdder extends Striped64 implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /* + * Note that we must use "long" for underlying representations, + * because there is no compareAndSet for double, due to the fact + * that the bitwise equals used in any CAS implementation is not + * the same as double-precision equals. However, we use CAS only + * to detect and alleviate contention, for which bitwise equals + * works best anyway. In principle, the long/double conversions + * used here should be essentially free on most platforms since + * they just re-interpret bits. + */ + + /** + * Creates a new adder with initial sum of zero. + */ + public DoubleAdder() { + } + + /** + * Adds the given value. + * + * @param x the value to add + */ + public void add(double x) { + Cell[] as; long b, v; int m; Cell a; + if ((as = cells) != null || + !casBase(b = base, + Double.doubleToRawLongBits + (Double.longBitsToDouble(b) + x))) { + boolean uncontended = true; + if (as == null || (m = as.length - 1) < 0 || + (a = as[getProbe() & m]) == null || + !(uncontended = a.cas(v = a.value, + Double.doubleToRawLongBits + (Double.longBitsToDouble(v) + x)))) + doubleAccumulate(x, null, uncontended); + } + } + + /** + * Returns the current sum. The returned value is NOT an + * atomic snapshot; invocation in the absence of concurrent + * updates returns an accurate result, but concurrent updates that + * occur while the sum is being calculated might not be + * incorporated. Also, because floating-point arithmetic is not + * strictly associative, the returned result need not be identical + * to the value that would be obtained in a sequential series of + * updates to a single variable. + * + * @return the sum + */ + public double sum() { + Cell[] as = cells; + double sum = Double.longBitsToDouble(base); + if (as != null) { + for (Cell a : as) + if (a != null) + sum += Double.longBitsToDouble(a.value); + } + return sum; + } + + /** + * Resets variables maintaining the sum to zero. This method may + * be a useful alternative to creating a new adder, but is only + * effective if there are no concurrent updates. Because this + * method is intrinsically racy, it should only be used when it is + * known that no threads are concurrently updating. + */ + public void reset() { + Cell[] as = cells; + base = 0L; // relies on fact that double 0 must have same rep as long + if (as != null) { + for (Cell a : as) + if (a != null) + a.reset(); + } + } + + /** + * Equivalent in effect to {@link #sum} followed by {@link + * #reset}. This method may apply for example during quiescent + * points between multithreaded computations. If there are + * updates concurrent with this method, the returned value is + * not guaranteed to be the final value occurring before + * the reset. + * + * @return the sum + */ + public double sumThenReset() { + Cell[] as = cells; + double sum = Double.longBitsToDouble(base); + base = 0L; + if (as != null) { + for (Cell a : as) { + if (a != null) { + long v = a.value; + a.reset(); + sum += Double.longBitsToDouble(v); + } + } + } + return sum; + } + + /** + * Returns the String representation of the {@link #sum}. + * @return the String representation of the {@link #sum} + */ + public String toString() { + return Double.toString(sum()); + } + + /** + * Equivalent to {@link #sum}. + * + * @return the sum + */ + public double doubleValue() { + return sum(); + } + + /** + * Returns the {@link #sum} as a {@code long} after a + * narrowing primitive conversion. + */ + public long longValue() { + return (long)sum(); + } + + /** + * Returns the {@link #sum} as an {@code int} after a + * narrowing primitive conversion. + */ + public int intValue() { + return (int)sum(); + } + + /** + * Returns the {@link #sum} as a {@code float} + * after a narrowing primitive conversion. + */ + public float floatValue() { + return (float)sum(); + } + + /** + * Serialization proxy, used to avoid reference to the non-public + * Striped64 superclass in serialized forms. + * @serial include + */ + private static class SerializationProxy implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /** + * The current value returned by sum(). + * @serial + */ + private final double value; + + SerializationProxy(DoubleAdder a) { + value = a.sum(); + } + + /** + * Returns a {@code DoubleAdder} object with initial state + * held by this proxy. + * + * @return a {@code DoubleAdder} object with initial state + * held by this proxy + */ + private Object readResolve() { + DoubleAdder a = new DoubleAdder(); + a.base = Double.doubleToRawLongBits(value); + return a; + } + } + + /** + * Returns a + * + * SerializationProxy + * representing the state of this instance. + * + * @return a {@link SerializationProxy} + * representing the state of this instance + */ + private Object writeReplace() { + return new SerializationProxy(this); + } + + /** + * @param s the stream + * @throws java.io.InvalidObjectException always + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.InvalidObjectException { + throw new java.io.InvalidObjectException("Proxy required"); + } + +} diff --git a/luni/src/main/java/java/util/concurrent/atomic/Fences.java b/luni/src/main/java/java/util/concurrent/atomic/Fences.java deleted file mode 100644 index d907fafc1..000000000 --- a/luni/src/main/java/java/util/concurrent/atomic/Fences.java +++ /dev/null @@ -1,538 +0,0 @@ -/* - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -package java.util.concurrent.atomic; - -/** - * A set of methods providing fine-grained control over happens-before - * and synchronization order relations among reads and/or writes. The - * methods of this class are designed for use in uncommon situations - * where declaring variables {@code volatile} or {@code final}, using - * instances of atomic classes, using {@code synchronized} blocks or - * methods, or using other synchronization facilities are not possible - * or do not provide the desired control. - * - *

        Memory Ordering. There are three methods for controlling - * ordering relations among memory accesses (i.e., reads and - * writes). Method {@code orderWrites} is typically used to enforce - * order between two writes, and {@code orderAccesses} between a write - * and a read. Method {@code orderReads} is used to enforce order - * between two reads with respect to other {@code orderWrites} and/or - * {@code orderAccesses} invocations. The formally specified - * properties of these methods described below provide - * platform-independent guarantees that are honored by all levels of a - * platform (compilers, systems, processors). The use of these - * methods may result in the suppression of otherwise valid compiler - * transformations and optimizations that could visibly violate the - * specified orderings, and may or may not entail the use of - * processor-level "memory barrier" instructions. - * - *

        Each ordering method accepts a {@code ref} argument, and - * controls ordering among accesses with respect to this reference. - * Invocations must be placed between accesses performed in - * expression evaluations and assignment statements to control the - * orderings of prior versus subsequent accesses appearing in program - * order. These methods also return their arguments to simplify - * correct usage in these contexts. - * - *

        Usages of ordering methods almost always take one of the forms - * illustrated in the examples below. These idioms arrange some of - * the ordering properties associated with {@code volatile} and - * related language-based constructions, but without other - * compile-time and runtime benefits that make language-based - * constructions far better choices when they are applicable. Usages - * should be restricted to the control of strictly internal - * implementation matters inside a class or package, and must either - * avoid or document any consequent violations of ordering or safety - * properties expected by users of a class employing them. - * - *

        Reachability. Method {@code reachabilityFence} - * establishes an ordering for strong reachability (as defined in the - * {@link java.lang.ref} package specification) with respect to - * garbage collection. Method {@code reachabilityFence} differs from - * the others in that it controls relations that are otherwise only - * implicit in a program -- the reachability conditions triggering - * garbage collection. As illustrated in the sample usages below, - * this method is applicable only when reclamation may have visible - * effects, which is possible for objects with finalizers (see Section - * 12.6 of the Java Language Specification) that are implemented in - * ways that rely on ordering control for correctness. - * - *

        Sample Usages - * - *

        Safe publication. With care, method {@code orderWrites} - * may be used to obtain the memory safety effects of {@code final} - * for a field that cannot be declared as {@code final}, because its - * primary initialization cannot be performed in a constructor, in - * turn because it is used in a framework requiring that all classes - * have a no-argument constructor; as in: - * - *

         {@code
        - * class WidgetHolder {
        - *   private Widget widget;
        - *   public WidgetHolder() {}
        - *   public static WidgetHolder newWidgetHolder(Params params) {
        - *     WidgetHolder h = new WidgetHolder();
        - *     h.widget = new Widget(params);
        - *     return Fences.orderWrites(h);
        - *   }
        - * }}
        - * - * Here, the invocation of {@code orderWrites} ensures that the - * effects of the widget assignment are ordered before those of any - * (unknown) subsequent stores of {@code h} in other variables that - * make {@code h} available for use by other objects. Initialization - * sequences using {@code orderWrites} require more care than those - * involving {@code final} fields. When {@code final} is not used, - * compilers cannot help you to ensure that the field is set correctly - * across all usages. You must fully initialize objects - * before the {@code orderWrites} invocation that makes - * references to them safe to assign to accessible variables. Further, - * initialization sequences must not internally "leak" the reference - * by using it as an argument to a callback method or adding it to a - * static data structure. If less constrained usages were required, - * it may be possible to cope using more extensive sets of fences, or - * as a normally better choice, using synchronization (locking). - * Conversely, if it were possible to do so, the best option would be - * to rewrite class {@code WidgetHolder} to use {@code final}. - * - *

        An alternative approach is to place similar mechanics in the - * (sole) method that makes such objects available for use by others. - * Here is a stripped-down example illustrating the essentials. In - * practice, among other changes, you would use access methods instead - * of a public field. - * - *

         {@code
        - * class AnotherWidgetHolder {
        - *   public Widget widget;
        - *   void publish(Widget w) {
        - *     this.widget = Fences.orderWrites(w);
        - *   }
        - *   // ...
        - * }}
        - * - * In this case, the {@code orderWrites} invocation occurs before the - * store making the object available. Correctness again relies on - * ensuring that there are no leaks prior to invoking this method, and - * that it really is the only means of accessing the - * published object. This approach is not often applicable -- - * normally you would publish objects using a thread-safe collection - * that itself guarantees the expected ordering relations. However, it - * may come into play in the construction of such classes themselves. - * - *

        Safely updating fields. Outside of the initialization - * idioms illustrated above, Fence methods ordering writes must be - * paired with those ordering reads. To illustrate, suppose class - * {@code c} contains an accessible variable {@code data} that should - * have been declared as {@code volatile} but wasn't: - * - *

         {@code
        - * class C {
        - *   Object data;  // need volatile access but not volatile
        - *   // ...
        - * }
        - *
        - * class App {
        - *   Object getData(C c) {
        - *     return Fences.orderReads(c).data;
        - *   }
        - *
        - *   void setData(C c) {
        - *     Object newValue = ...;
        - *     c.data = Fences.orderWrites(newValue);
        - *     Fences.orderAccesses(c);
        - *   }
        - *   // ...
        - * }}
        - * - * Method {@code getData} provides an emulation of {@code volatile} - * reads of (non-long/double) fields by ensuring that the read of - * {@code c} obtained as an argument is ordered before subsequent - * reads using this reference, and then performs the read of its - * field. Method {@code setData} provides an emulation of volatile - * writes, ensuring that all other relevant writes have completed, - * then performing the assignment, and then ensuring that the write is - * ordered before any other access. These techniques may apply even - * when fields are not directly accessible, in which case calls to - * fence methods would surround calls to methods such as {@code - * c.getData()}. However, these techniques cannot be applied to - * {@code long} or {@code double} fields because reads and writes of - * fields of these types are not guaranteed to be - * atomic. Additionally, correctness may require that all accesses of - * such data use these kinds of wrapper methods, which you would need - * to manually ensure. - * - *

        More generally, Fence methods can be used in this way to achieve - * the safety properties of {@code volatile}. However their use does - * not necessarily guarantee the full sequential consistency - * properties specified in the Java Language Specification chapter 17 - * for programs using {@code volatile}. In particular, emulation using - * Fence methods is not guaranteed to maintain the property that - * {@code volatile} operations performed by different threads are - * observed in the same order by all observer threads. - * - *

        Acquire/Release management of threadsafe objects. It may - * be possible to use weaker conventions for volatile-like variables - * when they are used to keep track of objects that fully manage their - * own thread-safety and synchronization. Here, an acquiring read - * operation remains the same as a volatile-read, but a releasing - * write differs by virtue of not itself ensuring an ordering of its - * write with subsequent reads, because the required effects are - * already ensured by the referenced objects. - * For example: - * - *

         {@code
        - * class Item {
        - *   synchronized f(); // ALL methods are synchronized
        - *   // ...
        - * }
        - *
        - * class ItemHolder {
        - *   private Item item;
        - *   Item acquireItem() {
        - *     return Fences.orderReads(item);
        - *   }
        - *
        - *   void releaseItem(Item x) {
        - *     item = Fences.orderWrites(x);
        - *   }
        - *
        - *   // ...
        - * }}
        - * - * Because this construction avoids use of {@code orderAccesses}, - * which is typically more costly than the other fence methods, it may - * result in better performance than using {@code volatile} or its - * emulation. However, as is the case with most applications of fence - * methods, correctness relies on the usage context -- here, the - * thread safety of {@code Item}, as well as the lack of need for full - * volatile semantics inside this class itself. However, the second - * concern means that it can be difficult to extend the {@code - * ItemHolder} class in this example to be more useful. - * - *

        Avoiding premature finalization. Finalization may occur - * whenever a Java Virtual Machine detects that no reference to an - * object will ever be stored in the heap: A garbage collector may - * reclaim an object even if the fields of that object are still in - * use, so long as the object has otherwise become unreachable. This - * may have surprising and undesirable effects in cases such as the - * following example in which the bookkeeping associated with a class - * is managed through array indices. Here, method {@code action} - * uses a {@code reachabilityFence} to ensure that the Resource - * object is not reclaimed before bookkeeping on an associated - * ExternalResource has been performed; in particular here, to ensure - * that the array slot holding the ExternalResource is not nulled out - * in method {@link Object#finalize}, which may otherwise run - * concurrently. - * - *

         {@code
        - * class Resource {
        - *   private static ExternalResource[] externalResourceArray = ...
        - *
        - *   int myIndex;
        - *   Resource(...) {
        - *     myIndex = ...
        - *     externalResourceArray[myIndex] = ...;
        - *     ...
        - *   }
        - *   protected void finalize() {
        - *     externalResourceArray[myIndex] = null;
        - *     ...
        - *   }
        - *   public void action() {
        - *     try {
        - *       // ...
        - *       int i = myIndex;
        - *       Resource.update(externalResourceArray[i]);
        - *     } finally {
        - *       Fences.reachabilityFence(this);
        - *     }
        - *   }
        - *   private static void update(ExternalResource ext) {
        - *     ext.status = ...;
        - *   }
        - * }}
        - * - * Here, the call to {@code reachabilityFence} is nonintuitively - * placed after the call to {@code update}, to ensure that - * the array slot is not nulled out by {@link Object#finalize} before - * the update, even if the call to {@code action} was the last use of - * this object. This might be the case if for example a usage in a - * user program had the form {@code new Resource().action();} which - * retains no other reference to this Resource. While probably - * overkill here, {@code reachabilityFence} is placed in a {@code - * finally} block to ensure that it is invoked across all paths in the - * method. In a method with more complex control paths, you might - * need further precautions to ensure that {@code reachabilityFence} - * is encountered along all of them. - * - *

        It is sometimes possible to better encapsulate use of - * {@code reachabilityFence}. Continuing the above example, if it - * were OK for the call to method update to proceed even if the - * finalizer had already executed (nulling out slot), then you could - * localize use of {@code reachabilityFence}: - * - *

         {@code
        - * public void action2() {
        - *   // ...
        - *   Resource.update(getExternalResource());
        - * }
        - * private ExternalResource getExternalResource() {
        - *   ExternalResource ext = externalResourceArray[myIndex];
        - *   Fences.reachabilityFence(this);
        - *   return ext;
        - * }}
        - * - *

        Method {@code reachabilityFence} is not required in - * constructions that themselves ensure reachability. For example, - * because objects that are locked cannot in general be reclaimed, it - * would suffice if all accesses of the object, in all methods of - * class Resource (including {@code finalize}) were enclosed in {@code - * synchronized (this)} blocks. (Further, such blocks must not include - * infinite loops, or themselves be unreachable, which fall into the - * corner case exceptions to the "in general" disclaimer.) However, - * method {@code reachabilityFence} remains a better option in cases - * where this approach is not as efficient, desirable, or possible; - * for example because it would encounter deadlock. - * - *

        Formal Properties. - * - *

        Using the terminology of The Java Language Specification chapter - * 17, the rules governing the semantics of the methods of this class - * are as follows: - * - *

        The following is still under construction. - * - *

        - * - *
        [Definitions] - *
        - *
          - * - *
        • Define sequenced(a, b) to be true if a - * occurs before b in program order. - * - *
        • Define accesses(a, p) to be true if - * a is a read or write of a field (or if an array, an - * element) of the object referenced by p. - * - *
        • Define deeplyAccesses(a, p) to be true if either - * accesses(a, p) or deeplyAccesses(a, q) where - * q is the value seen by some read r - * such that accesses(r, p). - * - *
        - *
        [Matching] - *
        Given: - * - *
          - * - *
        • p, a reference to an object - * - *
        • wf, an invocation of {@code orderWrites(p)} or - * {@code orderAccesses(p)} - * - *
        • w, a write of value p - * - *
        • rf, an invocation of {@code orderReads(p)} or - * {@code orderAccesses(p)} - * - *
        • r, a read returning value p - * - *
        - * If: - *
          - *
        • sequenced(wf, w) - *
        • read r sees write w - *
        • sequenced(r, rf) - *
        - * Then: - *
          - * - *
        • wf happens-before rf - * - *
        • wf precedes rf in the - * synchronization order - * - *
        • If (r1, w1) and (r2, - * w2) are two pairs of reads and writes, both - * respectively satisfying the above conditions for p, - * and sequenced(r1, r2) then it is not the case that w2 - * happens-before w1. - * - *
        - *
        [Initial Reads] - *
        Given: - * - *
          - * - *
        • p, a reference to an object - * - *
        • a, an access where deeplyAccesses(a, p) - * - *
        • wf, an invocation of {@code orderWrites(p)} or - * {@code orderAccesses(p)} - * - *
        • w, a write of value p - * - *
        • r, a read returning value p - * - *
        • b, an access where accesses(b, p) - * - *
        - * If: - *
          - *
        • sequenced(a, wf); - *
        • sequenced(wf, w) - *
        • read r sees write w, and - * r is the first read by some thread - * t that sees value p - *
        • sequenced(r, b) - *
        - * Then: - *
          - *
        • the effects of b are constrained - * by the relation a happens-before b. - *
        - *
        [orderAccesses] - *
        Given: - * - *
          - *
        • p, a reference to an object - *
        • f, an invocation of {@code orderAccesses(p)} - *
        - * If: - *
          - *
        • sequenced(f, w) - *
        - * - * Then: - * - *
          - * - *
        • f is an element of the synchronization order. - * - *
        - *
        [Reachability] - *
        Given: - * - *
          - * - *
        • p, a reference to an object - * - *
        • f, an invocation of {@code reachabilityFence(p)} - * - *
        • a, an access where accesses(a, p) - * - *
        • b, an action (by a garbage collector) taking - * the form of an invocation of {@code - * p.finalize()} or of enqueuing any {@link - * java.lang.ref.Reference} constructed with argument p - * - *
        - * - * If: - *
          - *
        • sequenced(a, f) - *
        - * - * Then: - * - *
          - * - *
        • a happens-before b. - * - *
        - * - *
        - * - * @hide - * @author Doug Lea - */ -public class Fences { - private Fences() {} // Non-instantiable - - /** - * The methods of this class are intended to be intrinisified by a - * JVM. However, we provide correct but inefficient Java-level - * code that simply reads and writes a static volatile - * variable. Without JVM support, the consistency effects are - * stronger than necessary, and the memory contention effects can - * be a serious performance issue. - */ - private static volatile int theVolatile; - - /** - * Informally: Ensures that a read of the given reference prior to - * the invocation of this method occurs before a subsequent use of - * the given reference with the effect of reading or writing a - * field (or if an array, element) of the referenced object. The - * use of this method is sensible only when paired with other - * invocations of {@link #orderWrites} and/or {@link - * #orderAccesses} for the given reference. For details, see the - * class documentation for this class. - * - * @param ref the reference. If null, this method has no effect. - * @param the type of the reference - * @return the given ref, to simplify usage - */ - public static T orderReads(T ref) { - int ignore = theVolatile; - return ref; - } - - /** - * Informally: Ensures that a use of the given reference with the - * effect of reading or writing a field (or if an array, element) - * of the referenced object, prior to the invocation of this - * method occur before a subsequent write of the reference. For - * details, see the class documentation for this class. - * - * @param ref the reference. If null, this method has no effect. - * @param the type of the reference - * @return the given ref, to simplify usage - */ - public static T orderWrites(T ref) { - theVolatile = 0; - return ref; - } - - /** - * Informally: Ensures that accesses (reads or writes) using the - * given reference prior to the invocation of this method occur - * before subsequent accesses. For details, see the class - * documentation for this class. - * - * @param ref the reference. If null, this method has no effect. - * @param the type of the reference - * @return the given ref, to simplify usage - */ - public static T orderAccesses(T ref) { - theVolatile = 0; - return ref; - } - - /** - * Ensures that the object referenced by the given reference - * remains strongly reachable (as defined in the {@link - * java.lang.ref} package documentation), regardless of any prior - * actions of the program that might otherwise cause the object to - * become unreachable; thus, the referenced object is not - * reclaimable by garbage collection at least until after the - * invocation of this method. Invocation of this method does not - * itself initiate garbage collection or finalization. - * - *

        See the class-level documentation for further explanation - * and usage examples. - * - * @param ref the reference. If null, this method has no effect. - */ - public static void reachabilityFence(Object ref) { - if (ref != null) { - synchronized (ref) {} - } - } -} diff --git a/luni/src/main/java/java/util/concurrent/atomic/LongAccumulator.java b/luni/src/main/java/java/util/concurrent/atomic/LongAccumulator.java new file mode 100644 index 000000000..85e32416c --- /dev/null +++ b/luni/src/main/java/java/util/concurrent/atomic/LongAccumulator.java @@ -0,0 +1,264 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent.atomic; + +import java.io.Serializable; +import java.util.function.LongBinaryOperator; + +/** + * One or more variables that together maintain a running {@code long} + * value updated using a supplied function. When updates (method + * {@link #accumulate}) are contended across threads, the set of variables + * may grow dynamically to reduce contention. Method {@link #get} + * (or, equivalently, {@link #longValue}) returns the current value + * across the variables maintaining updates. + * + *

        This class is usually preferable to {@link AtomicLong} when + * multiple threads update a common value that is used for purposes such + * as collecting statistics, not for fine-grained synchronization + * control. Under low update contention, the two classes have similar + * characteristics. But under high contention, expected throughput of + * this class is significantly higher, at the expense of higher space + * consumption. + * + *

        The order of accumulation within or across threads is not + * guaranteed and cannot be depended upon, so this class is only + * applicable to functions for which the order of accumulation does + * not matter. The supplied accumulator function should be + * side-effect-free, since it may be re-applied when attempted updates + * fail due to contention among threads. The function is applied with + * the current value as its first argument, and the given update as + * the second argument. For example, to maintain a running maximum + * value, you could supply {@code Long::max} along with {@code + * Long.MIN_VALUE} as the identity. + * + *

        Class {@link LongAdder} provides analogs of the functionality of + * this class for the common special case of maintaining counts and + * sums. The call {@code new LongAdder()} is equivalent to {@code new + * LongAccumulator((x, y) -> x + y, 0L}. + * + *

        This class extends {@link Number}, but does not define + * methods such as {@code equals}, {@code hashCode} and {@code + * compareTo} because instances are expected to be mutated, and so are + * not useful as collection keys. + * + * @since 1.8 + * @author Doug Lea + */ +public class LongAccumulator extends Striped64 implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + private final LongBinaryOperator function; + private final long identity; + + /** + * Creates a new instance using the given accumulator function + * and identity element. + * @param accumulatorFunction a side-effect-free function of two arguments + * @param identity identity (initial value) for the accumulator function + */ + public LongAccumulator(LongBinaryOperator accumulatorFunction, + long identity) { + this.function = accumulatorFunction; + base = this.identity = identity; + } + + /** + * Updates with the given value. + * + * @param x the value + */ + public void accumulate(long x) { + Cell[] as; long b, v, r; int m; Cell a; + if ((as = cells) != null || + (r = function.applyAsLong(b = base, x)) != b && !casBase(b, r)) { + boolean uncontended = true; + if (as == null || (m = as.length - 1) < 0 || + (a = as[getProbe() & m]) == null || + !(uncontended = + (r = function.applyAsLong(v = a.value, x)) == v || + a.cas(v, r))) + longAccumulate(x, function, uncontended); + } + } + + /** + * Returns the current value. The returned value is NOT + * an atomic snapshot; invocation in the absence of concurrent + * updates returns an accurate result, but concurrent updates that + * occur while the value is being calculated might not be + * incorporated. + * + * @return the current value + */ + public long get() { + Cell[] as = cells; + long result = base; + if (as != null) { + for (Cell a : as) + if (a != null) + result = function.applyAsLong(result, a.value); + } + return result; + } + + /** + * Resets variables maintaining updates to the identity value. + * This method may be a useful alternative to creating a new + * updater, but is only effective if there are no concurrent + * updates. Because this method is intrinsically racy, it should + * only be used when it is known that no threads are concurrently + * updating. + */ + public void reset() { + Cell[] as = cells; + base = identity; + if (as != null) { + for (Cell a : as) + if (a != null) + a.reset(identity); + } + } + + /** + * Equivalent in effect to {@link #get} followed by {@link + * #reset}. This method may apply for example during quiescent + * points between multithreaded computations. If there are + * updates concurrent with this method, the returned value is + * not guaranteed to be the final value occurring before + * the reset. + * + * @return the value before reset + */ + public long getThenReset() { + Cell[] as = cells; + long result = base; + base = identity; + if (as != null) { + for (Cell a : as) { + if (a != null) { + long v = a.value; + a.reset(identity); + result = function.applyAsLong(result, v); + } + } + } + return result; + } + + /** + * Returns the String representation of the current value. + * @return the String representation of the current value + */ + public String toString() { + return Long.toString(get()); + } + + /** + * Equivalent to {@link #get}. + * + * @return the current value + */ + public long longValue() { + return get(); + } + + /** + * Returns the {@linkplain #get current value} as an {@code int} + * after a narrowing primitive conversion. + */ + public int intValue() { + return (int)get(); + } + + /** + * Returns the {@linkplain #get current value} as a {@code float} + * after a widening primitive conversion. + */ + public float floatValue() { + return (float)get(); + } + + /** + * Returns the {@linkplain #get current value} as a {@code double} + * after a widening primitive conversion. + */ + public double doubleValue() { + return (double)get(); + } + + /** + * Serialization proxy, used to avoid reference to the non-public + * Striped64 superclass in serialized forms. + * @serial include + */ + private static class SerializationProxy implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /** + * The current value returned by get(). + * @serial + */ + private final long value; + + /** + * The function used for updates. + * @serial + */ + private final LongBinaryOperator function; + + /** + * The identity value. + * @serial + */ + private final long identity; + + SerializationProxy(long value, + LongBinaryOperator function, + long identity) { + this.value = value; + this.function = function; + this.identity = identity; + } + + /** + * Returns a {@code LongAccumulator} object with initial state + * held by this proxy. + * + * @return a {@code LongAccumulator} object with initial state + * held by this proxy + */ + private Object readResolve() { + LongAccumulator a = new LongAccumulator(function, identity); + a.base = value; + return a; + } + } + + /** + * Returns a + * + * SerializationProxy + * representing the state of this instance. + * + * @return a {@link SerializationProxy} + * representing the state of this instance + */ + private Object writeReplace() { + return new SerializationProxy(get(), function, identity); + } + + /** + * @param s the stream + * @throws java.io.InvalidObjectException always + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.InvalidObjectException { + throw new java.io.InvalidObjectException("Proxy required"); + } + +} diff --git a/luni/src/main/java/java/util/concurrent/atomic/LongAdder.java b/luni/src/main/java/java/util/concurrent/atomic/LongAdder.java new file mode 100644 index 000000000..a17d52ac2 --- /dev/null +++ b/luni/src/main/java/java/util/concurrent/atomic/LongAdder.java @@ -0,0 +1,238 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent.atomic; + +import java.io.Serializable; + +/** + * One or more variables that together maintain an initially zero + * {@code long} sum. When updates (method {@link #add}) are contended + * across threads, the set of variables may grow dynamically to reduce + * contention. Method {@link #sum} (or, equivalently, {@link + * #longValue}) returns the current total combined across the + * variables maintaining the sum. + * + *

        This class is usually preferable to {@link AtomicLong} when + * multiple threads update a common sum that is used for purposes such + * as collecting statistics, not for fine-grained synchronization + * control. Under low update contention, the two classes have similar + * characteristics. But under high contention, expected throughput of + * this class is significantly higher, at the expense of higher space + * consumption. + * + *

        LongAdders can be used with a {@link + * java.util.concurrent.ConcurrentHashMap} to maintain a scalable + * frequency map (a form of histogram or multiset). For example, to + * add a count to a {@code ConcurrentHashMap freqs}, + * initializing if not already present, you can use {@code + * freqs.computeIfAbsent(key, k -> new LongAdder()).increment();} + * + *

        This class extends {@link Number}, but does not define + * methods such as {@code equals}, {@code hashCode} and {@code + * compareTo} because instances are expected to be mutated, and so are + * not useful as collection keys. + * + * @since 1.8 + * @author Doug Lea + */ +public class LongAdder extends Striped64 implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /** + * Creates a new adder with initial sum of zero. + */ + public LongAdder() { + } + + /** + * Adds the given value. + * + * @param x the value to add + */ + public void add(long x) { + Cell[] as; long b, v; int m; Cell a; + if ((as = cells) != null || !casBase(b = base, b + x)) { + boolean uncontended = true; + if (as == null || (m = as.length - 1) < 0 || + (a = as[getProbe() & m]) == null || + !(uncontended = a.cas(v = a.value, v + x))) + longAccumulate(x, null, uncontended); + } + } + + /** + * Equivalent to {@code add(1)}. + */ + public void increment() { + add(1L); + } + + /** + * Equivalent to {@code add(-1)}. + */ + public void decrement() { + add(-1L); + } + + /** + * Returns the current sum. The returned value is NOT an + * atomic snapshot; invocation in the absence of concurrent + * updates returns an accurate result, but concurrent updates that + * occur while the sum is being calculated might not be + * incorporated. + * + * @return the sum + */ + public long sum() { + Cell[] as = cells; + long sum = base; + if (as != null) { + for (Cell a : as) + if (a != null) + sum += a.value; + } + return sum; + } + + /** + * Resets variables maintaining the sum to zero. This method may + * be a useful alternative to creating a new adder, but is only + * effective if there are no concurrent updates. Because this + * method is intrinsically racy, it should only be used when it is + * known that no threads are concurrently updating. + */ + public void reset() { + Cell[] as = cells; + base = 0L; + if (as != null) { + for (Cell a : as) + if (a != null) + a.reset(); + } + } + + /** + * Equivalent in effect to {@link #sum} followed by {@link + * #reset}. This method may apply for example during quiescent + * points between multithreaded computations. If there are + * updates concurrent with this method, the returned value is + * not guaranteed to be the final value occurring before + * the reset. + * + * @return the sum + */ + public long sumThenReset() { + Cell[] as = cells; + long sum = base; + base = 0L; + if (as != null) { + for (Cell a : as) { + if (a != null) { + sum += a.value; + a.reset(); + } + } + } + return sum; + } + + /** + * Returns the String representation of the {@link #sum}. + * @return the String representation of the {@link #sum} + */ + public String toString() { + return Long.toString(sum()); + } + + /** + * Equivalent to {@link #sum}. + * + * @return the sum + */ + public long longValue() { + return sum(); + } + + /** + * Returns the {@link #sum} as an {@code int} after a narrowing + * primitive conversion. + */ + public int intValue() { + return (int)sum(); + } + + /** + * Returns the {@link #sum} as a {@code float} + * after a widening primitive conversion. + */ + public float floatValue() { + return (float)sum(); + } + + /** + * Returns the {@link #sum} as a {@code double} after a widening + * primitive conversion. + */ + public double doubleValue() { + return (double)sum(); + } + + /** + * Serialization proxy, used to avoid reference to the non-public + * Striped64 superclass in serialized forms. + * @serial include + */ + private static class SerializationProxy implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /** + * The current value returned by sum(). + * @serial + */ + private final long value; + + SerializationProxy(LongAdder a) { + value = a.sum(); + } + + /** + * Returns a {@code LongAdder} object with initial state + * held by this proxy. + * + * @return a {@code LongAdder} object with initial state + * held by this proxy + */ + private Object readResolve() { + LongAdder a = new LongAdder(); + a.base = value; + return a; + } + } + + /** + * Returns a + * + * SerializationProxy + * representing the state of this instance. + * + * @return a {@link SerializationProxy} + * representing the state of this instance + */ + private Object writeReplace() { + return new SerializationProxy(this); + } + + /** + * @param s the stream + * @throws java.io.InvalidObjectException always + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.InvalidObjectException { + throw new java.io.InvalidObjectException("Proxy required"); + } + +} diff --git a/luni/src/main/java/java/util/concurrent/atomic/Striped64.java b/luni/src/main/java/java/util/concurrent/atomic/Striped64.java new file mode 100644 index 000000000..fc88849da --- /dev/null +++ b/luni/src/main/java/java/util/concurrent/atomic/Striped64.java @@ -0,0 +1,365 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent.atomic; + +import java.util.Arrays; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.DoubleBinaryOperator; +import java.util.function.LongBinaryOperator; + +/** + * A package-local class holding common representation and mechanics + * for classes supporting dynamic striping on 64bit values. The class + * extends Number so that concrete subclasses must publicly do so. + */ +@SuppressWarnings("serial") +abstract class Striped64 extends Number { + /* + * This class maintains a lazily-initialized table of atomically + * updated variables, plus an extra "base" field. The table size + * is a power of two. Indexing uses masked per-thread hash codes. + * Nearly all declarations in this class are package-private, + * accessed directly by subclasses. + * + * Table entries are of class Cell; a variant of AtomicLong padded + * (via @Contended) to reduce cache contention. Padding is + * overkill for most Atomics because they are usually irregularly + * scattered in memory and thus don't interfere much with each + * other. But Atomic objects residing in arrays will tend to be + * placed adjacent to each other, and so will most often share + * cache lines (with a huge negative performance impact) without + * this precaution. + * + * In part because Cells are relatively large, we avoid creating + * them until they are needed. When there is no contention, all + * updates are made to the base field. Upon first contention (a + * failed CAS on base update), the table is initialized to size 2. + * The table size is doubled upon further contention until + * reaching the nearest power of two greater than or equal to the + * number of CPUS. Table slots remain empty (null) until they are + * needed. + * + * A single spinlock ("cellsBusy") is used for initializing and + * resizing the table, as well as populating slots with new Cells. + * There is no need for a blocking lock; when the lock is not + * available, threads try other slots (or the base). During these + * retries, there is increased contention and reduced locality, + * which is still better than alternatives. + * + * The Thread probe fields maintained via ThreadLocalRandom serve + * as per-thread hash codes. We let them remain uninitialized as + * zero (if they come in this way) until they contend at slot + * 0. They are then initialized to values that typically do not + * often conflict with others. Contention and/or table collisions + * are indicated by failed CASes when performing an update + * operation. Upon a collision, if the table size is less than + * the capacity, it is doubled in size unless some other thread + * holds the lock. If a hashed slot is empty, and lock is + * available, a new Cell is created. Otherwise, if the slot + * exists, a CAS is tried. Retries proceed by "double hashing", + * using a secondary hash (Marsaglia XorShift) to try to find a + * free slot. + * + * The table size is capped because, when there are more threads + * than CPUs, supposing that each thread were bound to a CPU, + * there would exist a perfect hash function mapping threads to + * slots that eliminates collisions. When we reach capacity, we + * search for this mapping by randomly varying the hash codes of + * colliding threads. Because search is random, and collisions + * only become known via CAS failures, convergence can be slow, + * and because threads are typically not bound to CPUS forever, + * may not occur at all. However, despite these limitations, + * observed contention rates are typically low in these cases. + * + * It is possible for a Cell to become unused when threads that + * once hashed to it terminate, as well as in the case where + * doubling the table causes no thread to hash to it under + * expanded mask. We do not try to detect or remove such cells, + * under the assumption that for long-running instances, observed + * contention levels will recur, so the cells will eventually be + * needed again; and for short-lived ones, it does not matter. + */ + + /** + * Padded variant of AtomicLong supporting only raw accesses plus CAS. + * + * JVM intrinsics note: It would be possible to use a release-only + * form of CAS here, if it were provided. + */ + // @jdk.internal.vm.annotation.Contended // android-removed + static final class Cell { + volatile long value; + Cell(long x) { value = x; } + final boolean cas(long cmp, long val) { + return U.compareAndSwapLong(this, VALUE, cmp, val); + } + final void reset() { + U.putLongVolatile(this, VALUE, 0L); + } + final void reset(long identity) { + U.putLongVolatile(this, VALUE, identity); + } + + // Unsafe mechanics + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long VALUE; + static { + try { + VALUE = U.objectFieldOffset + (Cell.class.getDeclaredField("value")); + } catch (ReflectiveOperationException e) { + throw new Error(e); + } + } + } + + /** Number of CPUS, to place bound on table size */ + static final int NCPU = Runtime.getRuntime().availableProcessors(); + + /** + * Table of cells. When non-null, size is a power of 2. + */ + transient volatile Cell[] cells; + + /** + * Base value, used mainly when there is no contention, but also as + * a fallback during table initialization races. Updated via CAS. + */ + transient volatile long base; + + /** + * Spinlock (locked via CAS) used when resizing and/or creating Cells. + */ + transient volatile int cellsBusy; + + /** + * Package-private default constructor. + */ + Striped64() { + } + + /** + * CASes the base field. + */ + final boolean casBase(long cmp, long val) { + return U.compareAndSwapLong(this, BASE, cmp, val); + } + + /** + * CASes the cellsBusy field from 0 to 1 to acquire lock. + */ + final boolean casCellsBusy() { + return U.compareAndSwapInt(this, CELLSBUSY, 0, 1); + } + + /** + * Returns the probe value for the current thread. + * Duplicated from ThreadLocalRandom because of packaging restrictions. + */ + static final int getProbe() { + return U.getInt(Thread.currentThread(), PROBE); + } + + /** + * Pseudo-randomly advances and records the given probe value for the + * given thread. + * Duplicated from ThreadLocalRandom because of packaging restrictions. + */ + static final int advanceProbe(int probe) { + probe ^= probe << 13; // xorshift + probe ^= probe >>> 17; + probe ^= probe << 5; + U.putInt(Thread.currentThread(), PROBE, probe); + return probe; + } + + /** + * Handles cases of updates involving initialization, resizing, + * creating new Cells, and/or contention. See above for + * explanation. This method suffers the usual non-modularity + * problems of optimistic retry code, relying on rechecked sets of + * reads. + * + * @param x the value + * @param fn the update function, or null for add (this convention + * avoids the need for an extra field or function in LongAdder). + * @param wasUncontended false if CAS failed before call + */ + final void longAccumulate(long x, LongBinaryOperator fn, + boolean wasUncontended) { + int h; + if ((h = getProbe()) == 0) { + ThreadLocalRandom.current(); // force initialization + h = getProbe(); + wasUncontended = true; + } + boolean collide = false; // True if last slot nonempty + done: for (;;) { + Cell[] as; Cell a; int n; long v; + if ((as = cells) != null && (n = as.length) > 0) { + if ((a = as[(n - 1) & h]) == null) { + if (cellsBusy == 0) { // Try to attach new Cell + Cell r = new Cell(x); // Optimistically create + if (cellsBusy == 0 && casCellsBusy()) { + try { // Recheck under lock + Cell[] rs; int m, j; + if ((rs = cells) != null && + (m = rs.length) > 0 && + rs[j = (m - 1) & h] == null) { + rs[j] = r; + break done; + } + } finally { + cellsBusy = 0; + } + continue; // Slot is now non-empty + } + } + collide = false; + } + else if (!wasUncontended) // CAS already known to fail + wasUncontended = true; // Continue after rehash + else if (a.cas(v = a.value, + (fn == null) ? v + x : fn.applyAsLong(v, x))) + break; + else if (n >= NCPU || cells != as) + collide = false; // At max size or stale + else if (!collide) + collide = true; + else if (cellsBusy == 0 && casCellsBusy()) { + try { + if (cells == as) // Expand table unless stale + cells = Arrays.copyOf(as, n << 1); + } finally { + cellsBusy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h = advanceProbe(h); + } + else if (cellsBusy == 0 && cells == as && casCellsBusy()) { + try { // Initialize table + if (cells == as) { + Cell[] rs = new Cell[2]; + rs[h & 1] = new Cell(x); + cells = rs; + break done; + } + } finally { + cellsBusy = 0; + } + } + // Fall back on using base + else if (casBase(v = base, + (fn == null) ? v + x : fn.applyAsLong(v, x))) + break done; + } + } + + private static long apply(DoubleBinaryOperator fn, long v, double x) { + double d = Double.longBitsToDouble(v); + d = (fn == null) ? d + x : fn.applyAsDouble(d, x); + return Double.doubleToRawLongBits(d); + } + + /** + * Same as longAccumulate, but injecting long/double conversions + * in too many places to sensibly merge with long version, given + * the low-overhead requirements of this class. So must instead be + * maintained by copy/paste/adapt. + */ + final void doubleAccumulate(double x, DoubleBinaryOperator fn, + boolean wasUncontended) { + int h; + if ((h = getProbe()) == 0) { + ThreadLocalRandom.current(); // force initialization + h = getProbe(); + wasUncontended = true; + } + boolean collide = false; // True if last slot nonempty + done: for (;;) { + Cell[] as; Cell a; int n; long v; + if ((as = cells) != null && (n = as.length) > 0) { + if ((a = as[(n - 1) & h]) == null) { + if (cellsBusy == 0) { // Try to attach new Cell + Cell r = new Cell(Double.doubleToRawLongBits(x)); + if (cellsBusy == 0 && casCellsBusy()) { + try { // Recheck under lock + Cell[] rs; int m, j; + if ((rs = cells) != null && + (m = rs.length) > 0 && + rs[j = (m - 1) & h] == null) { + rs[j] = r; + break done; + } + } finally { + cellsBusy = 0; + } + continue; // Slot is now non-empty + } + } + collide = false; + } + else if (!wasUncontended) // CAS already known to fail + wasUncontended = true; // Continue after rehash + else if (a.cas(v = a.value, apply(fn, v, x))) + break; + else if (n >= NCPU || cells != as) + collide = false; // At max size or stale + else if (!collide) + collide = true; + else if (cellsBusy == 0 && casCellsBusy()) { + try { + if (cells == as) // Expand table unless stale + cells = Arrays.copyOf(as, n << 1); + } finally { + cellsBusy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h = advanceProbe(h); + } + else if (cellsBusy == 0 && cells == as && casCellsBusy()) { + try { // Initialize table + if (cells == as) { + Cell[] rs = new Cell[2]; + rs[h & 1] = new Cell(Double.doubleToRawLongBits(x)); + cells = rs; + break done; + } + } finally { + cellsBusy = 0; + } + } + // Fall back on using base + else if (casBase(v = base, apply(fn, v, x))) + break done; + } + } + + // Unsafe mechanics + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long BASE; + private static final long CELLSBUSY; + private static final long PROBE; + static { + try { + BASE = U.objectFieldOffset + (Striped64.class.getDeclaredField("base")); + CELLSBUSY = U.objectFieldOffset + (Striped64.class.getDeclaredField("cellsBusy")); + + PROBE = U.objectFieldOffset + (Thread.class.getDeclaredField("threadLocalRandomProbe")); + } catch (ReflectiveOperationException e) { + throw new Error(e); + } + } + +} diff --git a/luni/src/main/java/java/util/concurrent/atomic/package-info.java b/luni/src/main/java/java/util/concurrent/atomic/package-info.java index 568d2c601..b19dd49a0 100644 --- a/luni/src/main/java/java/util/concurrent/atomic/package-info.java +++ b/luni/src/main/java/java/util/concurrent/atomic/package-info.java @@ -11,7 +11,7 @@ * array elements to those that also provide an atomic conditional update * operation of the form: * - *

         {@code boolean compareAndSet(expectedValue, updateValue);}
        + *
         {@code boolean compareAndSet(expectedValue, updateValue);}
        * *

        This method (which varies in argument types across different * classes) atomically sets a variable to the {@code updateValue} if it @@ -38,7 +38,7 @@ * {@code AtomicInteger} provide atomic increment methods. One * application is to generate sequence numbers, as in: * - *

         {@code
        + * 
         {@code
          * class Sequencer {
          *   private final AtomicLong sequenceNumber
          *     = new AtomicLong(0);
        @@ -53,31 +53,31 @@
          * 
         {@code long transform(long input)}
        * * write your utility method as follows: - *
         {@code
        + * 
         {@code
          * long getAndTransform(AtomicLong var) {
        - *   while (true) {
        - *     long current = var.get();
        - *     long next = transform(current);
        - *     if (var.compareAndSet(current, next))
        - *         return current;
        - *         // return next; for transformAndGet
        - *   }
        + *   long prev, next;
        + *   do {
        + *     prev = var.get();
        + *     next = transform(prev);
        + *   } while (!var.compareAndSet(prev, next));
        + *   return prev; // return next; for transformAndGet
          * }}
        * *

        The memory effects for accesses and updates of atomics generally * follow the rules for volatiles, as stated in - * - * The Java Language Specification (17.4 Memory Model): + * + * Chapter 17 of + * The Java™ Language Specification: * *

          * - *
        • {@code get} has the memory effects of reading a + *
        • {@code get} has the memory effects of reading a * {@code volatile} variable. * - *
        • {@code set} has the memory effects of writing (assigning) a + *
        • {@code set} has the memory effects of writing (assigning) a * {@code volatile} variable. * - *
        • {@code lazySet} has the memory effects of writing (assigning) + *
        • {@code lazySet} has the memory effects of writing (assigning) * a {@code volatile} variable except that it permits reorderings with * subsequent (but not previous) memory actions that do not themselves * impose reordering constraints with ordinary non-{@code volatile} @@ -91,7 +91,7 @@ * with respect to previous or subsequent reads and writes of any * variables other than the target of the {@code weakCompareAndSet}. * - *
        • {@code compareAndSet} + *
        • {@code compareAndSet} * and all other read-and-update operations such as {@code getAndIncrement} * have the memory effects of both reading and * writing {@code volatile} variables. diff --git a/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java b/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java index a74fb2469..3c10805a8 100644 --- a/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java +++ b/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java @@ -6,11 +6,11 @@ package java.util.concurrent.locks; -import java.util.concurrent.TimeUnit; import java.util.ArrayList; import java.util.Collection; import java.util.Date; -import sun.misc.Unsafe; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.AbstractQueuedSynchronizer.Node; /** * A version of {@link AbstractQueuedSynchronizer} in @@ -48,221 +48,6 @@ public abstract class AbstractQueuedLongSynchronizer */ protected AbstractQueuedLongSynchronizer() { } - /** - * Wait queue node class. - * - *

          The wait queue is a variant of a "CLH" (Craig, Landin, and - * Hagersten) lock queue. CLH locks are normally used for - * spinlocks. We instead use them for blocking synchronizers, but - * use the same basic tactic of holding some of the control - * information about a thread in the predecessor of its node. A - * "status" field in each node keeps track of whether a thread - * should block. A node is signalled when its predecessor - * releases. Each node of the queue otherwise serves as a - * specific-notification-style monitor holding a single waiting - * thread. The status field does NOT control whether threads are - * granted locks etc though. A thread may try to acquire if it is - * first in the queue. But being first does not guarantee success; - * it only gives the right to contend. So the currently released - * contender thread may need to rewait. - * - *

          To enqueue into a CLH lock, you atomically splice it in as new - * tail. To dequeue, you just set the head field. - *

          -     *      +------+  prev +-----+       +-----+
          -     * head |      | <---- |     | <---- |     |  tail
          -     *      +------+       +-----+       +-----+
          -     * 
          - * - *

          Insertion into a CLH queue requires only a single atomic - * operation on "tail", so there is a simple atomic point of - * demarcation from unqueued to queued. Similarly, dequeuing - * involves only updating the "head". However, it takes a bit - * more work for nodes to determine who their successors are, - * in part to deal with possible cancellation due to timeouts - * and interrupts. - * - *

          The "prev" links (not used in original CLH locks), are mainly - * needed to handle cancellation. If a node is cancelled, its - * successor is (normally) relinked to a non-cancelled - * predecessor. For explanation of similar mechanics in the case - * of spin locks, see the papers by Scott and Scherer at - * http://www.cs.rochester.edu/u/scott/synchronization/ - * - *

          We also use "next" links to implement blocking mechanics. - * The thread id for each node is kept in its own node, so a - * predecessor signals the next node to wake up by traversing - * next link to determine which thread it is. Determination of - * successor must avoid races with newly queued nodes to set - * the "next" fields of their predecessors. This is solved - * when necessary by checking backwards from the atomically - * updated "tail" when a node's successor appears to be null. - * (Or, said differently, the next-links are an optimization - * so that we don't usually need a backward scan.) - * - *

          Cancellation introduces some conservatism to the basic - * algorithms. Since we must poll for cancellation of other - * nodes, we can miss noticing whether a cancelled node is - * ahead or behind us. This is dealt with by always unparking - * successors upon cancellation, allowing them to stabilize on - * a new predecessor, unless we can identify an uncancelled - * predecessor who will carry this responsibility. - * - *

          CLH queues need a dummy header node to get started. But - * we don't create them on construction, because it would be wasted - * effort if there is never contention. Instead, the node - * is constructed and head and tail pointers are set upon first - * contention. - * - *

          Threads waiting on Conditions use the same nodes, but - * use an additional link. Conditions only need to link nodes - * in simple (non-concurrent) linked queues because they are - * only accessed when exclusively held. Upon await, a node is - * inserted into a condition queue. Upon signal, the node is - * transferred to the main queue. A special value of status - * field is used to mark which queue a node is on. - * - *

          Thanks go to Dave Dice, Mark Moir, Victor Luchangco, Bill - * Scherer and Michael Scott, along with members of JSR-166 - * expert group, for helpful ideas, discussions, and critiques - * on the design of this class. - */ - static final class Node { - /** Marker to indicate a node is waiting in shared mode */ - static final Node SHARED = new Node(); - /** Marker to indicate a node is waiting in exclusive mode */ - static final Node EXCLUSIVE = null; - - /** waitStatus value to indicate thread has cancelled */ - static final int CANCELLED = 1; - /** waitStatus value to indicate successor's thread needs unparking */ - static final int SIGNAL = -1; - /** waitStatus value to indicate thread is waiting on condition */ - static final int CONDITION = -2; - /** - * waitStatus value to indicate the next acquireShared should - * unconditionally propagate - */ - static final int PROPAGATE = -3; - - /** - * Status field, taking on only the values: - * SIGNAL: The successor of this node is (or will soon be) - * blocked (via park), so the current node must - * unpark its successor when it releases or - * cancels. To avoid races, acquire methods must - * first indicate they need a signal, - * then retry the atomic acquire, and then, - * on failure, block. - * CANCELLED: This node is cancelled due to timeout or interrupt. - * Nodes never leave this state. In particular, - * a thread with cancelled node never again blocks. - * CONDITION: This node is currently on a condition queue. - * It will not be used as a sync queue node - * until transferred, at which time the status - * will be set to 0. (Use of this value here has - * nothing to do with the other uses of the - * field, but simplifies mechanics.) - * PROPAGATE: A releaseShared should be propagated to other - * nodes. This is set (for head node only) in - * doReleaseShared to ensure propagation - * continues, even if other operations have - * since intervened. - * 0: None of the above - * - * The values are arranged numerically to simplify use. - * Non-negative values mean that a node doesn't need to - * signal. So, most code doesn't need to check for particular - * values, just for sign. - * - * The field is initialized to 0 for normal sync nodes, and - * CONDITION for condition nodes. It is modified using CAS - * (or when possible, unconditional volatile writes). - */ - volatile int waitStatus; - - /** - * Link to predecessor node that current node/thread relies on - * for checking waitStatus. Assigned during enqueuing, and nulled - * out (for sake of GC) only upon dequeuing. Also, upon - * cancellation of a predecessor, we short-circuit while - * finding a non-cancelled one, which will always exist - * because the head node is never cancelled: A node becomes - * head only as a result of successful acquire. A - * cancelled thread never succeeds in acquiring, and a thread only - * cancels itself, not any other node. - */ - volatile Node prev; - - /** - * Link to the successor node that the current node/thread - * unparks upon release. Assigned during enqueuing, adjusted - * when bypassing cancelled predecessors, and nulled out (for - * sake of GC) when dequeued. The enq operation does not - * assign next field of a predecessor until after attachment, - * so seeing a null next field does not necessarily mean that - * node is at end of queue. However, if a next field appears - * to be null, we can scan prev's from the tail to - * double-check. The next field of cancelled nodes is set to - * point to the node itself instead of null, to make life - * easier for isOnSyncQueue. - */ - volatile Node next; - - /** - * The thread that enqueued this node. Initialized on - * construction and nulled out after use. - */ - volatile Thread thread; - - /** - * Link to next node waiting on condition, or the special - * value SHARED. Because condition queues are accessed only - * when holding in exclusive mode, we just need a simple - * linked queue to hold nodes while they are waiting on - * conditions. They are then transferred to the queue to - * re-acquire. And because conditions can only be exclusive, - * we save a field by using special value to indicate shared - * mode. - */ - Node nextWaiter; - - /** - * Returns true if node is waiting in shared mode. - */ - final boolean isShared() { - return nextWaiter == SHARED; - } - - /** - * Returns previous node, or throws NullPointerException if null. - * Use when predecessor cannot be null. The null check could - * be elided, but is present to help the VM. - * - * @return the predecessor of this node - */ - final Node predecessor() throws NullPointerException { - Node p = prev; - if (p == null) - throw new NullPointerException(); - else - return p; - } - - Node() { // Used to establish initial head or SHARED marker - } - - Node(Thread thread, Node mode) { // Used by addWaiter - this.nextWaiter = mode; - this.thread = thread; - } - - Node(Thread thread, int waitStatus) { // Used by Condition - this.waitStatus = waitStatus; - this.thread = thread; - } - } - /** * Head of the wait queue, lazily initialized. Except for * initialization, it is modified only via method setHead. Note: @@ -297,7 +82,9 @@ protected final long getState() { * @param newState the new state value */ protected final void setState(long newState) { - state = newState; + // Use putLongVolatile instead of ordinary volatile store when + // using compareAndSwapLong, for sake of some 32bit systems. + U.putLongVolatile(this, STATE, newState); } /** @@ -308,12 +95,11 @@ protected final void setState(long newState) { * * @param expect the expected value * @param update the new value - * @return true if successful. False return indicates that the actual + * @return {@code true} if successful. False return indicates that the actual * value was not equal to the expected value. */ protected final boolean compareAndSetState(long expect, long update) { - // See below for intrinsics setup to support this - return unsafe.compareAndSwapLong(this, stateOffset, expect, update); + return U.compareAndSwapLong(this, STATE, expect, update); } // Queuing utilities @@ -323,25 +109,24 @@ protected final boolean compareAndSetState(long expect, long update) { * rather than to use timed park. A rough estimate suffices * to improve responsiveness with very short timeouts. */ - static final long spinForTimeoutThreshold = 1000L; + static final long SPIN_FOR_TIMEOUT_THRESHOLD = 1000L; /** * Inserts node into queue, initializing if necessary. See picture above. * @param node the node to insert * @return node's predecessor */ - private Node enq(final Node node) { + private Node enq(Node node) { for (;;) { - Node t = tail; - if (t == null) { // Must initialize - if (compareAndSetHead(new Node())) - tail = head; - } else { - node.prev = t; - if (compareAndSetTail(t, node)) { - t.next = node; - return t; + Node oldTail = tail; + if (oldTail != null) { + U.putObject(node, Node.PREV, oldTail); + if (compareAndSetTail(oldTail, node)) { + oldTail.next = node; + return oldTail; } + } else { + initializeSyncQueue(); } } } @@ -353,18 +138,20 @@ private Node enq(final Node node) { * @return the new node */ private Node addWaiter(Node mode) { - Node node = new Node(Thread.currentThread(), mode); - // Try the fast path of enq; backup to full enq on failure - Node pred = tail; - if (pred != null) { - node.prev = pred; - if (compareAndSetTail(pred, node)) { - pred.next = node; - return node; + Node node = new Node(mode); + + for (;;) { + Node oldTail = tail; + if (oldTail != null) { + U.putObject(node, Node.PREV, oldTail); + if (compareAndSetTail(oldTail, node)) { + oldTail.next = node; + return node; + } + } else { + initializeSyncQueue(); } } - enq(node); - return node; } /** @@ -393,7 +180,7 @@ private void unparkSuccessor(Node node) { */ int ws = node.waitStatus; if (ws < 0) - compareAndSetWaitStatus(node, ws, 0); + node.compareAndSetWaitStatus(ws, 0); /* * Thread to unpark is held in successor, which is normally @@ -404,7 +191,7 @@ private void unparkSuccessor(Node node) { Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; - for (Node p = tail; p != null && p != node; p = p.prev) + for (Node p = tail; p != node && p != null; p = p.prev) if (p.waitStatus <= 0) s = p; } @@ -434,12 +221,12 @@ private void doReleaseShared() { if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { - if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) + if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0)) continue; // loop to recheck cases unparkSuccessor(h); } else if (ws == 0 && - !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) + !h.compareAndSetWaitStatus(0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed @@ -461,7 +248,8 @@ private void setHeadAndPropagate(Node node, long propagate) { /* * Try to signal next queued node if: * Propagation was indicated by caller, - * or was recorded (as h.waitStatus) by a previous operation + * or was recorded (as h.waitStatus either before + * or after setHead) by a previous operation * (note: this uses sign-check of waitStatus because * PROPAGATE status may transition to SIGNAL.) * and @@ -473,7 +261,8 @@ private void setHeadAndPropagate(Node node, long propagate) { * racing acquires/releases, so most need signals now or soon * anyway. */ - if (propagate > 0 || h == null || h.waitStatus < 0) { + if (propagate > 0 || h == null || h.waitStatus < 0 || + (h = head) == null || h.waitStatus < 0) { Node s = node.next; if (s == null || s.isShared()) doReleaseShared(); @@ -511,18 +300,18 @@ private void cancelAcquire(Node node) { // If we are the tail, remove ourselves. if (node == tail && compareAndSetTail(node, pred)) { - compareAndSetNext(pred, predNext, null); + pred.compareAndSetNext(predNext, null); } else { // If successor needs signal, try to set pred's next-link // so it will get one. Otherwise wake it up to propagate. int ws; if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || - (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && + (ws <= 0 && pred.compareAndSetWaitStatus(ws, Node.SIGNAL))) && pred.thread != null) { Node next = node.next; if (next != null && next.waitStatus <= 0) - compareAndSetNext(pred, predNext, next); + pred.compareAndSetNext(predNext, next); } else { unparkSuccessor(node); } @@ -563,7 +352,7 @@ private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ - compareAndSetWaitStatus(pred, ws, Node.SIGNAL); + pred.compareAndSetWaitStatus(ws, Node.SIGNAL); } return false; } @@ -576,7 +365,7 @@ static void selfInterrupt() { } /** - * Convenience method to park and then check if interrupted + * Convenience method to park and then check if interrupted. * * @return {@code true} if interrupted */ @@ -603,7 +392,6 @@ private final boolean parkAndCheckInterrupt() { * @return {@code true} if interrupted while waiting */ final boolean acquireQueued(final Node node, long arg) { - boolean failed = true; try { boolean interrupted = false; for (;;) { @@ -611,16 +399,15 @@ final boolean acquireQueued(final Node node, long arg) { if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC - failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } - } finally { - if (failed) - cancelAcquire(node); + } catch (Throwable t) { + cancelAcquire(node); + throw t; } } @@ -631,23 +418,21 @@ final boolean acquireQueued(final Node node, long arg) { private void doAcquireInterruptibly(long arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); - boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC - failed = false; return; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } - } finally { - if (failed) - cancelAcquire(node); + } catch (Throwable t) { + cancelAcquire(node); + throw t; } } @@ -664,28 +449,28 @@ private boolean doAcquireNanos(long arg, long nanosTimeout) return false; final long deadline = System.nanoTime() + nanosTimeout; final Node node = addWaiter(Node.EXCLUSIVE); - boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC - failed = false; return true; } nanosTimeout = deadline - System.nanoTime(); - if (nanosTimeout <= 0L) + if (nanosTimeout <= 0L) { + cancelAcquire(node); return false; + } if (shouldParkAfterFailedAcquire(p, node) && - nanosTimeout > spinForTimeoutThreshold) + nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) LockSupport.parkNanos(this, nanosTimeout); if (Thread.interrupted()) throw new InterruptedException(); } - } finally { - if (failed) - cancelAcquire(node); + } catch (Throwable t) { + cancelAcquire(node); + throw t; } } @@ -695,7 +480,6 @@ private boolean doAcquireNanos(long arg, long nanosTimeout) */ private void doAcquireShared(long arg) { final Node node = addWaiter(Node.SHARED); - boolean failed = true; try { boolean interrupted = false; for (;;) { @@ -707,7 +491,6 @@ private void doAcquireShared(long arg) { p.next = null; // help GC if (interrupted) selfInterrupt(); - failed = false; return; } } @@ -715,9 +498,9 @@ private void doAcquireShared(long arg) { parkAndCheckInterrupt()) interrupted = true; } - } finally { - if (failed) - cancelAcquire(node); + } catch (Throwable t) { + cancelAcquire(node); + throw t; } } @@ -728,7 +511,6 @@ private void doAcquireShared(long arg) { private void doAcquireSharedInterruptibly(long arg) throws InterruptedException { final Node node = addWaiter(Node.SHARED); - boolean failed = true; try { for (;;) { final Node p = node.predecessor(); @@ -737,7 +519,6 @@ private void doAcquireSharedInterruptibly(long arg) if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC - failed = false; return; } } @@ -745,9 +526,9 @@ private void doAcquireSharedInterruptibly(long arg) parkAndCheckInterrupt()) throw new InterruptedException(); } - } finally { - if (failed) - cancelAcquire(node); + } catch (Throwable t) { + cancelAcquire(node); + throw t; } } @@ -764,7 +545,6 @@ private boolean doAcquireSharedNanos(long arg, long nanosTimeout) return false; final long deadline = System.nanoTime() + nanosTimeout; final Node node = addWaiter(Node.SHARED); - boolean failed = true; try { for (;;) { final Node p = node.predecessor(); @@ -773,22 +553,23 @@ private boolean doAcquireSharedNanos(long arg, long nanosTimeout) if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC - failed = false; return true; } } nanosTimeout = deadline - System.nanoTime(); - if (nanosTimeout <= 0L) + if (nanosTimeout <= 0L) { + cancelAcquire(node); return false; + } if (shouldParkAfterFailedAcquire(p, node) && - nanosTimeout > spinForTimeoutThreshold) + nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) LockSupport.parkNanos(this, nanosTimeout); if (Thread.interrupted()) throw new InterruptedException(); } - } finally { - if (failed) - cancelAcquire(node); + } catch (Throwable t) { + cancelAcquire(node); + throw t; } } @@ -1112,7 +893,7 @@ public final boolean hasQueuedThreads() { /** * Queries whether any threads have ever contended to acquire this - * synchronizer; that is if an acquire method has ever blocked. + * synchronizer; that is, if an acquire method has ever blocked. * *

          In this implementation, this operation returns in * constant time. @@ -1140,7 +921,7 @@ public final Thread getFirstQueuedThread() { } /** - * Version of getFirstQueuedThread called when fastpath fails + * Version of getFirstQueuedThread called when fastpath fails. */ private Thread fullGetFirstQueuedThread() { /* @@ -1167,13 +948,11 @@ private Thread fullGetFirstQueuedThread() { * guaranteeing termination. */ - Node t = tail; Thread firstThread = null; - while (t != null && t != head) { - Thread tt = t.thread; - if (tt != null) - firstThread = tt; - t = t.prev; + for (Node p = tail; p != null && p != head; p = p.prev) { + Thread t = p.thread; + if (t != null) + firstThread = t; } return firstThread; } @@ -1220,9 +999,9 @@ final boolean apparentlyFirstQueuedIsExclusive() { * *

          An invocation of this method is equivalent to (but may be * more efficient than): - *

           {@code
          -     * getFirstQueuedThread() != Thread.currentThread() &&
          -     * hasQueuedThreads()}
          + *
           {@code
          +     * getFirstQueuedThread() != Thread.currentThread()
          +     *   && hasQueuedThreads()}
          * *

          Note that because cancellations due to interrupts and * timeouts may occur at any time, a {@code true} return does not @@ -1240,7 +1019,7 @@ final boolean apparentlyFirstQueuedIsExclusive() { * tryAcquire} method for a fair, reentrant, exclusive mode * synchronizer might look like this: * - *

           {@code
          +     * 
           {@code
                * protected boolean tryAcquire(int arg) {
                *   if (isHeldExclusively()) {
                *     // A reentrant acquire; increment hold count
          @@ -1276,8 +1055,7 @@ public final boolean hasQueuedPredecessors() {
                * acquire.  The value is only an estimate because the number of
                * threads may change dynamically while this method traverses
                * internal data structures.  This method is designed for use in
          -     * monitoring system state, not for synchronization
          -     * control.
          +     * monitoring system state, not for synchronization control.
                *
                * @return the estimated number of threads waiting to acquire
                */
          @@ -1302,7 +1080,7 @@ public final int getQueueLength() {
                * @return the collection of threads
                */
               public final Collection getQueuedThreads() {
          -        ArrayList list = new ArrayList();
          +        ArrayList list = new ArrayList<>();
                   for (Node p = tail; p != null; p = p.prev) {
                       Thread t = p.thread;
                       if (t != null)
          @@ -1320,7 +1098,7 @@ public final Collection getQueuedThreads() {
                * @return the collection of threads
                */
               public final Collection getExclusiveQueuedThreads() {
          -        ArrayList list = new ArrayList();
          +        ArrayList list = new ArrayList<>();
                   for (Node p = tail; p != null; p = p.prev) {
                       if (!p.isShared()) {
                           Thread t = p.thread;
          @@ -1340,7 +1118,7 @@ public final Collection getExclusiveQueuedThreads() {
                * @return the collection of threads
                */
               public final Collection getSharedQueuedThreads() {
          -        ArrayList list = new ArrayList();
          +        ArrayList list = new ArrayList<>();
                   for (Node p = tail; p != null; p = p.prev) {
                       if (p.isShared()) {
                           Thread t = p.thread;
          @@ -1361,10 +1139,9 @@ public final Collection getSharedQueuedThreads() {
                * @return a string identifying this synchronizer, as well as its state
                */
               public String toString() {
          -        long s = getState();
          -        String q  = hasQueuedThreads() ? "non" : "";
          -        return super.toString() +
          -            "[State = " + s + ", " + q + "empty queue]";
          +        return super.toString()
          +            + "[State = " + getState() + ", "
          +            + (hasQueuedThreads() ? "non" : "") + "empty queue]";
               }
           
           
          @@ -1398,8 +1175,10 @@ final boolean isOnSyncQueue(Node node) {
                * @return true if present
                */
               private boolean findNodeFromTail(Node node) {
          -        Node p = tail;
          -        for (;;) {
          +        // We check for node first, since it's likely to be at or near tail.
          +        // tail is known to be non-null, so we could re-order to "save"
          +        // one null check, but we leave it this way to help the VM.
          +        for (Node p = tail;;) {
                       if (p == node)
                           return true;
                       if (p == null)
          @@ -1419,7 +1198,7 @@ final boolean transferForSignal(Node node) {
                   /*
                    * If cannot change waitStatus, the node has been cancelled.
                    */
          -        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
          +        if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
                       return false;
           
                   /*
          @@ -1430,7 +1209,7 @@ final boolean transferForSignal(Node node) {
                    */
                   Node p = enq(node);
                   int ws = p.waitStatus;
          -        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
          +        if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
                       LockSupport.unpark(node.thread);
                   return true;
               }
          @@ -1443,7 +1222,7 @@ final boolean transferForSignal(Node node) {
                * @return true if cancelled before the node was signalled
                */
               final boolean transferAfterCancelledWait(Node node) {
          -        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
          +        if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) {
                       enq(node);
                       return true;
                   }
          @@ -1465,18 +1244,14 @@ final boolean transferAfterCancelledWait(Node node) {
                * @return previous sync state
                */
               final long fullyRelease(Node node) {
          -        boolean failed = true;
                   try {
                       long savedState = getState();
          -            if (release(savedState)) {
          -                failed = false;
          +            if (release(savedState))
                           return savedState;
          -            } else {
          -                throw new IllegalMonitorStateException();
          -            }
          -        } finally {
          -            if (failed)
          -                node.waitStatus = Node.CANCELLED;
          +            throw new IllegalMonitorStateException();
          +        } catch (Throwable t) {
          +            node.waitStatus = Node.CANCELLED;
          +            throw t;
                   }
               }
           
          @@ -1521,8 +1296,8 @@ public final boolean hasWaiters(ConditionObject condition) {
                * given condition associated with this synchronizer. Note that
                * because timeouts and interrupts may occur at any time, the
                * estimate serves only as an upper bound on the actual number of
          -     * waiters.  This method is designed for use in monitoring of the
          -     * system state, not for synchronization control.
          +     * waiters.  This method is designed for use in monitoring system
          +     * state, not for synchronization control.
                *
                * @param condition the condition
                * @return the estimated number of waiting threads
          @@ -1602,7 +1377,9 @@ private Node addConditionWaiter() {
                           unlinkCancelledWaiters();
                           t = lastWaiter;
                       }
          -            Node node = new Node(Thread.currentThread(), Node.CONDITION);
          +
          +            Node node = new Node(Node.CONDITION);
          +
                       if (t == null)
                           firstWaiter = node;
                       else
          @@ -1710,12 +1487,12 @@ public final void signalAll() {
                   /**
                    * Implements uninterruptible condition wait.
                    * 
            - *
          1. Save lock state returned by {@link #getState}. - *
          2. Invoke {@link #release} with saved state as argument, - * throwing IllegalMonitorStateException if it fails. - *
          3. Block until signalled. - *
          4. Reacquire by invoking specialized version of - * {@link #acquire} with saved state as argument. + *
          5. Save lock state returned by {@link #getState}. + *
          6. Invoke {@link #release} with saved state as argument, + * throwing IllegalMonitorStateException if it fails. + *
          7. Block until signalled. + *
          8. Reacquire by invoking specialized version of + * {@link #acquire} with saved state as argument. *
          */ public final void awaitUninterruptibly() { @@ -1769,14 +1546,14 @@ else if (interruptMode == REINTERRUPT) /** * Implements interruptible condition wait. *
            - *
          1. If current thread is interrupted, throw InterruptedException. - *
          2. Save lock state returned by {@link #getState}. - *
          3. Invoke {@link #release} with saved state as argument, - * throwing IllegalMonitorStateException if it fails. - *
          4. Block until signalled or interrupted. - *
          5. Reacquire by invoking specialized version of - * {@link #acquire} with saved state as argument. - *
          6. If interrupted while blocked in step 4, throw InterruptedException. + *
          7. If current thread is interrupted, throw InterruptedException. + *
          8. Save lock state returned by {@link #getState}. + *
          9. Invoke {@link #release} with saved state as argument, + * throwing IllegalMonitorStateException if it fails. + *
          10. Block until signalled or interrupted. + *
          11. Reacquire by invoking specialized version of + * {@link #acquire} with saved state as argument. + *
          12. If interrupted while blocked in step 4, throw InterruptedException. *
          */ public final void await() throws InterruptedException { @@ -1801,31 +1578,33 @@ public final void await() throws InterruptedException { /** * Implements timed condition wait. *
            - *
          1. If current thread is interrupted, throw InterruptedException. - *
          2. Save lock state returned by {@link #getState}. - *
          3. Invoke {@link #release} with saved state as argument, - * throwing IllegalMonitorStateException if it fails. - *
          4. Block until signalled, interrupted, or timed out. - *
          5. Reacquire by invoking specialized version of - * {@link #acquire} with saved state as argument. - *
          6. If interrupted while blocked in step 4, throw InterruptedException. + *
          7. If current thread is interrupted, throw InterruptedException. + *
          8. Save lock state returned by {@link #getState}. + *
          9. Invoke {@link #release} with saved state as argument, + * throwing IllegalMonitorStateException if it fails. + *
          10. Block until signalled, interrupted, or timed out. + *
          11. Reacquire by invoking specialized version of + * {@link #acquire} with saved state as argument. + *
          12. If interrupted while blocked in step 4, throw InterruptedException. *
          */ public final long awaitNanos(long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); + // We don't check for nanosTimeout <= 0L here, to allow + // awaitNanos(0) as a way to "yield the lock". + final long deadline = System.nanoTime() + nanosTimeout; long initialNanos = nanosTimeout; Node node = addConditionWaiter(); long savedState = fullyRelease(node); - final long deadline = System.nanoTime() + nanosTimeout; int interruptMode = 0; while (!isOnSyncQueue(node)) { if (nanosTimeout <= 0L) { transferAfterCancelledWait(node); break; } - if (nanosTimeout >= spinForTimeoutThreshold) + if (nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) LockSupport.parkNanos(this, nanosTimeout); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; @@ -1838,24 +1617,21 @@ public final long awaitNanos(long nanosTimeout) if (interruptMode != 0) reportInterruptAfterWait(interruptMode); long remaining = deadline - System.nanoTime(); // avoid overflow - // BEGIN android-note Changed from < to <= http://b/24284239 - // return (remaining < initialNanos) ? remaining : Long.MIN_VALUE; return (remaining <= initialNanos) ? remaining : Long.MIN_VALUE; - // END android-note } /** * Implements absolute timed condition wait. *
            - *
          1. If current thread is interrupted, throw InterruptedException. - *
          2. Save lock state returned by {@link #getState}. - *
          3. Invoke {@link #release} with saved state as argument, - * throwing IllegalMonitorStateException if it fails. - *
          4. Block until signalled, interrupted, or timed out. - *
          5. Reacquire by invoking specialized version of - * {@link #acquire} with saved state as argument. - *
          6. If interrupted while blocked in step 4, throw InterruptedException. - *
          7. If timed out while blocked in step 4, return false, else true. + *
          8. If current thread is interrupted, throw InterruptedException. + *
          9. Save lock state returned by {@link #getState}. + *
          10. Invoke {@link #release} with saved state as argument, + * throwing IllegalMonitorStateException if it fails. + *
          11. Block until signalled, interrupted, or timed out. + *
          12. Reacquire by invoking specialized version of + * {@link #acquire} with saved state as argument. + *
          13. If interrupted while blocked in step 4, throw InterruptedException. + *
          14. If timed out while blocked in step 4, return false, else true. *
          */ public final boolean awaitUntil(Date deadline) @@ -1868,7 +1644,7 @@ public final boolean awaitUntil(Date deadline) boolean timedout = false; int interruptMode = 0; while (!isOnSyncQueue(node)) { - if (System.currentTimeMillis() > abstime) { + if (System.currentTimeMillis() >= abstime) { timedout = transferAfterCancelledWait(node); break; } @@ -1888,15 +1664,15 @@ public final boolean awaitUntil(Date deadline) /** * Implements timed condition wait. *
            - *
          1. If current thread is interrupted, throw InterruptedException. - *
          2. Save lock state returned by {@link #getState}. - *
          3. Invoke {@link #release} with saved state as argument, - * throwing IllegalMonitorStateException if it fails. - *
          4. Block until signalled, interrupted, or timed out. - *
          5. Reacquire by invoking specialized version of - * {@link #acquire} with saved state as argument. - *
          6. If interrupted while blocked in step 4, throw InterruptedException. - *
          7. If timed out while blocked in step 4, return false, else true. + *
          8. If current thread is interrupted, throw InterruptedException. + *
          9. Save lock state returned by {@link #getState}. + *
          10. Invoke {@link #release} with saved state as argument, + * throwing IllegalMonitorStateException if it fails. + *
          11. Block until signalled, interrupted, or timed out. + *
          12. Reacquire by invoking specialized version of + * {@link #acquire} with saved state as argument. + *
          13. If interrupted while blocked in step 4, throw InterruptedException. + *
          14. If timed out while blocked in step 4, return false, else true. *
          */ public final boolean await(long time, TimeUnit unit) @@ -1904,9 +1680,11 @@ public final boolean await(long time, TimeUnit unit) long nanosTimeout = unit.toNanos(time); if (Thread.interrupted()) throw new InterruptedException(); + // We don't check for nanosTimeout <= 0L here, to allow + // await(0, unit) as a way to "yield the lock". + final long deadline = System.nanoTime() + nanosTimeout; Node node = addConditionWaiter(); long savedState = fullyRelease(node); - final long deadline = System.nanoTime() + nanosTimeout; boolean timedout = false; int interruptMode = 0; while (!isOnSyncQueue(node)) { @@ -1914,7 +1692,7 @@ public final boolean await(long time, TimeUnit unit) timedout = transferAfterCancelledWait(node); break; } - if (nanosTimeout >= spinForTimeoutThreshold) + if (nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) LockSupport.parkNanos(this, nanosTimeout); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; @@ -1943,7 +1721,7 @@ final boolean isOwnedBy(AbstractQueuedLongSynchronizer sync) { /** * Queries whether any threads are waiting on this condition. - * Implements {@link AbstractQueuedLongSynchronizer#hasWaiters}. + * Implements {@link AbstractQueuedLongSynchronizer#hasWaiters(ConditionObject)}. * * @return {@code true} if there are any waiting threads * @throws IllegalMonitorStateException if {@link #isHeldExclusively} @@ -1962,7 +1740,7 @@ protected final boolean hasWaiters() { /** * Returns an estimate of the number of threads waiting on * this condition. - * Implements {@link AbstractQueuedLongSynchronizer#getWaitQueueLength}. + * Implements {@link AbstractQueuedLongSynchronizer#getWaitQueueLength(ConditionObject)}. * * @return the estimated number of waiting threads * @throws IllegalMonitorStateException if {@link #isHeldExclusively} @@ -1982,7 +1760,7 @@ protected final int getWaitQueueLength() { /** * Returns a collection containing those threads that may be * waiting on this Condition. - * Implements {@link AbstractQueuedLongSynchronizer#getWaitingThreads}. + * Implements {@link AbstractQueuedLongSynchronizer#getWaitingThreads(ConditionObject)}. * * @return the collection of threads * @throws IllegalMonitorStateException if {@link #isHeldExclusively} @@ -1991,7 +1769,7 @@ protected final int getWaitQueueLength() { protected final Collection getWaitingThreads() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); - ArrayList list = new ArrayList(); + ArrayList list = new ArrayList<>(); for (Node w = firstWaiter; w != null; w = w.nextWaiter) { if (w.waitStatus == Node.CONDITION) { Thread t = w.thread; @@ -2012,27 +1790,22 @@ protected final Collection getWaitingThreads() { * are at it, we do the same for other CASable fields (which could * otherwise be done with atomic field updaters). */ - private static final Unsafe unsafe = Unsafe.getUnsafe(); - private static final long stateOffset; - private static final long headOffset; - private static final long tailOffset; - private static final long waitStatusOffset; - private static final long nextOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long STATE; + private static final long HEAD; + private static final long TAIL; static { try { - stateOffset = unsafe.objectFieldOffset + STATE = U.objectFieldOffset (AbstractQueuedLongSynchronizer.class.getDeclaredField("state")); - headOffset = unsafe.objectFieldOffset + HEAD = U.objectFieldOffset (AbstractQueuedLongSynchronizer.class.getDeclaredField("head")); - tailOffset = unsafe.objectFieldOffset + TAIL = U.objectFieldOffset (AbstractQueuedLongSynchronizer.class.getDeclaredField("tail")); - waitStatusOffset = unsafe.objectFieldOffset - (Node.class.getDeclaredField("waitStatus")); - nextOffset = unsafe.objectFieldOffset - (Node.class.getDeclaredField("next")); - - } catch (Exception ex) { throw new Error(ex); } + } catch (ReflectiveOperationException e) { + throw new Error(e); + } // Reduce the risk of rare disastrous classloading in first call to // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773 @@ -2040,35 +1813,18 @@ protected final Collection getWaitingThreads() { } /** - * CAS head field. Used only by enq. + * Initializes head and tail fields on first contention. */ - private final boolean compareAndSetHead(Node update) { - return unsafe.compareAndSwapObject(this, headOffset, null, update); + private final void initializeSyncQueue() { + Node h; + if (U.compareAndSwapObject(this, HEAD, null, (h = new Node()))) + tail = h; } /** - * CAS tail field. Used only by enq. + * CASes tail field. */ private final boolean compareAndSetTail(Node expect, Node update) { - return unsafe.compareAndSwapObject(this, tailOffset, expect, update); - } - - /** - * CAS waitStatus field of a node. - */ - private static final boolean compareAndSetWaitStatus(Node node, - int expect, - int update) { - return unsafe.compareAndSwapInt(node, waitStatusOffset, - expect, update); - } - - /** - * CAS next field of a node. - */ - private static final boolean compareAndSetNext(Node node, - Node expect, - Node update) { - return unsafe.compareAndSwapObject(node, nextOffset, expect, update); + return U.compareAndSwapObject(this, TAIL, expect, update); } } diff --git a/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedSynchronizer.java b/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedSynchronizer.java index 8823b6fe6..2c4100047 100644 --- a/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedSynchronizer.java +++ b/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedSynchronizer.java @@ -6,15 +6,11 @@ package java.util.concurrent.locks; -import java.util.concurrent.TimeUnit; import java.util.ArrayList; import java.util.Collection; import java.util.Date; -import sun.misc.Unsafe; - -// BEGIN android-note -// Use older class level documentation to not @link to hasQueuedPredecessors -// END android-changed +import java.util.concurrent.TimeUnit; +/// OPENJDK-9 import jdk.internal.vm.annotation.ReservedStackAccess; /** * Provides a framework for implementing blocking locks and related @@ -86,11 +82,11 @@ * #setState} and/or {@link #compareAndSetState}: * *
            - *
          • {@link #tryAcquire} - *
          • {@link #tryRelease} - *
          • {@link #tryAcquireShared} - *
          • {@link #tryReleaseShared} - *
          • {@link #isHeldExclusively} + *
          • {@link #tryAcquire} + *
          • {@link #tryRelease} + *
          • {@link #tryAcquireShared} + *
          • {@link #tryReleaseShared} + *
          • {@link #isHeldExclusively} *
          * * Each of these methods by default throws {@link @@ -131,7 +127,7 @@ * disable barging by internally invoking one or more of the inspection * methods, thereby providing a fair FIFO acquisition order. * In particular, most fair synchronizers can define {@code tryAcquire} - * to return {@code false} if {@code hasQueuedPredecessors} (a method + * to return {@code false} if {@link #hasQueuedPredecessors} (a method * specifically designed to be used by fair synchronizers) returns * {@code true}. Other variations are possible. * @@ -171,7 +167,7 @@ * It also supports conditions and exposes * one of the instrumentation methods: * - *
           {@code
          + * 
           {@code
            * class Mutex implements Lock, java.io.Serializable {
            *
            *   // Our internal helper class
          @@ -235,7 +231,7 @@
            * fire. Because a latch is non-exclusive, it uses the {@code shared}
            * acquire and release methods.
            *
          - *  
           {@code
          + * 
           {@code
            * class BooleanLatch {
            *
            *   private static class Sync extends AbstractQueuedSynchronizer {
          @@ -359,15 +355,15 @@ static final class Node {
                   /** Marker to indicate a node is waiting in exclusive mode */
                   static final Node EXCLUSIVE = null;
           
          -        /** waitStatus value to indicate thread has cancelled */
          +        /** waitStatus value to indicate thread has cancelled. */
                   static final int CANCELLED =  1;
          -        /** waitStatus value to indicate successor's thread needs unparking */
          +        /** waitStatus value to indicate successor's thread needs unparking. */
                   static final int SIGNAL    = -1;
          -        /** waitStatus value to indicate thread is waiting on condition */
          +        /** waitStatus value to indicate thread is waiting on condition. */
                   static final int CONDITION = -2;
                   /**
                    * waitStatus value to indicate the next acquireShared should
          -         * unconditionally propagate
          +         * unconditionally propagate.
                    */
                   static final int PROPAGATE = -3;
           
          @@ -475,17 +471,49 @@ final Node predecessor() throws NullPointerException {
                           return p;
                   }
           
          -        Node() {    // Used to establish initial head or SHARED marker
          +        /** Establishes initial head or SHARED marker. */
          +        Node() {}
          +
          +        /** Constructor used by addWaiter. */
          +        Node(Node nextWaiter) {
          +            this.nextWaiter = nextWaiter;
          +            U.putObject(this, THREAD, Thread.currentThread());
          +        }
          +
          +        /** Constructor used by addConditionWaiter. */
          +        Node(int waitStatus) {
          +            U.putInt(this, WAITSTATUS, waitStatus);
          +            U.putObject(this, THREAD, Thread.currentThread());
                   }
           
          -        Node(Thread thread, Node mode) {     // Used by addWaiter
          -            this.nextWaiter = mode;
          -            this.thread = thread;
          +        /** CASes waitStatus field. */
          +        final boolean compareAndSetWaitStatus(int expect, int update) {
          +            return U.compareAndSwapInt(this, WAITSTATUS, expect, update);
                   }
           
          -        Node(Thread thread, int waitStatus) { // Used by Condition
          -            this.waitStatus = waitStatus;
          -            this.thread = thread;
          +        /** CASes next field. */
          +        final boolean compareAndSetNext(Node expect, Node update) {
          +            return U.compareAndSwapObject(this, NEXT, expect, update);
          +        }
          +
          +        private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
          +        private static final long NEXT;
          +        static final long PREV;
          +        private static final long THREAD;
          +        private static final long WAITSTATUS;
          +        static {
          +            try {
          +                NEXT = U.objectFieldOffset
          +                    (Node.class.getDeclaredField("next"));
          +                PREV = U.objectFieldOffset
          +                    (Node.class.getDeclaredField("prev"));
          +                THREAD = U.objectFieldOffset
          +                    (Node.class.getDeclaredField("thread"));
          +                WAITSTATUS = U.objectFieldOffset
          +                    (Node.class.getDeclaredField("waitStatus"));
          +            } catch (ReflectiveOperationException e) {
          +                throw new Error(e);
          +            }
                   }
               }
           
          @@ -534,12 +562,11 @@ protected final void setState(int newState) {
                *
                * @param expect the expected value
                * @param update the new value
          -     * @return true if successful. False return indicates that the actual
          +     * @return {@code true} if successful. False return indicates that the actual
                *         value was not equal to the expected value.
                */
               protected final boolean compareAndSetState(int expect, int update) {
          -        // See below for intrinsics setup to support this
          -        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
          +        return U.compareAndSwapInt(this, STATE, expect, update);
               }
           
               // Queuing utilities
          @@ -549,25 +576,24 @@ protected final boolean compareAndSetState(int expect, int update) {
                * rather than to use timed park. A rough estimate suffices
                * to improve responsiveness with very short timeouts.
                */
          -    static final long spinForTimeoutThreshold = 1000L;
          +    static final long SPIN_FOR_TIMEOUT_THRESHOLD = 1000L;
           
               /**
                * Inserts node into queue, initializing if necessary. See picture above.
                * @param node the node to insert
                * @return node's predecessor
                */
          -    private Node enq(final Node node) {
          +    private Node enq(Node node) {
                   for (;;) {
          -            Node t = tail;
          -            if (t == null) { // Must initialize
          -                if (compareAndSetHead(new Node()))
          -                    tail = head;
          -            } else {
          -                node.prev = t;
          -                if (compareAndSetTail(t, node)) {
          -                    t.next = node;
          -                    return t;
          +            Node oldTail = tail;
          +            if (oldTail != null) {
          +                U.putObject(node, Node.PREV, oldTail);
          +                if (compareAndSetTail(oldTail, node)) {
          +                    oldTail.next = node;
          +                    return oldTail;
                           }
          +            } else {
          +                initializeSyncQueue();
                       }
                   }
               }
          @@ -579,18 +605,20 @@ private Node enq(final Node node) {
                * @return the new node
                */
               private Node addWaiter(Node mode) {
          -        Node node = new Node(Thread.currentThread(), mode);
          -        // Try the fast path of enq; backup to full enq on failure
          -        Node pred = tail;
          -        if (pred != null) {
          -            node.prev = pred;
          -            if (compareAndSetTail(pred, node)) {
          -                pred.next = node;
          -                return node;
          +        Node node = new Node(mode);
          +
          +        for (;;) {
          +            Node oldTail = tail;
          +            if (oldTail != null) {
          +                U.putObject(node, Node.PREV, oldTail);
          +                if (compareAndSetTail(oldTail, node)) {
          +                    oldTail.next = node;
          +                    return node;
          +                }
          +            } else {
          +                initializeSyncQueue();
                       }
                   }
          -        enq(node);
          -        return node;
               }
           
               /**
          @@ -619,7 +647,7 @@ private void unparkSuccessor(Node node) {
                    */
                   int ws = node.waitStatus;
                   if (ws < 0)
          -            compareAndSetWaitStatus(node, ws, 0);
          +            node.compareAndSetWaitStatus(ws, 0);
           
                   /*
                    * Thread to unpark is held in successor, which is normally
          @@ -630,9 +658,9 @@ private void unparkSuccessor(Node node) {
                   Node s = node.next;
                   if (s == null || s.waitStatus > 0) {
                       s = null;
          -            for (Node t = tail; t != null && t != node; t = t.prev)
          -                if (t.waitStatus <= 0)
          -                    s = t;
          +            for (Node p = tail; p != node && p != null; p = p.prev)
          +                if (p.waitStatus <= 0)
          +                    s = p;
                   }
                   if (s != null)
                       LockSupport.unpark(s.thread);
          @@ -660,12 +688,12 @@ private void doReleaseShared() {
                       if (h != null && h != tail) {
                           int ws = h.waitStatus;
                           if (ws == Node.SIGNAL) {
          -                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
          +                    if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
                                   continue;            // loop to recheck cases
                               unparkSuccessor(h);
                           }
                           else if (ws == 0 &&
          -                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
          +                         !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
                               continue;                // loop on failed CAS
                       }
                       if (h == head)                   // loop if head changed
          @@ -687,7 +715,8 @@ private void setHeadAndPropagate(Node node, int propagate) {
                   /*
                    * Try to signal next queued node if:
                    *   Propagation was indicated by caller,
          -         *     or was recorded (as h.waitStatus) by a previous operation
          +         *     or was recorded (as h.waitStatus either before
          +         *     or after setHead) by a previous operation
                    *     (note: this uses sign-check of waitStatus because
                    *      PROPAGATE status may transition to SIGNAL.)
                    * and
          @@ -699,7 +728,8 @@ private void setHeadAndPropagate(Node node, int propagate) {
                    * racing acquires/releases, so most need signals now or soon
                    * anyway.
                    */
          -        if (propagate > 0 || h == null || h.waitStatus < 0) {
          +        if (propagate > 0 || h == null || h.waitStatus < 0 ||
          +            (h = head) == null || h.waitStatus < 0) {
                       Node s = node.next;
                       if (s == null || s.isShared())
                           doReleaseShared();
          @@ -737,18 +767,18 @@ private void cancelAcquire(Node node) {
           
                   // If we are the tail, remove ourselves.
                   if (node == tail && compareAndSetTail(node, pred)) {
          -            compareAndSetNext(pred, predNext, null);
          +            pred.compareAndSetNext(predNext, null);
                   } else {
                       // If successor needs signal, try to set pred's next-link
                       // so it will get one. Otherwise wake it up to propagate.
                       int ws;
                       if (pred != head &&
                           ((ws = pred.waitStatus) == Node.SIGNAL ||
          -                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
          +                 (ws <= 0 && pred.compareAndSetWaitStatus(ws, Node.SIGNAL))) &&
                           pred.thread != null) {
                           Node next = node.next;
                           if (next != null && next.waitStatus <= 0)
          -                    compareAndSetNext(pred, predNext, next);
          +                    pred.compareAndSetNext(predNext, next);
                       } else {
                           unparkSuccessor(node);
                       }
          @@ -789,7 +819,7 @@ private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
                        * need a signal, but don't park yet.  Caller will need to
                        * retry to make sure it cannot acquire before parking.
                        */
          -            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
          +            pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
                   }
                   return false;
               }
          @@ -802,7 +832,7 @@ static void selfInterrupt() {
               }
           
               /**
          -     * Convenience method to park and then check if interrupted
          +     * Convenience method to park and then check if interrupted.
                *
                * @return {@code true} if interrupted
                */
          @@ -828,8 +858,8 @@ private final boolean parkAndCheckInterrupt() {
                * @param arg the acquire argument
                * @return {@code true} if interrupted while waiting
                */
          +/// OPENJDK-9     @ReservedStackAccess
               final boolean acquireQueued(final Node node, int arg) {
          -        boolean failed = true;
                   try {
                       boolean interrupted = false;
                       for (;;) {
          @@ -837,16 +867,15 @@ final boolean acquireQueued(final Node node, int arg) {
                           if (p == head && tryAcquire(arg)) {
                               setHead(node);
                               p.next = null; // help GC
          -                    failed = false;
                               return interrupted;
                           }
                           if (shouldParkAfterFailedAcquire(p, node) &&
                               parkAndCheckInterrupt())
                               interrupted = true;
                       }
          -        } finally {
          -            if (failed)
          -                cancelAcquire(node);
          +        } catch (Throwable t) {
          +            cancelAcquire(node);
          +            throw t;
                   }
               }
           
          @@ -857,23 +886,21 @@ final boolean acquireQueued(final Node node, int arg) {
               private void doAcquireInterruptibly(int arg)
                   throws InterruptedException {
                   final Node node = addWaiter(Node.EXCLUSIVE);
          -        boolean failed = true;
                   try {
                       for (;;) {
                           final Node p = node.predecessor();
                           if (p == head && tryAcquire(arg)) {
                               setHead(node);
                               p.next = null; // help GC
          -                    failed = false;
                               return;
                           }
                           if (shouldParkAfterFailedAcquire(p, node) &&
                               parkAndCheckInterrupt())
                               throw new InterruptedException();
                       }
          -        } finally {
          -            if (failed)
          -                cancelAcquire(node);
          +        } catch (Throwable t) {
          +            cancelAcquire(node);
          +            throw t;
                   }
               }
           
          @@ -890,28 +917,28 @@ private boolean doAcquireNanos(int arg, long nanosTimeout)
                       return false;
                   final long deadline = System.nanoTime() + nanosTimeout;
                   final Node node = addWaiter(Node.EXCLUSIVE);
          -        boolean failed = true;
                   try {
                       for (;;) {
                           final Node p = node.predecessor();
                           if (p == head && tryAcquire(arg)) {
                               setHead(node);
                               p.next = null; // help GC
          -                    failed = false;
                               return true;
                           }
                           nanosTimeout = deadline - System.nanoTime();
          -                if (nanosTimeout <= 0L)
          +                if (nanosTimeout <= 0L) {
          +                    cancelAcquire(node);
                               return false;
          +                }
                           if (shouldParkAfterFailedAcquire(p, node) &&
          -                    nanosTimeout > spinForTimeoutThreshold)
          +                    nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD)
                               LockSupport.parkNanos(this, nanosTimeout);
                           if (Thread.interrupted())
                               throw new InterruptedException();
                       }
          -        } finally {
          -            if (failed)
          -                cancelAcquire(node);
          +        } catch (Throwable t) {
          +            cancelAcquire(node);
          +            throw t;
                   }
               }
           
          @@ -921,7 +948,6 @@ private boolean doAcquireNanos(int arg, long nanosTimeout)
                */
               private void doAcquireShared(int arg) {
                   final Node node = addWaiter(Node.SHARED);
          -        boolean failed = true;
                   try {
                       boolean interrupted = false;
                       for (;;) {
          @@ -933,7 +959,6 @@ private void doAcquireShared(int arg) {
                                   p.next = null; // help GC
                                   if (interrupted)
                                       selfInterrupt();
          -                        failed = false;
                                   return;
                               }
                           }
          @@ -941,9 +966,9 @@ private void doAcquireShared(int arg) {
                               parkAndCheckInterrupt())
                               interrupted = true;
                       }
          -        } finally {
          -            if (failed)
          -                cancelAcquire(node);
          +        } catch (Throwable t) {
          +            cancelAcquire(node);
          +            throw t;
                   }
               }
           
          @@ -954,7 +979,6 @@ private void doAcquireShared(int arg) {
               private void doAcquireSharedInterruptibly(int arg)
                   throws InterruptedException {
                   final Node node = addWaiter(Node.SHARED);
          -        boolean failed = true;
                   try {
                       for (;;) {
                           final Node p = node.predecessor();
          @@ -963,7 +987,6 @@ private void doAcquireSharedInterruptibly(int arg)
                               if (r >= 0) {
                                   setHeadAndPropagate(node, r);
                                   p.next = null; // help GC
          -                        failed = false;
                                   return;
                               }
                           }
          @@ -971,9 +994,9 @@ private void doAcquireSharedInterruptibly(int arg)
                               parkAndCheckInterrupt())
                               throw new InterruptedException();
                       }
          -        } finally {
          -            if (failed)
          -                cancelAcquire(node);
          +        } catch (Throwable t) {
          +            cancelAcquire(node);
          +            throw t;
                   }
               }
           
          @@ -990,7 +1013,6 @@ private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
                       return false;
                   final long deadline = System.nanoTime() + nanosTimeout;
                   final Node node = addWaiter(Node.SHARED);
          -        boolean failed = true;
                   try {
                       for (;;) {
                           final Node p = node.predecessor();
          @@ -999,22 +1021,23 @@ private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
                               if (r >= 0) {
                                   setHeadAndPropagate(node, r);
                                   p.next = null; // help GC
          -                        failed = false;
                                   return true;
                               }
                           }
                           nanosTimeout = deadline - System.nanoTime();
          -                if (nanosTimeout <= 0L)
          +                if (nanosTimeout <= 0L) {
          +                    cancelAcquire(node);
                               return false;
          +                }
                           if (shouldParkAfterFailedAcquire(p, node) &&
          -                    nanosTimeout > spinForTimeoutThreshold)
          +                    nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD)
                               LockSupport.parkNanos(this, nanosTimeout);
                           if (Thread.interrupted())
                               throw new InterruptedException();
                       }
          -        } finally {
          -            if (failed)
          -                cancelAcquire(node);
          +        } catch (Throwable t) {
          +            cancelAcquire(node);
          +            throw t;
                   }
               }
           
          @@ -1168,6 +1191,7 @@ protected boolean isHeldExclusively() {
                *        {@link #tryAcquire} but is otherwise uninterpreted and
                *        can represent anything you like.
                */
          +/// OPENJDK-9     @ReservedStackAccess
               public final void acquire(int arg) {
                   if (!tryAcquire(arg) &&
                       acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
          @@ -1231,6 +1255,7 @@ public final boolean tryAcquireNanos(int arg, long nanosTimeout)
                *        can represent anything you like.
                * @return the value returned from {@link #tryRelease}
                */
          +/// OPENJDK-9     @ReservedStackAccess
               public final boolean release(int arg) {
                   if (tryRelease(arg)) {
                       Node h = head;
          @@ -1311,6 +1336,7 @@ public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
                *        and can represent anything you like.
                * @return the value returned from {@link #tryReleaseShared}
                */
          +/// OPENJDK-9     @ReservedStackAccess
               public final boolean releaseShared(int arg) {
                   if (tryReleaseShared(arg)) {
                       doReleaseShared();
          @@ -1338,7 +1364,7 @@ public final boolean hasQueuedThreads() {
           
               /**
                * Queries whether any threads have ever contended to acquire this
          -     * synchronizer; that is if an acquire method has ever blocked.
          +     * synchronizer; that is, if an acquire method has ever blocked.
                *
                * 

          In this implementation, this operation returns in * constant time. @@ -1366,7 +1392,7 @@ public final Thread getFirstQueuedThread() { } /** - * Version of getFirstQueuedThread called when fastpath fails + * Version of getFirstQueuedThread called when fastpath fails. */ private Thread fullGetFirstQueuedThread() { /* @@ -1393,13 +1419,11 @@ private Thread fullGetFirstQueuedThread() { * guaranteeing termination. */ - Node t = tail; Thread firstThread = null; - while (t != null && t != head) { - Thread tt = t.thread; - if (tt != null) - firstThread = tt; - t = t.prev; + for (Node p = tail; p != null && p != head; p = p.prev) { + Thread t = p.thread; + if (t != null) + firstThread = t; } return firstThread; } @@ -1446,9 +1470,9 @@ final boolean apparentlyFirstQueuedIsExclusive() { * *

          An invocation of this method is equivalent to (but may be * more efficient than): - *

           {@code
          -     * getFirstQueuedThread() != Thread.currentThread() &&
          -     * hasQueuedThreads()}
          + *
           {@code
          +     * getFirstQueuedThread() != Thread.currentThread()
          +     *   && hasQueuedThreads()}
          * *

          Note that because cancellations due to interrupts and * timeouts may occur at any time, a {@code true} return does not @@ -1466,7 +1490,7 @@ final boolean apparentlyFirstQueuedIsExclusive() { * tryAcquire} method for a fair, reentrant, exclusive mode * synchronizer might look like this: * - *

           {@code
          +     * 
           {@code
                * protected boolean tryAcquire(int arg) {
                *   if (isHeldExclusively()) {
                *     // A reentrant acquire; increment hold count
          @@ -1502,8 +1526,7 @@ public final boolean hasQueuedPredecessors() {
                * acquire.  The value is only an estimate because the number of
                * threads may change dynamically while this method traverses
                * internal data structures.  This method is designed for use in
          -     * monitoring system state, not for synchronization
          -     * control.
          +     * monitoring system state, not for synchronization control.
                *
                * @return the estimated number of threads waiting to acquire
                */
          @@ -1528,7 +1551,7 @@ public final int getQueueLength() {
                * @return the collection of threads
                */
               public final Collection getQueuedThreads() {
          -        ArrayList list = new ArrayList();
          +        ArrayList list = new ArrayList<>();
                   for (Node p = tail; p != null; p = p.prev) {
                       Thread t = p.thread;
                       if (t != null)
          @@ -1546,7 +1569,7 @@ public final Collection getQueuedThreads() {
                * @return the collection of threads
                */
               public final Collection getExclusiveQueuedThreads() {
          -        ArrayList list = new ArrayList();
          +        ArrayList list = new ArrayList<>();
                   for (Node p = tail; p != null; p = p.prev) {
                       if (!p.isShared()) {
                           Thread t = p.thread;
          @@ -1566,7 +1589,7 @@ public final Collection getExclusiveQueuedThreads() {
                * @return the collection of threads
                */
               public final Collection getSharedQueuedThreads() {
          -        ArrayList list = new ArrayList();
          +        ArrayList list = new ArrayList<>();
                   for (Node p = tail; p != null; p = p.prev) {
                       if (p.isShared()) {
                           Thread t = p.thread;
          @@ -1587,10 +1610,9 @@ public final Collection getSharedQueuedThreads() {
                * @return a string identifying this synchronizer, as well as its state
                */
               public String toString() {
          -        int s = getState();
          -        String q  = hasQueuedThreads() ? "non" : "";
          -        return super.toString() +
          -            "[State = " + s + ", " + q + "empty queue]";
          +        return super.toString()
          +            + "[State = " + getState() + ", "
          +            + (hasQueuedThreads() ? "non" : "") + "empty queue]";
               }
           
           
          @@ -1624,13 +1646,15 @@ final boolean isOnSyncQueue(Node node) {
                * @return true if present
                */
               private boolean findNodeFromTail(Node node) {
          -        Node t = tail;
          -        for (;;) {
          -            if (t == node)
          +        // We check for node first, since it's likely to be at or near tail.
          +        // tail is known to be non-null, so we could re-order to "save"
          +        // one null check, but we leave it this way to help the VM.
          +        for (Node p = tail;;) {
          +            if (p == node)
                           return true;
          -            if (t == null)
          +            if (p == null)
                           return false;
          -            t = t.prev;
          +            p = p.prev;
                   }
               }
           
          @@ -1645,7 +1669,7 @@ final boolean transferForSignal(Node node) {
                   /*
                    * If cannot change waitStatus, the node has been cancelled.
                    */
          -        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
          +        if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
                       return false;
           
                   /*
          @@ -1656,7 +1680,7 @@ final boolean transferForSignal(Node node) {
                    */
                   Node p = enq(node);
                   int ws = p.waitStatus;
          -        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
          +        if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
                       LockSupport.unpark(node.thread);
                   return true;
               }
          @@ -1669,7 +1693,7 @@ final boolean transferForSignal(Node node) {
                * @return true if cancelled before the node was signalled
                */
               final boolean transferAfterCancelledWait(Node node) {
          -        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
          +        if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) {
                       enq(node);
                       return true;
                   }
          @@ -1691,18 +1715,14 @@ final boolean transferAfterCancelledWait(Node node) {
                * @return previous sync state
                */
               final int fullyRelease(Node node) {
          -        boolean failed = true;
                   try {
                       int savedState = getState();
          -            if (release(savedState)) {
          -                failed = false;
          +            if (release(savedState))
                           return savedState;
          -            } else {
          -                throw new IllegalMonitorStateException();
          -            }
          -        } finally {
          -            if (failed)
          -                node.waitStatus = Node.CANCELLED;
          +            throw new IllegalMonitorStateException();
          +        } catch (Throwable t) {
          +            node.waitStatus = Node.CANCELLED;
          +            throw t;
                   }
               }
           
          @@ -1747,8 +1767,8 @@ public final boolean hasWaiters(ConditionObject condition) {
                * given condition associated with this synchronizer. Note that
                * because timeouts and interrupts may occur at any time, the
                * estimate serves only as an upper bound on the actual number of
          -     * waiters.  This method is designed for use in monitoring of the
          -     * system state, not for synchronization control.
          +     * waiters.  This method is designed for use in monitoring system
          +     * state, not for synchronization control.
                *
                * @param condition the condition
                * @return the estimated number of waiting threads
          @@ -1826,7 +1846,9 @@ private Node addConditionWaiter() {
                           unlinkCancelledWaiters();
                           t = lastWaiter;
                       }
          -            Node node = new Node(Thread.currentThread(), Node.CONDITION);
          +
          +            Node node = new Node(Node.CONDITION);
          +
                       if (t == null)
                           firstWaiter = node;
                       else
          @@ -1934,12 +1956,12 @@ public final void signalAll() {
                   /**
                    * Implements uninterruptible condition wait.
                    * 
            - *
          1. Save lock state returned by {@link #getState}. - *
          2. Invoke {@link #release} with saved state as argument, - * throwing IllegalMonitorStateException if it fails. - *
          3. Block until signalled. - *
          4. Reacquire by invoking specialized version of - * {@link #acquire} with saved state as argument. + *
          5. Save lock state returned by {@link #getState}. + *
          6. Invoke {@link #release} with saved state as argument, + * throwing IllegalMonitorStateException if it fails. + *
          7. Block until signalled. + *
          8. Reacquire by invoking specialized version of + * {@link #acquire} with saved state as argument. *
          */ public final void awaitUninterruptibly() { @@ -1993,14 +2015,14 @@ else if (interruptMode == REINTERRUPT) /** * Implements interruptible condition wait. *
            - *
          1. If current thread is interrupted, throw InterruptedException. - *
          2. Save lock state returned by {@link #getState}. - *
          3. Invoke {@link #release} with saved state as argument, - * throwing IllegalMonitorStateException if it fails. - *
          4. Block until signalled or interrupted. - *
          5. Reacquire by invoking specialized version of - * {@link #acquire} with saved state as argument. - *
          6. If interrupted while blocked in step 4, throw InterruptedException. + *
          7. If current thread is interrupted, throw InterruptedException. + *
          8. Save lock state returned by {@link #getState}. + *
          9. Invoke {@link #release} with saved state as argument, + * throwing IllegalMonitorStateException if it fails. + *
          10. Block until signalled or interrupted. + *
          11. Reacquire by invoking specialized version of + * {@link #acquire} with saved state as argument. + *
          12. If interrupted while blocked in step 4, throw InterruptedException. *
          */ public final void await() throws InterruptedException { @@ -2025,31 +2047,33 @@ public final void await() throws InterruptedException { /** * Implements timed condition wait. *
            - *
          1. If current thread is interrupted, throw InterruptedException. - *
          2. Save lock state returned by {@link #getState}. - *
          3. Invoke {@link #release} with saved state as argument, - * throwing IllegalMonitorStateException if it fails. - *
          4. Block until signalled, interrupted, or timed out. - *
          5. Reacquire by invoking specialized version of - * {@link #acquire} with saved state as argument. - *
          6. If interrupted while blocked in step 4, throw InterruptedException. + *
          7. If current thread is interrupted, throw InterruptedException. + *
          8. Save lock state returned by {@link #getState}. + *
          9. Invoke {@link #release} with saved state as argument, + * throwing IllegalMonitorStateException if it fails. + *
          10. Block until signalled, interrupted, or timed out. + *
          11. Reacquire by invoking specialized version of + * {@link #acquire} with saved state as argument. + *
          12. If interrupted while blocked in step 4, throw InterruptedException. *
          */ public final long awaitNanos(long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); + // We don't check for nanosTimeout <= 0L here, to allow + // awaitNanos(0) as a way to "yield the lock". + final long deadline = System.nanoTime() + nanosTimeout; long initialNanos = nanosTimeout; Node node = addConditionWaiter(); int savedState = fullyRelease(node); - final long deadline = System.nanoTime() + nanosTimeout; int interruptMode = 0; while (!isOnSyncQueue(node)) { if (nanosTimeout <= 0L) { transferAfterCancelledWait(node); break; } - if (nanosTimeout >= spinForTimeoutThreshold) + if (nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) LockSupport.parkNanos(this, nanosTimeout); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; @@ -2062,24 +2086,21 @@ public final long awaitNanos(long nanosTimeout) if (interruptMode != 0) reportInterruptAfterWait(interruptMode); long remaining = deadline - System.nanoTime(); // avoid overflow - // BEGIN android-note Changed from < to <= http://b/24284239 - // return (remaining < initialNanos) ? remaining : Long.MIN_VALUE; return (remaining <= initialNanos) ? remaining : Long.MIN_VALUE; - // END android-note } /** * Implements absolute timed condition wait. *
            - *
          1. If current thread is interrupted, throw InterruptedException. - *
          2. Save lock state returned by {@link #getState}. - *
          3. Invoke {@link #release} with saved state as argument, - * throwing IllegalMonitorStateException if it fails. - *
          4. Block until signalled, interrupted, or timed out. - *
          5. Reacquire by invoking specialized version of - * {@link #acquire} with saved state as argument. - *
          6. If interrupted while blocked in step 4, throw InterruptedException. - *
          7. If timed out while blocked in step 4, return false, else true. + *
          8. If current thread is interrupted, throw InterruptedException. + *
          9. Save lock state returned by {@link #getState}. + *
          10. Invoke {@link #release} with saved state as argument, + * throwing IllegalMonitorStateException if it fails. + *
          11. Block until signalled, interrupted, or timed out. + *
          12. Reacquire by invoking specialized version of + * {@link #acquire} with saved state as argument. + *
          13. If interrupted while blocked in step 4, throw InterruptedException. + *
          14. If timed out while blocked in step 4, return false, else true. *
          */ public final boolean awaitUntil(Date deadline) @@ -2092,7 +2113,7 @@ public final boolean awaitUntil(Date deadline) boolean timedout = false; int interruptMode = 0; while (!isOnSyncQueue(node)) { - if (System.currentTimeMillis() > abstime) { + if (System.currentTimeMillis() >= abstime) { timedout = transferAfterCancelledWait(node); break; } @@ -2112,15 +2133,15 @@ public final boolean awaitUntil(Date deadline) /** * Implements timed condition wait. *
            - *
          1. If current thread is interrupted, throw InterruptedException. - *
          2. Save lock state returned by {@link #getState}. - *
          3. Invoke {@link #release} with saved state as argument, - * throwing IllegalMonitorStateException if it fails. - *
          4. Block until signalled, interrupted, or timed out. - *
          5. Reacquire by invoking specialized version of - * {@link #acquire} with saved state as argument. - *
          6. If interrupted while blocked in step 4, throw InterruptedException. - *
          7. If timed out while blocked in step 4, return false, else true. + *
          8. If current thread is interrupted, throw InterruptedException. + *
          9. Save lock state returned by {@link #getState}. + *
          10. Invoke {@link #release} with saved state as argument, + * throwing IllegalMonitorStateException if it fails. + *
          11. Block until signalled, interrupted, or timed out. + *
          12. Reacquire by invoking specialized version of + * {@link #acquire} with saved state as argument. + *
          13. If interrupted while blocked in step 4, throw InterruptedException. + *
          14. If timed out while blocked in step 4, return false, else true. *
          */ public final boolean await(long time, TimeUnit unit) @@ -2128,9 +2149,11 @@ public final boolean await(long time, TimeUnit unit) long nanosTimeout = unit.toNanos(time); if (Thread.interrupted()) throw new InterruptedException(); + // We don't check for nanosTimeout <= 0L here, to allow + // await(0, unit) as a way to "yield the lock". + final long deadline = System.nanoTime() + nanosTimeout; Node node = addConditionWaiter(); int savedState = fullyRelease(node); - final long deadline = System.nanoTime() + nanosTimeout; boolean timedout = false; int interruptMode = 0; while (!isOnSyncQueue(node)) { @@ -2138,7 +2161,7 @@ public final boolean await(long time, TimeUnit unit) timedout = transferAfterCancelledWait(node); break; } - if (nanosTimeout >= spinForTimeoutThreshold) + if (nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) LockSupport.parkNanos(this, nanosTimeout); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; @@ -2167,7 +2190,7 @@ final boolean isOwnedBy(AbstractQueuedSynchronizer sync) { /** * Queries whether any threads are waiting on this condition. - * Implements {@link AbstractQueuedSynchronizer#hasWaiters}. + * Implements {@link AbstractQueuedSynchronizer#hasWaiters(ConditionObject)}. * * @return {@code true} if there are any waiting threads * @throws IllegalMonitorStateException if {@link #isHeldExclusively} @@ -2186,7 +2209,7 @@ protected final boolean hasWaiters() { /** * Returns an estimate of the number of threads waiting on * this condition. - * Implements {@link AbstractQueuedSynchronizer#getWaitQueueLength}. + * Implements {@link AbstractQueuedSynchronizer#getWaitQueueLength(ConditionObject)}. * * @return the estimated number of waiting threads * @throws IllegalMonitorStateException if {@link #isHeldExclusively} @@ -2206,7 +2229,7 @@ protected final int getWaitQueueLength() { /** * Returns a collection containing those threads that may be * waiting on this Condition. - * Implements {@link AbstractQueuedSynchronizer#getWaitingThreads}. + * Implements {@link AbstractQueuedSynchronizer#getWaitingThreads(ConditionObject)}. * * @return the collection of threads * @throws IllegalMonitorStateException if {@link #isHeldExclusively} @@ -2215,7 +2238,7 @@ protected final int getWaitQueueLength() { protected final Collection getWaitingThreads() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); - ArrayList list = new ArrayList(); + ArrayList list = new ArrayList<>(); for (Node w = firstWaiter; w != null; w = w.nextWaiter) { if (w.waitStatus == Node.CONDITION) { Thread t = w.thread; @@ -2236,27 +2259,22 @@ protected final Collection getWaitingThreads() { * are at it, we do the same for other CASable fields (which could * otherwise be done with atomic field updaters). */ - private static final Unsafe unsafe = Unsafe.getUnsafe(); - private static final long stateOffset; - private static final long headOffset; - private static final long tailOffset; - private static final long waitStatusOffset; - private static final long nextOffset; + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long STATE; + private static final long HEAD; + private static final long TAIL; static { try { - stateOffset = unsafe.objectFieldOffset + STATE = U.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("state")); - headOffset = unsafe.objectFieldOffset + HEAD = U.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("head")); - tailOffset = unsafe.objectFieldOffset + TAIL = U.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("tail")); - waitStatusOffset = unsafe.objectFieldOffset - (Node.class.getDeclaredField("waitStatus")); - nextOffset = unsafe.objectFieldOffset - (Node.class.getDeclaredField("next")); - - } catch (Exception ex) { throw new Error(ex); } + } catch (ReflectiveOperationException e) { + throw new Error(e); + } // Reduce the risk of rare disastrous classloading in first call to // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773 @@ -2264,35 +2282,18 @@ protected final Collection getWaitingThreads() { } /** - * CAS head field. Used only by enq. + * Initializes head and tail fields on first contention. */ - private final boolean compareAndSetHead(Node update) { - return unsafe.compareAndSwapObject(this, headOffset, null, update); + private final void initializeSyncQueue() { + Node h; + if (U.compareAndSwapObject(this, HEAD, null, (h = new Node()))) + tail = h; } /** - * CAS tail field. Used only by enq. + * CASes tail field. */ private final boolean compareAndSetTail(Node expect, Node update) { - return unsafe.compareAndSwapObject(this, tailOffset, expect, update); - } - - /** - * CAS waitStatus field of a node. - */ - private static final boolean compareAndSetWaitStatus(Node node, - int expect, - int update) { - return unsafe.compareAndSwapInt(node, waitStatusOffset, - expect, update); - } - - /** - * CAS next field of a node. - */ - private static final boolean compareAndSetNext(Node node, - Node expect, - Node update) { - return unsafe.compareAndSwapObject(node, nextOffset, expect, update); + return U.compareAndSwapObject(this, TAIL, expect, update); } } diff --git a/luni/src/main/java/java/util/concurrent/locks/Condition.java b/luni/src/main/java/java/util/concurrent/locks/Condition.java index 11a709041..b3132e7ed 100644 --- a/luni/src/main/java/java/util/concurrent/locks/Condition.java +++ b/luni/src/main/java/java/util/concurrent/locks/Condition.java @@ -6,8 +6,8 @@ package java.util.concurrent.locks; -import java.util.concurrent.TimeUnit; import java.util.Date; +import java.util.concurrent.TimeUnit; /** * {@code Condition} factors out the {@code Object} monitor @@ -98,7 +98,7 @@ *

          Note that {@code Condition} instances are just normal objects and can * themselves be used as the target in a {@code synchronized} statement, * and can have their own monitor {@link Object#wait wait} and - * {@link Object#notify notification} methods invoked. + * {@link Object#notify notify} methods invoked. * Acquiring the monitor lock of a {@code Condition} instance, or using its * monitor methods, has no specified relationship with acquiring the * {@link Lock} associated with that {@code Condition} or the use of its @@ -280,7 +280,7 @@ public interface Condition { * condition still does not hold. Typical uses of this method take * the following form: * - *

           {@code
          +     * 
           {@code
                * boolean aMethod(long timeout, TimeUnit unit) {
                *   long nanos = unit.toNanos(timeout);
                *   lock.lock();
          @@ -333,7 +333,7 @@ public interface Condition {
                * Causes the current thread to wait until it is signalled or interrupted,
                * or the specified waiting time elapses. This method is behaviorally
                * equivalent to:
          -     *  
           {@code awaitNanos(unit.toNanos(time)) > 0}
          + *
           {@code awaitNanos(unit.toNanos(time)) > 0}
          * * @param time the maximum time to wait * @param unit the time unit of the {@code time} argument @@ -382,7 +382,7 @@ public interface Condition { * *

          The return value indicates whether the deadline has elapsed, * which can be used as follows: - *

           {@code
          +     * 
           {@code
                * boolean aMethod(Date deadline) {
                *   boolean stillWaiting = true;
                *   lock.lock();
          diff --git a/luni/src/main/java/java/util/concurrent/locks/Lock.java b/luni/src/main/java/java/util/concurrent/locks/Lock.java
          index a7ca00107..a2d7b4811 100644
          --- a/luni/src/main/java/java/util/concurrent/locks/Lock.java
          +++ b/luni/src/main/java/java/util/concurrent/locks/Lock.java
          @@ -49,7 +49,7 @@
            * methods and statements. In most cases, the following idiom
            * should be used:
            *
          - *  
           {@code
          + * 
           {@code
            * Lock l = ...;
            * l.lock();
            * try {
          @@ -93,8 +93,9 @@
            * 

          All {@code Lock} implementations must enforce the same * memory synchronization semantics as provided by the built-in monitor * lock, as described in - * - * The Java Language Specification (17.4 Memory Model): + * + * Chapter 17 of + * The Java™ Language Specification: *

            *
          • A successful {@code lock} operation has the same memory * synchronization effects as a successful Lock action. @@ -212,7 +213,7 @@ public interface Lock { * immediately with the value {@code false}. * *

            A typical usage idiom for this method would be: - *

             {@code
            +     * 
             {@code
                  * Lock lock = ...;
                  * if (lock.tryLock()) {
                  *   try {
            diff --git a/luni/src/main/java/java/util/concurrent/locks/LockSupport.java b/luni/src/main/java/java/util/concurrent/locks/LockSupport.java
            index 089d81812..694f4ca19 100644
            --- a/luni/src/main/java/java/util/concurrent/locks/LockSupport.java
            +++ b/luni/src/main/java/java/util/concurrent/locks/LockSupport.java
            @@ -6,8 +6,6 @@
             
             package java.util.concurrent.locks;
             
            -import sun.misc.Unsafe;
            -
             /**
              * Basic thread blocking primitives for creating locks and other
              * synchronization classes.
            @@ -19,6 +17,10 @@
              * it may block.  A call to {@code unpark} makes the permit
              * available, if it was not already available. (Unlike with Semaphores
              * though, permits do not accumulate. There is at most one.)
            + * Reliable usage requires the use of volatile (or atomic) variables
            + * to control when to park or unpark.  Orderings of calls to these
            + * methods are maintained with respect to volatile variable accesses,
            + * but not necessarily non-volatile variable accesses.
              *
              * 

            Methods {@code park} and {@code unpark} provide efficient * means of blocking and unblocking threads that do not encounter the @@ -39,73 +41,74 @@ * {@code blocker} object parameter. This object is recorded while * the thread is blocked to permit monitoring and diagnostic tools to * identify the reasons that threads are blocked. (Such tools may - * access blockers using method {@link #getBlocker}.) The use of these - * forms rather than the original forms without this parameter is - * strongly encouraged. The normal argument to supply as a - * {@code blocker} within a lock implementation is {@code this}. + * access blockers using method {@link #getBlocker(Thread)}.) + * The use of these forms rather than the original forms without this + * parameter is strongly encouraged. The normal argument to supply as + * a {@code blocker} within a lock implementation is {@code this}. * *

            These methods are designed to be used as tools for creating * higher-level synchronization utilities, and are not in themselves * useful for most concurrency control applications. The {@code park} * method is designed for use only in constructions of the form: * - *

             {@code
            - * while (!canProceed()) { ... LockSupport.park(this); }}
            + *
             {@code
            + * while (!canProceed()) {
            + *   // ensure request to unpark is visible to other threads
            + *   ...
            + *   LockSupport.park(this);
            + * }}
            * - * where neither {@code canProceed} nor any other actions prior to the - * call to {@code park} entail locking or blocking. Because only one - * permit is associated with each thread, any intermediary uses of - * {@code park} could interfere with its intended effects. + * where no actions by the thread publishing a request to unpark, + * prior to the call to {@code park}, entail locking or blocking. + * Because only one permit is associated with each thread, any + * intermediary uses of {@code park}, including implicitly via class + * loading, could lead to an unresponsive thread (a "lost unpark"). * *

            Sample Usage. Here is a sketch of a first-in-first-out * non-reentrant lock class: - *

             {@code
            + * 
             {@code
              * class FIFOMutex {
              *   private final AtomicBoolean locked = new AtomicBoolean(false);
              *   private final Queue waiters
            - *     = new ConcurrentLinkedQueue();
            + *     = new ConcurrentLinkedQueue<>();
              *
              *   public void lock() {
              *     boolean wasInterrupted = false;
            - *     Thread current = Thread.currentThread();
            - *     waiters.add(current);
            + *     // publish current thread for unparkers
            + *     waiters.add(Thread.currentThread());
              *
              *     // Block while not first in queue or cannot acquire lock
            - *     while (waiters.peek() != current ||
            + *     while (waiters.peek() != Thread.currentThread() ||
              *            !locked.compareAndSet(false, true)) {
              *       LockSupport.park(this);
            - *       if (Thread.interrupted()) // ignore interrupts while waiting
            + *       // ignore interrupts while waiting
            + *       if (Thread.interrupted())
              *         wasInterrupted = true;
              *     }
              *
              *     waiters.remove();
            - *     if (wasInterrupted)          // reassert interrupt status on exit
            - *       current.interrupt();
            + *     // ensure correct interrupt status on return
            + *     if (wasInterrupted)
            + *       Thread.currentThread().interrupt();
              *   }
              *
              *   public void unlock() {
              *     locked.set(false);
              *     LockSupport.unpark(waiters.peek());
              *   }
            + *
            + *   static {
            + *     // Reduce the risk of "lost unpark" due to classloading
            + *     Class ensureLoaded = LockSupport.class;
            + *   }
              * }}
            */ public class LockSupport { private LockSupport() {} // Cannot be instantiated. - // Hotspot implementation via intrinsics API - private static final Unsafe unsafe = Unsafe.getUnsafe(); - private static final long parkBlockerOffset; - - static { - try { - parkBlockerOffset = unsafe.objectFieldOffset - (java.lang.Thread.class.getDeclaredField("parkBlocker")); - } catch (Exception ex) { throw new Error(ex); } - } - private static void setBlocker(Thread t, Object arg) { // Even though volatile, hotspot doesn't need a write barrier here. - unsafe.putObject(t, parkBlockerOffset, arg); + U.putObject(t, PARKBLOCKER, arg); } /** @@ -121,7 +124,7 @@ private static void setBlocker(Thread t, Object arg) { */ public static void unpark(Thread thread) { if (thread != null) - unsafe.unpark(thread); + U.unpark(thread); } /** @@ -155,7 +158,7 @@ public static void unpark(Thread thread) { public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); - unsafe.park(false, 0L); + U.park(false, 0L); setBlocker(t, null); } @@ -195,7 +198,7 @@ public static void parkNanos(Object blocker, long nanos) { if (nanos > 0) { Thread t = Thread.currentThread(); setBlocker(t, blocker); - unsafe.park(false, nanos); + U.park(false, nanos); setBlocker(t, null); } } @@ -236,7 +239,7 @@ public static void parkNanos(Object blocker, long nanos) { public static void parkUntil(Object blocker, long deadline) { Thread t = Thread.currentThread(); setBlocker(t, blocker); - unsafe.park(true, deadline); + U.park(true, deadline); setBlocker(t, null); } @@ -255,7 +258,7 @@ public static void parkUntil(Object blocker, long deadline) { public static Object getBlocker(Thread t) { if (t == null) throw new NullPointerException(); - return unsafe.getObjectVolatile(t, parkBlockerOffset); + return U.getObjectVolatile(t, PARKBLOCKER); } /** @@ -284,7 +287,7 @@ public static Object getBlocker(Thread t) { * for example, the interrupt status of the thread upon return. */ public static void park() { - unsafe.park(false, 0L); + U.park(false, 0L); } /** @@ -318,7 +321,7 @@ public static void park() { */ public static void parkNanos(long nanos) { if (nanos > 0) - unsafe.park(false, nanos); + U.park(false, nanos); } /** @@ -352,6 +355,40 @@ public static void parkNanos(long nanos) { * to wait until */ public static void parkUntil(long deadline) { - unsafe.park(true, deadline); + U.park(true, deadline); } + + /** + * Returns the pseudo-randomly initialized or updated secondary seed. + * Copied from ThreadLocalRandom due to package access restrictions. + */ + static final int nextSecondarySeed() { + int r; + Thread t = Thread.currentThread(); + if ((r = U.getInt(t, SECONDARY)) != 0) { + r ^= r << 13; // xorshift + r ^= r >>> 17; + r ^= r << 5; + } + else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0) + r = 1; // avoid zero + U.putInt(t, SECONDARY, r); + return r; + } + + // Hotspot implementation via intrinsics API + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long PARKBLOCKER; + private static final long SECONDARY; + static { + try { + PARKBLOCKER = U.objectFieldOffset + (Thread.class.getDeclaredField("parkBlocker")); + SECONDARY = U.objectFieldOffset + (Thread.class.getDeclaredField("threadLocalRandomSecondarySeed")); + } catch (ReflectiveOperationException e) { + throw new Error(e); + } + } + } diff --git a/luni/src/main/java/java/util/concurrent/locks/ReadWriteLock.java b/luni/src/main/java/java/util/concurrent/locks/ReadWriteLock.java index 8690355a5..a1a211af0 100644 --- a/luni/src/main/java/java/util/concurrent/locks/ReadWriteLock.java +++ b/luni/src/main/java/java/util/concurrent/locks/ReadWriteLock.java @@ -9,9 +9,9 @@ /** * A {@code ReadWriteLock} maintains a pair of associated {@link * Lock locks}, one for read-only operations and one for writing. - * The {@link #readLock read lock} may be held simultaneously by - * multiple reader threads, so long as there are no writers. The - * {@link #writeLock write lock} is exclusive. + * The {@linkplain #readLock read lock} may be held simultaneously + * by multiple reader threads, so long as there are no writers. + * The {@linkplain #writeLock write lock} is exclusive. * *

            All {@code ReadWriteLock} implementations must guarantee that * the memory synchronization effects of {@code writeLock} operations diff --git a/luni/src/main/java/java/util/concurrent/locks/ReentrantLock.java b/luni/src/main/java/java/util/concurrent/locks/ReentrantLock.java index 365424884..5fedcaa09 100644 --- a/luni/src/main/java/java/util/concurrent/locks/ReentrantLock.java +++ b/luni/src/main/java/java/util/concurrent/locks/ReentrantLock.java @@ -6,8 +6,9 @@ package java.util.concurrent.locks; -import java.util.concurrent.TimeUnit; import java.util.Collection; +import java.util.concurrent.TimeUnit; +/// OPENJDK-9 import jdk.internal.vm.annotation.ReservedStackAccess; /** * A reentrant mutual exclusion {@link Lock} with the same basic @@ -44,7 +45,7 @@ * follow a call to {@code lock} with a {@code try} block, most * typically in a before/after construction such as: * - *

             {@code
            + * 
             {@code
              * class X {
              *   private final ReentrantLock lock = new ReentrantLock();
              *   // ...
            @@ -98,6 +99,7 @@ abstract static class Sync extends AbstractQueuedSynchronizer {
                      * Performs non-fair tryLock.  tryAcquire is implemented in
                      * subclasses, but both need nonfair try for trylock method.
                      */
            +/// OPENJDK-9         @ReservedStackAccess
                     final boolean nonfairTryAcquire(int acquires) {
                         final Thread current = Thread.currentThread();
                         int c = getState();
            @@ -117,6 +119,7 @@ else if (current == getExclusiveOwnerThread()) {
                         return false;
                     }
             
            +/// OPENJDK-9         @ReservedStackAccess
                     protected final boolean tryRelease(int releases) {
                         int c = getState() - releases;
                         if (Thread.currentThread() != getExclusiveOwnerThread())
            @@ -174,6 +177,7 @@ static final class NonfairSync extends Sync {
                      * Performs lock.  Try immediate barge, backing up to normal
                      * acquire on failure.
                      */
            +/// OPENJDK-9         @ReservedStackAccess
                     final void lock() {
                         if (compareAndSetState(0, 1))
                             setExclusiveOwnerThread(Thread.currentThread());
            @@ -200,6 +204,7 @@ final void lock() {
                      * Fair version of tryAcquire.  Don't grant access unless
                      * recursive call or no waiters or is first.
                      */
            +/// OPENJDK-9         @ReservedStackAccess
                     protected final boolean tryAcquire(int acquires) {
                         final Thread current = Thread.currentThread();
                         int c = getState();
            @@ -350,7 +355,7 @@ public boolean tryLock() {
                  * method. If you want a timed {@code tryLock} that does permit barging on
                  * a fair lock then combine the timed and un-timed forms together:
                  *
            -     *  
             {@code
            +     * 
             {@code
                  * if (lock.tryLock() ||
                  *     lock.tryLock(timeout, unit)) {
                  *   ...
            @@ -456,7 +461,7 @@ public void unlock() {
                  * InterruptedException} will be thrown, and the thread's
                  * interrupted status will be cleared.
                  *
            -     * 
          • Waiting threads are signalled in FIFO order. + *
          • Waiting threads are signalled in FIFO order. * *
          • The ordering of lock reacquisition for threads returning * from waiting methods is the same as for threads initially @@ -483,7 +488,7 @@ public Condition newCondition() { * not be entered with the lock already held then we can assert that * fact: * - *
             {@code
            +     * 
             {@code
                  * class X {
                  *   ReentrantLock lock = new ReentrantLock();
                  *   // ...
            @@ -508,12 +513,12 @@ public int getHoldCount() {
                 /**
                  * Queries if this lock is held by the current thread.
                  *
            -     * 

            Analogous to the {@link Thread#holdsLock} method for built-in - * monitor locks, this method is typically used for debugging and - * testing. For example, a method that should only be called while - * a lock is held can assert that this is the case: + *

            Analogous to the {@link Thread#holdsLock(Object)} method for + * built-in monitor locks, this method is typically used for + * debugging and testing. For example, a method that should only be + * called while a lock is held can assert that this is the case: * - *

             {@code
            +     * 
             {@code
                  * class X {
                  *   ReentrantLock lock = new ReentrantLock();
                  *   // ...
            @@ -527,7 +532,7 @@ public int getHoldCount() {
                  * 

            It can also be used to ensure that a reentrant lock is used * in a non-reentrant manner, for example: * - *

             {@code
            +     * 
             {@code
                  * class X {
                  *   ReentrantLock lock = new ReentrantLock();
                  *   // ...
            @@ -618,12 +623,11 @@ public final boolean hasQueuedThread(Thread thread) {
                 }
             
                 /**
            -     * Returns an estimate of the number of threads waiting to
            -     * acquire this lock.  The value is only an estimate because the number of
            +     * Returns an estimate of the number of threads waiting to acquire
            +     * this lock.  The value is only an estimate because the number of
                  * threads may change dynamically while this method traverses
                  * internal data structures.  This method is designed for use in
            -     * monitoring of the system state, not for synchronization
            -     * control.
            +     * monitoring system state, not for synchronization control.
                  *
                  * @return the estimated number of threads waiting for this lock
                  */
            diff --git a/luni/src/main/java/java/util/concurrent/locks/ReentrantReadWriteLock.java b/luni/src/main/java/java/util/concurrent/locks/ReentrantReadWriteLock.java
            index cc7ba4c8d..8969a54ec 100644
            --- a/luni/src/main/java/java/util/concurrent/locks/ReentrantReadWriteLock.java
            +++ b/luni/src/main/java/java/util/concurrent/locks/ReentrantReadWriteLock.java
            @@ -6,8 +6,8 @@
             
             package java.util.concurrent.locks;
             
            -import java.util.concurrent.TimeUnit;
             import java.util.Collection;
            +import java.util.concurrent.TimeUnit;
             
             /**
              * An implementation of {@link ReadWriteLock} supporting similar
            @@ -23,15 +23,16 @@
              *
              * 
            *
            Non-fair mode (default) - *
            When constructed as non-fair (the default), the order of entry + *
            + * When constructed as non-fair (the default), the order of entry * to the read and write lock is unspecified, subject to reentrancy * constraints. A nonfair lock that is continuously contended may * indefinitely postpone one or more reader or writer threads, but * will normally have higher throughput than a fair lock. - *

            * *

            Fair mode - *
            When constructed as fair, threads contend for entry using an + *
            + * When constructed as fair, threads contend for entry using an * approximately arrival-order policy. When the currently held lock * is released, either the longest-waiting single writer thread will * be assigned the write lock, or if there is a group of reader threads @@ -53,7 +54,6 @@ * {@link ReadLock#tryLock()} and {@link WriteLock#tryLock()} methods * do not honor this fair setting and will immediately acquire the lock * if it is possible, regardless of waiting threads.) - *

            *

            * *
          • Reentrancy @@ -147,9 +147,9 @@ * is a class using a TreeMap that is expected to be large and * concurrently accessed. * - *
             {@code
            + * 
             {@code
              * class RWDictionary {
            - *   private final Map m = new TreeMap();
            + *   private final Map m = new TreeMap<>();
              *   private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
              *   private final Lock r = rwl.readLock();
              *   private final Lock w = rwl.writeLock();
            @@ -159,9 +159,9 @@
              *     try { return m.get(key); }
              *     finally { r.unlock(); }
              *   }
            - *   public String[] allKeys() {
            + *   public List allKeys() {
              *     r.lock();
            - *     try { return m.keySet().toArray(); }
            + *     try { return new ArrayList<>(m.keySet()); }
              *     finally { r.unlock(); }
              *   }
              *   public Data put(String key, Data value) {
            @@ -247,9 +247,9 @@ abstract static class Sync extends AbstractQueuedSynchronizer {
                      * Maintained as a ThreadLocal; cached in cachedHoldCounter.
                      */
                     static final class HoldCounter {
            -            int count = 0;
            +            int count;          // initially 0
                         // Use id, not reference, to avoid garbage retention
            -            final long tid = Thread.currentThread().getId();
            +            final long tid = getThreadId(Thread.currentThread());
                     }
             
                     /**
            @@ -392,7 +392,7 @@ protected final boolean tryReleaseShared(int unused) {
                                 firstReaderHoldCount--;
                         } else {
                             HoldCounter rh = cachedHoldCounter;
            -                if (rh == null || rh.tid != current.getId())
            +                if (rh == null || rh.tid != getThreadId(current))
                                 rh = readHolds.get();
                             int count = rh.count;
                             if (count <= 1) {
            @@ -450,7 +450,7 @@ protected final int tryAcquireShared(int unused) {
                                 firstReaderHoldCount++;
                             } else {
                                 HoldCounter rh = cachedHoldCounter;
            -                    if (rh == null || rh.tid != current.getId())
            +                    if (rh == null || rh.tid != getThreadId(current))
                                     cachedHoldCounter = rh = readHolds.get();
                                 else if (rh.count == 0)
                                     readHolds.set(rh);
            @@ -487,7 +487,7 @@ final int fullTryAcquireShared(Thread current) {
                                 } else {
                                     if (rh == null) {
                                         rh = cachedHoldCounter;
            -                            if (rh == null || rh.tid != current.getId()) {
            +                            if (rh == null || rh.tid != getThreadId(current)) {
                                             rh = readHolds.get();
                                             if (rh.count == 0)
                                                 readHolds.remove();
            @@ -508,7 +508,7 @@ final int fullTryAcquireShared(Thread current) {
                                 } else {
                                     if (rh == null)
                                         rh = cachedHoldCounter;
            -                        if (rh == null || rh.tid != current.getId())
            +                        if (rh == null || rh.tid != getThreadId(current))
                                         rh = readHolds.get();
                                     else if (rh.count == 0)
                                         readHolds.set(rh);
            @@ -564,7 +564,7 @@ final boolean tryReadLock() {
                                     firstReaderHoldCount++;
                                 } else {
                                     HoldCounter rh = cachedHoldCounter;
            -                        if (rh == null || rh.tid != current.getId())
            +                        if (rh == null || rh.tid != getThreadId(current))
                                         cachedHoldCounter = rh = readHolds.get();
                                     else if (rh.count == 0)
                                         readHolds.set(rh);
            @@ -615,7 +615,7 @@ final int getReadHoldCount() {
                             return firstReaderHoldCount;
             
                         HoldCounter rh = cachedHoldCounter;
            -            if (rh != null && rh.tid == current.getId())
            +            if (rh != null && rh.tid == getThreadId(current))
                             return rh.count;
             
                         int count = readHolds.get().count;
            @@ -677,7 +677,7 @@ public static class ReadLock implements Lock, java.io.Serializable {
                     private final Sync sync;
             
                     /**
            -         * Constructor for use by subclasses
            +         * Constructor for use by subclasses.
                      *
                      * @param lock the outer lock object
                      * @throws NullPointerException if the lock is null
            @@ -788,7 +788,7 @@ public boolean tryLock() {
                      * permit barging on a fair lock then combine the timed and
                      * un-timed forms together:
                      *
            -         *  
             {@code
            +         * 
             {@code
                      * if (lock.tryLock() ||
                      *     lock.tryLock(timeout, unit)) {
                      *   ...
            @@ -848,7 +848,12 @@ public boolean tryLock(long timeout, TimeUnit unit)
                      * Attempts to release this lock.
                      *
                      * 

            If the number of readers is now zero then the lock - * is made available for write lock attempts. + * is made available for write lock attempts. If the current + * thread does not hold this lock then {@link + * IllegalMonitorStateException} is thrown. + * + * @throws IllegalMonitorStateException if the current thread + * does not hold this lock */ public void unlock() { sync.releaseShared(1); @@ -886,7 +891,7 @@ public static class WriteLock implements Lock, java.io.Serializable { private final Sync sync; /** - * Constructor for use by subclasses + * Constructor for use by subclasses. * * @param lock the outer lock object * @throws NullPointerException if the lock is null @@ -1000,7 +1005,7 @@ public void lockInterruptibly() throws InterruptedException { * by the current thread, or the write lock was already held * by the current thread; and {@code false} otherwise. */ - public boolean tryLock( ) { + public boolean tryLock() { return sync.tryWriteLock(); } @@ -1020,7 +1025,7 @@ public boolean tryLock( ) { * that does permit barging on a fair lock then combine the * timed and un-timed forms together: * - *

             {@code
            +         * 
             {@code
                      * if (lock.tryLock() ||
                      *     lock.tryLock(timeout, unit)) {
                      *   ...
            @@ -1135,7 +1140,7 @@ public void unlock() {
                      * InterruptedException} will be thrown, and the thread's
                      * interrupted status will be cleared.
                      *
            -         * 
          • Waiting threads are signalled in FIFO order. + *
          • Waiting threads are signalled in FIFO order. * *
          • The ordering of lock reacquisition for threads returning * from waiting methods is the same as for threads initially @@ -1343,7 +1348,7 @@ public final boolean hasQueuedThread(Thread thread) { * either the read or write lock. The value is only an estimate * because the number of threads may change dynamically while this * method traverses internal data structures. This method is - * designed for use in monitoring of the system state, not for + * designed for use in monitoring system state, not for * synchronization control. * * @return the estimated number of threads waiting for this lock @@ -1456,4 +1461,26 @@ public String toString() { "[Write locks = " + w + ", Read locks = " + r + "]"; } + /** + * Returns the thread id for the given thread. We must access + * this directly rather than via method Thread.getId() because + * getId() is not final, and has been known to be overridden in + * ways that do not preserve unique mappings. + */ + static final long getThreadId(Thread thread) { + return U.getLongVolatile(thread, TID); + } + + // Unsafe mechanics + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long TID; + static { + try { + TID = U.objectFieldOffset + (Thread.class.getDeclaredField("tid")); + } catch (ReflectiveOperationException e) { + throw new Error(e); + } + } + } diff --git a/luni/src/main/java/java/util/concurrent/locks/StampedLock.java b/luni/src/main/java/java/util/concurrent/locks/StampedLock.java new file mode 100644 index 000000000..1f4d54f99 --- /dev/null +++ b/luni/src/main/java/java/util/concurrent/locks/StampedLock.java @@ -0,0 +1,1403 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent.locks; + +import java.util.concurrent.TimeUnit; + +/** + * A capability-based lock with three modes for controlling read/write + * access. The state of a StampedLock consists of a version and mode. + * Lock acquisition methods return a stamp that represents and + * controls access with respect to a lock state; "try" versions of + * these methods may instead return the special value zero to + * represent failure to acquire access. Lock release and conversion + * methods require stamps as arguments, and fail if they do not match + * the state of the lock. The three modes are: + * + *
              + * + *
            • Writing. Method {@link #writeLock} possibly blocks + * waiting for exclusive access, returning a stamp that can be used + * in method {@link #unlockWrite} to release the lock. Untimed and + * timed versions of {@code tryWriteLock} are also provided. When + * the lock is held in write mode, no read locks may be obtained, + * and all optimistic read validations will fail. + * + *
            • Reading. Method {@link #readLock} possibly blocks + * waiting for non-exclusive access, returning a stamp that can be + * used in method {@link #unlockRead} to release the lock. Untimed + * and timed versions of {@code tryReadLock} are also provided. + * + *
            • Optimistic Reading. Method {@link #tryOptimisticRead} + * returns a non-zero stamp only if the lock is not currently held + * in write mode. Method {@link #validate} returns true if the lock + * has not been acquired in write mode since obtaining a given + * stamp. This mode can be thought of as an extremely weak version + * of a read-lock, that can be broken by a writer at any time. The + * use of optimistic mode for short read-only code segments often + * reduces contention and improves throughput. However, its use is + * inherently fragile. Optimistic read sections should only read + * fields and hold them in local variables for later use after + * validation. Fields read while in optimistic mode may be wildly + * inconsistent, so usage applies only when you are familiar enough + * with data representations to check consistency and/or repeatedly + * invoke method {@code validate()}. For example, such steps are + * typically required when first reading an object or array + * reference, and then accessing one of its fields, elements or + * methods. + * + *
            + * + *

            This class also supports methods that conditionally provide + * conversions across the three modes. For example, method {@link + * #tryConvertToWriteLock} attempts to "upgrade" a mode, returning + * a valid write stamp if (1) already in writing mode (2) in reading + * mode and there are no other readers or (3) in optimistic mode and + * the lock is available. The forms of these methods are designed to + * help reduce some of the code bloat that otherwise occurs in + * retry-based designs. + * + *

            StampedLocks are designed for use as internal utilities in the + * development of thread-safe components. Their use relies on + * knowledge of the internal properties of the data, objects, and + * methods they are protecting. They are not reentrant, so locked + * bodies should not call other unknown methods that may try to + * re-acquire locks (although you may pass a stamp to other methods + * that can use or convert it). The use of read lock modes relies on + * the associated code sections being side-effect-free. Unvalidated + * optimistic read sections cannot call methods that are not known to + * tolerate potential inconsistencies. Stamps use finite + * representations, and are not cryptographically secure (i.e., a + * valid stamp may be guessable). Stamp values may recycle after (no + * sooner than) one year of continuous operation. A stamp held without + * use or validation for longer than this period may fail to validate + * correctly. StampedLocks are serializable, but always deserialize + * into initial unlocked state, so they are not useful for remote + * locking. + * + *

            The scheduling policy of StampedLock does not consistently + * prefer readers over writers or vice versa. All "try" methods are + * best-effort and do not necessarily conform to any scheduling or + * fairness policy. A zero return from any "try" method for acquiring + * or converting locks does not carry any information about the state + * of the lock; a subsequent invocation may succeed. + * + *

            Because it supports coordinated usage across multiple lock + * modes, this class does not directly implement the {@link Lock} or + * {@link ReadWriteLock} interfaces. However, a StampedLock may be + * viewed {@link #asReadLock()}, {@link #asWriteLock()}, or {@link + * #asReadWriteLock()} in applications requiring only the associated + * set of functionality. + * + *

            Sample Usage. The following illustrates some usage idioms + * in a class that maintains simple two-dimensional points. The sample + * code illustrates some try/catch conventions even though they are + * not strictly needed here because no exceptions can occur in their + * bodies.
            + * + *

             {@code
            + * class Point {
            + *   private double x, y;
            + *   private final StampedLock sl = new StampedLock();
            + *
            + *   void move(double deltaX, double deltaY) { // an exclusively locked method
            + *     long stamp = sl.writeLock();
            + *     try {
            + *       x += deltaX;
            + *       y += deltaY;
            + *     } finally {
            + *       sl.unlockWrite(stamp);
            + *     }
            + *   }
            + *
            + *   double distanceFromOrigin() { // A read-only method
            + *     long stamp = sl.tryOptimisticRead();
            + *     double currentX = x, currentY = y;
            + *     if (!sl.validate(stamp)) {
            + *        stamp = sl.readLock();
            + *        try {
            + *          currentX = x;
            + *          currentY = y;
            + *        } finally {
            + *           sl.unlockRead(stamp);
            + *        }
            + *     }
            + *     return Math.sqrt(currentX * currentX + currentY * currentY);
            + *   }
            + *
            + *   void moveIfAtOrigin(double newX, double newY) { // upgrade
            + *     // Could instead start with optimistic, not read mode
            + *     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;
            + *           break;
            + *         }
            + *         else {
            + *           sl.unlockRead(stamp);
            + *           stamp = sl.writeLock();
            + *         }
            + *       }
            + *     } finally {
            + *       sl.unlock(stamp);
            + *     }
            + *   }
            + * }}
            + * + * @since 1.8 + * @author Doug Lea + */ +public class StampedLock implements java.io.Serializable { + /* + * Algorithmic notes: + * + * The design employs elements of Sequence locks + * (as used in linux kernels; see Lameter's + * http://www.lameter.com/gelato2005.pdf + * and elsewhere; see + * Boehm's http://www.hpl.hp.com/techreports/2012/HPL-2012-68.html) + * and Ordered RW locks (see Shirako et al + * http://dl.acm.org/citation.cfm?id=2312015) + * + * Conceptually, the primary state of the lock includes a sequence + * number that is odd when write-locked and even otherwise. + * However, this is offset by a reader count that is non-zero when + * read-locked. The read count is ignored when validating + * "optimistic" seqlock-reader-style stamps. Because we must use + * a small finite number of bits (currently 7) for readers, a + * supplementary reader overflow word is used when the number of + * readers exceeds the count field. We do this by treating the max + * reader count value (RBITS) as a spinlock protecting overflow + * updates. + * + * Waiters use a modified form of CLH lock used in + * AbstractQueuedSynchronizer (see its internal documentation for + * a fuller account), where each node is tagged (field mode) as + * either a reader or writer. Sets of waiting readers are grouped + * (linked) under a common node (field cowait) so act as a single + * node with respect to most CLH mechanics. By virtue of the + * queue structure, wait nodes need not actually carry sequence + * numbers; we know each is greater than its predecessor. This + * simplifies the scheduling policy to a mainly-FIFO scheme that + * incorporates elements of Phase-Fair locks (see Brandenburg & + * Anderson, especially http://www.cs.unc.edu/~bbb/diss/). In + * particular, we use the phase-fair anti-barging rule: If an + * incoming reader arrives while read lock is held but there is a + * queued writer, this incoming reader is queued. (This rule is + * responsible for some of the complexity of method acquireRead, + * but without it, the lock becomes highly unfair.) Method release + * does not (and sometimes cannot) itself wake up cowaiters. This + * is done by the primary thread, but helped by any other threads + * with nothing better to do in methods acquireRead and + * acquireWrite. + * + * These rules apply to threads actually queued. All tryLock forms + * opportunistically try to acquire locks regardless of preference + * rules, and so may "barge" their way in. Randomized spinning is + * used in the acquire methods to reduce (increasingly expensive) + * context switching while also avoiding sustained memory + * thrashing among many threads. We limit spins to the head of + * queue. A thread spin-waits up to SPINS times (where each + * iteration decreases spin count with 50% probability) before + * blocking. If, upon wakening it fails to obtain lock, and is + * still (or becomes) the first waiting thread (which indicates + * that some other thread barged and obtained lock), it escalates + * spins (up to MAX_HEAD_SPINS) to reduce the likelihood of + * continually losing to barging threads. + * + * Nearly all of these mechanics are carried out in methods + * acquireWrite and acquireRead, that, as typical of such code, + * sprawl out because actions and retries rely on consistent sets + * of locally cached reads. + * + * As noted in Boehm's paper (above), sequence validation (mainly + * method validate()) requires stricter ordering rules than apply + * to normal volatile reads (of "state"). To force orderings of + * reads before a validation and the validation itself in those + * cases where this is not already forced, we use + * Unsafe.loadFence. + * + * The memory layout keeps lock state and queue pointers together + * (normally on the same cache line). This usually works well for + * read-mostly loads. In most other cases, the natural tendency of + * adaptive-spin CLH locks to reduce memory contention lessens + * motivation to further spread out contended locations, but might + * be subject to future improvements. + */ + + private static final long serialVersionUID = -6001602636862214147L; + + /** Number of processors, for spin control */ + private static final int NCPU = Runtime.getRuntime().availableProcessors(); + + /** Maximum number of retries before enqueuing on acquisition */ + private static final int SPINS = (NCPU > 1) ? 1 << 6 : 0; + + /** Maximum number of retries before blocking at head on acquisition */ + private static final int HEAD_SPINS = (NCPU > 1) ? 1 << 10 : 0; + + /** Maximum number of retries before re-blocking */ + private static final int MAX_HEAD_SPINS = (NCPU > 1) ? 1 << 16 : 0; + + /** The period for yielding when waiting for overflow spinlock */ + private static final int OVERFLOW_YIELD_RATE = 7; // must be power 2 - 1 + + /** The number of bits to use for reader count before overflowing */ + private static final int LG_READERS = 7; + + // Values for lock state and stamp operations + private static final long RUNIT = 1L; + private static final long WBIT = 1L << LG_READERS; + private static final long RBITS = WBIT - 1L; + private static final long RFULL = RBITS - 1L; + private static final long ABITS = RBITS | WBIT; + private static final long SBITS = ~RBITS; // note overlap with ABITS + + // Initial value for lock state; avoid failure value zero + private static final long ORIGIN = WBIT << 1; + + // Special value from cancelled acquire methods so caller can throw IE + private static final long INTERRUPTED = 1L; + + // Values for node status; order matters + private static final int WAITING = -1; + private static final int CANCELLED = 1; + + // Modes for nodes (int not boolean to allow arithmetic) + private static final int RMODE = 0; + private static final int WMODE = 1; + + /** Wait nodes */ + static final class WNode { + volatile WNode prev; + volatile WNode next; + volatile WNode cowait; // list of linked readers + volatile Thread thread; // non-null while possibly parked + volatile int status; // 0, WAITING, or CANCELLED + final int mode; // RMODE or WMODE + WNode(int m, WNode p) { mode = m; prev = p; } + } + + /** Head of CLH queue */ + private transient volatile WNode whead; + /** Tail (last) of CLH queue */ + private transient volatile WNode wtail; + + // views + transient ReadLockView readLockView; + transient WriteLockView writeLockView; + transient ReadWriteLockView readWriteLockView; + + /** Lock sequence/state */ + private transient volatile long state; + /** extra reader count when state read count saturated */ + private transient int readerOverflow; + + /** + * Creates a new lock, initially in unlocked state. + */ + public StampedLock() { + state = ORIGIN; + } + + /** + * Exclusively acquires the lock, blocking if necessary + * until available. + * + * @return a stamp that can be used to unlock or convert mode + */ + public long writeLock() { + long s, next; // bypass acquireWrite in fully unlocked case only + return ((((s = state) & ABITS) == 0L && + U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ? + next : acquireWrite(false, 0L)); + } + + /** + * Exclusively acquires the lock if it is immediately available. + * + * @return a stamp that can be used to unlock or convert mode, + * or zero if the lock is not available + */ + public long tryWriteLock() { + long s, next; + return ((((s = state) & ABITS) == 0L && + U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ? + next : 0L); + } + + /** + * Exclusively acquires the lock if it is available within the + * given time and the current thread has not been interrupted. + * Behavior under timeout and interruption matches that specified + * for method {@link Lock#tryLock(long,TimeUnit)}. + * + * @param time the maximum time to wait for the lock + * @param unit the time unit of the {@code time} argument + * @return a stamp that can be used to unlock or convert mode, + * or zero if the lock is not available + * @throws InterruptedException if the current thread is interrupted + * before acquiring the lock + */ + public long tryWriteLock(long time, TimeUnit unit) + throws InterruptedException { + long nanos = unit.toNanos(time); + if (!Thread.interrupted()) { + long next, deadline; + if ((next = tryWriteLock()) != 0L) + return next; + if (nanos <= 0L) + return 0L; + if ((deadline = System.nanoTime() + nanos) == 0L) + deadline = 1L; + if ((next = acquireWrite(true, deadline)) != INTERRUPTED) + return next; + } + throw new InterruptedException(); + } + + /** + * Exclusively acquires the lock, blocking if necessary + * until available or the current thread is interrupted. + * Behavior under interruption matches that specified + * for method {@link Lock#lockInterruptibly()}. + * + * @return a stamp that can be used to unlock or convert mode + * @throws InterruptedException if the current thread is interrupted + * before acquiring the lock + */ + public long writeLockInterruptibly() throws InterruptedException { + long next; + if (!Thread.interrupted() && + (next = acquireWrite(true, 0L)) != INTERRUPTED) + return next; + throw new InterruptedException(); + } + + /** + * Non-exclusively acquires the lock, blocking if necessary + * until available. + * + * @return a stamp that can be used to unlock or convert mode + */ + public long readLock() { + long s = state, next; // bypass acquireRead on common uncontended case + return ((whead == wtail && (s & ABITS) < RFULL && + U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ? + next : acquireRead(false, 0L)); + } + + /** + * Non-exclusively acquires the lock if it is immediately available. + * + * @return a stamp that can be used to unlock or convert mode, + * or zero if the lock is not available + */ + public long tryReadLock() { + for (;;) { + long s, m, next; + if ((m = (s = state) & ABITS) == WBIT) + return 0L; + else if (m < RFULL) { + if (U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) + return next; + } + else if ((next = tryIncReaderOverflow(s)) != 0L) + return next; + } + } + + /** + * Non-exclusively acquires the lock if it is available within the + * given time and the current thread has not been interrupted. + * Behavior under timeout and interruption matches that specified + * for method {@link Lock#tryLock(long,TimeUnit)}. + * + * @param time the maximum time to wait for the lock + * @param unit the time unit of the {@code time} argument + * @return a stamp that can be used to unlock or convert mode, + * or zero if the lock is not available + * @throws InterruptedException if the current thread is interrupted + * before acquiring the lock + */ + public long tryReadLock(long time, TimeUnit unit) + throws InterruptedException { + long s, m, next, deadline; + long nanos = unit.toNanos(time); + if (!Thread.interrupted()) { + if ((m = (s = state) & ABITS) != WBIT) { + if (m < RFULL) { + if (U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) + return next; + } + else if ((next = tryIncReaderOverflow(s)) != 0L) + return next; + } + if (nanos <= 0L) + return 0L; + if ((deadline = System.nanoTime() + nanos) == 0L) + deadline = 1L; + if ((next = acquireRead(true, deadline)) != INTERRUPTED) + return next; + } + throw new InterruptedException(); + } + + /** + * Non-exclusively acquires the lock, blocking if necessary + * until available or the current thread is interrupted. + * Behavior under interruption matches that specified + * for method {@link Lock#lockInterruptibly()}. + * + * @return a stamp that can be used to unlock or convert mode + * @throws InterruptedException if the current thread is interrupted + * before acquiring the lock + */ + public long readLockInterruptibly() throws InterruptedException { + long next; + if (!Thread.interrupted() && + (next = acquireRead(true, 0L)) != INTERRUPTED) + return next; + throw new InterruptedException(); + } + + /** + * Returns a stamp that can later be validated, or zero + * if exclusively locked. + * + * @return a stamp, or zero if exclusively locked + */ + public long tryOptimisticRead() { + long s; + return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L; + } + + /** + * Returns true if the lock has not been exclusively acquired + * since issuance of the given stamp. Always returns false if the + * stamp is zero. Always returns true if the stamp represents a + * currently held lock. Invoking this method with a value not + * obtained from {@link #tryOptimisticRead} or a locking method + * for this lock has no defined effect or result. + * + * @param stamp a stamp + * @return {@code true} if the lock has not been exclusively acquired + * since issuance of the given stamp; else false + */ + public boolean validate(long stamp) { + U.loadFence(); + return (stamp & SBITS) == (state & SBITS); + } + + /** + * If the lock state matches the given stamp, releases the + * exclusive lock. + * + * @param stamp a stamp returned by a write-lock operation + * @throws IllegalMonitorStateException if the stamp does + * not match the current state of this lock + */ + public void unlockWrite(long stamp) { + WNode h; + if (state != stamp || (stamp & WBIT) == 0L) + throw new IllegalMonitorStateException(); + U.putLongVolatile(this, STATE, (stamp += WBIT) == 0L ? ORIGIN : stamp); + if ((h = whead) != null && h.status != 0) + release(h); + } + + /** + * If the lock state matches the given stamp, releases the + * non-exclusive lock. + * + * @param stamp a stamp returned by a read-lock operation + * @throws IllegalMonitorStateException if the stamp does + * not match the current state of this lock + */ + public void unlockRead(long stamp) { + long s, m; WNode h; + for (;;) { + if (((s = state) & SBITS) != (stamp & SBITS) || + (stamp & ABITS) == 0L || (m = s & ABITS) == 0L || m == WBIT) + throw new IllegalMonitorStateException(); + if (m < RFULL) { + if (U.compareAndSwapLong(this, STATE, s, s - RUNIT)) { + if (m == RUNIT && (h = whead) != null && h.status != 0) + release(h); + break; + } + } + else if (tryDecReaderOverflow(s) != 0L) + break; + } + } + + /** + * If the lock state matches the given stamp, releases the + * corresponding mode of the lock. + * + * @param stamp a stamp returned by a lock operation + * @throws IllegalMonitorStateException if the stamp does + * not match the current state of this lock + */ + public void unlock(long stamp) { + long a = stamp & ABITS, m, s; WNode h; + while (((s = state) & SBITS) == (stamp & SBITS)) { + if ((m = s & ABITS) == 0L) + break; + else if (m == WBIT) { + if (a != m) + break; + U.putLongVolatile(this, STATE, (s += WBIT) == 0L ? ORIGIN : s); + if ((h = whead) != null && h.status != 0) + release(h); + return; + } + else if (a == 0L || a >= WBIT) + break; + else if (m < RFULL) { + if (U.compareAndSwapLong(this, STATE, s, s - RUNIT)) { + if (m == RUNIT && (h = whead) != null && h.status != 0) + release(h); + return; + } + } + else if (tryDecReaderOverflow(s) != 0L) + return; + } + throw new IllegalMonitorStateException(); + } + + /** + * If the lock state matches the given stamp, atomically performs one of + * the following actions. If the stamp represents holding a write + * lock, returns it. Or, if a read lock, if the write lock is + * available, releases the read lock and returns a write stamp. + * Or, if an optimistic read, returns a write stamp only if + * immediately available. This method returns zero in all other + * cases. + * + * @param stamp a stamp + * @return a valid write stamp, or zero on failure + */ + public long tryConvertToWriteLock(long stamp) { + long a = stamp & ABITS, m, s, next; + while (((s = state) & SBITS) == (stamp & SBITS)) { + if ((m = s & ABITS) == 0L) { + if (a != 0L) + break; + if (U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) + return next; + } + else if (m == WBIT) { + if (a != m) + break; + return stamp; + } + else if (m == RUNIT && a != 0L) { + if (U.compareAndSwapLong(this, STATE, s, + next = s - RUNIT + WBIT)) + return next; + } + else + break; + } + return 0L; + } + + /** + * If the lock state matches the given stamp, atomically performs one of + * the following actions. If the stamp represents holding a write + * lock, releases it and obtains a read lock. Or, if a read lock, + * returns it. Or, if an optimistic read, acquires a read lock and + * returns a read stamp only if immediately available. This method + * returns zero in all other cases. + * + * @param stamp a stamp + * @return a valid read stamp, or zero on failure + */ + public long tryConvertToReadLock(long stamp) { + long a = stamp & ABITS, m, s, next; WNode h; + while (((s = state) & SBITS) == (stamp & SBITS)) { + if ((m = s & ABITS) == 0L) { + if (a != 0L) + break; + else if (m < RFULL) { + if (U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) + return next; + } + else if ((next = tryIncReaderOverflow(s)) != 0L) + return next; + } + else if (m == WBIT) { + if (a != m) + break; + U.putLongVolatile(this, STATE, next = s + (WBIT + RUNIT)); + if ((h = whead) != null && h.status != 0) + release(h); + return next; + } + else if (a != 0L && a < WBIT) + return stamp; + else + break; + } + return 0L; + } + + /** + * If the lock state matches the given stamp then, atomically, if the stamp + * represents holding a lock, releases it and returns an + * observation stamp. Or, if an optimistic read, returns it if + * validated. This method returns zero in all other cases, and so + * may be useful as a form of "tryUnlock". + * + * @param stamp a stamp + * @return a valid optimistic read stamp, or zero on failure + */ + public long tryConvertToOptimisticRead(long stamp) { + long a = stamp & ABITS, m, s, next; WNode h; + U.loadFence(); + for (;;) { + if (((s = state) & SBITS) != (stamp & SBITS)) + break; + if ((m = s & ABITS) == 0L) { + if (a != 0L) + break; + return s; + } + else if (m == WBIT) { + if (a != m) + break; + U.putLongVolatile(this, STATE, + next = (s += WBIT) == 0L ? ORIGIN : s); + if ((h = whead) != null && h.status != 0) + release(h); + return next; + } + else if (a == 0L || a >= WBIT) + break; + else if (m < RFULL) { + if (U.compareAndSwapLong(this, STATE, s, next = s - RUNIT)) { + if (m == RUNIT && (h = whead) != null && h.status != 0) + release(h); + return next & SBITS; + } + } + else if ((next = tryDecReaderOverflow(s)) != 0L) + return next & SBITS; + } + return 0L; + } + + /** + * Releases the write lock if it is held, without requiring a + * stamp value. This method may be useful for recovery after + * errors. + * + * @return {@code true} if the lock was held, else false + */ + public boolean tryUnlockWrite() { + long s; WNode h; + if (((s = state) & WBIT) != 0L) { + U.putLongVolatile(this, STATE, (s += WBIT) == 0L ? ORIGIN : s); + if ((h = whead) != null && h.status != 0) + release(h); + return true; + } + return false; + } + + /** + * Releases one hold of the read lock if it is held, without + * requiring a stamp value. This method may be useful for recovery + * after errors. + * + * @return {@code true} if the read lock was held, else false + */ + public boolean tryUnlockRead() { + long s, m; WNode h; + while ((m = (s = state) & ABITS) != 0L && m < WBIT) { + if (m < RFULL) { + if (U.compareAndSwapLong(this, STATE, s, s - RUNIT)) { + if (m == RUNIT && (h = whead) != null && h.status != 0) + release(h); + return true; + } + } + else if (tryDecReaderOverflow(s) != 0L) + return true; + } + return false; + } + + // status monitoring methods + + /** + * Returns combined state-held and overflow read count for given + * state s. + */ + private int getReadLockCount(long s) { + long readers; + if ((readers = s & RBITS) >= RFULL) + readers = RFULL + readerOverflow; + return (int) readers; + } + + /** + * Returns {@code true} if the lock is currently held exclusively. + * + * @return {@code true} if the lock is currently held exclusively + */ + public boolean isWriteLocked() { + return (state & WBIT) != 0L; + } + + /** + * Returns {@code true} if the lock is currently held non-exclusively. + * + * @return {@code true} if the lock is currently held non-exclusively + */ + public boolean isReadLocked() { + return (state & RBITS) != 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. + * @return the number of read locks held + */ + public int getReadLockCount() { + return getReadLockCount(state); + } + + /** + * Returns a string identifying this lock, as well as its lock + * state. The state, in brackets, includes the String {@code + * "Unlocked"} or the String {@code "Write-locked"} or the String + * {@code "Read-locks:"} followed by the current number of + * read-locks held. + * + * @return a string identifying this lock, as well as its lock state + */ + public String toString() { + long s = state; + return super.toString() + + ((s & ABITS) == 0L ? "[Unlocked]" : + (s & WBIT) != 0L ? "[Write-locked]" : + "[Read-locks:" + getReadLockCount(s) + "]"); + } + + // views + + /** + * Returns a plain {@link Lock} view of this StampedLock in which + * the {@link Lock#lock} method is mapped to {@link #readLock}, + * and similarly for other methods. The returned Lock does not + * support a {@link Condition}; method {@link + * Lock#newCondition()} throws {@code + * UnsupportedOperationException}. + * + * @return the lock + */ + public Lock asReadLock() { + ReadLockView v; + return ((v = readLockView) != null ? v : + (readLockView = new ReadLockView())); + } + + /** + * Returns a plain {@link Lock} view of this StampedLock in which + * the {@link Lock#lock} method is mapped to {@link #writeLock}, + * and similarly for other methods. The returned Lock does not + * support a {@link Condition}; method {@link + * Lock#newCondition()} throws {@code + * UnsupportedOperationException}. + * + * @return the lock + */ + public Lock asWriteLock() { + WriteLockView v; + return ((v = writeLockView) != null ? v : + (writeLockView = new WriteLockView())); + } + + /** + * Returns a {@link ReadWriteLock} view of this StampedLock in + * which the {@link ReadWriteLock#readLock()} method is mapped to + * {@link #asReadLock()}, and {@link ReadWriteLock#writeLock()} to + * {@link #asWriteLock()}. + * + * @return the lock + */ + public ReadWriteLock asReadWriteLock() { + ReadWriteLockView v; + return ((v = readWriteLockView) != null ? v : + (readWriteLockView = new ReadWriteLockView())); + } + + // view classes + + final class ReadLockView implements Lock { + public void lock() { readLock(); } + public void lockInterruptibly() throws InterruptedException { + readLockInterruptibly(); + } + public boolean tryLock() { return tryReadLock() != 0L; } + public boolean tryLock(long time, TimeUnit unit) + throws InterruptedException { + return tryReadLock(time, unit) != 0L; + } + public void unlock() { unstampedUnlockRead(); } + public Condition newCondition() { + throw new UnsupportedOperationException(); + } + } + + final class WriteLockView implements Lock { + public void lock() { writeLock(); } + public void lockInterruptibly() throws InterruptedException { + writeLockInterruptibly(); + } + public boolean tryLock() { return tryWriteLock() != 0L; } + public boolean tryLock(long time, TimeUnit unit) + throws InterruptedException { + return tryWriteLock(time, unit) != 0L; + } + public void unlock() { unstampedUnlockWrite(); } + public Condition newCondition() { + throw new UnsupportedOperationException(); + } + } + + final class ReadWriteLockView implements ReadWriteLock { + public Lock readLock() { return asReadLock(); } + public Lock writeLock() { return asWriteLock(); } + } + + // Unlock methods without stamp argument checks for view classes. + // Needed because view-class lock methods throw away stamps. + + final void unstampedUnlockWrite() { + WNode h; long s; + if (((s = state) & WBIT) == 0L) + throw new IllegalMonitorStateException(); + U.putLongVolatile(this, STATE, (s += WBIT) == 0L ? ORIGIN : s); + if ((h = whead) != null && h.status != 0) + release(h); + } + + final void unstampedUnlockRead() { + for (;;) { + long s, m; WNode h; + if ((m = (s = state) & ABITS) == 0L || m >= WBIT) + throw new IllegalMonitorStateException(); + else if (m < RFULL) { + if (U.compareAndSwapLong(this, STATE, s, s - RUNIT)) { + if (m == RUNIT && (h = whead) != null && h.status != 0) + release(h); + break; + } + } + else if (tryDecReaderOverflow(s) != 0L) + break; + } + } + + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + U.putLongVolatile(this, STATE, ORIGIN); // reset to unlocked state + } + + // internals + + /** + * Tries to increment readerOverflow by first setting state + * access bits value to RBITS, indicating hold of spinlock, + * then updating, then releasing. + * + * @param s a reader overflow stamp: (s & ABITS) >= RFULL + * @return new stamp on success, else zero + */ + private long tryIncReaderOverflow(long s) { + // assert (s & ABITS) >= RFULL; + if ((s & ABITS) == RFULL) { + if (U.compareAndSwapLong(this, STATE, s, s | RBITS)) { + ++readerOverflow; + U.putLongVolatile(this, STATE, s); + return s; + } + } + else if ((LockSupport.nextSecondarySeed() & + OVERFLOW_YIELD_RATE) == 0) + Thread.yield(); + return 0L; + } + + /** + * Tries to decrement readerOverflow. + * + * @param s a reader overflow stamp: (s & ABITS) >= RFULL + * @return new stamp on success, else zero + */ + private long tryDecReaderOverflow(long s) { + // assert (s & ABITS) >= RFULL; + if ((s & ABITS) == RFULL) { + if (U.compareAndSwapLong(this, STATE, s, s | RBITS)) { + int r; long next; + if ((r = readerOverflow) > 0) { + readerOverflow = r - 1; + next = s; + } + else + next = s - RUNIT; + U.putLongVolatile(this, STATE, next); + return next; + } + } + else if ((LockSupport.nextSecondarySeed() & + OVERFLOW_YIELD_RATE) == 0) + Thread.yield(); + return 0L; + } + + /** + * Wakes up the successor of h (normally whead). This is normally + * just h.next, but may require traversal from wtail if next + * pointers are lagging. This may fail to wake up an acquiring + * thread when one or more have been cancelled, but the cancel + * methods themselves provide extra safeguards to ensure liveness. + */ + private void release(WNode h) { + if (h != null) { + WNode q; Thread w; + U.compareAndSwapInt(h, WSTATUS, WAITING, 0); + if ((q = h.next) == null || q.status == CANCELLED) { + for (WNode t = wtail; t != null && t != h; t = t.prev) + if (t.status <= 0) + q = t; + } + if (q != null && (w = q.thread) != null) + U.unpark(w); + } + } + + /** + * See above for explanation. + * + * @param interruptible true if should check interrupts and if so + * return INTERRUPTED + * @param deadline if nonzero, the System.nanoTime value to timeout + * at (and return zero) + * @return next state, or INTERRUPTED + */ + private long acquireWrite(boolean interruptible, long deadline) { + WNode node = null, p; + for (int spins = -1;;) { // spin while enqueuing + long m, s, ns; + if ((m = (s = state) & ABITS) == 0L) { + if (U.compareAndSwapLong(this, STATE, s, ns = s + WBIT)) + return ns; + } + else if (spins < 0) + spins = (m == WBIT && wtail == whead) ? SPINS : 0; + else if (spins > 0) { + if (LockSupport.nextSecondarySeed() >= 0) + --spins; + } + else if ((p = wtail) == null) { // initialize queue + WNode hd = new WNode(WMODE, null); + if (U.compareAndSwapObject(this, WHEAD, null, hd)) + wtail = hd; + } + else if (node == null) + node = new WNode(WMODE, p); + else if (node.prev != p) + node.prev = p; + else if (U.compareAndSwapObject(this, WTAIL, p, node)) { + p.next = node; + break; + } + } + + boolean wasInterrupted = false; + for (int spins = -1;;) { + WNode h, np, pp; int ps; + if ((h = whead) == p) { + if (spins < 0) + spins = HEAD_SPINS; + else if (spins < MAX_HEAD_SPINS) + spins <<= 1; + for (int k = spins;;) { // spin at head + long s, ns; + if (((s = state) & ABITS) == 0L) { + if (U.compareAndSwapLong(this, STATE, s, + ns = s + WBIT)) { + whead = node; + node.prev = null; + if (wasInterrupted) + Thread.currentThread().interrupt(); + return ns; + } + } + else if (LockSupport.nextSecondarySeed() >= 0 && + --k <= 0) + break; + } + } + else if (h != null) { // help release stale waiters + WNode c; Thread w; + while ((c = h.cowait) != null) { + if (U.compareAndSwapObject(h, WCOWAIT, c, c.cowait) && + (w = c.thread) != null) + U.unpark(w); + } + } + if (whead == h) { + if ((np = node.prev) != p) { + if (np != null) + (p = np).next = node; // stale + } + else if ((ps = p.status) == 0) + U.compareAndSwapInt(p, WSTATUS, 0, WAITING); + else if (ps == CANCELLED) { + if ((pp = p.prev) != null) { + node.prev = pp; + pp.next = node; + } + } + else { + long time; // 0 argument to park means no timeout + if (deadline == 0L) + time = 0L; + else if ((time = deadline - System.nanoTime()) <= 0L) + return cancelWaiter(node, node, false); + Thread wt = Thread.currentThread(); + U.putObject(wt, PARKBLOCKER, this); + node.thread = wt; + if (p.status < 0 && (p != h || (state & ABITS) != 0L) && + whead == h && node.prev == p) + U.park(false, time); // emulate LockSupport.park + node.thread = null; + U.putObject(wt, PARKBLOCKER, null); + if (Thread.interrupted()) { + if (interruptible) + return cancelWaiter(node, node, true); + wasInterrupted = true; + } + } + } + } + } + + /** + * See above for explanation. + * + * @param interruptible true if should check interrupts and if so + * return INTERRUPTED + * @param deadline if nonzero, the System.nanoTime value to timeout + * at (and return zero) + * @return next state, or INTERRUPTED + */ + private long acquireRead(boolean interruptible, long deadline) { + boolean wasInterrupted = false; + WNode node = null, p; + for (int spins = -1;;) { + WNode h; + if ((h = whead) == (p = wtail)) { + for (long m, s, ns;;) { + if ((m = (s = state) & ABITS) < RFULL ? + U.compareAndSwapLong(this, STATE, s, ns = s + RUNIT) : + (m < WBIT && (ns = tryIncReaderOverflow(s)) != 0L)) { + if (wasInterrupted) + Thread.currentThread().interrupt(); + return ns; + } + else if (m >= WBIT) { + if (spins > 0) { + if (LockSupport.nextSecondarySeed() >= 0) + --spins; + } + else { + if (spins == 0) { + WNode nh = whead, np = wtail; + if ((nh == h && np == p) || (h = nh) != (p = np)) + break; + } + spins = SPINS; + } + } + } + } + if (p == null) { // initialize queue + WNode hd = new WNode(WMODE, null); + if (U.compareAndSwapObject(this, WHEAD, null, hd)) + wtail = hd; + } + else if (node == null) + node = new WNode(RMODE, p); + else if (h == p || p.mode != RMODE) { + if (node.prev != p) + node.prev = p; + else if (U.compareAndSwapObject(this, WTAIL, p, node)) { + p.next = node; + break; + } + } + else if (!U.compareAndSwapObject(p, WCOWAIT, + node.cowait = p.cowait, node)) + node.cowait = null; + else { + for (;;) { + WNode pp, c; Thread w; + if ((h = whead) != null && (c = h.cowait) != null && + U.compareAndSwapObject(h, WCOWAIT, c, c.cowait) && + (w = c.thread) != null) // help release + U.unpark(w); + if (h == (pp = p.prev) || h == p || pp == null) { + long m, s, ns; + do { + if ((m = (s = state) & ABITS) < RFULL ? + U.compareAndSwapLong(this, STATE, s, + ns = s + RUNIT) : + (m < WBIT && + (ns = tryIncReaderOverflow(s)) != 0L)) { + if (wasInterrupted) + Thread.currentThread().interrupt(); + return ns; + } + } while (m < WBIT); + } + if (whead == h && p.prev == pp) { + long time; + if (pp == null || h == p || p.status > 0) { + node = null; // throw away + break; + } + if (deadline == 0L) + time = 0L; + else if ((time = deadline - System.nanoTime()) <= 0L) { + if (wasInterrupted) + Thread.currentThread().interrupt(); + return cancelWaiter(node, p, false); + } + Thread wt = Thread.currentThread(); + U.putObject(wt, PARKBLOCKER, this); + node.thread = wt; + if ((h != pp || (state & ABITS) == WBIT) && + whead == h && p.prev == pp) + U.park(false, time); + node.thread = null; + U.putObject(wt, PARKBLOCKER, null); + if (Thread.interrupted()) { + if (interruptible) + return cancelWaiter(node, p, true); + wasInterrupted = true; + } + } + } + } + } + + for (int spins = -1;;) { + WNode h, np, pp; int ps; + if ((h = whead) == p) { + if (spins < 0) + spins = HEAD_SPINS; + else if (spins < MAX_HEAD_SPINS) + spins <<= 1; + for (int k = spins;;) { // spin at head + long m, s, ns; + if ((m = (s = state) & ABITS) < RFULL ? + U.compareAndSwapLong(this, STATE, s, ns = s + RUNIT) : + (m < WBIT && (ns = tryIncReaderOverflow(s)) != 0L)) { + WNode c; Thread w; + whead = node; + node.prev = null; + while ((c = node.cowait) != null) { + if (U.compareAndSwapObject(node, WCOWAIT, + c, c.cowait) && + (w = c.thread) != null) + U.unpark(w); + } + if (wasInterrupted) + Thread.currentThread().interrupt(); + return ns; + } + else if (m >= WBIT && + LockSupport.nextSecondarySeed() >= 0 && --k <= 0) + break; + } + } + else if (h != null) { + WNode c; Thread w; + while ((c = h.cowait) != null) { + if (U.compareAndSwapObject(h, WCOWAIT, c, c.cowait) && + (w = c.thread) != null) + U.unpark(w); + } + } + if (whead == h) { + if ((np = node.prev) != p) { + if (np != null) + (p = np).next = node; // stale + } + else if ((ps = p.status) == 0) + U.compareAndSwapInt(p, WSTATUS, 0, WAITING); + else if (ps == CANCELLED) { + if ((pp = p.prev) != null) { + node.prev = pp; + pp.next = node; + } + } + else { + long time; + if (deadline == 0L) + time = 0L; + else if ((time = deadline - System.nanoTime()) <= 0L) + return cancelWaiter(node, node, false); + Thread wt = Thread.currentThread(); + U.putObject(wt, PARKBLOCKER, this); + node.thread = wt; + if (p.status < 0 && + (p != h || (state & ABITS) == WBIT) && + whead == h && node.prev == p) + U.park(false, time); + node.thread = null; + U.putObject(wt, PARKBLOCKER, null); + if (Thread.interrupted()) { + if (interruptible) + return cancelWaiter(node, node, true); + wasInterrupted = true; + } + } + } + } + } + + /** + * If node non-null, forces cancel status and unsplices it from + * queue if possible and wakes up any cowaiters (of the node, or + * group, as applicable), and in any case helps release current + * first waiter if lock is free. (Calling with null arguments + * serves as a conditional form of release, which is not currently + * needed but may be needed under possible future cancellation + * policies). This is a variant of cancellation methods in + * AbstractQueuedSynchronizer (see its detailed explanation in AQS + * internal documentation). + * + * @param node if nonnull, the waiter + * @param group either node or the group node is cowaiting with + * @param interrupted if already interrupted + * @return INTERRUPTED if interrupted or Thread.interrupted, else zero + */ + private long cancelWaiter(WNode node, WNode group, boolean interrupted) { + if (node != null && group != null) { + Thread w; + node.status = CANCELLED; + // unsplice cancelled nodes from group + for (WNode p = group, q; (q = p.cowait) != null;) { + if (q.status == CANCELLED) { + U.compareAndSwapObject(p, WCOWAIT, q, q.cowait); + p = group; // restart + } + else + p = q; + } + if (group == node) { + for (WNode r = group.cowait; r != null; r = r.cowait) { + if ((w = r.thread) != null) + U.unpark(w); // wake up uncancelled co-waiters + } + for (WNode pred = node.prev; pred != null; ) { // unsplice + WNode succ, pp; // find valid successor + while ((succ = node.next) == null || + succ.status == CANCELLED) { + WNode q = null; // find successor the slow way + for (WNode t = wtail; t != null && t != node; t = t.prev) + if (t.status != CANCELLED) + q = t; // don't link if succ cancelled + if (succ == q || // ensure accurate successor + U.compareAndSwapObject(node, WNEXT, + succ, succ = q)) { + if (succ == null && node == wtail) + U.compareAndSwapObject(this, WTAIL, node, pred); + break; + } + } + if (pred.next == node) // unsplice pred link + U.compareAndSwapObject(pred, WNEXT, node, succ); + if (succ != null && (w = succ.thread) != null) { + succ.thread = null; + U.unpark(w); // wake up succ to observe new pred + } + if (pred.status != CANCELLED || (pp = pred.prev) == null) + break; + node.prev = pp; // repeat if new pred wrong/cancelled + U.compareAndSwapObject(pp, WNEXT, pred, succ); + pred = pp; + } + } + } + WNode h; // Possibly release first waiter + while ((h = whead) != null) { + long s; WNode q; // similar to release() but check eligibility + if ((q = h.next) == null || q.status == CANCELLED) { + for (WNode t = wtail; t != null && t != h; t = t.prev) + if (t.status <= 0) + q = t; + } + if (h == whead) { + if (q != null && h.status == 0 && + ((s = state) & ABITS) != WBIT && // waiter is eligible + (s == 0L || q.mode == RMODE)) + release(h); + break; + } + } + return (interrupted || Thread.interrupted()) ? INTERRUPTED : 0L; + } + + // Unsafe mechanics + private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); + private static final long STATE; + private static final long WHEAD; + private static final long WTAIL; + private static final long WNEXT; + private static final long WSTATUS; + private static final long WCOWAIT; + private static final long PARKBLOCKER; + + static { + try { + STATE = U.objectFieldOffset + (StampedLock.class.getDeclaredField("state")); + WHEAD = U.objectFieldOffset + (StampedLock.class.getDeclaredField("whead")); + WTAIL = U.objectFieldOffset + (StampedLock.class.getDeclaredField("wtail")); + + WSTATUS = U.objectFieldOffset + (WNode.class.getDeclaredField("status")); + WNEXT = U.objectFieldOffset + (WNode.class.getDeclaredField("next")); + WCOWAIT = U.objectFieldOffset + (WNode.class.getDeclaredField("cowait")); + + PARKBLOCKER = U.objectFieldOffset + (Thread.class.getDeclaredField("parkBlocker")); + } catch (ReflectiveOperationException e) { + throw new Error(e); + } + } +} diff --git a/luni/src/main/java/java/util/concurrent/package-info.java b/luni/src/main/java/java/util/concurrent/package-info.java index afc8ca428..5dc12284b 100644 --- a/luni/src/main/java/java/util/concurrent/package-info.java +++ b/luni/src/main/java/java/util/concurrent/package-info.java @@ -181,18 +181,25 @@ * collections are unshared, or are accessible only when * holding other locks. * - *

            Most concurrent Collection implementations (including most - * Queues) also differ from the usual java.util conventions in that - * their Iterators provide weakly consistent rather than - * fast-fail traversal. A weakly consistent iterator is thread-safe, - * but does not necessarily freeze the collection while iterating, so - * it may (or may not) reflect any updates since the iterator was - * created. + *

            Most concurrent Collection implementations + * (including most Queues) also differ from the usual {@code java.util} + * conventions in that their {@linkplain java.util.Iterator Iterators} + * and {@linkplain java.util.Spliterator Spliterators} provide + * weakly consistent rather than fast-fail traversal: + *

              + *
            • they may proceed concurrently with other operations + *
            • they will never throw {@link java.util.ConcurrentModificationException + * ConcurrentModificationException} + *
            • they are guaranteed to traverse elements as they existed upon + * construction exactly once, and may (but are not guaranteed to) + * reflect any modifications subsequent to construction. + *
            * *

            Memory Consistency Properties

            * - * - * Chapter 17 of the Java Language Specification defines the + * + * Chapter 17 of + * The Java™ Language Specification defines the * happens-before relation on memory operations such as reads and * writes of shared variables. The results of a write by one thread are * guaranteed to be visible to a read by another thread only if the write diff --git a/non_openjdk_java_files.mk b/non_openjdk_java_files.mk index 0c5502b2d..104a6f3e8 100644 --- a/non_openjdk_java_files.mk +++ b/non_openjdk_java_files.mk @@ -88,7 +88,10 @@ non_openjdk_javadoc_files := \ luni/src/main/java/java/util/concurrent/BrokenBarrierException.java \ luni/src/main/java/java/util/concurrent/Callable.java \ luni/src/main/java/java/util/concurrent/CancellationException.java \ + luni/src/main/java/java/util/concurrent/CompletableFuture.java \ + luni/src/main/java/java/util/concurrent/CompletionException.java \ luni/src/main/java/java/util/concurrent/CompletionService.java \ + luni/src/main/java/java/util/concurrent/CompletionStage.java \ luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java \ luni/src/main/java/java/util/concurrent/ConcurrentLinkedDeque.java \ luni/src/main/java/java/util/concurrent/ConcurrentLinkedQueue.java \ @@ -114,6 +117,7 @@ non_openjdk_javadoc_files := \ luni/src/main/java/java/util/concurrent/ForkJoinWorkerThread.java \ luni/src/main/java/java/util/concurrent/Future.java \ luni/src/main/java/java/util/concurrent/FutureTask.java \ + luni/src/main/java/java/util/concurrent/Helpers.java \ luni/src/main/java/java/util/concurrent/LinkedBlockingDeque.java \ luni/src/main/java/java/util/concurrent/LinkedBlockingQueue.java \ luni/src/main/java/java/util/concurrent/LinkedTransferQueue.java \ @@ -148,7 +152,11 @@ non_openjdk_javadoc_files := \ luni/src/main/java/java/util/concurrent/atomic/AtomicReferenceArray.java \ luni/src/main/java/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java \ luni/src/main/java/java/util/concurrent/atomic/AtomicStampedReference.java \ - luni/src/main/java/java/util/concurrent/atomic/Fences.java \ + luni/src/main/java/java/util/concurrent/atomic/DoubleAccumulator.java \ + luni/src/main/java/java/util/concurrent/atomic/DoubleAdder.java \ + luni/src/main/java/java/util/concurrent/atomic/LongAccumulator.java \ + luni/src/main/java/java/util/concurrent/atomic/LongAdder.java \ + luni/src/main/java/java/util/concurrent/atomic/Striped64.java \ luni/src/main/java/java/util/concurrent/atomic/package-info.java \ luni/src/main/java/java/util/concurrent/locks/AbstractOwnableSynchronizer.java \ luni/src/main/java/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java \ @@ -159,6 +167,7 @@ non_openjdk_javadoc_files := \ luni/src/main/java/java/util/concurrent/locks/ReadWriteLock.java \ luni/src/main/java/java/util/concurrent/locks/ReentrantLock.java \ luni/src/main/java/java/util/concurrent/locks/ReentrantReadWriteLock.java \ + luni/src/main/java/java/util/concurrent/locks/StampedLock.java \ luni/src/main/java/java/util/concurrent/locks/package-info.java \ luni/src/main/java/java/util/concurrent/package-info.java \ luni/src/main/java/javax/xml/XMLConstants.java \ diff --git a/ojluni/src/main/java/java/lang/Thread.java b/ojluni/src/main/java/java/lang/Thread.java index 2afeccfce..c14022bd6 100755 --- a/ojluni/src/main/java/java/lang/Thread.java +++ b/ojluni/src/main/java/java/lang/Thread.java @@ -2050,6 +2050,25 @@ public boolean equals(Object obj) { } } + + // The following three initially uninitialized fields are exclusively + // managed by class java.util.concurrent.ThreadLocalRandom. These + // fields are used to build the high-performance PRNGs in the + // concurrent code, and we can not risk accidental false sharing. + // Hence, the fields are isolated with @Contended. + + /** The current seed for a ThreadLocalRandom */ + // @sun.misc.Contended("tlr") + long threadLocalRandomSeed; + + /** Probe hash value; nonzero if threadLocalRandomSeed initialized */ + // @sun.misc.Contended("tlr") + int threadLocalRandomProbe; + + /** Secondary seed isolated from public ThreadLocalRandom sequence */ + // @sun.misc.Contended("tlr") + int threadLocalRandomSecondarySeed; + /* Some private helper methods */ private native void nativeSetName(String newName); diff --git a/ojluni/src/main/java/java/util/AbstractQueue.java b/ojluni/src/main/java/java/util/AbstractQueue.java index ebe270079..44e11f7c1 100644 --- a/ojluni/src/main/java/java/util/AbstractQueue.java +++ b/ojluni/src/main/java/java/util/AbstractQueue.java @@ -58,7 +58,7 @@ * * @since 1.5 * @author Doug Lea - * @param the type of elements held in this collection + * @param the type of elements held in this queue */ public abstract class AbstractQueue extends AbstractCollection diff --git a/ojluni/src/main/java/java/util/ArrayDeque.java b/ojluni/src/main/java/java/util/ArrayDeque.java index eb31bccd1..83ae07665 100644 --- a/ojluni/src/main/java/java/util/ArrayDeque.java +++ b/ojluni/src/main/java/java/util/ArrayDeque.java @@ -33,7 +33,7 @@ package java.util; -import java.io.*; +import java.io.Serializable; import java.util.function.Consumer; // BEGIN android-note @@ -81,10 +81,10 @@ * * @author Josh Bloch and Doug Lea * @since 1.6 - * @param the type of elements held in this collection + * @param the type of elements held in this deque */ public class ArrayDeque extends AbstractCollection - implements Deque, Cloneable, java.io.Serializable + implements Deque, Cloneable, Serializable { /** * The array in which the elements of the deque are stored. @@ -96,20 +96,20 @@ public class ArrayDeque extends AbstractCollection * other. We also guarantee that all array cells not holding * deque elements are always null. */ - private transient Object[] elements; + transient Object[] elements; // non-private to simplify nested class access /** * The index of the element at the head of the deque (which is the * element that would be removed by remove() or pop()); or an * arbitrary number equal to tail if the deque is empty. */ - private transient int head; + transient int head; /** * The index at which the next element would be added to the tail * of the deque (via addLast(E), add(E), or push(E)). */ - private transient int tail; + transient int tail; /** * The minimum capacity that we'll use for a newly created deque. @@ -137,8 +137,8 @@ private void allocateElements(int numElements) { initialCapacity |= (initialCapacity >>> 16); initialCapacity++; - if (initialCapacity < 0) // Too many elements, must back off - initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements + if (initialCapacity < 0) // Too many elements, must back off + initialCapacity >>>= 1; // Good luck allocating 2^30 elements } elements = new Object[initialCapacity]; } @@ -275,25 +275,27 @@ public E removeLast() { } public E pollFirst() { - int h = head; + final Object[] elements = this.elements; + final int h = head; @SuppressWarnings("unchecked") E result = (E) elements[h]; // Element is null if deque empty - if (result == null) - return null; - elements[h] = null; // Must null out slot - head = (h + 1) & (elements.length - 1); + if (result != null) { + elements[h] = null; // Must null out slot + head = (h + 1) & (elements.length - 1); + } return result; } public E pollLast() { - int t = (tail - 1) & (elements.length - 1); + final Object[] elements = this.elements; + final int t = (tail - 1) & (elements.length - 1); @SuppressWarnings("unchecked") E result = (E) elements[t]; - if (result == null) - return null; - elements[t] = null; - tail = t; + if (result != null) { + elements[t] = null; + tail = t; + } return result; } @@ -343,17 +345,15 @@ public E peekLast() { * @return {@code true} if the deque contained the specified element */ public boolean removeFirstOccurrence(Object o) { - if (o == null) - return false; - int mask = elements.length - 1; - int i = head; - Object x; - while ( (x = elements[i]) != null) { - if (o.equals(x)) { - delete(i); - return true; + if (o != null) { + int mask = elements.length - 1; + int i = head; + for (Object x; (x = elements[i]) != null; i = (i + 1) & mask) { + if (o.equals(x)) { + delete(i); + return true; + } } - i = (i + 1) & mask; } return false; } @@ -371,17 +371,15 @@ public boolean removeFirstOccurrence(Object o) { * @return {@code true} if the deque contained the specified element */ public boolean removeLastOccurrence(Object o) { - if (o == null) - return false; - int mask = elements.length - 1; - int i = (tail - 1) & mask; - Object x; - while ( (x = elements[i]) != null) { - if (o.equals(x)) { - delete(i); - return true; + if (o != null) { + int mask = elements.length - 1; + int i = (tail - 1) & mask; + for (Object x; (x = elements[i]) != null; i = (i - 1) & mask) { + if (o.equals(x)) { + delete(i); + return true; + } } - i = (i - 1) & mask; } return false; } @@ -501,11 +499,11 @@ public E pop() { } private void checkInvariants() { - // assert elements[tail] == null; - // assert head == tail ? elements[head] == null : - // (elements[head] != null && - // elements[(tail - 1) & (elements.length - 1)] != null); - // assert elements[(head - 1) & (elements.length - 1)] == null; + assert elements[tail] == null; + assert head == tail ? elements[head] == null : + (elements[head] != null && + elements[(tail - 1) & (elements.length - 1)] != null); + assert elements[(head - 1) & (elements.length - 1)] == null; } /** @@ -518,8 +516,8 @@ private void checkInvariants() { * * @return true if elements moved backwards */ - private boolean delete(int i) { - //checkInvariants(); + boolean delete(int i) { + checkInvariants(); final Object[] elements = this.elements; final int mask = elements.length - 1; final int h = head; @@ -704,15 +702,13 @@ public void remove() { * @return {@code true} if this deque contains the specified element */ public boolean contains(Object o) { - if (o == null) - return false; - int mask = elements.length - 1; - int i = head; - Object x; - while ( (x = elements[i]) != null) { - if (o.equals(x)) - return true; - i = (i + 1) & mask; + if (o != null) { + int mask = elements.length - 1; + int i = head; + for (Object x; (x = elements[i]) != null; i = (i + 1) & mask) { + if (o.equals(x)) + return true; + } } return false; } @@ -725,7 +721,7 @@ public boolean contains(Object o) { * Returns {@code true} if this deque contained the specified element * (or equivalently, if this deque changed as a result of the call). * - *

            This method is equivalent to {@link #removeFirstOccurrence}. + *

            This method is equivalent to {@link #removeFirstOccurrence(Object)}. * * @param o element to be removed from this deque, if present * @return {@code true} if this deque contained the specified element @@ -798,7 +794,7 @@ public Object[] toArray() { * The following code can be used to dump the deque into a newly * allocated array of {@code String}: * - *

             {@code String[] y = x.toArray(new String[0]);}
            + *
             {@code String[] y = x.toArray(new String[0]);}
            * * Note that {@code toArray(new Object[0])} is identical in function to * {@code toArray()}. @@ -856,6 +852,8 @@ public ArrayDeque clone() { /** * Saves this deque to a stream (that is, serializes it). * + * @param s the stream + * @throws java.io.IOException if an I/O error occurs * @serialData The current size ({@code int}) of the deque, * followed by all of its elements (each an object reference) in * first-to-last order. @@ -875,6 +873,10 @@ private void writeObject(java.io.ObjectOutputStream s) /** * Reconstitutes this deque from a stream (that is, deserializes it). + * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws java.io.IOException if an I/O error occurs */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { @@ -913,7 +915,7 @@ static final class DeqSpliterator implements Spliterator { private int fence; // -1 until first use private int index; // current index, modified on traverse/split - /** Creates new spliterator covering the given array and range */ + /** Creates new spliterator covering the given array and range. */ DeqSpliterator(ArrayDeque deq, int origin, int fence) { this.deq = deq; this.index = origin; @@ -935,7 +937,7 @@ public DeqSpliterator trySplit() { if (h > t) t += n; int m = ((h + t) >>> 1) & (n - 1); - return new DeqSpliterator<>(deq, h, index = m); + return new DeqSpliterator(deq, h, index = m); } return null; } @@ -960,7 +962,7 @@ public boolean tryAdvance(Consumer consumer) { throw new NullPointerException(); Object[] a = deq.elements; int m = a.length - 1, f = getFence(), i = index; - if (i != fence) { + if (i != f) { @SuppressWarnings("unchecked") E e = (E)a[i]; index = (i + 1) & m; if (e == null) diff --git a/ojluni/src/main/java/java/util/ArrayPrefixHelpers.java b/ojluni/src/main/java/java/util/ArrayPrefixHelpers.java new file mode 100644 index 000000000..d3b5614f9 --- /dev/null +++ b/ojluni/src/main/java/java/util/ArrayPrefixHelpers.java @@ -0,0 +1,677 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util; + +import java.util.concurrent.CountedCompleter; +import java.util.concurrent.ForkJoinPool; +import java.util.function.BinaryOperator; +import java.util.function.DoubleBinaryOperator; +import java.util.function.IntBinaryOperator; +import java.util.function.LongBinaryOperator; + +/** + * ForkJoin tasks to perform Arrays.parallelPrefix operations. + * + * @author Doug Lea + * @since 1.8 + */ +class ArrayPrefixHelpers { + private ArrayPrefixHelpers() {} // non-instantiable + + /* + * Parallel prefix (aka cumulate, scan) task classes + * are based loosely on Guy Blelloch's original + * algorithm (http://www.cs.cmu.edu/~scandal/alg/scan.html): + * Keep dividing by two to threshold segment size, and then: + * Pass 1: Create tree of partial sums for each segment + * Pass 2: For each segment, cumulate with offset of left sibling + * + * This version improves performance within FJ framework mainly by + * allowing the second pass of ready left-hand sides to proceed + * even if some right-hand side first passes are still executing. + * It also combines first and second pass for leftmost segment, + * and skips the first pass for rightmost segment (whose result is + * not needed for second pass). It similarly manages to avoid + * requiring that users supply an identity basis for accumulations + * by tracking those segments/subtasks for which the first + * existing element is used as base. + * + * Managing this relies on ORing some bits in the pendingCount for + * phases/states: CUMULATE, SUMMED, and FINISHED. CUMULATE is the + * main phase bit. When false, segments compute only their sum. + * When true, they cumulate array elements. CUMULATE is set at + * root at beginning of second pass and then propagated down. But + * it may also be set earlier for subtrees with lo==0 (the left + * spine of tree). SUMMED is a one bit join count. For leafs, it + * is set when summed. For internal nodes, it becomes true when + * one child is summed. When the second child finishes summing, + * we then moves up tree to trigger the cumulate phase. FINISHED + * is also a one bit join count. For leafs, it is set when + * cumulated. For internal nodes, it becomes true when one child + * is cumulated. When the second child finishes cumulating, it + * then moves up tree, completing at the root. + * + * To better exploit locality and reduce overhead, the compute + * method loops starting with the current task, moving if possible + * to one of its subtasks rather than forking. + * + * As usual for this sort of utility, there are 4 versions, that + * are simple copy/paste/adapt variants of each other. (The + * double and int versions differ from long version solely by + * replacing "long" (with case-matching)). + */ + + // see above + static final int CUMULATE = 1; + static final int SUMMED = 2; + static final int FINISHED = 4; + + /** The smallest subtask array partition size to use as threshold */ + static final int MIN_PARTITION = 16; + + static final class CumulateTask extends CountedCompleter { + final T[] array; + final BinaryOperator function; + CumulateTask left, right; + T in, out; + final int lo, hi, origin, fence, threshold; + + /** Root task constructor */ + public CumulateTask(CumulateTask parent, + BinaryOperator function, + T[] array, int lo, int hi) { + super(parent); + this.function = function; this.array = array; + this.lo = this.origin = lo; this.hi = this.fence = hi; + int p; + this.threshold = + (p = (hi - lo) / (ForkJoinPool.getCommonPoolParallelism() << 3)) + <= MIN_PARTITION ? MIN_PARTITION : p; + } + + /** Subtask constructor */ + CumulateTask(CumulateTask parent, BinaryOperator function, + T[] array, int origin, int fence, int threshold, + int lo, int hi) { + super(parent); + this.function = function; this.array = array; + this.origin = origin; this.fence = fence; + this.threshold = threshold; + this.lo = lo; this.hi = hi; + } + + public final void compute() { + final BinaryOperator fn; + final T[] a; + if ((fn = this.function) == null || (a = this.array) == null) + throw new NullPointerException(); // hoist checks + int th = threshold, org = origin, fnc = fence, l, h; + CumulateTask t = this; + outer: while ((l = t.lo) >= 0 && (h = t.hi) <= a.length) { + if (h - l > th) { + CumulateTask lt = t.left, rt = t.right, f; + if (lt == null) { // first pass + int mid = (l + h) >>> 1; + f = rt = t.right = + new CumulateTask(t, fn, a, org, fnc, th, mid, h); + t = lt = t.left = + new CumulateTask(t, fn, a, org, fnc, th, l, mid); + } + else { // possibly refork + T pin = t.in; + lt.in = pin; + f = t = null; + if (rt != null) { + T lout = lt.out; + rt.in = (l == org ? lout : + fn.apply(pin, lout)); + for (int c;;) { + if (((c = rt.getPendingCount()) & CUMULATE) != 0) + break; + if (rt.compareAndSetPendingCount(c, c|CUMULATE)){ + t = rt; + break; + } + } + } + for (int c;;) { + if (((c = lt.getPendingCount()) & CUMULATE) != 0) + break; + if (lt.compareAndSetPendingCount(c, c|CUMULATE)) { + if (t != null) + f = t; + t = lt; + break; + } + } + if (t == null) + break; + } + if (f != null) + f.fork(); + } + else { + int state; // Transition to sum, cumulate, or both + for (int b;;) { + if (((b = t.getPendingCount()) & FINISHED) != 0) + break outer; // already done + state = ((b & CUMULATE) != 0 ? FINISHED : + (l > org) ? SUMMED : (SUMMED|FINISHED)); + if (t.compareAndSetPendingCount(b, b|state)) + break; + } + + T sum; + if (state != SUMMED) { + int first; + if (l == org) { // leftmost; no in + sum = a[org]; + first = org + 1; + } + else { + sum = t.in; + first = l; + } + for (int i = first; i < h; ++i) // cumulate + a[i] = sum = fn.apply(sum, a[i]); + } + else if (h < fnc) { // skip rightmost + sum = a[l]; + for (int i = l + 1; i < h; ++i) // sum only + sum = fn.apply(sum, a[i]); + } + else + sum = t.in; + t.out = sum; + for (CumulateTask par;;) { // propagate + @SuppressWarnings("unchecked") CumulateTask partmp + = (CumulateTask)t.getCompleter(); + if ((par = partmp) == null) { + if ((state & FINISHED) != 0) // enable join + t.quietlyComplete(); + break outer; + } + int b = par.getPendingCount(); + if ((b & state & FINISHED) != 0) + t = par; // both done + else if ((b & state & SUMMED) != 0) { // both summed + int nextState; CumulateTask lt, rt; + if ((lt = par.left) != null && + (rt = par.right) != null) { + T lout = lt.out; + par.out = (rt.hi == fnc ? lout : + fn.apply(lout, rt.out)); + } + int refork = (((b & CUMULATE) == 0 && + par.lo == org) ? CUMULATE : 0); + if ((nextState = b|state|refork) == b || + par.compareAndSetPendingCount(b, nextState)) { + state = SUMMED; // drop finished + t = par; + if (refork != 0) + par.fork(); + } + } + else if (par.compareAndSetPendingCount(b, b|state)) + break outer; // sib not ready + } + } + } + } + private static final long serialVersionUID = 5293554502939613543L; + } + + static final class LongCumulateTask extends CountedCompleter { + final long[] array; + final LongBinaryOperator function; + LongCumulateTask left, right; + long in, out; + final int lo, hi, origin, fence, threshold; + + /** Root task constructor */ + public LongCumulateTask(LongCumulateTask parent, + LongBinaryOperator function, + long[] array, int lo, int hi) { + super(parent); + this.function = function; this.array = array; + this.lo = this.origin = lo; this.hi = this.fence = hi; + int p; + this.threshold = + (p = (hi - lo) / (ForkJoinPool.getCommonPoolParallelism() << 3)) + <= MIN_PARTITION ? MIN_PARTITION : p; + } + + /** Subtask constructor */ + LongCumulateTask(LongCumulateTask parent, LongBinaryOperator function, + long[] array, int origin, int fence, int threshold, + int lo, int hi) { + super(parent); + this.function = function; this.array = array; + this.origin = origin; this.fence = fence; + this.threshold = threshold; + this.lo = lo; this.hi = hi; + } + + public final void compute() { + final LongBinaryOperator fn; + final long[] a; + if ((fn = this.function) == null || (a = this.array) == null) + throw new NullPointerException(); // hoist checks + int th = threshold, org = origin, fnc = fence, l, h; + LongCumulateTask t = this; + outer: while ((l = t.lo) >= 0 && (h = t.hi) <= a.length) { + if (h - l > th) { + LongCumulateTask lt = t.left, rt = t.right, f; + if (lt == null) { // first pass + int mid = (l + h) >>> 1; + f = rt = t.right = + new LongCumulateTask(t, fn, a, org, fnc, th, mid, h); + t = lt = t.left = + new LongCumulateTask(t, fn, a, org, fnc, th, l, mid); + } + else { // possibly refork + long pin = t.in; + lt.in = pin; + f = t = null; + if (rt != null) { + long lout = lt.out; + rt.in = (l == org ? lout : + fn.applyAsLong(pin, lout)); + for (int c;;) { + if (((c = rt.getPendingCount()) & CUMULATE) != 0) + break; + if (rt.compareAndSetPendingCount(c, c|CUMULATE)){ + t = rt; + break; + } + } + } + for (int c;;) { + if (((c = lt.getPendingCount()) & CUMULATE) != 0) + break; + if (lt.compareAndSetPendingCount(c, c|CUMULATE)) { + if (t != null) + f = t; + t = lt; + break; + } + } + if (t == null) + break; + } + if (f != null) + f.fork(); + } + else { + int state; // Transition to sum, cumulate, or both + for (int b;;) { + if (((b = t.getPendingCount()) & FINISHED) != 0) + break outer; // already done + state = ((b & CUMULATE) != 0 ? FINISHED : + (l > org) ? SUMMED : (SUMMED|FINISHED)); + if (t.compareAndSetPendingCount(b, b|state)) + break; + } + + long sum; + if (state != SUMMED) { + int first; + if (l == org) { // leftmost; no in + sum = a[org]; + first = org + 1; + } + else { + sum = t.in; + first = l; + } + for (int i = first; i < h; ++i) // cumulate + a[i] = sum = fn.applyAsLong(sum, a[i]); + } + else if (h < fnc) { // skip rightmost + sum = a[l]; + for (int i = l + 1; i < h; ++i) // sum only + sum = fn.applyAsLong(sum, a[i]); + } + else + sum = t.in; + t.out = sum; + for (LongCumulateTask par;;) { // propagate + if ((par = (LongCumulateTask)t.getCompleter()) == null) { + if ((state & FINISHED) != 0) // enable join + t.quietlyComplete(); + break outer; + } + int b = par.getPendingCount(); + if ((b & state & FINISHED) != 0) + t = par; // both done + else if ((b & state & SUMMED) != 0) { // both summed + int nextState; LongCumulateTask lt, rt; + if ((lt = par.left) != null && + (rt = par.right) != null) { + long lout = lt.out; + par.out = (rt.hi == fnc ? lout : + fn.applyAsLong(lout, rt.out)); + } + int refork = (((b & CUMULATE) == 0 && + par.lo == org) ? CUMULATE : 0); + if ((nextState = b|state|refork) == b || + par.compareAndSetPendingCount(b, nextState)) { + state = SUMMED; // drop finished + t = par; + if (refork != 0) + par.fork(); + } + } + else if (par.compareAndSetPendingCount(b, b|state)) + break outer; // sib not ready + } + } + } + } + private static final long serialVersionUID = -5074099945909284273L; + } + + static final class DoubleCumulateTask extends CountedCompleter { + final double[] array; + final DoubleBinaryOperator function; + DoubleCumulateTask left, right; + double in, out; + final int lo, hi, origin, fence, threshold; + + /** Root task constructor */ + public DoubleCumulateTask(DoubleCumulateTask parent, + DoubleBinaryOperator function, + double[] array, int lo, int hi) { + super(parent); + this.function = function; this.array = array; + this.lo = this.origin = lo; this.hi = this.fence = hi; + int p; + this.threshold = + (p = (hi - lo) / (ForkJoinPool.getCommonPoolParallelism() << 3)) + <= MIN_PARTITION ? MIN_PARTITION : p; + } + + /** Subtask constructor */ + DoubleCumulateTask(DoubleCumulateTask parent, DoubleBinaryOperator function, + double[] array, int origin, int fence, int threshold, + int lo, int hi) { + super(parent); + this.function = function; this.array = array; + this.origin = origin; this.fence = fence; + this.threshold = threshold; + this.lo = lo; this.hi = hi; + } + + public final void compute() { + final DoubleBinaryOperator fn; + final double[] a; + if ((fn = this.function) == null || (a = this.array) == null) + throw new NullPointerException(); // hoist checks + int th = threshold, org = origin, fnc = fence, l, h; + DoubleCumulateTask t = this; + outer: while ((l = t.lo) >= 0 && (h = t.hi) <= a.length) { + if (h - l > th) { + DoubleCumulateTask lt = t.left, rt = t.right, f; + if (lt == null) { // first pass + int mid = (l + h) >>> 1; + f = rt = t.right = + new DoubleCumulateTask(t, fn, a, org, fnc, th, mid, h); + t = lt = t.left = + new DoubleCumulateTask(t, fn, a, org, fnc, th, l, mid); + } + else { // possibly refork + double pin = t.in; + lt.in = pin; + f = t = null; + if (rt != null) { + double lout = lt.out; + rt.in = (l == org ? lout : + fn.applyAsDouble(pin, lout)); + for (int c;;) { + if (((c = rt.getPendingCount()) & CUMULATE) != 0) + break; + if (rt.compareAndSetPendingCount(c, c|CUMULATE)){ + t = rt; + break; + } + } + } + for (int c;;) { + if (((c = lt.getPendingCount()) & CUMULATE) != 0) + break; + if (lt.compareAndSetPendingCount(c, c|CUMULATE)) { + if (t != null) + f = t; + t = lt; + break; + } + } + if (t == null) + break; + } + if (f != null) + f.fork(); + } + else { + int state; // Transition to sum, cumulate, or both + for (int b;;) { + if (((b = t.getPendingCount()) & FINISHED) != 0) + break outer; // already done + state = ((b & CUMULATE) != 0 ? FINISHED : + (l > org) ? SUMMED : (SUMMED|FINISHED)); + if (t.compareAndSetPendingCount(b, b|state)) + break; + } + + double sum; + if (state != SUMMED) { + int first; + if (l == org) { // leftmost; no in + sum = a[org]; + first = org + 1; + } + else { + sum = t.in; + first = l; + } + for (int i = first; i < h; ++i) // cumulate + a[i] = sum = fn.applyAsDouble(sum, a[i]); + } + else if (h < fnc) { // skip rightmost + sum = a[l]; + for (int i = l + 1; i < h; ++i) // sum only + sum = fn.applyAsDouble(sum, a[i]); + } + else + sum = t.in; + t.out = sum; + for (DoubleCumulateTask par;;) { // propagate + if ((par = (DoubleCumulateTask)t.getCompleter()) == null) { + if ((state & FINISHED) != 0) // enable join + t.quietlyComplete(); + break outer; + } + int b = par.getPendingCount(); + if ((b & state & FINISHED) != 0) + t = par; // both done + else if ((b & state & SUMMED) != 0) { // both summed + int nextState; DoubleCumulateTask lt, rt; + if ((lt = par.left) != null && + (rt = par.right) != null) { + double lout = lt.out; + par.out = (rt.hi == fnc ? lout : + fn.applyAsDouble(lout, rt.out)); + } + int refork = (((b & CUMULATE) == 0 && + par.lo == org) ? CUMULATE : 0); + if ((nextState = b|state|refork) == b || + par.compareAndSetPendingCount(b, nextState)) { + state = SUMMED; // drop finished + t = par; + if (refork != 0) + par.fork(); + } + } + else if (par.compareAndSetPendingCount(b, b|state)) + break outer; // sib not ready + } + } + } + } + private static final long serialVersionUID = -586947823794232033L; + } + + static final class IntCumulateTask extends CountedCompleter { + final int[] array; + final IntBinaryOperator function; + IntCumulateTask left, right; + int in, out; + final int lo, hi, origin, fence, threshold; + + /** Root task constructor */ + public IntCumulateTask(IntCumulateTask parent, + IntBinaryOperator function, + int[] array, int lo, int hi) { + super(parent); + this.function = function; this.array = array; + this.lo = this.origin = lo; this.hi = this.fence = hi; + int p; + this.threshold = + (p = (hi - lo) / (ForkJoinPool.getCommonPoolParallelism() << 3)) + <= MIN_PARTITION ? MIN_PARTITION : p; + } + + /** Subtask constructor */ + IntCumulateTask(IntCumulateTask parent, IntBinaryOperator function, + int[] array, int origin, int fence, int threshold, + int lo, int hi) { + super(parent); + this.function = function; this.array = array; + this.origin = origin; this.fence = fence; + this.threshold = threshold; + this.lo = lo; this.hi = hi; + } + + public final void compute() { + final IntBinaryOperator fn; + final int[] a; + if ((fn = this.function) == null || (a = this.array) == null) + throw new NullPointerException(); // hoist checks + int th = threshold, org = origin, fnc = fence, l, h; + IntCumulateTask t = this; + outer: while ((l = t.lo) >= 0 && (h = t.hi) <= a.length) { + if (h - l > th) { + IntCumulateTask lt = t.left, rt = t.right, f; + if (lt == null) { // first pass + int mid = (l + h) >>> 1; + f = rt = t.right = + new IntCumulateTask(t, fn, a, org, fnc, th, mid, h); + t = lt = t.left = + new IntCumulateTask(t, fn, a, org, fnc, th, l, mid); + } + else { // possibly refork + int pin = t.in; + lt.in = pin; + f = t = null; + if (rt != null) { + int lout = lt.out; + rt.in = (l == org ? lout : + fn.applyAsInt(pin, lout)); + for (int c;;) { + if (((c = rt.getPendingCount()) & CUMULATE) != 0) + break; + if (rt.compareAndSetPendingCount(c, c|CUMULATE)){ + t = rt; + break; + } + } + } + for (int c;;) { + if (((c = lt.getPendingCount()) & CUMULATE) != 0) + break; + if (lt.compareAndSetPendingCount(c, c|CUMULATE)) { + if (t != null) + f = t; + t = lt; + break; + } + } + if (t == null) + break; + } + if (f != null) + f.fork(); + } + else { + int state; // Transition to sum, cumulate, or both + for (int b;;) { + if (((b = t.getPendingCount()) & FINISHED) != 0) + break outer; // already done + state = ((b & CUMULATE) != 0 ? FINISHED : + (l > org) ? SUMMED : (SUMMED|FINISHED)); + if (t.compareAndSetPendingCount(b, b|state)) + break; + } + + int sum; + if (state != SUMMED) { + int first; + if (l == org) { // leftmost; no in + sum = a[org]; + first = org + 1; + } + else { + sum = t.in; + first = l; + } + for (int i = first; i < h; ++i) // cumulate + a[i] = sum = fn.applyAsInt(sum, a[i]); + } + else if (h < fnc) { // skip rightmost + sum = a[l]; + for (int i = l + 1; i < h; ++i) // sum only + sum = fn.applyAsInt(sum, a[i]); + } + else + sum = t.in; + t.out = sum; + for (IntCumulateTask par;;) { // propagate + if ((par = (IntCumulateTask)t.getCompleter()) == null) { + if ((state & FINISHED) != 0) // enable join + t.quietlyComplete(); + break outer; + } + int b = par.getPendingCount(); + if ((b & state & FINISHED) != 0) + t = par; // both done + else if ((b & state & SUMMED) != 0) { // both summed + int nextState; IntCumulateTask lt, rt; + if ((lt = par.left) != null && + (rt = par.right) != null) { + int lout = lt.out; + par.out = (rt.hi == fnc ? lout : + fn.applyAsInt(lout, rt.out)); + } + int refork = (((b & CUMULATE) == 0 && + par.lo == org) ? CUMULATE : 0); + if ((nextState = b|state|refork) == b || + par.compareAndSetPendingCount(b, nextState)) { + state = SUMMED; // drop finished + t = par; + if (refork != 0) + par.fork(); + } + } + else if (par.compareAndSetPendingCount(b, b|state)) + break outer; // sib not ready + } + } + } + } + private static final long serialVersionUID = 3731755594596840961L; + } +} diff --git a/ojluni/src/main/java/java/util/Deque.java b/ojluni/src/main/java/java/util/Deque.java index f2306773e..9fec73b51 100644 --- a/ojluni/src/main/java/java/util/Deque.java +++ b/ojluni/src/main/java/java/util/Deque.java @@ -59,7 +59,6 @@ *

            The twelve methods described above are summarized in the * following table: * - *

            *

  • Summary of BlockingQueue methods
    * * @@ -103,7 +102,6 @@ * inherited from the {@code Queue} interface are precisely equivalent to * {@code Deque} methods as indicated in the following table: * - *

    *

    Summary of Deque methods
    * * @@ -142,7 +140,6 @@ * beginning of the deque. Stack methods are precisely equivalent to * {@code Deque} methods as indicated in the table below: * - *

    *

    Comparison of Queue and Deque methods
    * * @@ -190,7 +187,7 @@ * @author Doug Lea * @author Josh Bloch * @since 1.6 - * @param the type of elements held in this collection + * @param the type of elements held in this deque */ public interface Deque extends Queue { /** @@ -346,17 +343,18 @@ public interface Deque extends Queue { * Removes the first occurrence of the specified element from this deque. * If the deque does not contain the element, it is unchanged. * More formally, removes the first element {@code e} such that - * (o==null ? e==null : o.equals(e)) - * (if such an element exists). + * {@code Objects.equals(o, e)} (if such an element exists). * Returns {@code true} if this deque contained the specified element * (or equivalently, if this deque changed as a result of the call). * * @param o element to be removed from this deque, if present * @return {@code true} if an element was removed as a result of this call * @throws ClassCastException if the class of the specified element - * is incompatible with this deque (optional) + * is incompatible with this deque + * (optional) * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements (optional) + * deque does not permit null elements + * (optional) */ boolean removeFirstOccurrence(Object o); @@ -364,17 +362,18 @@ public interface Deque extends Queue { * Removes the last occurrence of the specified element from this deque. * If the deque does not contain the element, it is unchanged. * More formally, removes the last element {@code e} such that - * (o==null ? e==null : o.equals(e)) - * (if such an element exists). + * {@code Objects.equals(o, e)} (if such an element exists). * Returns {@code true} if this deque contained the specified element * (or equivalently, if this deque changed as a result of the call). * * @param o element to be removed from this deque, if present * @return {@code true} if an element was removed as a result of this call * @throws ClassCastException if the class of the specified element - * is incompatible with this deque (optional) + * is incompatible with this deque + * (optional) * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements (optional) + * deque does not permit null elements + * (optional) */ boolean removeLastOccurrence(Object o); @@ -519,8 +518,7 @@ public interface Deque extends Queue { * Removes the first occurrence of the specified element from this deque. * If the deque does not contain the element, it is unchanged. * More formally, removes the first element {@code e} such that - * (o==null ? e==null : o.equals(e)) - * (if such an element exists). + * {@code Objects.equals(o, e)} (if such an element exists). * Returns {@code true} if this deque contained the specified element * (or equivalently, if this deque changed as a result of the call). * @@ -529,24 +527,27 @@ public interface Deque extends Queue { * @param o element to be removed from this deque, if present * @return {@code true} if an element was removed as a result of this call * @throws ClassCastException if the class of the specified element - * is incompatible with this deque (optional) + * is incompatible with this deque + * (optional) * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements (optional) + * deque does not permit null elements + * (optional) */ boolean remove(Object o); /** * Returns {@code true} if this deque contains the specified element. * More formally, returns {@code true} if and only if this deque contains - * at least one element {@code e} such that - * (o==null ? e==null : o.equals(e)). + * at least one element {@code e} such that {@code Objects.equals(o, e)}. * * @param o element whose presence in this deque is to be tested * @return {@code true} if this deque contains the specified element - * @throws ClassCastException if the type of the specified element - * is incompatible with this deque (optional) + * @throws ClassCastException if the class of the specified element + * is incompatible with this deque + * (optional) * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements (optional) + * deque does not permit null elements + * (optional) */ boolean contains(Object o); @@ -555,7 +556,7 @@ public interface Deque extends Queue { * * @return the number of elements in this deque */ - public int size(); + int size(); /** * Returns an iterator over the elements in this deque in proper sequence. diff --git a/ojluni/src/main/java/java/util/Map.java b/ojluni/src/main/java/java/util/Map.java old mode 100755 new mode 100644 index f59b5a9ba..74ba7012c --- a/ojluni/src/main/java/java/util/Map.java +++ b/ojluni/src/main/java/java/util/Map.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,40 +25,43 @@ package java.util; - -import java.io.Serializable; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; +import java.io.Serializable; +// BEGIN android-note +// removed link to collections framework docs +// removed java 9 methods +// END android-note /** * An object that maps keys to values. A map cannot contain duplicate keys; * each key can map to at most one value. * - *

    This interface takes the place of the Dictionary class, which + *

    This interface takes the place of the {@code Dictionary} class, which * was a totally abstract class rather than an interface. * - *

    The Map interface provides three collection views, which + *

    The {@code Map} interface provides three collection views, which * allow a map's contents to be viewed as a set of keys, collection of values, * or set of key-value mappings. The order of a map is defined as * the order in which the iterators on the map's collection views return their - * elements. Some map implementations, like the TreeMap class, make - * specific guarantees as to their order; others, like the HashMap + * elements. Some map implementations, like the {@code TreeMap} class, make + * specific guarantees as to their order; others, like the {@code HashMap} * class, do not. * *

    Note: great care must be exercised if mutable objects are used as map * keys. The behavior of a map is not specified if the value of an object is - * changed in a manner that affects equals comparisons while the + * changed in a manner that affects {@code equals} comparisons while the * object is a key in the map. A special case of this prohibition is that it * is not permissible for a map to contain itself as a key. While it is * permissible for a map to contain itself as a value, extreme caution is - * advised: the equals and hashCode methods are no longer + * advised: the {@code equals} and {@code hashCode} methods are no longer * well defined on such a map. * *

    All general-purpose map implementation classes should provide two * "standard" constructors: a void (no arguments) constructor which creates an - * empty map, and a constructor with a single argument of type Map, + * empty map, and a constructor with a single argument of type {@code Map}, * which creates a new map with the same key-value mappings as its argument. * In effect, the latter constructor allows the user to copy any map, * producing an equivalent map of the desired class. There is no way to @@ -67,9 +70,9 @@ * *

    The "destructive" methods contained in this interface, that is, the * methods that modify the map on which they operate, are specified to throw - * UnsupportedOperationException if this map does not support the + * {@code UnsupportedOperationException} if this map does not support the * operation. If this is the case, these methods may, but are not required - * to, throw an UnsupportedOperationException if the invocation would + * to, throw an {@code UnsupportedOperationException} if the invocation would * have no effect on the map. For example, invoking the {@link #putAll(Map)} * method on an unmodifiable map may, but is not required to, throw the * exception if the map whose mappings are to be "superimposed" is empty. @@ -78,7 +81,7 @@ * may contain. For example, some implementations prohibit null keys and * values, and some have restrictions on the types of their keys. Attempting * to insert an ineligible key or value throws an unchecked exception, - * typically NullPointerException or ClassCastException. + * typically {@code NullPointerException} or {@code ClassCastException}. * Attempting to query the presence of an ineligible key or value may throw an * exception, or it may simply return false; some implementations will exhibit * the former behavior and some will exhibit the latter. More generally, @@ -88,20 +91,16 @@ * Such exceptions are marked as "optional" in the specification for this * interface. * - *

    This interface is a member of the - * - * Java Collections Framework. - * *

    Many methods in Collections Framework interfaces are defined * in terms of the {@link Object#equals(Object) equals} method. For * example, the specification for the {@link #containsKey(Object) - * containsKey(Object key)} method says: "returns true if and - * only if this map contains a mapping for a key k such that - * (key==null ? k==null : key.equals(k))." This specification should - * not be construed to imply that invoking Map.containsKey - * with a non-null argument key will cause key.equals(k) to - * be invoked for any key k. Implementations are free to - * implement optimizations whereby the equals invocation is avoided, + * containsKey(Object key)} method says: "returns {@code true} if and + * only if this map contains a mapping for a key {@code k} such that + * {@code (key==null ? k==null : key.equals(k))}." This specification should + * not be construed to imply that invoking {@code Map.containsKey} + * with a non-null argument {@code key} will cause {@code key.equals(k)} to + * be invoked for any key {@code k}. Implementations are free to + * implement optimizations whereby the {@code equals} invocation is avoided, * for example, by first comparing the hash codes of the two keys. (The * {@link Object#hashCode()} specification guarantees that two objects with * unequal hash codes cannot be equal.) More generally, implementations of @@ -109,6 +108,13 @@ * the specified behavior of underlying {@link Object} methods wherever the * implementor deems it appropriate. * + *

    Some map operations which perform recursive traversal of the map may fail + * with an exception for self-referential instances where the map directly or + * indirectly contains itself. This includes the {@code clone()}, + * {@code equals()}, {@code hashCode()} and {@code toString()} methods. + * Implementations may optionally handle the self-referential scenario, however + * most current implementations do not do so. + * * @param the type of keys maintained by this map * @param the type of mapped values * @@ -121,34 +127,34 @@ * @see Set * @since 1.2 */ -public interface Map { +public interface Map { // Query Operations /** * Returns the number of key-value mappings in this map. If the - * map contains more than Integer.MAX_VALUE elements, returns - * Integer.MAX_VALUE. + * map contains more than {@code Integer.MAX_VALUE} elements, returns + * {@code Integer.MAX_VALUE}. * * @return the number of key-value mappings in this map */ int size(); /** - * Returns true if this map contains no key-value mappings. + * Returns {@code true} if this map contains no key-value mappings. * - * @return true if this map contains no key-value mappings + * @return {@code true} if this map contains no key-value mappings */ boolean isEmpty(); /** - * Returns true if this map contains a mapping for the specified - * key. More formally, returns true if and only if - * this map contains a mapping for a key k such that - * (key==null ? k==null : key.equals(k)). (There can be + * Returns {@code true} if this map contains a mapping for the specified + * key. More formally, returns {@code true} if and only if + * this map contains a mapping for a key {@code k} such that + * {@code Objects.equals(key, k)}. (There can be * at most one such mapping.) * * @param key key whose presence in this map is to be tested - * @return true if this map contains a mapping for the specified + * @return {@code true} if this map contains a mapping for the specified * key * @throws ClassCastException if the key is of an inappropriate type for * this map @@ -160,15 +166,15 @@ public interface Map { boolean containsKey(Object key); /** - * Returns true if this map maps one or more keys to the - * specified value. More formally, returns true if and only if - * this map contains at least one mapping to a value v such that - * (value==null ? v==null : value.equals(v)). This operation + * Returns {@code true} if this map maps one or more keys to the + * specified value. More formally, returns {@code true} if and only if + * this map contains at least one mapping to a value {@code v} such that + * {@code Objects.equals(value, v)}. This operation * will probably require time linear in the map size for most - * implementations of the Map interface. + * implementations of the {@code Map} interface. * * @param value value whose presence in this map is to be tested - * @return true if this map maps one or more keys to the + * @return {@code true} if this map maps one or more keys to the * specified value * @throws ClassCastException if the value is of an inappropriate type for * this map @@ -184,8 +190,9 @@ public interface Map { * or {@code null} if this map contains no mapping for the key. * *

    More formally, if this map contains a mapping from a key - * {@code k} to a value {@code v} such that {@code (key==null ? k==null : - * key.equals(k))}, then this method returns {@code v}; otherwise + * {@code k} to a value {@code v} such that + * {@code Objects.equals(key, k)}, + * then this method returns {@code v}; otherwise * it returns {@code null}. (There can be at most one such mapping.) * *

    If this map permits null values, then a return value of @@ -212,18 +219,18 @@ public interface Map { * Associates the specified value with the specified key in this map * (optional operation). If the map previously contained a mapping for * the key, the old value is replaced by the specified value. (A map - * m is said to contain a mapping for a key k if and only + * {@code m} is said to contain a mapping for a key {@code k} if and only * if {@link #containsKey(Object) m.containsKey(k)} would return - * true.) + * {@code true}.) * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key - * @return the previous value associated with key, or - * null if there was no mapping for key. - * (A null return can also indicate that the map - * previously associated null with key, - * if the implementation supports null values.) - * @throws UnsupportedOperationException if the put operation + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key}. + * (A {@code null} return can also indicate that the map + * previously associated {@code null} with {@code key}, + * if the implementation supports {@code null} values.) + * @throws UnsupportedOperationException if the {@code put} operation * is not supported by this map * @throws ClassCastException if the class of the specified key or value * prevents it from being stored in this map @@ -237,25 +244,25 @@ public interface Map { /** * Removes the mapping for a key from this map if it is present * (optional operation). More formally, if this map contains a mapping - * from key k to value v such that - * (key==null ? k==null : key.equals(k)), that mapping + * from key {@code k} to value {@code v} such that + * {@code Objects.equals(key, k)}, that mapping * is removed. (The map can contain at most one such mapping.) * *

    Returns the value to which this map previously associated the key, - * or null if the map contained no mapping for the key. + * or {@code null} if the map contained no mapping for the key. * *

    If this map permits null values, then a return value of - * null does not necessarily indicate that the map + * {@code null} does not necessarily indicate that the map * contained no mapping for the key; it's also possible that the map - * explicitly mapped the key to null. + * explicitly mapped the key to {@code null}. * *

    The map will not contain a mapping for the specified key once the * call returns. * * @param key key whose mapping is to be removed from the map - * @return the previous value associated with key, or - * null if there was no mapping for key. - * @throws UnsupportedOperationException if the remove operation + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key}. + * @throws UnsupportedOperationException if the {@code remove} operation * is not supported by this map * @throws ClassCastException if the key is of an inappropriate type for * this map @@ -273,12 +280,12 @@ public interface Map { * Copies all of the mappings from the specified map to this map * (optional operation). The effect of this call is equivalent to that * of calling {@link #put(Object,Object) put(k, v)} on this map once - * for each mapping from key k to value v in the + * for each mapping from key {@code k} to value {@code v} in the * specified map. The behavior of this operation is undefined if the * specified map is modified while the operation is in progress. * * @param m mappings to be stored in this map - * @throws UnsupportedOperationException if the putAll operation + * @throws UnsupportedOperationException if the {@code putAll} operation * is not supported by this map * @throws ClassCastException if the class of a key or value in the * specified map prevents it from being stored in this map @@ -294,7 +301,7 @@ public interface Map { * Removes all of the mappings from this map (optional operation). * The map will be empty after this call returns. * - * @throws UnsupportedOperationException if the clear operation + * @throws UnsupportedOperationException if the {@code clear} operation * is not supported by this map */ void clear(); @@ -307,12 +314,12 @@ public interface Map { * The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa. If the map is modified * while an iteration over the set is in progress (except through - * the iterator's own remove operation), the results of + * the iterator's own {@code remove} operation), the results of * the iteration are undefined. The set supports element removal, * which removes the corresponding mapping from the map, via the - * Iterator.remove, Set.remove, - * removeAll, retainAll, and clear - * operations. It does not support the add or addAll + * {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} + * operations. It does not support the {@code add} or {@code addAll} * operations. * * @return a set view of the keys contained in this map @@ -324,13 +331,13 @@ public interface Map { * The collection is backed by the map, so changes to the map are * reflected in the collection, and vice-versa. If the map is * modified while an iteration over the collection is in progress - * (except through the iterator's own remove operation), + * (except through the iterator's own {@code remove} operation), * the results of the iteration are undefined. The collection * supports element removal, which removes the corresponding - * mapping from the map, via the Iterator.remove, - * Collection.remove, removeAll, - * retainAll and clear operations. It does not - * support the add or addAll operations. + * mapping from the map, via the {@code Iterator.remove}, + * {@code Collection.remove}, {@code removeAll}, + * {@code retainAll} and {@code clear} operations. It does not + * support the {@code add} or {@code addAll} operations. * * @return a collection view of the values contained in this map */ @@ -341,33 +348,33 @@ public interface Map { * The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa. If the map is modified * while an iteration over the set is in progress (except through - * the iterator's own remove operation, or through the - * setValue operation on a map entry returned by the + * the iterator's own {@code remove} operation, or through the + * {@code setValue} operation on a map entry returned by the * iterator) the results of the iteration are undefined. The set * supports element removal, which removes the corresponding - * mapping from the map, via the Iterator.remove, - * Set.remove, removeAll, retainAll and - * clear operations. It does not support the - * add or addAll operations. + * mapping from the map, via the {@code Iterator.remove}, + * {@code Set.remove}, {@code removeAll}, {@code retainAll} and + * {@code clear} operations. It does not support the + * {@code add} or {@code addAll} operations. * * @return a set view of the mappings contained in this map */ Set> entrySet(); /** - * A map entry (key-value pair). The Map.entrySet method returns + * A map entry (key-value pair). The {@code Map.entrySet} method returns * a collection-view of the map, whose elements are of this class. The * only way to obtain a reference to a map entry is from the - * iterator of this collection-view. These Map.Entry objects are + * iterator of this collection-view. These {@code Map.Entry} objects are * valid only for the duration of the iteration; more formally, * the behavior of a map entry is undefined if the backing map has been * modified after the entry was returned by the iterator, except through - * the setValue operation on the map entry. + * the {@code setValue} operation on the map entry. * * @see Map#entrySet() * @since 1.2 */ - interface Entry { + interface Entry { /** * Returns the key corresponding to this entry. * @@ -381,7 +388,7 @@ interface Entry { /** * Returns the value corresponding to this entry. If the mapping * has been removed from the backing map (by the iterator's - * remove operation), the results of this call are undefined. + * {@code remove} operation), the results of this call are undefined. * * @return the value corresponding to this entry * @throws IllegalStateException implementations may, but are not @@ -394,11 +401,11 @@ interface Entry { * Replaces the value corresponding to this entry with the specified * value (optional operation). (Writes through to the map.) The * behavior of this call is undefined if the mapping has already been - * removed from the map (by the iterator's remove operation). + * removed from the map (by the iterator's {@code remove} operation). * * @param value new value to be stored in this entry * @return old value corresponding to the entry - * @throws UnsupportedOperationException if the put operation + * @throws UnsupportedOperationException if the {@code put} operation * is not supported by the backing map * @throws ClassCastException if the class of the specified value * prevents it from being stored in the backing map @@ -414,34 +421,34 @@ interface Entry { /** * Compares the specified object with this entry for equality. - * Returns true if the given object is also a map entry and + * Returns {@code true} if the given object is also a map entry and * the two entries represent the same mapping. More formally, two - * entries e1 and e2 represent the same mapping + * entries {@code e1} and {@code e2} represent the same mapping * if

              *     (e1.getKey()==null ?
              *      e2.getKey()==null : e1.getKey().equals(e2.getKey()))  &&
              *     (e1.getValue()==null ?
              *      e2.getValue()==null : e1.getValue().equals(e2.getValue()))
              * 
    - * This ensures that the equals method works properly across - * different implementations of the Map.Entry interface. + * This ensures that the {@code equals} method works properly across + * different implementations of the {@code Map.Entry} interface. * * @param o object to be compared for equality with this map entry - * @return true if the specified object is equal to this map + * @return {@code true} if the specified object is equal to this map * entry */ boolean equals(Object o); /** * Returns the hash code value for this map entry. The hash code - * of a map entry e is defined to be:
    +         * of a map entry {@code e} is defined to be: 
              *     (e.getKey()==null   ? 0 : e.getKey().hashCode()) ^
              *     (e.getValue()==null ? 0 : e.getValue().hashCode())
              * 
    - * This ensures that e1.equals(e2) implies that - * e1.hashCode()==e2.hashCode() for any two Entries - * e1 and e2, as required by the general - * contract of Object.hashCode. + * This ensures that {@code e1.equals(e2)} implies that + * {@code e1.hashCode()==e2.hashCode()} for any two Entries + * {@code e1} and {@code e2}, as required by the general + * contract of {@code Object.hashCode}. * * @return the hash code value for this map entry * @see Object#hashCode() @@ -462,11 +469,28 @@ interface Entry { * @see Comparable * @since 1.8 */ - public static , V> Comparator> comparingByKey() { + public static , V> Comparator> comparingByKey() { return (Comparator> & Serializable) (c1, c2) -> c1.getKey().compareTo(c2.getKey()); } + /** + * Returns a comparator that compares {@link Map.Entry} in natural order on value. + * + *

    The returned comparator is serializable and throws {@link + * NullPointerException} when comparing an entry with null values. + * + * @param the type of the map keys + * @param the {@link Comparable} type of the map values + * @return a comparator that compares {@link Map.Entry} in natural order on value. + * @see Comparable + * @since 1.8 + */ + public static > Comparator> comparingByValue() { + return (Comparator> & Serializable) + (c1, c2) -> c1.getValue().compareTo(c2.getValue()); + } + /** * Returns a comparator that compares {@link Map.Entry} by key using the given * {@link Comparator}. @@ -485,30 +509,49 @@ public static Comparator> comparingByKey(Comparator> & Serializable) (c1, c2) -> cmp.compare(c1.getKey(), c2.getKey()); } + + /** + * Returns a comparator that compares {@link Map.Entry} by value using the given + * {@link Comparator}. + * + *

    The returned comparator is serializable if the specified comparator + * is also serializable. + * + * @param the type of the map keys + * @param the type of the map values + * @param cmp the value {@link Comparator} + * @return a comparator that compares {@link Map.Entry} by the value. + * @since 1.8 + */ + public static Comparator> comparingByValue(Comparator cmp) { + Objects.requireNonNull(cmp); + return (Comparator> & Serializable) + (c1, c2) -> cmp.compare(c1.getValue(), c2.getValue()); + } } // Comparison and hashing /** * Compares the specified object with this map for equality. Returns - * true if the given object is also a map and the two maps - * represent the same mappings. More formally, two maps m1 and - * m2 represent the same mappings if - * m1.entrySet().equals(m2.entrySet()). This ensures that the - * equals method works properly across different implementations - * of the Map interface. + * {@code true} if the given object is also a map and the two maps + * represent the same mappings. More formally, two maps {@code m1} and + * {@code m2} represent the same mappings if + * {@code m1.entrySet().equals(m2.entrySet())}. This ensures that the + * {@code equals} method works properly across different implementations + * of the {@code Map} interface. * * @param o object to be compared for equality with this map - * @return true if the specified object is equal to this map + * @return {@code true} if the specified object is equal to this map */ boolean equals(Object o); /** * Returns the hash code value for this map. The hash code of a map is * defined to be the sum of the hash codes of each entry in the map's - * entrySet() view. This ensures that m1.equals(m2) - * implies that m1.hashCode()==m2.hashCode() for any two maps - * m1 and m2, as required by the general contract of + * {@code entrySet()} view. This ensures that {@code m1.equals(m2)} + * implies that {@code m1.hashCode()==m2.hashCode()} for any two maps + * {@code m1} and {@code m2}, as required by the general contract of * {@link Object#hashCode}. * * @return the hash code value for this map @@ -518,6 +561,37 @@ public static Comparator> comparingByKey(Comparatoroptional) + * @throws NullPointerException if the specified key is null and this map + * does not permit null keys + * (optional) + * @since 1.8 + */ + default V getOrDefault(Object key, V defaultValue) { + V v; + return (((v = get(key)) != null) || containsKey(key)) + ? v + : defaultValue; + } + /** * Performs the given action for each entry in this map until all entries * have been processed or the action throws an exception. Unless @@ -551,11 +625,625 @@ default void forEach(BiConsumer action) { try { k = entry.getKey(); v = entry.getValue(); - } catch(IllegalStateException ise) { + } catch (IllegalStateException ise) { // this usually means the entry is no longer in the map. throw new ConcurrentModificationException(ise); } action.accept(k, v); } } + + /** + * Replaces each entry's value with the result of invoking the given + * function on that entry until all entries have been processed or the + * function throws an exception. Exceptions thrown by the function are + * relayed to the caller. + * + * @implSpec + *

    The default implementation is equivalent to, for this {@code map}: + *

     {@code
    +     * for (Map.Entry entry : map.entrySet())
    +     *     entry.setValue(function.apply(entry.getKey(), entry.getValue()));
    +     * }
    + * + *

    The default implementation makes no guarantees about synchronization + * or atomicity properties of this method. Any implementation providing + * atomicity guarantees must override this method and document its + * concurrency properties. + * + * @param function the function to apply to each entry + * @throws UnsupportedOperationException if the {@code set} operation + * is not supported by this map's entry set iterator. + * @throws ClassCastException if the class of a replacement value + * prevents it from being stored in this map + * @throws NullPointerException if the specified function is null, or the + * specified replacement value is null, and this map does not permit null + * values + * @throws ClassCastException if a replacement value is of an inappropriate + * type for this map + * (optional) + * @throws NullPointerException if function or a replacement value is null, + * and this map does not permit null keys or values + * (optional) + * @throws IllegalArgumentException if some property of a replacement value + * prevents it from being stored in this map + * (optional) + * @throws ConcurrentModificationException if an entry is found to be + * removed during iteration + * @since 1.8 + */ + default void replaceAll(BiFunction function) { + Objects.requireNonNull(function); + for (Map.Entry entry : entrySet()) { + K k; + V v; + try { + k = entry.getKey(); + v = entry.getValue(); + } catch (IllegalStateException ise) { + // this usually means the entry is no longer in the map. + throw new ConcurrentModificationException(ise); + } + + // ise thrown from function is not a cme. + v = function.apply(k, v); + + try { + entry.setValue(v); + } catch (IllegalStateException ise) { + // this usually means the entry is no longer in the map. + throw new ConcurrentModificationException(ise); + } + } + } + + /** + * If the specified key is not already associated with a value (or is mapped + * to {@code null}) associates it with the given value and returns + * {@code null}, else returns the current value. + * + * @implSpec + * The default implementation is equivalent to, for this {@code + * map}: + * + *

     {@code
    +     * V v = map.get(key);
    +     * if (v == null)
    +     *     v = map.put(key, value);
    +     *
    +     * return v;
    +     * }
    + * + *

    The default implementation makes no guarantees about synchronization + * or atomicity properties of this method. Any implementation providing + * atomicity guarantees must override this method and document its + * concurrency properties. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with the specified key, or + * {@code null} if there was no mapping for the key. + * (A {@code null} return can also indicate that the map + * previously associated {@code null} with the key, + * if the implementation supports null values.) + * @throws UnsupportedOperationException if the {@code put} operation + * is not supported by this map + * (optional) + * @throws ClassCastException if the key or value is of an inappropriate + * type for this map + * (optional) + * @throws NullPointerException if the specified key or value is null, + * and this map does not permit null keys or values + * (optional) + * @throws IllegalArgumentException if some property of the specified key + * or value prevents it from being stored in this map + * (optional) + * @since 1.8 + */ + default V putIfAbsent(K key, V value) { + V v = get(key); + if (v == null) { + v = put(key, value); + } + + return v; + } + + /** + * Removes the entry for the specified key only if it is currently + * mapped to the specified value. + * + * @implSpec + * The default implementation is equivalent to, for this {@code map}: + * + *

     {@code
    +     * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
    +     *     map.remove(key);
    +     *     return true;
    +     * } else
    +     *     return false;
    +     * }
    + * + *

    The default implementation makes no guarantees about synchronization + * or atomicity properties of this method. Any implementation providing + * atomicity guarantees must override this method and document its + * concurrency properties. + * + * @param key key with which the specified value is associated + * @param value value expected to be associated with the specified key + * @return {@code true} if the value was removed + * @throws UnsupportedOperationException if the {@code remove} operation + * is not supported by this map + * (optional) + * @throws ClassCastException if the key or value is of an inappropriate + * type for this map + * (optional) + * @throws NullPointerException if the specified key or value is null, + * and this map does not permit null keys or values + * (optional) + * @since 1.8 + */ + default boolean remove(Object key, Object value) { + Object curValue = get(key); + if (!Objects.equals(curValue, value) || + (curValue == null && !containsKey(key))) { + return false; + } + remove(key); + return true; + } + + /** + * Replaces the entry for the specified key only if currently + * mapped to the specified value. + * + * @implSpec + * The default implementation is equivalent to, for this {@code map}: + * + *

     {@code
    +     * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
    +     *     map.put(key, newValue);
    +     *     return true;
    +     * } else
    +     *     return false;
    +     * }
    + * + * The default implementation does not throw NullPointerException + * for maps that do not support null values if oldValue is null unless + * newValue is also null. + * + *

    The default implementation makes no guarantees about synchronization + * or atomicity properties of this method. Any implementation providing + * atomicity guarantees must override this method and document its + * concurrency properties. + * + * @param key key with which the specified value is associated + * @param oldValue value expected to be associated with the specified key + * @param newValue value to be associated with the specified key + * @return {@code true} if the value was replaced + * @throws UnsupportedOperationException if the {@code put} operation + * is not supported by this map + * (optional) + * @throws ClassCastException if the class of a specified key or value + * prevents it from being stored in this map + * @throws NullPointerException if a specified key or newValue is null, + * and this map does not permit null keys or values + * @throws NullPointerException if oldValue is null and this map does not + * permit null values + * (optional) + * @throws IllegalArgumentException if some property of a specified key + * or value prevents it from being stored in this map + * @since 1.8 + */ + default boolean replace(K key, V oldValue, V newValue) { + Object curValue = get(key); + if (!Objects.equals(curValue, oldValue) || + (curValue == null && !containsKey(key))) { + return false; + } + put(key, newValue); + return true; + } + + /** + * Replaces the entry for the specified key only if it is + * currently mapped to some value. + * + * @implSpec + * The default implementation is equivalent to, for this {@code map}: + * + *

     {@code
    +     * if (map.containsKey(key)) {
    +     *     return map.put(key, value);
    +     * } else
    +     *     return null;
    +     * }
    + * + *

    The default implementation makes no guarantees about synchronization + * or atomicity properties of this method. Any implementation providing + * atomicity guarantees must override this method and document its + * concurrency properties. + * + * @param key key with which the specified value is associated + * @param value value to be associated with the specified key + * @return the previous value associated with the specified key, or + * {@code null} if there was no mapping for the key. + * (A {@code null} return can also indicate that the map + * previously associated {@code null} with the key, + * if the implementation supports null values.) + * @throws UnsupportedOperationException if the {@code put} operation + * is not supported by this map + * (optional) + * @throws ClassCastException if the class of the specified key or value + * prevents it from being stored in this map + * (optional) + * @throws NullPointerException if the specified key or value is null, + * and this map does not permit null keys or values + * @throws IllegalArgumentException if some property of the specified key + * or value prevents it from being stored in this map + * @since 1.8 + */ + default V replace(K key, V value) { + V curValue; + if (((curValue = get(key)) != null) || containsKey(key)) { + curValue = put(key, value); + } + return curValue; + } + + /** + * If the specified key is not already associated with a value (or is mapped + * to {@code null}), attempts to compute its value using the given mapping + * function and enters it into this map unless {@code null}. + * + *

    If the mapping function returns {@code null}, no mapping is recorded. + * If the mapping function itself throws an (unchecked) exception, the + * exception is rethrown, and no mapping is recorded. The most + * common usage is to construct a new object serving as an initial + * mapped value or memoized result, as in: + * + *

     {@code
    +     * map.computeIfAbsent(key, k -> new Value(f(k)));
    +     * }
    + * + *

    Or to implement a multi-value map, {@code Map>}, + * supporting multiple values per key: + * + *

     {@code
    +     * map.computeIfAbsent(key, k -> new HashSet()).add(v);
    +     * }
    + * + *

    The mapping function should not modify this map during computation. + * + * @implSpec + * The default implementation is equivalent to the following steps for this + * {@code map}, then returning the current value or {@code null} if now + * absent: + * + *

     {@code
    +     * if (map.get(key) == null) {
    +     *     V newValue = mappingFunction.apply(key);
    +     *     if (newValue != null)
    +     *         map.put(key, newValue);
    +     * }
    +     * }
    + * + *

    The default implementation makes no guarantees about detecting if the + * mapping function modifies this map during computation and, if + * appropriate, reporting an error. Non-concurrent implementations should + * override this method and, on a best-effort basis, throw a + * {@code ConcurrentModificationException} if it is detected that the + * mapping function modifies this map during computation. Concurrent + * implementations should override this method and, on a best-effort basis, + * throw an {@code IllegalStateException} if it is detected that the + * mapping function modifies this map during computation and as a result + * computation would never complete. + * + *

    The default implementation makes no guarantees about synchronization + * or atomicity properties of this method. Any implementation providing + * atomicity guarantees must override this method and document its + * concurrency properties. In particular, all implementations of + * subinterface {@link java.util.concurrent.ConcurrentMap} must document + * whether the mapping function is applied once atomically only if the value + * is not present. + * + * @param key key with which the specified value is to be associated + * @param mappingFunction the mapping function to compute a value + * @return the current (existing or computed) value associated with + * the specified key, or null if the computed value is null + * @throws NullPointerException if the specified key is null and + * this map does not support null keys, or the mappingFunction + * is null + * @throws UnsupportedOperationException if the {@code put} operation + * is not supported by this map + * (optional) + * @throws ClassCastException if the class of the specified key or value + * prevents it from being stored in this map + * (optional) + * @throws IllegalArgumentException if some property of the specified key + * or value prevents it from being stored in this map + * (optional) + * @since 1.8 + */ + default V computeIfAbsent(K key, + Function mappingFunction) { + Objects.requireNonNull(mappingFunction); + V v; + if ((v = get(key)) == null) { + V newValue; + if ((newValue = mappingFunction.apply(key)) != null) { + put(key, newValue); + return newValue; + } + } + + return v; + } + + /** + * If the value for the specified key is present and non-null, attempts to + * compute a new mapping given the key and its current mapped value. + * + *

    If the remapping function returns {@code null}, the mapping is removed. + * If the remapping function itself throws an (unchecked) exception, the + * exception is rethrown, and the current mapping is left unchanged. + * + *

    The remapping function should not modify this map during computation. + * + * @implSpec + * The default implementation is equivalent to performing the following + * steps for this {@code map}, then returning the current value or + * {@code null} if now absent: + * + *

     {@code
    +     * if (map.get(key) != null) {
    +     *     V oldValue = map.get(key);
    +     *     V newValue = remappingFunction.apply(key, oldValue);
    +     *     if (newValue != null)
    +     *         map.put(key, newValue);
    +     *     else
    +     *         map.remove(key);
    +     * }
    +     * }
    + * + *

    The default implementation makes no guarantees about detecting if the + * remapping function modifies this map during computation and, if + * appropriate, reporting an error. Non-concurrent implementations should + * override this method and, on a best-effort basis, throw a + * {@code ConcurrentModificationException} if it is detected that the + * remapping function modifies this map during computation. Concurrent + * implementations should override this method and, on a best-effort basis, + * throw an {@code IllegalStateException} if it is detected that the + * remapping function modifies this map during computation and as a result + * computation would never complete. + * + *

    The default implementation makes no guarantees about synchronization + * or atomicity properties of this method. Any implementation providing + * atomicity guarantees must override this method and document its + * concurrency properties. In particular, all implementations of + * subinterface {@link java.util.concurrent.ConcurrentMap} must document + * whether the remapping function is applied once atomically only if the + * value is not present. + * + * @param key key with which the specified value is to be associated + * @param remappingFunction the remapping function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key is null and + * this map does not support null keys, or the + * remappingFunction is null + * @throws UnsupportedOperationException if the {@code put} operation + * is not supported by this map + * (optional) + * @throws ClassCastException if the class of the specified key or value + * prevents it from being stored in this map + * (optional) + * @throws IllegalArgumentException if some property of the specified key + * or value prevents it from being stored in this map + * (optional) + * @since 1.8 + */ + default V computeIfPresent(K key, + BiFunction remappingFunction) { + Objects.requireNonNull(remappingFunction); + V oldValue; + if ((oldValue = get(key)) != null) { + V newValue = remappingFunction.apply(key, oldValue); + if (newValue != null) { + put(key, newValue); + return newValue; + } else { + remove(key); + return null; + } + } else { + return null; + } + } + + /** + * Attempts to compute a mapping for the specified key and its current + * mapped value (or {@code null} if there is no current mapping). For + * example, to either create or append a {@code String} msg to a value + * mapping: + * + *

     {@code
    +     * map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))}
    + * (Method {@link #merge merge()} is often simpler to use for such purposes.) + * + *

    If the remapping function returns {@code null}, the mapping is removed + * (or remains absent if initially absent). If the remapping function + * itself throws an (unchecked) exception, the exception is rethrown, and + * the current mapping is left unchanged. + * + *

    The remapping function should not modify this map during computation. + * + * @implSpec + * The default implementation is equivalent to performing the following + * steps for this {@code map}, then returning the current value or + * {@code null} if absent: + * + *

     {@code
    +     * V oldValue = map.get(key);
    +     * V newValue = remappingFunction.apply(key, oldValue);
    +     * if (oldValue != null) {
    +     *    if (newValue != null)
    +     *       map.put(key, newValue);
    +     *    else
    +     *       map.remove(key);
    +     * } else {
    +     *    if (newValue != null)
    +     *       map.put(key, newValue);
    +     *    else
    +     *       return null;
    +     * }
    +     * }
    + * + *

    The default implementation makes no guarantees about detecting if the + * remapping function modifies this map during computation and, if + * appropriate, reporting an error. Non-concurrent implementations should + * override this method and, on a best-effort basis, throw a + * {@code ConcurrentModificationException} if it is detected that the + * remapping function modifies this map during computation. Concurrent + * implementations should override this method and, on a best-effort basis, + * throw an {@code IllegalStateException} if it is detected that the + * remapping function modifies this map during computation and as a result + * computation would never complete. + * + *

    The default implementation makes no guarantees about synchronization + * or atomicity properties of this method. Any implementation providing + * atomicity guarantees must override this method and document its + * concurrency properties. In particular, all implementations of + * subinterface {@link java.util.concurrent.ConcurrentMap} must document + * whether the remapping function is applied once atomically only if the + * value is not present. + * + * @param key key with which the specified value is to be associated + * @param remappingFunction the remapping function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key is null and + * this map does not support null keys, or the + * remappingFunction is null + * @throws UnsupportedOperationException if the {@code put} operation + * is not supported by this map + * (optional) + * @throws ClassCastException if the class of the specified key or value + * prevents it from being stored in this map + * (optional) + * @throws IllegalArgumentException if some property of the specified key + * or value prevents it from being stored in this map + * (optional) + * @since 1.8 + */ + default V compute(K key, + BiFunction remappingFunction) { + Objects.requireNonNull(remappingFunction); + V oldValue = get(key); + + V newValue = remappingFunction.apply(key, oldValue); + if (newValue == null) { + // delete mapping + if (oldValue != null || containsKey(key)) { + // something to remove + remove(key); + return null; + } else { + // nothing to do. Leave things as they were. + return null; + } + } else { + // add or replace old mapping + put(key, newValue); + return newValue; + } + } + + /** + * If the specified key is not already associated with a value or is + * associated with null, associates it with the given non-null value. + * Otherwise, replaces the associated value with the results of the given + * remapping function, or removes if the result is {@code null}. This + * method may be of use when combining multiple mapped values for a key. + * For example, to either create or append a {@code String msg} to a + * value mapping: + * + *

     {@code
    +     * map.merge(key, msg, String::concat)
    +     * }
    + * + *

    If the remapping function returns {@code null}, the mapping is removed. + * If the remapping function itself throws an (unchecked) exception, the + * exception is rethrown, and the current mapping is left unchanged. + * + *

    The remapping function should not modify this map during computation. + * + * @implSpec + * The default implementation is equivalent to performing the following + * steps for this {@code map}, then returning the current value or + * {@code null} if absent: + * + *

     {@code
    +     * V oldValue = map.get(key);
    +     * V newValue = (oldValue == null) ? value :
    +     *              remappingFunction.apply(oldValue, value);
    +     * if (newValue == null)
    +     *     map.remove(key);
    +     * else
    +     *     map.put(key, newValue);
    +     * }
    + * + *

    The default implementation makes no guarantees about detecting if the + * remapping function modifies this map during computation and, if + * appropriate, reporting an error. Non-concurrent implementations should + * override this method and, on a best-effort basis, throw a + * {@code ConcurrentModificationException} if it is detected that the + * remapping function modifies this map during computation. Concurrent + * implementations should override this method and, on a best-effort basis, + * throw an {@code IllegalStateException} if it is detected that the + * remapping function modifies this map during computation and as a result + * computation would never complete. + * + *

    The default implementation makes no guarantees about synchronization + * or atomicity properties of this method. Any implementation providing + * atomicity guarantees must override this method and document its + * concurrency properties. In particular, all implementations of + * subinterface {@link java.util.concurrent.ConcurrentMap} must document + * whether the remapping function is applied once atomically only if the + * value is not present. + * + * @param key key with which the resulting value is to be associated + * @param value the non-null value to be merged with the existing value + * associated with the key or, if no existing value or a null value + * is associated with the key, to be associated with the key + * @param remappingFunction the remapping function to recompute a value if + * present + * @return the new value associated with the specified key, or null if no + * value is associated with the key + * @throws UnsupportedOperationException if the {@code put} operation + * is not supported by this map + * (optional) + * @throws ClassCastException if the class of the specified key or value + * prevents it from being stored in this map + * (optional) + * @throws IllegalArgumentException if some property of the specified key + * or value prevents it from being stored in this map + * (optional) + * @throws NullPointerException if the specified key is null and this map + * does not support null keys or the value or remappingFunction is + * null + * @since 1.8 + */ + default V merge(K key, V value, + BiFunction remappingFunction) { + Objects.requireNonNull(remappingFunction); + Objects.requireNonNull(value); + V oldValue = get(key); + V newValue = (oldValue == null) ? value : + remappingFunction.apply(oldValue, value); + if (newValue == null) { + remove(key); + } else { + put(key, newValue); + } + return newValue; + } } diff --git a/ojluni/src/main/java/java/util/NavigableMap.java b/ojluni/src/main/java/java/util/NavigableMap.java index b0d945342..fdbc0b557 100644 --- a/ojluni/src/main/java/java/util/NavigableMap.java +++ b/ojluni/src/main/java/java/util/NavigableMap.java @@ -41,30 +41,32 @@ /** * A {@link SortedMap} extended with navigation methods returning the * closest matches for given search targets. Methods - * {@code lowerEntry}, {@code floorEntry}, {@code ceilingEntry}, - * and {@code higherEntry} return {@code Map.Entry} objects + * {@link #lowerEntry}, {@link #floorEntry}, {@link #ceilingEntry}, + * and {@link #higherEntry} return {@code Map.Entry} objects * associated with keys respectively less than, less than or equal, * greater than or equal, and greater than a given key, returning * {@code null} if there is no such key. Similarly, methods - * {@code lowerKey}, {@code floorKey}, {@code ceilingKey}, and - * {@code higherKey} return only the associated keys. All of these + * {@link #lowerKey}, {@link #floorKey}, {@link #ceilingKey}, and + * {@link #higherKey} return only the associated keys. All of these * methods are designed for locating, not traversing entries. * *

    A {@code NavigableMap} may be accessed and traversed in either - * ascending or descending key order. The {@code descendingMap} + * ascending or descending key order. The {@link #descendingMap} * method returns a view of the map with the senses of all relational * and directional methods inverted. The performance of ascending * operations and views is likely to be faster than that of descending - * ones. Methods {@code subMap}, {@code headMap}, - * and {@code tailMap} differ from the like-named {@code - * SortedMap} methods in accepting additional arguments describing - * whether lower and upper bounds are inclusive versus exclusive. - * Submaps of any {@code NavigableMap} must implement the {@code - * NavigableMap} interface. + * ones. Methods + * {@link #subMap(Object, boolean, Object, boolean) subMap(K, boolean, K, boolean)}, + * {@link #headMap(Object, boolean) headMap(K, boolean)}, and + * {@link #tailMap(Object, boolean) tailMap(K, boolean)} + * differ from the like-named {@code SortedMap} methods in accepting + * additional arguments describing whether lower and upper bounds are + * inclusive versus exclusive. Submaps of any {@code NavigableMap} + * must implement the {@code NavigableMap} interface. * - *

    This interface additionally defines methods {@code firstEntry}, - * {@code pollFirstEntry}, {@code lastEntry}, and - * {@code pollLastEntry} that return and/or remove the least and + *

    This interface additionally defines methods {@link #firstEntry}, + * {@link #pollFirstEntry}, {@link #lastEntry}, and + * {@link #pollLastEntry} that return and/or remove the least and * greatest mappings, if any exist, else returning {@code null}. * *

    Implementations of entry-returning methods are expected to @@ -83,7 +85,7 @@ * implement {@code NavigableMap}, but extensions and implementations * of this interface are encouraged to override these methods to return * {@code NavigableMap}. Similarly, - * {@link #keySet()} can be overridden to return {@code NavigableSet}. + * {@link #keySet()} can be overridden to return {@link NavigableSet}. * * @author Doug Lea * @author Josh Bloch @@ -297,7 +299,7 @@ public interface NavigableMap extends SortedMap { * Returns a view of the portion of this map whose keys range from * {@code fromKey} to {@code toKey}. If {@code fromKey} and * {@code toKey} are equal, the returned map is empty unless - * {@code fromExclusive} and {@code toExclusive} are both true. The + * {@code fromInclusive} and {@code toInclusive} are both true. The * returned map is backed by this map, so changes in the returned map are * reflected in this map, and vice-versa. The returned map supports all * optional map operations that this map supports. diff --git a/ojluni/src/main/java/java/util/NavigableSet.java b/ojluni/src/main/java/java/util/NavigableSet.java index 11a9bdb79..412a07d9a 100644 --- a/ojluni/src/main/java/java/util/NavigableSet.java +++ b/ojluni/src/main/java/java/util/NavigableSet.java @@ -32,32 +32,36 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -package java.util; - // BEGIN android-note // removed link to collections framework docs // END android-note +package java.util; + /** * A {@link SortedSet} extended with navigation methods reporting - * closest matches for given search targets. Methods {@code lower}, - * {@code floor}, {@code ceiling}, and {@code higher} return elements + * closest matches for given search targets. Methods {@link #lower}, + * {@link #floor}, {@link #ceiling}, and {@link #higher} return elements * respectively less than, less than or equal, greater than or equal, * and greater than a given element, returning {@code null} if there - * is no such element. A {@code NavigableSet} may be accessed and - * traversed in either ascending or descending order. The {@code - * descendingSet} method returns a view of the set with the senses of - * all relational and directional methods inverted. The performance of - * ascending operations and views is likely to be faster than that of - * descending ones. This interface additionally defines methods - * {@code pollFirst} and {@code pollLast} that return and remove the - * lowest and highest element, if one exists, else returning {@code - * null}. Methods {@code subSet}, {@code headSet}, - * and {@code tailSet} differ from the like-named {@code - * SortedSet} methods in accepting additional arguments describing - * whether lower and upper bounds are inclusive versus exclusive. - * Subsets of any {@code NavigableSet} must implement the {@code - * NavigableSet} interface. + * is no such element. + * + *

    A {@code NavigableSet} may be accessed and traversed in either + * ascending or descending order. The {@link #descendingSet} method + * returns a view of the set with the senses of all relational and + * directional methods inverted. The performance of ascending + * operations and views is likely to be faster than that of descending + * ones. This interface additionally defines methods {@link + * #pollFirst} and {@link #pollLast} that return and remove the lowest + * and highest element, if one exists, else returning {@code null}. + * Methods + * {@link #subSet(Object, boolean, Object, boolean) subSet(E, boolean, E, boolean)}, + * {@link #headSet(Object, boolean) headSet(E, boolean)}, and + * {@link #tailSet(Object, boolean) tailSet(E, boolean)} + * differ from the like-named {@code SortedSet} methods in accepting + * additional arguments describing whether lower and upper bounds are + * inclusive versus exclusive. Subsets of any {@code NavigableSet} + * must implement the {@code NavigableSet} interface. * *

    The return values of navigation methods may be ambiguous in * implementations that permit {@code null} elements. However, even @@ -191,7 +195,7 @@ public interface NavigableSet extends SortedSet { * Returns a view of the portion of this set whose elements range from * {@code fromElement} to {@code toElement}. If {@code fromElement} and * {@code toElement} are equal, the returned set is empty unless {@code - * fromExclusive} and {@code toExclusive} are both true. The returned set + * fromInclusive} and {@code toInclusive} are both true. The returned set * is backed by this set, so changes in the returned set are reflected in * this set, and vice-versa. The returned set supports all optional set * operations that this set supports. diff --git a/ojluni/src/main/java/java/util/PriorityQueue.java b/ojluni/src/main/java/java/util/PriorityQueue.java old mode 100755 new mode 100644 index b42647e45..7c929bda6 --- a/ojluni/src/main/java/java/util/PriorityQueue.java +++ b/ojluni/src/main/java/java/util/PriorityQueue.java @@ -77,7 +77,7 @@ * * @since 1.5 * @author Josh Bloch, Doug Lea - * @param the type of elements held in this collection + * @param the type of elements held in this queue */ public class PriorityQueue extends AbstractQueue implements java.io.Serializable { @@ -99,7 +99,7 @@ public class PriorityQueue extends AbstractQueue /** * The number of elements in the priority queue. */ - private int size = 0; + int size; /** * The comparator, or null if priority queue uses elements' @@ -111,7 +111,7 @@ public class PriorityQueue extends AbstractQueue * The number of times this priority queue has been * structurally modified. See AbstractList for gory details. */ - transient int modCount = 0; // non-private to simplify nested class access + transient int modCount; // non-private to simplify nested class access /** * Creates a {@code PriorityQueue} with the default initial @@ -258,8 +258,8 @@ private void initElementsFromCollection(Collection c) { a = Arrays.copyOf(a, a.length, Object[].class); int len = a.length; if (len == 1 || this.comparator != null) - for (int i = 0; i < len; i++) - if (a[i] == null) + for (Object e : a) + if (e == null) throw new NullPointerException(); this.queue = a; this.size = a.length; @@ -406,7 +406,7 @@ boolean removeEq(Object o) { * @return {@code true} if this queue contains the specified element */ public boolean contains(Object o) { - return indexOf(o) != -1; + return indexOf(o) >= 0; } /** @@ -448,7 +448,7 @@ public Object[] toArray() { * The following code can be used to dump the queue into a newly * allocated array of {@code String}: * - *

     {@code String[] y = x.toArray(new String[0]);}
    + *
     {@code String[] y = x.toArray(new String[0]);}
    * * Note that {@code toArray(new Object[0])} is identical in function to * {@code toArray()}. @@ -489,7 +489,7 @@ private final class Itr implements Iterator { * Index (into queue array) of element to be returned by * subsequent call to next. */ - private int cursor = 0; + private int cursor; /** * Index of element returned by most recent call to next, @@ -509,13 +509,13 @@ private final class Itr implements Iterator { * We expect that most iterations, even those involving removals, * will not need to store elements in this field. */ - private ArrayDeque forgetMeNot = null; + private ArrayDeque forgetMeNot; /** * Element returned by the most recent call to next iff that * element was drawn from the forgetMeNot list. */ - private E lastRetElt = null; + private E lastRetElt; /** * The modCount value that the iterator believes that the backing @@ -609,8 +609,8 @@ public E poll() { * avoid missing traversing elements. */ @SuppressWarnings("unchecked") - private E removeAt(int i) { - assert i >= 0 && i < size; + E removeAt(int i) { + // assert i >= 0 && i < size; modCount++; int s = --size; if (s == i) // removed last element @@ -756,6 +756,7 @@ public Comparator comparator() { * emitted (int), followed by all of its elements * (each an {@code Object}) in the proper order. * @param s the stream + * @throws java.io.IOException if an I/O error occurs */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { @@ -775,6 +776,9 @@ private void writeObject(java.io.ObjectOutputStream s) * (that is, deserializes it). * * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws java.io.IOException if an I/O error occurs */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { @@ -809,7 +813,7 @@ private void readObject(java.io.ObjectInputStream s) * @since 1.8 */ public final Spliterator spliterator() { - return new PriorityQueueSpliterator(this, 0, -1, 0); + return new PriorityQueueSpliterator<>(this, 0, -1, 0); } static final class PriorityQueueSpliterator implements Spliterator { @@ -822,9 +826,9 @@ static final class PriorityQueueSpliterator implements Spliterator { private int fence; // -1 until first use private int expectedModCount; // initialized when fence set - /** Creates new spliterator covering the given range */ + /** Creates new spliterator covering the given range. */ PriorityQueueSpliterator(PriorityQueue pq, int origin, int fence, - int expectedModCount) { + int expectedModCount) { this.pq = pq; this.index = origin; this.fence = fence; @@ -843,8 +847,8 @@ private int getFence() { // initialize fence to size on first use public PriorityQueueSpliterator trySplit() { int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; return (lo >= mid) ? null : - new PriorityQueueSpliterator(pq, lo, index = mid, - expectedModCount); + new PriorityQueueSpliterator<>(pq, lo, index = mid, + expectedModCount); } @SuppressWarnings("unchecked") diff --git a/ojluni/src/main/java/java/util/Queue.java b/ojluni/src/main/java/java/util/Queue.java index b43c6c1f6..652033752 100644 --- a/ojluni/src/main/java/java/util/Queue.java +++ b/ojluni/src/main/java/java/util/Queue.java @@ -50,7 +50,6 @@ * implementations; in most implementations, insert operations cannot * fail. * - *

    *

    Comparison of Stack and Deque methods
    * * @@ -128,17 +127,9 @@ * always well-defined for queues with the same elements but different * ordering properties. * - * @see java.util.Collection - * @see LinkedList - * @see PriorityQueue - * @see java.util.concurrent.LinkedBlockingQueue - * @see java.util.concurrent.BlockingQueue - * @see java.util.concurrent.ArrayBlockingQueue - * @see java.util.concurrent.LinkedBlockingQueue - * @see java.util.concurrent.PriorityBlockingQueue * @since 1.5 * @author Doug Lea - * @param the type of elements held in this collection + * @param the type of elements held in this queue */ public interface Queue extends Collection { /** diff --git a/ojluni/src/main/java/java/util/SplittableRandom.java b/ojluni/src/main/java/java/util/SplittableRandom.java new file mode 100644 index 000000000..b2a07eca8 --- /dev/null +++ b/ojluni/src/main/java/java/util/SplittableRandom.java @@ -0,0 +1,998 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.DoubleConsumer; +import java.util.function.IntConsumer; +import java.util.function.LongConsumer; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.StreamSupport; + + +// TODO(streams): Include in openjdk_java_files.mk +/** + * A generator of uniform pseudorandom values applicable for use in + * (among other contexts) isolated parallel computations that may + * generate subtasks. Class {@code SplittableRandom} supports methods for + * producing pseudorandom numbers of type {@code int}, {@code long}, + * and {@code double} with similar usages as for class + * {@link java.util.Random} but differs in the following ways: + * + *
      + * + *
    • Series of generated values pass the DieHarder suite testing + * independence and uniformity properties of random number generators. + * (Most recently validated with version + * 3.31.1.) These tests validate only the methods for certain + * types and ranges, but similar properties are expected to hold, at + * least approximately, for others as well. The period + * (length of any series of generated values before it repeats) is at + * least 264. + * + *
    • Method {@link #split} constructs and returns a new + * SplittableRandom instance that shares no mutable state with the + * current instance. However, with very high probability, the + * values collectively generated by the two objects have the same + * statistical properties as if the same quantity of values were + * generated by a single thread using a single {@code + * SplittableRandom} object. + * + *
    • Instances of SplittableRandom are not thread-safe. + * They are designed to be split, not shared, across threads. For + * example, a {@link java.util.concurrent.ForkJoinTask + * fork/join-style} computation using random numbers might include a + * construction of the form {@code new + * Subtask(aSplittableRandom.split()).fork()}. + * + *
    • This class provides additional methods for generating random + * streams, that employ the above techniques when used in {@code + * stream.parallel()} mode. + * + *
    + * + *

    Instances of {@code SplittableRandom} are not cryptographically + * secure. Consider instead using {@link java.security.SecureRandom} + * in security-sensitive applications. Additionally, + * default-constructed instances do not use a cryptographically random + * seed unless the {@linkplain System#getProperty system property} + * {@code java.util.secureRandomSeed} is set to {@code true}. + * + * @author Guy Steele + * @author Doug Lea + * @since 1.8 + */ +public final class SplittableRandom { + + /* + * Implementation Overview. + * + * This algorithm was inspired by the "DotMix" algorithm by + * Leiserson, Schardl, and Sukha "Deterministic Parallel + * Random-Number Generation for Dynamic-Multithreading Platforms", + * PPoPP 2012, as well as those in "Parallel random numbers: as + * easy as 1, 2, 3" by Salmon, Morae, Dror, and Shaw, SC 2011. It + * differs mainly in simplifying and cheapening operations. + * + * The primary update step (method nextSeed()) is to add a + * constant ("gamma") to the current (64 bit) seed, forming a + * simple sequence. The seed and the gamma values for any two + * SplittableRandom instances are highly likely to be different. + * + * Methods nextLong, nextInt, and derivatives do not return the + * sequence (seed) values, but instead a hash-like bit-mix of + * their bits, producing more independently distributed sequences. + * For nextLong, the mix64 function is based on David Stafford's + * (http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html) + * "Mix13" variant of the "64-bit finalizer" function in Austin + * Appleby's MurmurHash3 algorithm (see + * http://code.google.com/p/smhasher/wiki/MurmurHash3). The mix32 + * function is based on Stafford's Mix04 mix function, but returns + * the upper 32 bits cast as int. + * + * The split operation uses the current generator to form the seed + * and gamma for another SplittableRandom. To conservatively + * avoid potential correlations between seed and value generation, + * gamma selection (method mixGamma) uses different + * (Murmurhash3's) mix constants. To avoid potential weaknesses + * in bit-mixing transformations, we restrict gammas to odd values + * with at least 24 0-1 or 1-0 bit transitions. Rather than + * rejecting candidates with too few or too many bits set, method + * mixGamma flips some bits (which has the effect of mapping at + * most 4 to any given gamma value). This reduces the effective + * set of 64bit odd gamma values by about 2%, and serves as an + * automated screening for sequence constant selection that is + * left as an empirical decision in some other hashing and crypto + * algorithms. + * + * The resulting generator thus transforms a sequence in which + * (typically) many bits change on each step, with an inexpensive + * mixer with good (but less than cryptographically secure) + * avalanching. + * + * The default (no-argument) constructor, in essence, invokes + * split() for a common "defaultGen" SplittableRandom. Unlike + * other cases, this split must be performed in a thread-safe + * manner, so we use an AtomicLong to represent the seed rather + * than use an explicit SplittableRandom. To bootstrap the + * defaultGen, we start off using a seed based on current time + * unless the java.util.secureRandomSeed property is set. This + * serves as a slimmed-down (and insecure) variant of SecureRandom + * that also avoids stalls that may occur when using /dev/random. + * + * It is a relatively simple matter to apply the basic design here + * to use 128 bit seeds. However, emulating 128bit arithmetic and + * carrying around twice the state add more overhead than appears + * warranted for current usages. + * + * File organization: First the non-public methods that constitute + * the main algorithm, then the main public methods, followed by + * some custom spliterator classes needed for stream methods. + */ + + /** + * The golden ratio scaled to 64bits, used as the initial gamma + * value for (unsplit) SplittableRandoms. + */ + private static final long GOLDEN_GAMMA = 0x9e3779b97f4a7c15L; + + /** + * The least non-zero value returned by nextDouble(). This value + * is scaled by a random value of 53 bits to produce a result. + */ + private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53); + + /** + * The seed. Updated only via method nextSeed. + */ + private long seed; + + /** + * The step value. + */ + private final long gamma; + + /** + * Internal constructor used by all others except default constructor. + */ + private SplittableRandom(long seed, long gamma) { + this.seed = seed; + this.gamma = gamma; + } + + /** + * Computes Stafford variant 13 of 64bit mix function. + */ + private static long mix64(long z) { + z = (z ^ (z >>> 30)) * 0xbf58476d1ce4e5b9L; + z = (z ^ (z >>> 27)) * 0x94d049bb133111ebL; + return z ^ (z >>> 31); + } + + /** + * Returns the 32 high bits of Stafford variant 4 mix64 function as int. + */ + private static int mix32(long z) { + z = (z ^ (z >>> 33)) * 0x62a9d9ed799705f5L; + return (int)(((z ^ (z >>> 28)) * 0xcb24d0a5c88c35b3L) >>> 32); + } + + /** + * Returns the gamma value to use for a new split instance. + */ + private static long mixGamma(long z) { + z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL; // MurmurHash3 mix constants + z = (z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L; + z = (z ^ (z >>> 33)) | 1L; // force to be odd + int n = Long.bitCount(z ^ (z >>> 1)); // ensure enough transitions + return (n < 24) ? z ^ 0xaaaaaaaaaaaaaaaaL : z; + } + + /** + * Adds gamma to seed. + */ + private long nextSeed() { + return seed += gamma; + } + + // IllegalArgumentException messages + static final String BAD_BOUND = "bound must be positive"; + static final String BAD_RANGE = "bound must be greater than origin"; + static final String BAD_SIZE = "size must be non-negative"; + + /** + * The seed generator for default constructors. + */ + private static final AtomicLong defaultGen + = new AtomicLong(mix64(System.currentTimeMillis()) ^ + mix64(System.nanoTime())); + + // at end of to survive static initialization circularity + static { + if (java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Boolean run() { + return Boolean.getBoolean("java.util.secureRandomSeed"); + }})) { + byte[] seedBytes = java.security.SecureRandom.getSeed(8); + long s = (long)seedBytes[0] & 0xffL; + for (int i = 1; i < 8; ++i) + s = (s << 8) | ((long)seedBytes[i] & 0xffL); + defaultGen.set(s); + } + } + + /* + * Internal versions of nextX methods used by streams, as well as + * the public nextX(origin, bound) methods. These exist mainly to + * avoid the need for multiple versions of stream spliterators + * across the different exported forms of streams. + */ + + /** + * The form of nextLong used by LongStream Spliterators. If + * origin is greater than bound, acts as unbounded form of + * nextLong, else as bounded form. + * + * @param origin the least value, unless greater than bound + * @param bound the upper bound (exclusive), must not equal origin + * @return a pseudorandom value + */ + final long internalNextLong(long origin, long bound) { + /* + * Four Cases: + * + * 1. If the arguments indicate unbounded form, act as + * nextLong(). + * + * 2. If the range is an exact power of two, apply the + * associated bit mask. + * + * 3. If the range is positive, loop to avoid potential bias + * when the implicit nextLong() bound (264) is not + * evenly divisible by the range. The loop rejects candidates + * computed from otherwise over-represented values. The + * expected number of iterations under an ideal generator + * varies from 1 to 2, depending on the bound. The loop itself + * takes an unlovable form. Because the first candidate is + * already available, we need a break-in-the-middle + * construction, which is concisely but cryptically performed + * within the while-condition of a body-less for loop. + * + * 4. Otherwise, the range cannot be represented as a positive + * long. The loop repeatedly generates unbounded longs until + * obtaining a candidate meeting constraints (with an expected + * number of iterations of less than two). + */ + + long r = mix64(nextSeed()); + if (origin < bound) { + long n = bound - origin, m = n - 1; + if ((n & m) == 0L) // power of two + r = (r & m) + origin; + else if (n > 0L) { // reject over-represented candidates + for (long u = r >>> 1; // ensure nonnegative + u + m - (r = u % n) < 0L; // rejection check + u = mix64(nextSeed()) >>> 1) // retry + ; + r += origin; + } + else { // range not representable as long + while (r < origin || r >= bound) + r = mix64(nextSeed()); + } + } + return r; + } + + /** + * The form of nextInt used by IntStream Spliterators. + * Exactly the same as long version, except for types. + * + * @param origin the least value, unless greater than bound + * @param bound the upper bound (exclusive), must not equal origin + * @return a pseudorandom value + */ + final int internalNextInt(int origin, int bound) { + int r = mix32(nextSeed()); + if (origin < bound) { + int n = bound - origin, m = n - 1; + if ((n & m) == 0) + r = (r & m) + origin; + else if (n > 0) { + for (int u = r >>> 1; + u + m - (r = u % n) < 0; + u = mix32(nextSeed()) >>> 1) + ; + r += origin; + } + else { + while (r < origin || r >= bound) + r = mix32(nextSeed()); + } + } + return r; + } + + /** + * The form of nextDouble used by DoubleStream Spliterators. + * + * @param origin the least value, unless greater than bound + * @param bound the upper bound (exclusive), must not equal origin + * @return a pseudorandom value + */ + final double internalNextDouble(double origin, double bound) { + double r = (nextLong() >>> 11) * DOUBLE_UNIT; + if (origin < bound) { + r = r * (bound - origin) + origin; + if (r >= bound) // correct for rounding + r = Double.longBitsToDouble(Double.doubleToLongBits(bound) - 1); + } + return r; + } + + /* ---------------- public methods ---------------- */ + + /** + * Creates a new SplittableRandom instance using the specified + * initial seed. SplittableRandom instances created with the same + * seed in the same program generate identical sequences of values. + * + * @param seed the initial seed + */ + public SplittableRandom(long seed) { + this(seed, GOLDEN_GAMMA); + } + + /** + * Creates a new SplittableRandom instance that is likely to + * generate sequences of values that are statistically independent + * of those of any other instances in the current program; and + * may, and typically does, vary across program invocations. + */ + public SplittableRandom() { // emulate defaultGen.split() + long s = defaultGen.getAndAdd(2 * GOLDEN_GAMMA); + this.seed = mix64(s); + this.gamma = mixGamma(s + GOLDEN_GAMMA); + } + + /** + * Constructs and returns a new SplittableRandom instance that + * shares no mutable state with this instance. However, with very + * high probability, the set of values collectively generated by + * the two objects has the same statistical properties as if the + * same quantity of values were generated by a single thread using + * a single SplittableRandom object. Either or both of the two + * objects may be further split using the {@code split()} method, + * and the same expected statistical properties apply to the + * entire set of generators constructed by such recursive + * splitting. + * + * @return the new SplittableRandom instance + */ + public SplittableRandom split() { + return new SplittableRandom(nextLong(), mixGamma(nextSeed())); + } + + /** + * Returns a pseudorandom {@code int} value. + * + * @return a pseudorandom {@code int} value + */ + public int nextInt() { + return mix32(nextSeed()); + } + + /** + * Returns a pseudorandom {@code int} value between zero (inclusive) + * and the specified bound (exclusive). + * + * @param bound the upper bound (exclusive). Must be positive. + * @return a pseudorandom {@code int} value between zero + * (inclusive) and the bound (exclusive) + * @throws IllegalArgumentException if {@code bound} is not positive + */ + public int nextInt(int bound) { + if (bound <= 0) + throw new IllegalArgumentException(BAD_BOUND); + // Specialize internalNextInt for origin 0 + int r = mix32(nextSeed()); + int m = bound - 1; + if ((bound & m) == 0) // power of two + r &= m; + else { // reject over-represented candidates + for (int u = r >>> 1; + u + m - (r = u % bound) < 0; + u = mix32(nextSeed()) >>> 1) + ; + } + return r; + } + + /** + * Returns a pseudorandom {@code int} value between the specified + * origin (inclusive) and the specified bound (exclusive). + * + * @param origin the least value returned + * @param bound the upper bound (exclusive) + * @return a pseudorandom {@code int} value between the origin + * (inclusive) and the bound (exclusive) + * @throws IllegalArgumentException if {@code origin} is greater than + * or equal to {@code bound} + */ + public int nextInt(int origin, int bound) { + if (origin >= bound) + throw new IllegalArgumentException(BAD_RANGE); + return internalNextInt(origin, bound); + } + + /** + * Returns a pseudorandom {@code long} value. + * + * @return a pseudorandom {@code long} value + */ + public long nextLong() { + return mix64(nextSeed()); + } + + /** + * Returns a pseudorandom {@code long} value between zero (inclusive) + * and the specified bound (exclusive). + * + * @param bound the upper bound (exclusive). Must be positive. + * @return a pseudorandom {@code long} value between zero + * (inclusive) and the bound (exclusive) + * @throws IllegalArgumentException if {@code bound} is not positive + */ + public long nextLong(long bound) { + if (bound <= 0) + throw new IllegalArgumentException(BAD_BOUND); + // Specialize internalNextLong for origin 0 + long r = mix64(nextSeed()); + long m = bound - 1; + if ((bound & m) == 0L) // power of two + r &= m; + else { // reject over-represented candidates + for (long u = r >>> 1; + u + m - (r = u % bound) < 0L; + u = mix64(nextSeed()) >>> 1) + ; + } + return r; + } + + /** + * Returns a pseudorandom {@code long} value between the specified + * origin (inclusive) and the specified bound (exclusive). + * + * @param origin the least value returned + * @param bound the upper bound (exclusive) + * @return a pseudorandom {@code long} value between the origin + * (inclusive) and the bound (exclusive) + * @throws IllegalArgumentException if {@code origin} is greater than + * or equal to {@code bound} + */ + public long nextLong(long origin, long bound) { + if (origin >= bound) + throw new IllegalArgumentException(BAD_RANGE); + return internalNextLong(origin, bound); + } + + /** + * Returns a pseudorandom {@code double} value between zero + * (inclusive) and one (exclusive). + * + * @return a pseudorandom {@code double} value between zero + * (inclusive) and one (exclusive) + */ + public double nextDouble() { + return (mix64(nextSeed()) >>> 11) * DOUBLE_UNIT; + } + + /** + * Returns a pseudorandom {@code double} value between 0.0 + * (inclusive) and the specified bound (exclusive). + * + * @param bound the upper bound (exclusive). Must be positive. + * @return a pseudorandom {@code double} value between zero + * (inclusive) and the bound (exclusive) + * @throws IllegalArgumentException if {@code bound} is not positive + */ + public double nextDouble(double bound) { + if (!(bound > 0.0)) + throw new IllegalArgumentException(BAD_BOUND); + double result = (mix64(nextSeed()) >>> 11) * DOUBLE_UNIT * bound; + return (result < bound) ? result : // correct for rounding + Double.longBitsToDouble(Double.doubleToLongBits(bound) - 1); + } + + /** + * Returns a pseudorandom {@code double} value between the specified + * origin (inclusive) and bound (exclusive). + * + * @param origin the least value returned + * @param bound the upper bound (exclusive) + * @return a pseudorandom {@code double} value between the origin + * (inclusive) and the bound (exclusive) + * @throws IllegalArgumentException if {@code origin} is greater than + * or equal to {@code bound} + */ + public double nextDouble(double origin, double bound) { + if (!(origin < bound)) + throw new IllegalArgumentException(BAD_RANGE); + return internalNextDouble(origin, bound); + } + + /** + * Returns a pseudorandom {@code boolean} value. + * + * @return a pseudorandom {@code boolean} value + */ + public boolean nextBoolean() { + return mix32(nextSeed()) < 0; + } + + // stream methods, coded in a way intended to better isolate for + // maintenance purposes the small differences across forms. + + /** + * Returns a stream producing the given {@code streamSize} number + * of pseudorandom {@code int} values from this generator and/or + * one split from it. + * + * @param streamSize the number of values to generate + * @return a stream of pseudorandom {@code int} values + * @throws IllegalArgumentException if {@code streamSize} is + * less than zero + */ + public IntStream ints(long streamSize) { + if (streamSize < 0L) + throw new IllegalArgumentException(BAD_SIZE); + return StreamSupport.intStream + (new RandomIntsSpliterator + (this, 0L, streamSize, Integer.MAX_VALUE, 0), + false); + } + + /** + * Returns an effectively unlimited stream of pseudorandom {@code int} + * values from this generator and/or one split from it. + * + * @implNote This method is implemented to be equivalent to {@code + * ints(Long.MAX_VALUE)}. + * + * @return a stream of pseudorandom {@code int} values + */ + public IntStream ints() { + return StreamSupport.intStream + (new RandomIntsSpliterator + (this, 0L, Long.MAX_VALUE, Integer.MAX_VALUE, 0), + false); + } + + /** + * Returns a stream producing the given {@code streamSize} number + * of pseudorandom {@code int} values from this generator and/or one split + * from it; each value conforms to the given origin (inclusive) and bound + * (exclusive). + * + * @param streamSize the number of values to generate + * @param randomNumberOrigin the origin (inclusive) of each random value + * @param randomNumberBound the bound (exclusive) of each random value + * @return a stream of pseudorandom {@code int} values, + * each with the given origin (inclusive) and bound (exclusive) + * @throws IllegalArgumentException if {@code streamSize} is + * less than zero, or {@code randomNumberOrigin} + * is greater than or equal to {@code randomNumberBound} + */ + public IntStream ints(long streamSize, int randomNumberOrigin, + int randomNumberBound) { + if (streamSize < 0L) + throw new IllegalArgumentException(BAD_SIZE); + if (randomNumberOrigin >= randomNumberBound) + throw new IllegalArgumentException(BAD_RANGE); + return StreamSupport.intStream + (new RandomIntsSpliterator + (this, 0L, streamSize, randomNumberOrigin, randomNumberBound), + false); + } + + /** + * Returns an effectively unlimited stream of pseudorandom {@code + * int} values from this generator and/or one split from it; each value + * conforms to the given origin (inclusive) and bound (exclusive). + * + * @implNote This method is implemented to be equivalent to {@code + * ints(Long.MAX_VALUE, randomNumberOrigin, randomNumberBound)}. + * + * @param randomNumberOrigin the origin (inclusive) of each random value + * @param randomNumberBound the bound (exclusive) of each random value + * @return a stream of pseudorandom {@code int} values, + * each with the given origin (inclusive) and bound (exclusive) + * @throws IllegalArgumentException if {@code randomNumberOrigin} + * is greater than or equal to {@code randomNumberBound} + */ + public IntStream ints(int randomNumberOrigin, int randomNumberBound) { + if (randomNumberOrigin >= randomNumberBound) + throw new IllegalArgumentException(BAD_RANGE); + return StreamSupport.intStream + (new RandomIntsSpliterator + (this, 0L, Long.MAX_VALUE, randomNumberOrigin, randomNumberBound), + false); + } + + /** + * Returns a stream producing the given {@code streamSize} number + * of pseudorandom {@code long} values from this generator and/or + * one split from it. + * + * @param streamSize the number of values to generate + * @return a stream of pseudorandom {@code long} values + * @throws IllegalArgumentException if {@code streamSize} is + * less than zero + */ + public LongStream longs(long streamSize) { + if (streamSize < 0L) + throw new IllegalArgumentException(BAD_SIZE); + return StreamSupport.longStream + (new RandomLongsSpliterator + (this, 0L, streamSize, Long.MAX_VALUE, 0L), + false); + } + + /** + * Returns an effectively unlimited stream of pseudorandom {@code + * long} values from this generator and/or one split from it. + * + * @implNote This method is implemented to be equivalent to {@code + * longs(Long.MAX_VALUE)}. + * + * @return a stream of pseudorandom {@code long} values + */ + public LongStream longs() { + return StreamSupport.longStream + (new RandomLongsSpliterator + (this, 0L, Long.MAX_VALUE, Long.MAX_VALUE, 0L), + false); + } + + /** + * Returns a stream producing the given {@code streamSize} number of + * pseudorandom {@code long} values from this generator and/or one split + * from it; each value conforms to the given origin (inclusive) and bound + * (exclusive). + * + * @param streamSize the number of values to generate + * @param randomNumberOrigin the origin (inclusive) of each random value + * @param randomNumberBound the bound (exclusive) of each random value + * @return a stream of pseudorandom {@code long} values, + * each with the given origin (inclusive) and bound (exclusive) + * @throws IllegalArgumentException if {@code streamSize} is + * less than zero, or {@code randomNumberOrigin} + * is greater than or equal to {@code randomNumberBound} + */ + public LongStream longs(long streamSize, long randomNumberOrigin, + long randomNumberBound) { + if (streamSize < 0L) + throw new IllegalArgumentException(BAD_SIZE); + if (randomNumberOrigin >= randomNumberBound) + throw new IllegalArgumentException(BAD_RANGE); + return StreamSupport.longStream + (new RandomLongsSpliterator + (this, 0L, streamSize, randomNumberOrigin, randomNumberBound), + false); + } + + /** + * Returns an effectively unlimited stream of pseudorandom {@code + * long} values from this generator and/or one split from it; each value + * conforms to the given origin (inclusive) and bound (exclusive). + * + * @implNote This method is implemented to be equivalent to {@code + * longs(Long.MAX_VALUE, randomNumberOrigin, randomNumberBound)}. + * + * @param randomNumberOrigin the origin (inclusive) of each random value + * @param randomNumberBound the bound (exclusive) of each random value + * @return a stream of pseudorandom {@code long} values, + * each with the given origin (inclusive) and bound (exclusive) + * @throws IllegalArgumentException if {@code randomNumberOrigin} + * is greater than or equal to {@code randomNumberBound} + */ + public LongStream longs(long randomNumberOrigin, long randomNumberBound) { + if (randomNumberOrigin >= randomNumberBound) + throw new IllegalArgumentException(BAD_RANGE); + return StreamSupport.longStream + (new RandomLongsSpliterator + (this, 0L, Long.MAX_VALUE, randomNumberOrigin, randomNumberBound), + false); + } + + /** + * Returns a stream producing the given {@code streamSize} number of + * pseudorandom {@code double} values from this generator and/or one split + * from it; each value is between zero (inclusive) and one (exclusive). + * + * @param streamSize the number of values to generate + * @return a stream of {@code double} values + * @throws IllegalArgumentException if {@code streamSize} is + * less than zero + */ + public DoubleStream doubles(long streamSize) { + if (streamSize < 0L) + throw new IllegalArgumentException(BAD_SIZE); + return StreamSupport.doubleStream + (new RandomDoublesSpliterator + (this, 0L, streamSize, Double.MAX_VALUE, 0.0), + false); + } + + /** + * Returns an effectively unlimited stream of pseudorandom {@code + * double} values from this generator and/or one split from it; each value + * is between zero (inclusive) and one (exclusive). + * + * @implNote This method is implemented to be equivalent to {@code + * doubles(Long.MAX_VALUE)}. + * + * @return a stream of pseudorandom {@code double} values + */ + public DoubleStream doubles() { + return StreamSupport.doubleStream + (new RandomDoublesSpliterator + (this, 0L, Long.MAX_VALUE, Double.MAX_VALUE, 0.0), + false); + } + + /** + * Returns a stream producing the given {@code streamSize} number of + * pseudorandom {@code double} values from this generator and/or one split + * from it; each value conforms to the given origin (inclusive) and bound + * (exclusive). + * + * @param streamSize the number of values to generate + * @param randomNumberOrigin the origin (inclusive) of each random value + * @param randomNumberBound the bound (exclusive) of each random value + * @return a stream of pseudorandom {@code double} values, + * each with the given origin (inclusive) and bound (exclusive) + * @throws IllegalArgumentException if {@code streamSize} is + * less than zero + * @throws IllegalArgumentException if {@code randomNumberOrigin} + * is greater than or equal to {@code randomNumberBound} + */ + public DoubleStream doubles(long streamSize, double randomNumberOrigin, + double randomNumberBound) { + if (streamSize < 0L) + throw new IllegalArgumentException(BAD_SIZE); + if (!(randomNumberOrigin < randomNumberBound)) + throw new IllegalArgumentException(BAD_RANGE); + return StreamSupport.doubleStream + (new RandomDoublesSpliterator + (this, 0L, streamSize, randomNumberOrigin, randomNumberBound), + false); + } + + /** + * Returns an effectively unlimited stream of pseudorandom {@code + * double} values from this generator and/or one split from it; each value + * conforms to the given origin (inclusive) and bound (exclusive). + * + * @implNote This method is implemented to be equivalent to {@code + * doubles(Long.MAX_VALUE, randomNumberOrigin, randomNumberBound)}. + * + * @param randomNumberOrigin the origin (inclusive) of each random value + * @param randomNumberBound the bound (exclusive) of each random value + * @return a stream of pseudorandom {@code double} values, + * each with the given origin (inclusive) and bound (exclusive) + * @throws IllegalArgumentException if {@code randomNumberOrigin} + * is greater than or equal to {@code randomNumberBound} + */ + public DoubleStream doubles(double randomNumberOrigin, double randomNumberBound) { + if (!(randomNumberOrigin < randomNumberBound)) + throw new IllegalArgumentException(BAD_RANGE); + return StreamSupport.doubleStream + (new RandomDoublesSpliterator + (this, 0L, Long.MAX_VALUE, randomNumberOrigin, randomNumberBound), + false); + } + + /** + * Spliterator for int streams. We multiplex the four int + * versions into one class by treating a bound less than origin as + * unbounded, and also by treating "infinite" as equivalent to + * Long.MAX_VALUE. For splits, it uses the standard divide-by-two + * approach. The long and double versions of this class are + * identical except for types. + */ + private static final class RandomIntsSpliterator + implements Spliterator.OfInt { + final SplittableRandom rng; + long index; + final long fence; + final int origin; + final int bound; + RandomIntsSpliterator(SplittableRandom rng, long index, long fence, + int origin, int bound) { + this.rng = rng; this.index = index; this.fence = fence; + this.origin = origin; this.bound = bound; + } + + public RandomIntsSpliterator trySplit() { + long i = index, m = (i + fence) >>> 1; + return (m <= i) ? null : + new RandomIntsSpliterator(rng.split(), i, index = m, origin, bound); + } + + public long estimateSize() { + return fence - index; + } + + public int characteristics() { + return (Spliterator.SIZED | Spliterator.SUBSIZED | + Spliterator.NONNULL | Spliterator.IMMUTABLE); + } + + public boolean tryAdvance(IntConsumer consumer) { + if (consumer == null) throw new NullPointerException(); + long i = index, f = fence; + if (i < f) { + consumer.accept(rng.internalNextInt(origin, bound)); + index = i + 1; + return true; + } + return false; + } + + public void forEachRemaining(IntConsumer consumer) { + if (consumer == null) throw new NullPointerException(); + long i = index, f = fence; + if (i < f) { + index = f; + SplittableRandom r = rng; + int o = origin, b = bound; + do { + consumer.accept(r.internalNextInt(o, b)); + } while (++i < f); + } + } + } + + /** + * Spliterator for long streams. + */ + private static final class RandomLongsSpliterator + implements Spliterator.OfLong { + final SplittableRandom rng; + long index; + final long fence; + final long origin; + final long bound; + RandomLongsSpliterator(SplittableRandom rng, long index, long fence, + long origin, long bound) { + this.rng = rng; this.index = index; this.fence = fence; + this.origin = origin; this.bound = bound; + } + + public RandomLongsSpliterator trySplit() { + long i = index, m = (i + fence) >>> 1; + return (m <= i) ? null : + new RandomLongsSpliterator(rng.split(), i, index = m, origin, bound); + } + + public long estimateSize() { + return fence - index; + } + + public int characteristics() { + return (Spliterator.SIZED | Spliterator.SUBSIZED | + Spliterator.NONNULL | Spliterator.IMMUTABLE); + } + + public boolean tryAdvance(LongConsumer consumer) { + if (consumer == null) throw new NullPointerException(); + long i = index, f = fence; + if (i < f) { + consumer.accept(rng.internalNextLong(origin, bound)); + index = i + 1; + return true; + } + return false; + } + + public void forEachRemaining(LongConsumer consumer) { + if (consumer == null) throw new NullPointerException(); + long i = index, f = fence; + if (i < f) { + index = f; + SplittableRandom r = rng; + long o = origin, b = bound; + do { + consumer.accept(r.internalNextLong(o, b)); + } while (++i < f); + } + } + + } + + /** + * Spliterator for double streams. + */ + private static final class RandomDoublesSpliterator + implements Spliterator.OfDouble { + final SplittableRandom rng; + long index; + final long fence; + final double origin; + final double bound; + RandomDoublesSpliterator(SplittableRandom rng, long index, long fence, + double origin, double bound) { + this.rng = rng; this.index = index; this.fence = fence; + this.origin = origin; this.bound = bound; + } + + public RandomDoublesSpliterator trySplit() { + long i = index, m = (i + fence) >>> 1; + return (m <= i) ? null : + new RandomDoublesSpliterator(rng.split(), i, index = m, origin, bound); + } + + public long estimateSize() { + return fence - index; + } + + public int characteristics() { + return (Spliterator.SIZED | Spliterator.SUBSIZED | + Spliterator.NONNULL | Spliterator.IMMUTABLE); + } + + public boolean tryAdvance(DoubleConsumer consumer) { + if (consumer == null) throw new NullPointerException(); + long i = index, f = fence; + if (i < f) { + consumer.accept(rng.internalNextDouble(origin, bound)); + index = i + 1; + return true; + } + return false; + } + + public void forEachRemaining(DoubleConsumer consumer) { + if (consumer == null) throw new NullPointerException(); + long i = index, f = fence; + if (i < f) { + index = f; + SplittableRandom r = rng; + double o = origin, b = bound; + do { + consumer.accept(r.internalNextDouble(o, b)); + } while (++i < f); + } + } + } + +}

    Summary of Queue methods