JSR-166 update without java 1.9 method/classes
Second attempt, in frist one I've submitted some code from openJdk 1.9
that shouldn't be here, orignial change can be found at
5328e07d282bef36ac8b757bbee16a761415b2c4
Adapted from sources taken from CVS using:
cvs -d ':pserver:anonymous@gee.cs.oswego.edu/home/jsr166/jsr166' checkout -D "03/03/2016 10:00:00 GMT" jsr166
This time with hidden/removed "@since 9" methods and classes
Bug: 27426599
Change-Id: Ibd8d26e13cba091bfd983c73d005e4f8d8f5946d
diff --git a/JavaLibrary.mk b/JavaLibrary.mk
index 21ad635..756ec02 100644
--- a/JavaLibrary.mk
+++ b/JavaLibrary.mk
@@ -215,7 +215,7 @@
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 9e83de2..c293f13 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 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(AbstractExecutorServiceTest.class);
// }
/**
@@ -196,28 +197,25 @@
public void testInterruptedSubmit() throws InterruptedException {
final CountDownLatch submitted = new CountDownLatch(1);
final CountDownLatch quittingTime = new CountDownLatch(1);
+ final Callable<Void> awaiter = new CheckedCallable<Void>() {
+ public Void realCall() throws InterruptedException {
+ assertTrue(quittingTime.await(2*LONG_DELAY_MS, MILLISECONDS));
+ return null;
+ }};
final ExecutorService p
= new ThreadPoolExecutor(1,1,60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10));
- final Callable<Void> awaiter = new CheckedCallable<Void>() {
- public Void realCall() throws InterruptedException {
- quittingTime.await();
- return null;
- }};
- try {
- Thread t = new Thread(new CheckedInterruptedRunnable() {
+ try (PoolCleaner cleaner = cleaner(p, quittingTime)) {
+ Thread t = newStartedThread(new CheckedInterruptedRunnable() {
public void realRun() throws Exception {
Future<Void> 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 @@
* throws exception
*/
public void testSubmitEE() throws InterruptedException {
- ThreadPoolExecutor p =
+ final ThreadPoolExecutor p =
new ThreadPoolExecutor(1, 1,
60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
* invokeAny(empty collection) throws IAE
*/
public void testInvokeAny2() throws Exception {
- ExecutorService e = new DirectExecutorService();
- try {
- e.invokeAny(new ArrayList<Callable<String>>());
- shouldThrow();
- } catch (IllegalArgumentException success) {
- } finally {
- joinPool(e);
+ final ExecutorService e = new DirectExecutorService();
+ try (PoolCleaner cleaner = cleaner(e)) {
+ try {
+ e.invokeAny(new ArrayList<Callable<String>>());
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
}
@@ -275,17 +270,16 @@
* invokeAny(c) throws NPE if c has null elements
*/
public void testInvokeAny3() throws Exception {
- ExecutorService e = new DirectExecutorService();
- List<Callable<Long>> l = new ArrayList<Callable<Long>>();
- l.add(new Callable<Long>() {
- 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<Callable<Long>> l = new ArrayList<Callable<Long>>();
+ l.add(new Callable<Long>() {
+ public Long call() { throw new ArithmeticException(); }});
+ l.add(null);
+ try {
+ e.invokeAny(l);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -293,16 +287,16 @@
* invokeAny(c) throws ExecutionException if no task in c completes
*/
public void testInvokeAny4() throws InterruptedException {
- ExecutorService e = new DirectExecutorService();
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ try {
+ e.invokeAny(l);
+ shouldThrow();
+ } catch (ExecutionException success) {
+ assertTrue(success.getCause() instanceof NullPointerException);
+ }
}
}
@@ -310,15 +304,13 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
String result = e.invokeAny(l);
assertSame(TEST_STRING, result);
- } finally {
- joinPool(e);
}
}
@@ -326,13 +318,12 @@
* 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 @@
* 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<Future<String>> r = e.invokeAll(new ArrayList<Callable<String>>());
assertTrue(r.isEmpty());
- } finally {
- joinPool(e);
}
}
@@ -353,16 +342,15 @@
* invokeAll(c) throws NPE if c has null elements
*/
public void testInvokeAll3() throws InterruptedException {
- ExecutorService e = new DirectExecutorService();
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ l.add(null);
+ try {
+ e.invokeAll(l);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -370,8 +358,8 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new NPETask());
List<Future<String>> futures = e.invokeAll(l);
@@ -382,8 +370,6 @@
} catch (ExecutionException success) {
assertTrue(success.getCause() instanceof NullPointerException);
}
- } finally {
- joinPool(e);
}
}
@@ -391,8 +377,8 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
@@ -400,8 +386,6 @@
assertEquals(2, futures.size());
for (Future<String> future : futures)
assertSame(TEST_STRING, future.get());
- } finally {
- joinPool(e);
}
}
@@ -409,13 +393,12 @@
* 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 @@
* timed invokeAny(null time unit) throws NPE
*/
public void testTimedInvokeAnyNullTimeUnit() throws Exception {
- ExecutorService e = new DirectExecutorService();
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ try {
+ e.invokeAny(l, MEDIUM_DELAY_MS, null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -439,13 +421,13 @@
* timed invokeAny(empty collection) throws IAE
*/
public void testTimedInvokeAny2() throws Exception {
- ExecutorService e = new DirectExecutorService();
- try {
- e.invokeAny(new ArrayList<Callable<String>>(), 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<Callable<String>>(),
+ MEDIUM_DELAY_MS, MILLISECONDS);
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
}
@@ -453,17 +435,16 @@
* timed invokeAny(c) throws NPE if c has null elements
*/
public void testTimedInvokeAny3() throws Exception {
- ExecutorService e = new DirectExecutorService();
- List<Callable<Long>> l = new ArrayList<Callable<Long>>();
- l.add(new Callable<Long>() {
- 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<Callable<Long>> l = new ArrayList<Callable<Long>>();
+ l.add(new Callable<Long>() {
+ 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 @@
* timed invokeAny(c) throws ExecutionException if no task completes
*/
public void testTimedInvokeAny4() throws Exception {
- ExecutorService e = new DirectExecutorService();
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ 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 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
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 @@
* 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 @@
* timed invokeAll(null time unit) throws NPE
*/
public void testTimedInvokeAllNullTimeUnit() throws InterruptedException {
- ExecutorService e = new DirectExecutorService();
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ try {
+ e.invokeAll(l, MEDIUM_DELAY_MS, null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -534,12 +515,10 @@
* 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<Future<String>> r = e.invokeAll(new ArrayList<Callable<String>>(), MEDIUM_DELAY_MS, MILLISECONDS);
assertTrue(r.isEmpty());
- } finally {
- joinPool(e);
}
}
@@ -547,16 +526,15 @@
* timed invokeAll(c) throws NPE if c has null elements
*/
public void testTimedInvokeAll3() throws InterruptedException {
- ExecutorService e = new DirectExecutorService();
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ l.add(null);
+ try {
+ e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -564,12 +542,12 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new NPETask());
List<Future<String>> 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 @@
} catch (ExecutionException success) {
assertTrue(success.getCause() instanceof NullPointerException);
}
- } finally {
- joinPool(e);
}
}
@@ -586,41 +562,51 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
List<Future<String>> futures =
- e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS);
+ e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS);
assertEquals(2, futures.size());
for (Future<String> 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<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new StringTask());
- l.add(Executors.callable(possiblyInterruptedRunnable(2 * SHORT_DELAY_MS), TEST_STRING));
- l.add(new StringTask());
- List<Future<String>> 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<Callable<String>> 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<Future<String>> 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 2aa7326..bf25668 100644
--- a/jsr166-tests/src/test/java/jsr166/AbstractQueueTest.java
+++ b/jsr166-tests/src/test/java/jsr166/AbstractQueueTest.java
@@ -24,7 +24,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(AbstractQueueTest.class);
// }
static class Succeed extends AbstractQueue<Integer> {
@@ -158,7 +158,7 @@
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 8604d86..c462c73 100644
--- a/jsr166-tests/src/test/java/jsr166/AbstractQueuedLongSynchronizerTest.java
+++ b/jsr166-tests/src/test/java/jsr166/AbstractQueuedLongSynchronizerTest.java
@@ -29,7 +29,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(AbstractQueuedLongSynchronizerTest.class);
// }
/**
@@ -241,25 +241,33 @@
*/
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 b3c4110..d102fc6 100644
--- a/jsr166-tests/src/test/java/jsr166/AbstractQueuedSynchronizerTest.java
+++ b/jsr166-tests/src/test/java/jsr166/AbstractQueuedSynchronizerTest.java
@@ -29,7 +29,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(AbstractQueuedSynchronizerTest.class);
// }
/**
@@ -244,25 +244,33 @@
*/
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 247c90e..902ae40 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 @@
// 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 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<Integer> elements = Arrays.asList(ints);
try {
@@ -171,7 +173,7 @@
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 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 @@
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 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.remove();
}
}
@@ -590,7 +590,7 @@
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 @@
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 @@
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 @@
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 @@
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());
- }});
+ 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());
- }});
-
- joinPool(executor);
+ 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 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);
- }});
+ 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);
- }});
-
- joinPool(executor);
+ executor.execute(new CheckedRunnable() {
+ public void realRun() throws InterruptedException {
+ threadsStarted.await();
+ q.put(one);
+ }});
+ }
}
/**
@@ -886,7 +886,7 @@
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 @@
* 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 @@
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 16290e9..23cc6b9 100644
--- a/jsr166-tests/src/test/java/jsr166/ArrayDequeTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ArrayDequeTest.java
@@ -26,7 +26,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ArrayDequeTest.class);
// }
/**
@@ -65,8 +65,7 @@
*/
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 @@
* 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 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 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 @@
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 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 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 @@
}
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 0000000..f81c44f
--- /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<Atomic8Test,Integer> 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<Integer> a = new AtomicReference<Integer>(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<Integer> a = new AtomicReference<Integer>(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<Integer> a = new AtomicReference<Integer>(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<Integer> a = new AtomicReference<Integer>(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<Integer> a = new AtomicReferenceArray<Integer>(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<Integer> a = new AtomicReferenceArray<Integer>(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<Integer> a = new AtomicReferenceArray<Integer>(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<Integer> a = new AtomicReferenceArray<Integer>(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<Atomic8Test,Integer> 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<Atomic8Test,Integer> 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<Atomic8Test,Integer> 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<Atomic8Test,Integer> 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 bfe3fc6..6f5decf 100644
--- a/jsr166-tests/src/test/java/jsr166/AtomicBooleanTest.java
+++ b/jsr166-tests/src/test/java/jsr166/AtomicBooleanTest.java
@@ -21,7 +21,7 @@
// 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 670b9ce..c9e32aa 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 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(AtomicIntegerArrayTest.class);
// }
/**
@@ -290,7 +291,7 @@
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 ef75b46..c8d3856 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 @@
// 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<AtomicIntegerFieldUpdaterTest> a =
+ // AtomicIntegerFieldUpdater.newUpdater
+ // (AtomicIntegerFieldUpdaterTest.class, "privateField");
+ // shouldThrow();
+ // } catch (RuntimeException success) {
+ // assertNotNull(success.getCause());
+ // }
+ // }
+
+ // public void checkCompareAndSetProtectedSub() {
+ // AtomicIntegerFieldUpdater<AtomicIntegerFieldUpdaterTest> 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<AtomicIntegerFieldUpdaterTest> 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<AtomicIntegerFieldUpdaterTest> a =
+ // AtomicIntegerFieldUpdater.newUpdater
+ // (AtomicIntegerFieldUpdaterTest.class, "privateField");
+ // throw new AssertionError("should throw");
+ // } catch (RuntimeException success) {
+ // assertNotNull(success.getCause());
+ // }
+ // }
// }
AtomicIntegerFieldUpdater<AtomicIntegerFieldUpdaterTest> updaterFor(String fieldName) {
@@ -65,6 +119,26 @@
}
/**
+ * 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
*/
public void testGetSet() {
@@ -109,6 +183,34 @@
}
/**
+ * compareAndSet succeeds in changing protected field value if
+ * equal to expected else fails
+ */
+ public void testCompareAndSetProtected() {
+ AtomicIntegerFieldUpdater<AtomicIntegerFieldUpdaterTest> 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 cf73810..6392c54 100644
--- a/jsr166-tests/src/test/java/jsr166/AtomicIntegerTest.java
+++ b/jsr166-tests/src/test/java/jsr166/AtomicIntegerTest.java
@@ -21,7 +21,7 @@
// 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 08df01e..a60ecfd 100644
--- a/jsr166-tests/src/test/java/jsr166/AtomicLongArrayTest.java
+++ b/jsr166-tests/src/test/java/jsr166/AtomicLongArrayTest.java
@@ -22,7 +22,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(AtomicLongArrayTest.class);
// }
/**
@@ -290,7 +290,7 @@
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 204f814..d46280b 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 @@
// 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<AtomicLongFieldUpdaterTest> a =
+ // AtomicLongFieldUpdater.newUpdater
+ // (AtomicLongFieldUpdaterTest.class, "privateField");
+ // shouldThrow();
+ // } catch (RuntimeException success) {
+ // assertNotNull(success.getCause());
+ // }
+ // }
+
+ // public void checkCompareAndSetProtectedSub() {
+ // AtomicLongFieldUpdater<AtomicLongFieldUpdaterTest> 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<AtomicLongFieldUpdaterTest> 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<AtomicLongFieldUpdaterTest> a =
+ // AtomicLongFieldUpdater.newUpdater
+ // (AtomicLongFieldUpdaterTest.class, "privateField");
+ // throw new AssertionError("should throw");
+ // } catch (RuntimeException success) {
+ // assertNotNull(success.getCause());
+ // }
+ // }
// }
AtomicLongFieldUpdater<AtomicLongFieldUpdaterTest> updaterFor(String fieldName) {
@@ -66,6 +119,26 @@
}
/**
+ * 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
*/
public void testGetSet() {
@@ -110,6 +183,34 @@
}
/**
+ * compareAndSet succeeds in changing protected field value if
+ * equal to expected else fails
+ */
+ public void testCompareAndSetProtected() {
+ AtomicLongFieldUpdater<AtomicLongFieldUpdaterTest> 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 b9c1722..a8ee7c6 100644
--- a/jsr166-tests/src/test/java/jsr166/AtomicLongTest.java
+++ b/jsr166-tests/src/test/java/jsr166/AtomicLongTest.java
@@ -21,7 +21,7 @@
// 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 61b6b1b..bd4e8cb 100644
--- a/jsr166-tests/src/test/java/jsr166/AtomicMarkableReferenceTest.java
+++ b/jsr166-tests/src/test/java/jsr166/AtomicMarkableReferenceTest.java
@@ -21,7 +21,7 @@
// 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 1df2f9f..f3aab44 100644
--- a/jsr166-tests/src/test/java/jsr166/AtomicReferenceArrayTest.java
+++ b/jsr166-tests/src/test/java/jsr166/AtomicReferenceArrayTest.java
@@ -22,7 +22,7 @@
// 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 4b0d946..9b2e9a9 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 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(AtomicReferenceFieldUpdaterTest.class);
// }
- AtomicReferenceFieldUpdater<AtomicReferenceFieldUpdaterTest, Integer> 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<AtomicReferenceFieldUpdaterTest,Integer> a =
+ // AtomicReferenceFieldUpdater.newUpdater
+ // (AtomicReferenceFieldUpdaterTest.class, Integer.class, "privateField");
+ // shouldThrow();
+ // } catch (RuntimeException success) {
+ // assertNotNull(success.getCause());
+ // }
+ // }
+
+ // public void checkCompareAndSetProtectedSub() {
+ // AtomicReferenceFieldUpdater<AtomicReferenceFieldUpdaterTest,Integer> 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<AtomicReferenceFieldUpdaterTest,Integer> 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<AtomicReferenceFieldUpdaterTest,Integer> a =
+ // AtomicReferenceFieldUpdater.newUpdater
+ // (AtomicReferenceFieldUpdaterTest.class, Integer.class, "privateField");
+ // throw new AssertionError("should throw");
+ // } catch (RuntimeException success) {
+ // assertNotNull(success.getCause());
+ // }
+ // }
+ // }
+
+ static AtomicReferenceFieldUpdater<AtomicReferenceFieldUpdaterTest, Integer> updaterFor(String fieldName) {
return AtomicReferenceFieldUpdater.newUpdater
(AtomicReferenceFieldUpdaterTest.class, Integer.class, fieldName);
}
@@ -77,6 +131,26 @@
}
/**
+ * 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
*/
public void testGetSet() {
diff --git a/jsr166-tests/src/test/java/jsr166/AtomicReferenceTest.java b/jsr166-tests/src/test/java/jsr166/AtomicReferenceTest.java
index 457182f..4728970 100644
--- a/jsr166-tests/src/test/java/jsr166/AtomicReferenceTest.java
+++ b/jsr166-tests/src/test/java/jsr166/AtomicReferenceTest.java
@@ -21,7 +21,7 @@
// 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 b3ff06a..a2e8c7f 100644
--- a/jsr166-tests/src/test/java/jsr166/AtomicStampedReferenceTest.java
+++ b/jsr166-tests/src/test/java/jsr166/AtomicStampedReferenceTest.java
@@ -21,7 +21,7 @@
// 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 db0f03d..1a188e1 100644
--- a/jsr166-tests/src/test/java/jsr166/BlockingQueueTest.java
+++ b/jsr166-tests/src/test/java/jsr166/BlockingQueueTest.java
@@ -39,9 +39,9 @@
// 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 @@
shouldThrow();
} catch (InterruptedException success) {}
assertFalse(Thread.interrupted());
+
+ assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS);
}});
barrier.await();
@@ -352,20 +354,20 @@
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 0000000..634182b
--- /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<Object> 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 0000000..4ba5bda
--- /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 0000000..44ef66d
--- /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 0000000..1372cc4
--- /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); }
+ }
+
+ <T> void checkCompletedNormally(CompletableFuture<T> 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.
+ */
+ <U> Throwable exceptionalCompletion(CompletableFuture<U> 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<Throwable> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> f = new CompletableFuture<>();
+ assertEquals(0, f.getNumberOfDependents());
+ final CompletableFuture<Void> g = m.thenRun(f, new Noop(m));
+ assertEquals(1, f.getNumberOfDependents());
+ assertEquals(0, g.getNumberOfDependents());
+ final CompletableFuture<Void> 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<String> f;
+
+ f = new CompletableFuture<String>();
+ assertTrue(f.toString().contains("[Not completed]"));
+
+ assertTrue(f.complete("foo"));
+ assertTrue(f.toString().contains("[Completed normally]"));
+
+ f = new CompletableFuture<String>();
+ assertTrue(f.completeExceptionally(new IndexOutOfBoundsException()));
+ assertTrue(f.toString().contains("[Completed exceptionally]"));
+
+ for (boolean mayInterruptIfRunning : new boolean[] { true, false }) {
+ f = new CompletableFuture<String>();
+ assertTrue(f.cancel(mayInterruptIfRunning));
+ assertTrue(f.toString().contains("[Completed exceptionally]"));
+ }
+ }
+
+ /**
+ * completedFuture returns a completed CompletableFuture with given value
+ */
+ public void testCompletedFuture() {
+ CompletableFuture<String> 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<Integer>
+ {
+ 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<Integer>
+ {
+ NoopConsumer(ExecutionMode m) { super(m); }
+ public void accept(Integer x) {
+ invoked();
+ value = x;
+ }
+ }
+
+ class IncFunction extends CheckedIntegerAction
+ implements Function<Integer,Integer>
+ {
+ 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<Integer, Integer>
+ {
+ SubtractAction(ExecutionMode m) { super(m); }
+ public void accept(Integer x, Integer y) {
+ invoked();
+ value = subtract(x, y);
+ }
+ }
+
+ class SubtractFunction extends CheckedIntegerAction
+ implements BiFunction<Integer, Integer, Integer>
+ {
+ 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<Integer>
+ {
+ FailingSupplier(ExecutionMode m) { super(m); }
+ public Integer get() {
+ invoked();
+ throw new CFException();
+ }
+ }
+
+ class FailingConsumer extends CheckedIntegerAction
+ implements Consumer<Integer>
+ {
+ FailingConsumer(ExecutionMode m) { super(m); }
+ public void accept(Integer x) {
+ invoked();
+ value = x;
+ throw new CFException();
+ }
+ }
+
+ class FailingBiConsumer extends CheckedIntegerAction
+ implements BiConsumer<Integer, Integer>
+ {
+ 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<Integer, Integer>
+ {
+ FailingFunction(ExecutionMode m) { super(m); }
+ public Integer apply(Integer x) {
+ invoked();
+ value = x;
+ throw new CFException();
+ }
+ }
+
+ class FailingBiFunction extends CheckedIntegerAction
+ implements BiFunction<Integer, Integer, Integer>
+ {
+ 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<Integer, CompletableFuture<Integer>>
+ {
+ CompletableFutureInc(ExecutionMode m) { super(m); }
+ public CompletableFuture<Integer> apply(Integer x) {
+ invoked();
+ value = x;
+ CompletableFuture<Integer> f = new CompletableFuture<>();
+ assertTrue(f.complete(inc(x)));
+ return f;
+ }
+ }
+
+ class FailingCompletableFutureFunction extends CheckedIntegerAction
+ implements Function<Integer, CompletableFuture<Integer>>
+ {
+ FailingCompletableFutureFunction(ExecutionMode m) { super(m); }
+ public CompletableFuture<Integer> 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<Void> runAsync(Runnable a) {
+ throw new UnsupportedOperationException();
+ }
+ public <U> CompletableFuture<U> supplyAsync(Supplier<U> a) {
+ throw new UnsupportedOperationException();
+ }
+ public <T> CompletableFuture<Void> thenRun
+ (CompletableFuture<T> f, Runnable a) {
+ return f.thenRun(a);
+ }
+ public <T> CompletableFuture<Void> thenAccept
+ (CompletableFuture<T> f, Consumer<? super T> a) {
+ return f.thenAccept(a);
+ }
+ public <T,U> CompletableFuture<U> thenApply
+ (CompletableFuture<T> f, Function<? super T,U> a) {
+ return f.thenApply(a);
+ }
+ public <T,U> CompletableFuture<U> thenCompose
+ (CompletableFuture<T> f,
+ Function<? super T,? extends CompletionStage<U>> a) {
+ return f.thenCompose(a);
+ }
+ public <T,U> CompletableFuture<U> handle
+ (CompletableFuture<T> f,
+ BiFunction<? super T,Throwable,? extends U> a) {
+ return f.handle(a);
+ }
+ public <T> CompletableFuture<T> whenComplete
+ (CompletableFuture<T> f,
+ BiConsumer<? super T,? super Throwable> a) {
+ return f.whenComplete(a);
+ }
+ public <T,U> CompletableFuture<Void> runAfterBoth
+ (CompletableFuture<T> f, CompletableFuture<U> g, Runnable a) {
+ return f.runAfterBoth(g, a);
+ }
+ public <T,U> CompletableFuture<Void> thenAcceptBoth
+ (CompletableFuture<T> f,
+ CompletionStage<? extends U> g,
+ BiConsumer<? super T,? super U> a) {
+ return f.thenAcceptBoth(g, a);
+ }
+ public <T,U,V> CompletableFuture<V> thenCombine
+ (CompletableFuture<T> f,
+ CompletionStage<? extends U> g,
+ BiFunction<? super T,? super U,? extends V> a) {
+ return f.thenCombine(g, a);
+ }
+ public <T> CompletableFuture<Void> runAfterEither
+ (CompletableFuture<T> f,
+ CompletionStage<?> g,
+ java.lang.Runnable a) {
+ return f.runAfterEither(g, a);
+ }
+ public <T> CompletableFuture<Void> acceptEither
+ (CompletableFuture<T> f,
+ CompletionStage<? extends T> g,
+ Consumer<? super T> a) {
+ return f.acceptEither(g, a);
+ }
+ public <T,U> CompletableFuture<U> applyToEither
+ (CompletableFuture<T> f,
+ CompletionStage<? extends T> g,
+ Function<? super T,U> a) {
+ return f.applyToEither(g, a);
+ }
+ },
+
+ ASYNC {
+ public void checkExecutionMode() {
+ assertEquals(defaultExecutorIsCommonPool,
+ (ForkJoinPool.commonPool() == ForkJoinTask.getPool()));
+ }
+ public CompletableFuture<Void> runAsync(Runnable a) {
+ return CompletableFuture.runAsync(a);
+ }
+ public <U> CompletableFuture<U> supplyAsync(Supplier<U> a) {
+ return CompletableFuture.supplyAsync(a);
+ }
+ public <T> CompletableFuture<Void> thenRun
+ (CompletableFuture<T> f, Runnable a) {
+ return f.thenRunAsync(a);
+ }
+ public <T> CompletableFuture<Void> thenAccept
+ (CompletableFuture<T> f, Consumer<? super T> a) {
+ return f.thenAcceptAsync(a);
+ }
+ public <T,U> CompletableFuture<U> thenApply
+ (CompletableFuture<T> f, Function<? super T,U> a) {
+ return f.thenApplyAsync(a);
+ }
+ public <T,U> CompletableFuture<U> thenCompose
+ (CompletableFuture<T> f,
+ Function<? super T,? extends CompletionStage<U>> a) {
+ return f.thenComposeAsync(a);
+ }
+ public <T,U> CompletableFuture<U> handle
+ (CompletableFuture<T> f,
+ BiFunction<? super T,Throwable,? extends U> a) {
+ return f.handleAsync(a);
+ }
+ public <T> CompletableFuture<T> whenComplete
+ (CompletableFuture<T> f,
+ BiConsumer<? super T,? super Throwable> a) {
+ return f.whenCompleteAsync(a);
+ }
+ public <T,U> CompletableFuture<Void> runAfterBoth
+ (CompletableFuture<T> f, CompletableFuture<U> g, Runnable a) {
+ return f.runAfterBothAsync(g, a);
+ }
+ public <T,U> CompletableFuture<Void> thenAcceptBoth
+ (CompletableFuture<T> f,
+ CompletionStage<? extends U> g,
+ BiConsumer<? super T,? super U> a) {
+ return f.thenAcceptBothAsync(g, a);
+ }
+ public <T,U,V> CompletableFuture<V> thenCombine
+ (CompletableFuture<T> f,
+ CompletionStage<? extends U> g,
+ BiFunction<? super T,? super U,? extends V> a) {
+ return f.thenCombineAsync(g, a);
+ }
+ public <T> CompletableFuture<Void> runAfterEither
+ (CompletableFuture<T> f,
+ CompletionStage<?> g,
+ java.lang.Runnable a) {
+ return f.runAfterEitherAsync(g, a);
+ }
+ public <T> CompletableFuture<Void> acceptEither
+ (CompletableFuture<T> f,
+ CompletionStage<? extends T> g,
+ Consumer<? super T> a) {
+ return f.acceptEitherAsync(g, a);
+ }
+ public <T,U> CompletableFuture<U> applyToEither
+ (CompletableFuture<T> f,
+ CompletionStage<? extends T> g,
+ Function<? super T,U> a) {
+ return f.applyToEitherAsync(g, a);
+ }
+ },
+
+ EXECUTOR {
+ public void checkExecutionMode() {
+ assertTrue(ThreadExecutor.startedCurrentThread());
+ }
+ public CompletableFuture<Void> runAsync(Runnable a) {
+ return CompletableFuture.runAsync(a, new ThreadExecutor());
+ }
+ public <U> CompletableFuture<U> supplyAsync(Supplier<U> a) {
+ return CompletableFuture.supplyAsync(a, new ThreadExecutor());
+ }
+ public <T> CompletableFuture<Void> thenRun
+ (CompletableFuture<T> f, Runnable a) {
+ return f.thenRunAsync(a, new ThreadExecutor());
+ }
+ public <T> CompletableFuture<Void> thenAccept
+ (CompletableFuture<T> f, Consumer<? super T> a) {
+ return f.thenAcceptAsync(a, new ThreadExecutor());
+ }
+ public <T,U> CompletableFuture<U> thenApply
+ (CompletableFuture<T> f, Function<? super T,U> a) {
+ return f.thenApplyAsync(a, new ThreadExecutor());
+ }
+ public <T,U> CompletableFuture<U> thenCompose
+ (CompletableFuture<T> f,
+ Function<? super T,? extends CompletionStage<U>> a) {
+ return f.thenComposeAsync(a, new ThreadExecutor());
+ }
+ public <T,U> CompletableFuture<U> handle
+ (CompletableFuture<T> f,
+ BiFunction<? super T,Throwable,? extends U> a) {
+ return f.handleAsync(a, new ThreadExecutor());
+ }
+ public <T> CompletableFuture<T> whenComplete
+ (CompletableFuture<T> f,
+ BiConsumer<? super T,? super Throwable> a) {
+ return f.whenCompleteAsync(a, new ThreadExecutor());
+ }
+ public <T,U> CompletableFuture<Void> runAfterBoth
+ (CompletableFuture<T> f, CompletableFuture<U> g, Runnable a) {
+ return f.runAfterBothAsync(g, a, new ThreadExecutor());
+ }
+ public <T,U> CompletableFuture<Void> thenAcceptBoth
+ (CompletableFuture<T> f,
+ CompletionStage<? extends U> g,
+ BiConsumer<? super T,? super U> a) {
+ return f.thenAcceptBothAsync(g, a, new ThreadExecutor());
+ }
+ public <T,U,V> CompletableFuture<V> thenCombine
+ (CompletableFuture<T> f,
+ CompletionStage<? extends U> g,
+ BiFunction<? super T,? super U,? extends V> a) {
+ return f.thenCombineAsync(g, a, new ThreadExecutor());
+ }
+ public <T> CompletableFuture<Void> runAfterEither
+ (CompletableFuture<T> f,
+ CompletionStage<?> g,
+ java.lang.Runnable a) {
+ return f.runAfterEitherAsync(g, a, new ThreadExecutor());
+ }
+ public <T> CompletableFuture<Void> acceptEither
+ (CompletableFuture<T> f,
+ CompletionStage<? extends T> g,
+ Consumer<? super T> a) {
+ return f.acceptEitherAsync(g, a, new ThreadExecutor());
+ }
+ public <T,U> CompletableFuture<U> applyToEither
+ (CompletableFuture<T> f,
+ CompletionStage<? extends T> g,
+ Function<? super T,U> a) {
+ return f.applyToEitherAsync(g, a, new ThreadExecutor());
+ }
+ };
+
+ public abstract void checkExecutionMode();
+ public abstract CompletableFuture<Void> runAsync(Runnable a);
+ public abstract <U> CompletableFuture<U> supplyAsync(Supplier<U> a);
+ public abstract <T> CompletableFuture<Void> thenRun
+ (CompletableFuture<T> f, Runnable a);
+ public abstract <T> CompletableFuture<Void> thenAccept
+ (CompletableFuture<T> f, Consumer<? super T> a);
+ public abstract <T,U> CompletableFuture<U> thenApply
+ (CompletableFuture<T> f, Function<? super T,U> a);
+ public abstract <T,U> CompletableFuture<U> thenCompose
+ (CompletableFuture<T> f,
+ Function<? super T,? extends CompletionStage<U>> a);
+ public abstract <T,U> CompletableFuture<U> handle
+ (CompletableFuture<T> f,
+ BiFunction<? super T,Throwable,? extends U> a);
+ public abstract <T> CompletableFuture<T> whenComplete
+ (CompletableFuture<T> f,
+ BiConsumer<? super T,? super Throwable> a);
+ public abstract <T,U> CompletableFuture<Void> runAfterBoth
+ (CompletableFuture<T> f, CompletableFuture<U> g, Runnable a);
+ public abstract <T,U> CompletableFuture<Void> thenAcceptBoth
+ (CompletableFuture<T> f,
+ CompletionStage<? extends U> g,
+ BiConsumer<? super T,? super U> a);
+ public abstract <T,U,V> CompletableFuture<V> thenCombine
+ (CompletableFuture<T> f,
+ CompletionStage<? extends U> g,
+ BiFunction<? super T,? super U,? extends V> a);
+ public abstract <T> CompletableFuture<Void> runAfterEither
+ (CompletableFuture<T> f,
+ CompletionStage<?> g,
+ java.lang.Runnable a);
+ public abstract <T> CompletableFuture<Void> acceptEither
+ (CompletableFuture<T> f,
+ CompletionStage<? extends T> g,
+ Consumer<? super T> a);
+ public abstract <T,U> CompletableFuture<U> applyToEither
+ (CompletableFuture<T> f,
+ CompletionStage<? extends T> g,
+ Function<? super T,U> 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<Integer> f = new CompletableFuture<>();
+ if (!createIncomplete) assertTrue(f.complete(v1));
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ if (!createIncomplete) f.completeExceptionally(ex);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ if (!createIncomplete) f.completeExceptionally(ex1);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ if (!createIncomplete) assertTrue(f.complete(v1));
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ if (!createIncomplete) f.completeExceptionally(ex);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ if (!createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning));
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ if (!createIncomplete) assertTrue(f.complete(v1));
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+
+ if (!createIncomplete) f.completeExceptionally(ex1);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final AtomicInteger a = new AtomicInteger(0);
+ if (!createIncomplete) assertTrue(f.complete(v1));
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final AtomicInteger a = new AtomicInteger(0);
+ final CFException ex = new CFException();
+ if (!createIncomplete) f.completeExceptionally(ex);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final AtomicInteger a = new AtomicInteger(0);
+ if (!createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning));
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final AtomicInteger a = new AtomicInteger(0);
+ final CFException ex = new CFException();
+ if (!createIncomplete) assertTrue(f.complete(v1));
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+
+ if (!createIncomplete) f.completeExceptionally(ex1);
+ final CompletableFuture<Integer> 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<Void> 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<Void> 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<Integer> 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<Integer> 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<Integer> f = new CompletableFuture<>();
+ final Noop[] rs = new Noop[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m);
+
+ final CompletableFuture<Void> h0 = m.thenRun(f, rs[0]);
+ final CompletableFuture<Void> h1 = m.runAfterBoth(f, f, rs[1]);
+ final CompletableFuture<Void> h2 = m.runAfterEither(f, f, rs[2]);
+ checkIncomplete(h0);
+ checkIncomplete(h1);
+ checkIncomplete(h2);
+ assertTrue(f.complete(v1));
+ final CompletableFuture<Void> h3 = m.thenRun(f, rs[3]);
+ final CompletableFuture<Void> h4 = m.runAfterBoth(f, f, rs[4]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final Noop[] rs = new Noop[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m);
+
+ final CompletableFuture<Void> h0 = m.thenRun(f, rs[0]);
+ final CompletableFuture<Void> h1 = m.runAfterBoth(f, f, rs[1]);
+ final CompletableFuture<Void> h2 = m.runAfterEither(f, f, rs[2]);
+ checkIncomplete(h0);
+ checkIncomplete(h1);
+ checkIncomplete(h2);
+ assertTrue(f.completeExceptionally(ex));
+ final CompletableFuture<Void> h3 = m.thenRun(f, rs[3]);
+ final CompletableFuture<Void> h4 = m.runAfterBoth(f, f, rs[4]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final Noop[] rs = new Noop[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m);
+
+ final CompletableFuture<Void> h0 = m.thenRun(f, rs[0]);
+ final CompletableFuture<Void> h1 = m.runAfterBoth(f, f, rs[1]);
+ final CompletableFuture<Void> h2 = m.runAfterEither(f, f, rs[2]);
+ checkIncomplete(h0);
+ checkIncomplete(h1);
+ checkIncomplete(h2);
+ assertTrue(f.cancel(mayInterruptIfRunning));
+ final CompletableFuture<Void> h3 = m.thenRun(f, rs[3]);
+ final CompletableFuture<Void> h4 = m.runAfterBoth(f, f, rs[4]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final FailingRunnable[] rs = new FailingRunnable[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new FailingRunnable(m);
+
+ final CompletableFuture<Void> h0 = m.thenRun(f, rs[0]);
+ final CompletableFuture<Void> h1 = m.runAfterBoth(f, f, rs[1]);
+ final CompletableFuture<Void> h2 = m.runAfterEither(f, f, rs[2]);
+ assertTrue(f.complete(v1));
+ final CompletableFuture<Void> h3 = m.thenRun(f, rs[3]);
+ final CompletableFuture<Void> h4 = m.runAfterBoth(f, f, rs[4]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final IncFunction[] rs = new IncFunction[4];
+ for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m);
+
+ final CompletableFuture<Integer> h0 = m.thenApply(f, rs[0]);
+ final CompletableFuture<Integer> h1 = m.applyToEither(f, f, rs[1]);
+ checkIncomplete(h0);
+ checkIncomplete(h1);
+ assertTrue(f.complete(v1));
+ final CompletableFuture<Integer> h2 = m.thenApply(f, rs[2]);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final IncFunction[] rs = new IncFunction[4];
+ for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m);
+
+ final CompletableFuture<Integer> h0 = m.thenApply(f, rs[0]);
+ final CompletableFuture<Integer> h1 = m.applyToEither(f, f, rs[1]);
+ assertTrue(f.completeExceptionally(ex));
+ final CompletableFuture<Integer> h2 = m.thenApply(f, rs[2]);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final IncFunction[] rs = new IncFunction[4];
+ for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m);
+
+ final CompletableFuture<Integer> h0 = m.thenApply(f, rs[0]);
+ final CompletableFuture<Integer> h1 = m.applyToEither(f, f, rs[1]);
+ assertTrue(f.cancel(mayInterruptIfRunning));
+ final CompletableFuture<Integer> h2 = m.thenApply(f, rs[2]);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final FailingFunction[] rs = new FailingFunction[4];
+ for (int i = 0; i < rs.length; i++) rs[i] = new FailingFunction(m);
+
+ final CompletableFuture<Integer> h0 = m.thenApply(f, rs[0]);
+ final CompletableFuture<Integer> h1 = m.applyToEither(f, f, rs[1]);
+ assertTrue(f.complete(v1));
+ final CompletableFuture<Integer> h2 = m.thenApply(f, rs[2]);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final NoopConsumer[] rs = new NoopConsumer[4];
+ for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m);
+
+ final CompletableFuture<Void> h0 = m.thenAccept(f, rs[0]);
+ final CompletableFuture<Void> h1 = m.acceptEither(f, f, rs[1]);
+ checkIncomplete(h0);
+ checkIncomplete(h1);
+ assertTrue(f.complete(v1));
+ final CompletableFuture<Void> h2 = m.thenAccept(f, rs[2]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final NoopConsumer[] rs = new NoopConsumer[4];
+ for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m);
+
+ final CompletableFuture<Void> h0 = m.thenAccept(f, rs[0]);
+ final CompletableFuture<Void> h1 = m.acceptEither(f, f, rs[1]);
+ assertTrue(f.completeExceptionally(ex));
+ final CompletableFuture<Void> h2 = m.thenAccept(f, rs[2]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final NoopConsumer[] rs = new NoopConsumer[4];
+ for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m);
+
+ final CompletableFuture<Void> h0 = m.thenAccept(f, rs[0]);
+ final CompletableFuture<Void> h1 = m.acceptEither(f, f, rs[1]);
+ assertTrue(f.cancel(mayInterruptIfRunning));
+ final CompletableFuture<Void> h2 = m.thenAccept(f, rs[2]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final FailingConsumer[] rs = new FailingConsumer[4];
+ for (int i = 0; i < rs.length; i++) rs[i] = new FailingConsumer(m);
+
+ final CompletableFuture<Void> h0 = m.thenAccept(f, rs[0]);
+ final CompletableFuture<Void> h1 = m.acceptEither(f, f, rs[1]);
+ assertTrue(f.complete(v1));
+ final CompletableFuture<Void> h2 = m.thenAccept(f, rs[2]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final SubtractFunction[] rs = new SubtractFunction[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new SubtractFunction(m);
+
+ final CompletableFuture<Integer> fst = fFirst ? f : g;
+ final CompletableFuture<Integer> snd = !fFirst ? f : g;
+ final Integer w1 = fFirst ? v1 : v2;
+ final Integer w2 = !fFirst ? v1 : v2;
+
+ final CompletableFuture<Integer> h0 = m.thenCombine(f, g, rs[0]);
+ final CompletableFuture<Integer> h1 = m.thenCombine(fst, fst, rs[1]);
+ assertTrue(fst.complete(w1));
+ final CompletableFuture<Integer> h2 = m.thenCombine(f, g, rs[2]);
+ final CompletableFuture<Integer> 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<Integer> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> 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<Integer> fst = fFirst ? f : g;
+ final CompletableFuture<Integer> snd = !fFirst ? f : g;
+ final Callable<Boolean> complete1 = failFirst ?
+ () -> fst.completeExceptionally(ex) :
+ () -> fst.complete(v1);
+ final Callable<Boolean> complete2 = failFirst ?
+ () -> snd.complete(v1) :
+ () -> snd.completeExceptionally(ex);
+
+ final CompletableFuture<Integer> h1 = m.thenCombine(f, g, r1);
+ assertTrue(complete1.call());
+ final CompletableFuture<Integer> h2 = m.thenCombine(f, g, r2);
+ checkIncomplete(h1);
+ checkIncomplete(h2);
+ assertTrue(complete2.call());
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final SubtractFunction r1 = new SubtractFunction(m);
+ final SubtractFunction r2 = new SubtractFunction(m);
+ final SubtractFunction r3 = new SubtractFunction(m);
+
+ final CompletableFuture<Integer> fst = fFirst ? f : g;
+ final CompletableFuture<Integer> snd = !fFirst ? f : g;
+ final Callable<Boolean> complete1 = failFirst ?
+ () -> fst.cancel(mayInterruptIfRunning) :
+ () -> fst.complete(v1);
+ final Callable<Boolean> complete2 = failFirst ?
+ () -> snd.complete(v1) :
+ () -> snd.cancel(mayInterruptIfRunning);
+
+ final CompletableFuture<Integer> h1 = m.thenCombine(f, g, r1);
+ assertTrue(complete1.call());
+ final CompletableFuture<Integer> h2 = m.thenCombine(f, g, r2);
+ checkIncomplete(h1);
+ checkIncomplete(h2);
+ assertTrue(complete2.call());
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final FailingBiFunction r1 = new FailingBiFunction(m);
+ final FailingBiFunction r2 = new FailingBiFunction(m);
+ final FailingBiFunction r3 = new FailingBiFunction(m);
+
+ final CompletableFuture<Integer> fst = fFirst ? f : g;
+ final CompletableFuture<Integer> snd = !fFirst ? f : g;
+ final Integer w1 = fFirst ? v1 : v2;
+ final Integer w2 = !fFirst ? v1 : v2;
+
+ final CompletableFuture<Integer> h1 = m.thenCombine(f, g, r1);
+ assertTrue(fst.complete(w1));
+ final CompletableFuture<Integer> h2 = m.thenCombine(f, g, r2);
+ assertTrue(snd.complete(w2));
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final SubtractAction r1 = new SubtractAction(m);
+ final SubtractAction r2 = new SubtractAction(m);
+ final SubtractAction r3 = new SubtractAction(m);
+
+ final CompletableFuture<Integer> fst = fFirst ? f : g;
+ final CompletableFuture<Integer> snd = !fFirst ? f : g;
+ final Integer w1 = fFirst ? v1 : v2;
+ final Integer w2 = !fFirst ? v1 : v2;
+
+ final CompletableFuture<Void> h1 = m.thenAcceptBoth(f, g, r1);
+ assertTrue(fst.complete(w1));
+ final CompletableFuture<Void> h2 = m.thenAcceptBoth(f, g, r2);
+ checkIncomplete(h1);
+ checkIncomplete(h2);
+ r1.assertNotInvoked();
+ r2.assertNotInvoked();
+ assertTrue(snd.complete(w2));
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> 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<Integer> fst = fFirst ? f : g;
+ final CompletableFuture<Integer> snd = !fFirst ? f : g;
+ final Callable<Boolean> complete1 = failFirst ?
+ () -> fst.completeExceptionally(ex) :
+ () -> fst.complete(v1);
+ final Callable<Boolean> complete2 = failFirst ?
+ () -> snd.complete(v1) :
+ () -> snd.completeExceptionally(ex);
+
+ final CompletableFuture<Void> h1 = m.thenAcceptBoth(f, g, r1);
+ assertTrue(complete1.call());
+ final CompletableFuture<Void> h2 = m.thenAcceptBoth(f, g, r2);
+ checkIncomplete(h1);
+ checkIncomplete(h2);
+ assertTrue(complete2.call());
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final SubtractAction r1 = new SubtractAction(m);
+ final SubtractAction r2 = new SubtractAction(m);
+ final SubtractAction r3 = new SubtractAction(m);
+
+ final CompletableFuture<Integer> fst = fFirst ? f : g;
+ final CompletableFuture<Integer> snd = !fFirst ? f : g;
+ final Callable<Boolean> complete1 = failFirst ?
+ () -> fst.cancel(mayInterruptIfRunning) :
+ () -> fst.complete(v1);
+ final Callable<Boolean> complete2 = failFirst ?
+ () -> snd.complete(v1) :
+ () -> snd.cancel(mayInterruptIfRunning);
+
+ final CompletableFuture<Void> h1 = m.thenAcceptBoth(f, g, r1);
+ assertTrue(complete1.call());
+ final CompletableFuture<Void> h2 = m.thenAcceptBoth(f, g, r2);
+ checkIncomplete(h1);
+ checkIncomplete(h2);
+ assertTrue(complete2.call());
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final FailingBiConsumer r1 = new FailingBiConsumer(m);
+ final FailingBiConsumer r2 = new FailingBiConsumer(m);
+ final FailingBiConsumer r3 = new FailingBiConsumer(m);
+
+ final CompletableFuture<Integer> fst = fFirst ? f : g;
+ final CompletableFuture<Integer> snd = !fFirst ? f : g;
+ final Integer w1 = fFirst ? v1 : v2;
+ final Integer w2 = !fFirst ? v1 : v2;
+
+ final CompletableFuture<Void> h1 = m.thenAcceptBoth(f, g, r1);
+ assertTrue(fst.complete(w1));
+ final CompletableFuture<Void> h2 = m.thenAcceptBoth(f, g, r2);
+ assertTrue(snd.complete(w2));
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final Noop r1 = new Noop(m);
+ final Noop r2 = new Noop(m);
+ final Noop r3 = new Noop(m);
+
+ final CompletableFuture<Integer> fst = fFirst ? f : g;
+ final CompletableFuture<Integer> snd = !fFirst ? f : g;
+ final Integer w1 = fFirst ? v1 : v2;
+ final Integer w2 = !fFirst ? v1 : v2;
+
+ final CompletableFuture<Void> h1 = m.runAfterBoth(f, g, r1);
+ assertTrue(fst.complete(w1));
+ final CompletableFuture<Void> h2 = m.runAfterBoth(f, g, r2);
+ checkIncomplete(h1);
+ checkIncomplete(h2);
+ r1.assertNotInvoked();
+ r2.assertNotInvoked();
+ assertTrue(snd.complete(w2));
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> 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<Integer> fst = fFirst ? f : g;
+ final CompletableFuture<Integer> snd = !fFirst ? f : g;
+ final Callable<Boolean> complete1 = failFirst ?
+ () -> fst.completeExceptionally(ex) :
+ () -> fst.complete(v1);
+ final Callable<Boolean> complete2 = failFirst ?
+ () -> snd.complete(v1) :
+ () -> snd.completeExceptionally(ex);
+
+ final CompletableFuture<Void> h1 = m.runAfterBoth(f, g, r1);
+ assertTrue(complete1.call());
+ final CompletableFuture<Void> h2 = m.runAfterBoth(f, g, r2);
+ checkIncomplete(h1);
+ checkIncomplete(h2);
+ assertTrue(complete2.call());
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final Noop r1 = new Noop(m);
+ final Noop r2 = new Noop(m);
+ final Noop r3 = new Noop(m);
+
+ final CompletableFuture<Integer> fst = fFirst ? f : g;
+ final CompletableFuture<Integer> snd = !fFirst ? f : g;
+ final Callable<Boolean> complete1 = failFirst ?
+ () -> fst.cancel(mayInterruptIfRunning) :
+ () -> fst.complete(v1);
+ final Callable<Boolean> complete2 = failFirst ?
+ () -> snd.complete(v1) :
+ () -> snd.cancel(mayInterruptIfRunning);
+
+ final CompletableFuture<Void> h1 = m.runAfterBoth(f, g, r1);
+ assertTrue(complete1.call());
+ final CompletableFuture<Void> h2 = m.runAfterBoth(f, g, r2);
+ checkIncomplete(h1);
+ checkIncomplete(h2);
+ assertTrue(complete2.call());
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final FailingRunnable r1 = new FailingRunnable(m);
+ final FailingRunnable r2 = new FailingRunnable(m);
+ final FailingRunnable r3 = new FailingRunnable(m);
+
+ final CompletableFuture<Integer> fst = fFirst ? f : g;
+ final CompletableFuture<Integer> snd = !fFirst ? f : g;
+ final Integer w1 = fFirst ? v1 : v2;
+ final Integer w2 = !fFirst ? v1 : v2;
+
+ final CompletableFuture<Void> h1 = m.runAfterBoth(f, g, r1);
+ assertTrue(fst.complete(w1));
+ final CompletableFuture<Void> h2 = m.runAfterBoth(f, g, r2);
+ assertTrue(snd.complete(w2));
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final IncFunction[] rs = new IncFunction[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m);
+
+ final CompletableFuture<Integer> h0 = m.applyToEither(f, g, rs[0]);
+ final CompletableFuture<Integer> 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<Integer> h2 = m.applyToEither(f, g, rs[2]);
+ final CompletableFuture<Integer> 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<Integer> h4 = m.applyToEither(f, g, rs[4]);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> 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<Integer> h0 = m.applyToEither(f, g, rs[0]);
+ final CompletableFuture<Integer> 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<Integer> h2 = m.applyToEither(f, g, rs[2]);
+ final CompletableFuture<Integer> h3 = m.applyToEither(g, f, rs[3]);
+ checkCompletedWithWrappedException(h2, ex);
+ checkCompletedWithWrappedException(h3, ex);
+ g.complete(v1);
+
+ // unspecified behavior - both source completions available
+ final CompletableFuture<Integer> h4 = m.applyToEither(f, g, rs[4]);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> 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<Integer> h0 = m.applyToEither(f, g, rs[0]);
+ final CompletableFuture<Integer> 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<Integer> h2 = m.applyToEither(f, g, rs[2]);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final IncFunction[] rs = new IncFunction[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m);
+
+ final CompletableFuture<Integer> h0 = m.applyToEither(f, g, rs[0]);
+ final CompletableFuture<Integer> 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<Integer> h2 = m.applyToEither(f, g, rs[2]);
+ final CompletableFuture<Integer> h3 = m.applyToEither(g, f, rs[3]);
+ checkCompletedWithWrappedCancellationException(h2);
+ checkCompletedWithWrappedCancellationException(h3);
+ g.complete(v1);
+
+ // unspecified behavior - both source completions available
+ final CompletableFuture<Integer> h4 = m.applyToEither(f, g, rs[4]);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final IncFunction[] rs = new IncFunction[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m);
+
+ final CompletableFuture<Integer> h0 = m.applyToEither(f, g, rs[0]);
+ final CompletableFuture<Integer> 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<Integer> h2 = m.applyToEither(f, g, rs[2]);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final FailingFunction[] rs = new FailingFunction[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new FailingFunction(m);
+
+ final CompletableFuture<Integer> h0 = m.applyToEither(f, g, rs[0]);
+ final CompletableFuture<Integer> h1 = m.applyToEither(g, f, rs[1]);
+ f.complete(v1);
+ final CompletableFuture<Integer> h2 = m.applyToEither(f, g, rs[2]);
+ final CompletableFuture<Integer> 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<Integer> h4 = m.applyToEither(f, g, rs[4]);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final NoopConsumer[] rs = new NoopConsumer[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m);
+
+ final CompletableFuture<Void> h0 = m.acceptEither(f, g, rs[0]);
+ final CompletableFuture<Void> 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<Void> h2 = m.acceptEither(f, g, rs[2]);
+ final CompletableFuture<Void> 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<Void> h4 = m.acceptEither(f, g, rs[4]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> 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<Void> h0 = m.acceptEither(f, g, rs[0]);
+ final CompletableFuture<Void> 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<Void> h2 = m.acceptEither(f, g, rs[2]);
+ final CompletableFuture<Void> h3 = m.acceptEither(g, f, rs[3]);
+ checkCompletedWithWrappedException(h2, ex);
+ checkCompletedWithWrappedException(h3, ex);
+
+ g.complete(v1);
+
+ // unspecified behavior - both source completions available
+ final CompletableFuture<Void> h4 = m.acceptEither(f, g, rs[4]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> 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<Void> h0 = m.acceptEither(f, g, rs[0]);
+ final CompletableFuture<Void> 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<Void> h2 = m.acceptEither(f, g, rs[2]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final NoopConsumer[] rs = new NoopConsumer[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m);
+
+ final CompletableFuture<Void> h0 = m.acceptEither(f, g, rs[0]);
+ final CompletableFuture<Void> 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<Void> h2 = m.acceptEither(f, g, rs[2]);
+ final CompletableFuture<Void> h3 = m.acceptEither(g, f, rs[3]);
+ checkCompletedWithWrappedCancellationException(h2);
+ checkCompletedWithWrappedCancellationException(h3);
+
+ g.complete(v1);
+
+ // unspecified behavior - both source completions available
+ final CompletableFuture<Void> h4 = m.acceptEither(f, g, rs[4]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final FailingConsumer[] rs = new FailingConsumer[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new FailingConsumer(m);
+
+ final CompletableFuture<Void> h0 = m.acceptEither(f, g, rs[0]);
+ final CompletableFuture<Void> h1 = m.acceptEither(g, f, rs[1]);
+ f.complete(v1);
+ final CompletableFuture<Void> h2 = m.acceptEither(f, g, rs[2]);
+ final CompletableFuture<Void> 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<Void> h4 = m.acceptEither(f, g, rs[4]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final Noop[] rs = new Noop[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m);
+
+ final CompletableFuture<Void> h0 = m.runAfterEither(f, g, rs[0]);
+ final CompletableFuture<Void> 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<Void> h2 = m.runAfterEither(f, g, rs[2]);
+ final CompletableFuture<Void> h3 = m.runAfterEither(g, f, rs[3]);
+ checkCompletedNormally(h2, null);
+ checkCompletedNormally(h3, null);
+ rs[2].assertInvoked();
+ rs[3].assertInvoked();
+
+ g.complete(v2);
+
+ final CompletableFuture<Void> h4 = m.runAfterEither(f, g, rs[4]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> 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<Void> h0 = m.runAfterEither(f, g, rs[0]);
+ final CompletableFuture<Void> 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<Void> h2 = m.runAfterEither(f, g, rs[2]);
+ final CompletableFuture<Void> 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<Void> h4 = m.runAfterEither(f, g, rs[4]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> 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<Void> h0 = m.runAfterEither(f, g, rs[0]);
+ final CompletableFuture<Void> 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<Void> h2 = m.runAfterEither(f, g, rs[2]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final Noop[] rs = new Noop[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m);
+
+ final CompletableFuture<Void> h0 = m.runAfterEither(f, g, rs[0]);
+ final CompletableFuture<Void> 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<Void> h2 = m.runAfterEither(f, g, rs[2]);
+ final CompletableFuture<Void> h3 = m.runAfterEither(g, f, rs[3]);
+ checkCompletedWithWrappedCancellationException(h2);
+ checkCompletedWithWrappedCancellationException(h3);
+
+ assertTrue(g.complete(v1));
+
+ // unspecified behavior - both source completions available
+ final CompletableFuture<Void> h4 = m.runAfterEither(f, g, rs[4]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final FailingRunnable[] rs = new FailingRunnable[6];
+ for (int i = 0; i < rs.length; i++) rs[i] = new FailingRunnable(m);
+
+ final CompletableFuture<Void> h0 = m.runAfterEither(f, g, rs[0]);
+ final CompletableFuture<Void> h1 = m.runAfterEither(g, f, rs[1]);
+ assertTrue(f.complete(v1));
+ final CompletableFuture<Void> h2 = m.runAfterEither(f, g, rs[2]);
+ final CompletableFuture<Void> 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<Void> h4 = m.runAfterEither(f, g, rs[4]);
+ final CompletableFuture<Void> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFutureInc r = new CompletableFutureInc(m);
+ if (!createIncomplete) assertTrue(f.complete(v1));
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ if (!createIncomplete) f.completeExceptionally(ex);
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final FailingCompletableFutureFunction r
+ = new FailingCompletableFutureFunction(m);
+ if (!createIncomplete) assertTrue(f.complete(v1));
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFutureInc r = new CompletableFutureInc(m);
+ if (!createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning));
+ final CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ final CompletableFuture<Integer> g = new CompletableFuture<>();
+ final CompletableFuture<Integer> 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<Void> 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<Integer>[] fs
+ = (CompletableFuture<Integer>[]) new CompletableFuture[k];
+ for (int i = 0; i < k; i++)
+ fs[i] = new CompletableFuture<>();
+ CompletableFuture<Void> 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<Integer>[] fs
+ = (CompletableFuture<Integer>[]) new CompletableFuture[k];
+ for (int i = 0; i < k; i++)
+ fs[i] = new CompletableFuture<>();
+ CompletableFuture<Void> 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<Integer>[] fs
+ = (CompletableFuture<Integer>[]) new CompletableFuture[k];
+ CFException ex = new CFException();
+ for (int i = 0; i < k; i++)
+ fs[i] = new CompletableFuture<>();
+ CompletableFuture<Void> 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<Object> 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<Object> 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<Object> 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<Object> 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<Object> 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<Integer> f = new CompletableFuture<>();
+ CompletableFuture<Integer> g = new CompletableFuture<>();
+ CompletableFuture<Integer> nullFuture = (CompletableFuture<Integer>)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<Integer> f = new CompletableFuture<>();
+ assertSame(f, f.toCompletableFuture());
+ }
+
+ // jdk9
+
+ /**
+ * newIncompleteFuture returns an incomplete CompletableFuture
+ */
+ public void testNewIncompleteFuture() {
+ for (Integer v1 : new Integer[] { 1, null })
+ {
+ CompletableFuture<Integer> f = new CompletableFuture<>();
+ CompletableFuture<Integer> 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<Throwable> r = new AtomicReference<Throwable>();
+ CompletionStage<Integer> 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<Integer> 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<Integer> f = CompletableFuture.failedFuture(ex);
+ checkCompletedExceptionally(f, ex);
+ }
+
+ /**
+ * failedFuture(null) throws NPE
+ */
+ public void testFailedFuture_null() {
+ try {
+ CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ CompletableFuture<Integer> 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<Integer> f = new CompletableFuture<>();
+ CompletionStage<Integer> g = f.minimalCompletionStage();
+ AtomicInteger x = new AtomicInteger(0);
+ AtomicReference<Throwable> r = new AtomicReference<Throwable>();
+ 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<Integer> f = new CompletableFuture<>();
+ CompletionStage<Integer> g = f.minimalCompletionStage();
+ AtomicInteger x = new AtomicInteger(0);
+ AtomicReference<Throwable> r = new AtomicReference<Throwable>();
+ 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<Integer> f = CompletableFuture.failedStage(ex);
+ AtomicInteger x = new AtomicInteger(0);
+ AtomicReference<Throwable> r = new AtomicReference<Throwable>();
+ 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<Integer> f = new CompletableFuture<>();
+ f.completeAsync(() -> v1);
+ f.join();
+ checkCompletedNormally(f, v1);
+ }}
+
+ /**
+ * completeAsync completes exceptionally if given supplier throws
+ */
+ public void testCompleteAsync2() {
+ CompletableFuture<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> f = new CompletableFuture<>();
+ CompletableFuture<Integer> 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<Integer> 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<Integer> f = new CompletableFuture<>();
+ CompletableFuture<Integer> 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<Integer> f =
+ CompletableFuture.supplyAsync(() -> v, delayer);
+ CompletableFuture<Integer> 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<Integer> v42 = CompletableFuture.completedFuture(42);
+ final CompletableFuture<Integer> incomplete = new CompletableFuture<>();
+
+ List<Function<CompletableFuture<Integer>, 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<Integer>, CompletableFuture<?>>
+ fun : funs) {
+ CompletableFuture<Integer> f = new CompletableFuture<>();
+ f.completeExceptionally(ex);
+ CompletableFuture<Integer> 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<Integer>, CompletableFuture<?>>
+ fun : funs) {
+ CompletableFuture<Integer> f = new CompletableFuture<>();
+ CompletableFuture<Integer> 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<Integer>, CompletableFuture<?>>
+ fun : funs) {
+ CompletableFuture<Integer> f = new CompletableFuture<>();
+ f.cancel(mayInterruptIfRunning);
+ checkCancelled(f);
+ CompletableFuture<Integer> 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<Integer>, CompletableFuture<?>>
+ fun : funs) {
+ CompletableFuture<Integer> f = new CompletableFuture<>();
+ CompletableFuture<Integer> 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<Method, String> toSignature =
+ // (method) -> method.getName() + Arrays.toString(method.getParameterTypes());
+ // Predicate<Method> isNotStatic =
+ // (method) -> (method.getModifiers() & Modifier.STATIC) == 0;
+ // List<Method> 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<String> permittedMethodSignatures =
+ // Stream.concat(minimalMethods.stream().map(toSignature),
+ // Stream.of(signatureWhitelist))
+ // .collect(Collectors.toSet());
+ // List<Method> allMethods = Stream.of(CompletableFuture.class.getMethods())
+ // .filter(isNotStatic)
+ // .filter((method) -> !permittedMethodSignatures.contains(toSignature.apply(method)))
+ // .collect(Collectors.toList());
+
+ // CompletionStage<Integer> minimalStage =
+ // new CompletableFuture<Integer>().minimalCompletionStage();
+
+ // List<Method> 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 <T> CompletableFuture<T> unit(T value) {
+ return completedFuture(value);
+ }
+ // monadic zero ?
+ static <T> CompletableFuture<T> zero() {
+ return failedFuture(new ZeroException());
+ }
+ // >=>
+ static <T,U,V> Function<T, CompletableFuture<V>> compose
+ (Function<T, CompletableFuture<U>> f,
+ Function<U, CompletableFuture<V>> 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 <T> void assertFutureEquals(CompletableFuture<T> f,
+ CompletableFuture<T> 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<T> extends CompletableFuture<T> {
+ AtomicReference<Throwable> firstFailure = new AtomicReference<>(null);
+ }
+
+ /** Implements "monadic plus". */
+ static <T> CompletableFuture<T> plus(CompletableFuture<? extends T> f,
+ CompletableFuture<? extends T> g) {
+ PlusFuture<T> plus = new PlusFuture<T>();
+ BiConsumer<T, Throwable> 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<Long, CompletableFuture<Long>> unit = Monad::unit;
+ CompletableFuture<Long> zero = Monad.zero();
+
+ // Some mutually non-commutative functions
+ Function<Long, CompletableFuture<Long>> triple
+ = (x) -> Monad.unit(3 * x);
+ Function<Long, CompletableFuture<Long>> 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<Long> f = Monad.plus(Monad.unit(5L),
+ Monad.unit(8L));
+ // non-determinism
+ assertTrue(f.get() == 5L || f.get() == 8L);
+ }
+
+ CompletableFuture<Long> 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> U join(CompletionStage<U> stage) {
+// CompletableFuture<U> f = new CompletableFuture<>();
+// stage.whenComplete((v, ex) -> {
+// if (ex != null) f.completeExceptionally(ex); else f.complete(v);
+// });
+// return f.join();
+// }
+
+// static <U> boolean isDone(CompletionStage<U> stage) {
+// CompletableFuture<U> f = new CompletableFuture<>();
+// stage.whenComplete((v, ex) -> {
+// if (ex != null) f.completeExceptionally(ex); else f.complete(v);
+// });
+// return f.isDone();
+// }
+
+// static <U> U join2(CompletionStage<U> stage) {
+// return stage.toCompletableFuture().copy().join();
+// }
+
+// static <U> boolean isDone2(CompletionStage<U> 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 0000000..60de8b9
--- /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<Integer> populatedSet(int n) {
+ Set<Integer> a = ConcurrentHashMap.<Integer>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<Integer> a = ConcurrentHashMap.<Integer>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<Integer, String> 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<Integer> sp = set.spliterator();
+ checkSpliteratorCharacteristics(sp, CONCURRENT | DISTINCT | NONNULL);
+ assertEquals(sp.estimateSize(), map.size());
+ Spliterator<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Long, Long> longMap;
+
+ static ConcurrentHashMap<Long, Long> longMap() {
+ if (longMap == null) {
+ longMap = new ConcurrentHashMap<Long, Long>(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<Long,Long>, Map.Entry<Long,Long>, Map.Entry<Long,Long>> {
+ public Map.Entry<Long,Long> apply(Map.Entry<Long,Long> x, Map.Entry<Long,Long> y) {
+ return new AbstractMap.SimpleEntry<Long,Long>
+ (Long.valueOf(x.getKey().longValue() + y.getKey().longValue()),
+ Long.valueOf(1L));
+ }
+ }
+
+ /**
+ * forEachKeySequentially traverses all keys
+ */
+ public void testForEachKeySequentially() {
+ LongAdder adder = new LongAdder();
+ ConcurrentHashMap<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> m = longMap();
+ m.forEachEntry(Long.MAX_VALUE, (Map.Entry<Long,Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> m = longMap();
+ m.forEachEntry(1L, (Map.Entry<Long,Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> m = longMap();
+ m.forEachEntry(Long.MAX_VALUE, (Map.Entry<Long,Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> m = longMap();
+ m.forEachEntry(1L, (Map.Entry<Long,Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> m = longMap();
+ Map.Entry<Long,Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> m = longMap();
+ Map.Entry<Long,Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> m = longMap();
+ Long r;
+ r = m.searchEntries(Long.MAX_VALUE, (Map.Entry<Long,Long> e) -> e.getKey().longValue() == (long)(SIZE/2) ? e.getKey() : null);
+ assertEquals((long)r, (long)(SIZE/2));
+ r = m.searchEntries(Long.MAX_VALUE, (Map.Entry<Long,Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> 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<Long, Long> m = longMap();
+ Long r;
+ r = m.searchEntries(1L, (Map.Entry<Long,Long> e) -> e.getKey().longValue() == (long)(SIZE/2) ? e.getKey() : null);
+ assertEquals((long)r, (long)(SIZE/2));
+ r = m.searchEntries(1L, (Map.Entry<Long,Long> 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 4650f41..f427127 100644
--- a/jsr166-tests/src/test/java/jsr166/ConcurrentHashMapTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ConcurrentHashMapTest.java
@@ -30,7 +30,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ConcurrentHashMapTest.class);
// }
/**
@@ -50,7 +50,9 @@
}
/** 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<BI> {
@@ -538,7 +540,7 @@
/**
* 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 c445957..6625e7e 100644
--- a/jsr166-tests/src/test/java/jsr166/ConcurrentLinkedDequeTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ConcurrentLinkedDequeTest.java
@@ -29,7 +29,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ConcurrentLinkedDequeTest.class);
// }
/**
@@ -69,8 +69,7 @@
*/
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 @@
* 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 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 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 @@
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 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 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 @@
}
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 @@
}
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 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.remove();
}
}
@@ -643,7 +641,7 @@
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 @@
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 @@
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 d3f5b1f..70519a4 100644
--- a/jsr166-tests/src/test/java/jsr166/ConcurrentLinkedQueueTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ConcurrentLinkedQueueTest.java
@@ -27,7 +27,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ConcurrentLinkedQueueTest.class);
// }
/**
@@ -66,8 +66,7 @@
*/
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 @@
* 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 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
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 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.remove();
}
}
@@ -374,7 +372,7 @@
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 0aadd23..f53a446 100644
--- a/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListMapTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListMapTest.java
@@ -30,7 +30,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ConcurrentSkipListMapTest.class);
// }
/**
@@ -1278,7 +1278,7 @@
}
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 41f8835..959d703 100644
--- a/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSetTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSetTest.java
@@ -29,7 +29,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ConcurrentSkipListSetTest.class);
// }
static class MyReverseComparator implements Comparator {
@@ -46,7 +46,7 @@
ConcurrentSkipListSet<Integer> q =
new ConcurrentSkipListSet<Integer>();
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 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 @@
* 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 @@
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 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 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 @@
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 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 @@
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 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.pollFirst();
}
}
@@ -371,7 +370,7 @@
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 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 5315bcb..a6510f5 100644
--- a/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSubMapTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSubMapTest.java
@@ -28,7 +28,7 @@
// 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 f1c4aae..220a092 100644
--- a/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSubSetTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ConcurrentSkipListSubSetTest.java
@@ -24,7 +24,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ConcurrentSkipListSubSetTest.class);
// }
static class MyReverseComparator implements Comparator {
@@ -42,7 +42,7 @@
new ConcurrentSkipListSet<Integer>();
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 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 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 @@
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 @@
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 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.pollFirst();
}
}
@@ -328,7 +328,7 @@
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 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 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 @@
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 @@
}
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 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.pollFirst();
}
}
@@ -818,7 +818,7 @@
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 658268a..8a37d03 100644
--- a/jsr166-tests/src/test/java/jsr166/CopyOnWriteArrayListTest.java
+++ b/jsr166-tests/src/test/java/jsr166/CopyOnWriteArrayListTest.java
@@ -31,7 +31,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(CopyOnWriteArrayListTest.class);
// }
static CopyOnWriteArrayList<Integer> populatedArray(int n) {
@@ -67,7 +67,7 @@
*/
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 2810802..a486c6a 100644
--- a/jsr166-tests/src/test/java/jsr166/CopyOnWriteArraySetTest.java
+++ b/jsr166-tests/src/test/java/jsr166/CopyOnWriteArraySetTest.java
@@ -28,7 +28,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(CopyOnWriteArraySetTest.class);
// }
static CopyOnWriteArraySet<Integer> populatedSet(int n) {
@@ -64,7 +64,7 @@
*/
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 @@
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 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 @@
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 @@
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 @@
* 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 da1ebb4..e764c9e 100644
--- a/jsr166-tests/src/test/java/jsr166/CountDownLatchTest.java
+++ b/jsr166-tests/src/test/java/jsr166/CountDownLatchTest.java
@@ -23,7 +23,7 @@
// 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 80d7b3b..8a38eff 100644
--- a/jsr166-tests/src/test/java/jsr166/CountedCompleterTest.java
+++ b/jsr166-tests/src/test/java/jsr166/CountedCompleterTest.java
@@ -31,7 +31,7 @@
// 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 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 @@
assertFalse(a.isCancelled());
assertNull(a.getException());
assertNull(a.getRawResult());
- } finally {
- joinPool(pool);
}
}
@@ -99,17 +97,17 @@
{
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 @@
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 @@
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 @@
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 @@
void testComplete(NoopCC cc, Object x, int pendingCount) {
cc.setPendingCount(pendingCount);
cc.checkCompletes(x);
+ assertEquals(pendingCount, cc.getPendingCount());
}
/**
@@ -315,14 +317,20 @@
}
/**
- * 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 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 @@
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 @@
}
/**
- * 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 a9d8c54..37adcb1 100644
--- a/jsr166-tests/src/test/java/jsr166/CyclicBarrierTest.java
+++ b/jsr166-tests/src/test/java/jsr166/CyclicBarrierTest.java
@@ -27,7 +27,7 @@
// 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 7619b48..e42ac2d 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 @@
private DelayQueue<PDelay> populatedQueue(int n) {
DelayQueue<PDelay> q = new DelayQueue<PDelay>();
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 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 @@
* 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 @@
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 @@
* 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 @@
* 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 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 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 @@
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 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.remove();
}
}
@@ -570,7 +574,7 @@
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 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);
- }});
+ 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);
+ }});
- executor.execute(new CheckedRunnable() {
- public void realRun() throws InterruptedException {
- threadsStarted.await();
- q.put(new PDelay(1));
- }});
-
- joinPool(executor);
+ executor.execute(new CheckedRunnable() {
+ public void realRun() throws InterruptedException {
+ threadsStarted.await();
+ q.put(new PDelay(1));
+ }});
+ }
}
/**
@@ -768,7 +772,7 @@
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 @@
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 0000000..e061f9a
--- /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 0000000..d02e2a1
--- /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 d141a84..72740e3 100644
--- a/jsr166-tests/src/test/java/jsr166/EntryTest.java
+++ b/jsr166-tests/src/test/java/jsr166/EntryTest.java
@@ -20,7 +20,7 @@
// 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 172fccd..b111980 100644
--- a/jsr166-tests/src/test/java/jsr166/ExchangerTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ExchangerTest.java
@@ -26,7 +26,7 @@
// 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 e988cc6..0f58e78 100644
--- a/jsr166-tests/src/test/java/jsr166/ExecutorCompletionServiceTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ExecutorCompletionServiceTest.java
@@ -33,7 +33,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ExecutorCompletionServiceTest.class);
// }
/**
@@ -61,15 +61,14 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
}
assertTrue(f.isDone());
assertSame(TEST_STRING, f.get());
- } finally {
- joinPool(e);
}
}
@@ -150,17 +142,15 @@
* 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 @@
MyCallableFuture(Callable<V> c) { super(c); }
protected void done() { done.set(true); }
}
- ExecutorService e = new ThreadPoolExecutor(
- 1, 1, 30L, TimeUnit.SECONDS,
- new ArrayBlockingQueue<Runnable>(1)) {
- protected <T> RunnableFuture<T> newTaskFor(Callable<T> c) {
- return new MyCallableFuture<T>(c);
- }};
+ final ExecutorService e =
+ new ThreadPoolExecutor(1, 1,
+ 30L, TimeUnit.SECONDS,
+ new ArrayBlockingQueue<Runnable>(1)) {
+ protected <T> RunnableFuture<T> newTaskFor(Callable<T> c) {
+ return new MyCallableFuture<T>(c);
+ }};
ExecutorCompletionService<String> ecs =
new ExecutorCompletionService<String>(e);
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
assertNull(ecs.poll());
Callable<String> c = new StringTask();
Future f1 = ecs.submit(c);
@@ -191,8 +182,6 @@
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 @@
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<Runnable>(1)) {
- protected <T> RunnableFuture<T> newTaskFor(Runnable t, T r) {
- return new MyRunnableFuture<T>(t, r);
- }};
- ExecutorCompletionService<String> ecs =
+ final ExecutorService e =
+ new ThreadPoolExecutor(1, 1,
+ 30L, TimeUnit.SECONDS,
+ new ArrayBlockingQueue<Runnable>(1)) {
+ protected <T> RunnableFuture<T> newTaskFor(Runnable t, T r) {
+ return new MyRunnableFuture<T>(t, r);
+ }};
+ final ExecutorCompletionService<String> ecs =
new ExecutorCompletionService<String>(e);
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
assertNull(ecs.poll());
Runnable r = new NoOpRunnable();
Future f1 = ecs.submit(r, null);
@@ -223,8 +213,6 @@
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 ae475f1..d70ae6e 100644
--- a/jsr166-tests/src/test/java/jsr166/ExecutorsTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ExecutorsTest.java
@@ -36,29 +36,31 @@
// 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
assertTrue(f.isDone());
assertFalse(f.isCancelled());
assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
- } finally {
- joinPool(p);
}
}
@@ -220,8 +224,8 @@
* 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 @@
assertTrue(f.isDone());
assertFalse(f.isCancelled());
assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
- } finally {
- joinPool(p);
}
}
@@ -246,10 +248,10 @@
* 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 @@
assertTrue(f.isDone());
assertFalse(f.isCancelled());
assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
- } finally {
- joinPool(p);
}
}
@@ -327,16 +327,10 @@
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 @@
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 0000000..f9f9239
--- /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 09a3511..e3bb428 100644
--- a/jsr166-tests/src/test/java/jsr166/ForkJoinPoolTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ForkJoinPoolTest.java
@@ -40,7 +40,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ForkJoinPoolTest.class);
// }
/*
@@ -68,10 +68,12 @@
}
}
+ 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 @@
*/
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 @@
assertFalse(p.isShutdown());
assertFalse(p.isTerminating());
assertFalse(p.isTerminated());
- } finally {
- joinPool(p);
}
}
@@ -208,10 +208,8 @@
*/
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 @@
* 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<String> 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 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 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 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 @@
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 testSubmitForkJoinTask() throws Throwable {
ForkJoinPool p = new ForkJoinPool(1);
- try {
+ try (PoolCleaner cleaner = cleaner(p)) {
ForkJoinTask<Integer> f = p.submit(new FibTask(8));
assertEquals(21, (int) f.get());
- } finally {
- joinPool(p);
}
}
@@ -343,15 +354,13 @@
*/
public void testSubmitAfterShutdown() {
ForkJoinPool p = new ForkJoinPool(1);
- try {
+ try (PoolCleaner cleaner = cleaner(p)) {
p.shutdown();
assertTrue(p.isShutdown());
try {
ForkJoinTask<Integer> f = p.submit(new FibTask(8));
shouldThrow();
} catch (RejectedExecutionException success) {}
- } finally {
- joinPool(p);
}
}
@@ -377,16 +386,14 @@
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 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 @@
assertTrue(r == a || r == b || r == c);
assertFalse(r.isDone());
}
- } finally {
done.countDown();
- joinPool(p);
}
}
@@ -420,7 +425,7 @@
*/
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 @@
assertTrue(done.get());
assertTrue(future.isDone());
assertFalse(future.isCancelled());
- } finally {
- joinPool(e);
}
}
@@ -441,13 +444,11 @@
*/
public void testSubmitCallable() throws Throwable {
ExecutorService e = new ForkJoinPool(1);
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
Future<String> 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 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 testSubmitRunnable2() throws Throwable {
ExecutorService e = new ForkJoinPool(1);
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
Future<String> 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 @@
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 @@
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 @@
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 @@
} catch (ExecutionException success) {
assertTrue(success.getCause() instanceof IndexOutOfBoundsException);
}
- } finally {
- joinPool(e);
}
}};
@@ -553,12 +544,11 @@
*/
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 testSubmitNullCallable() {
ExecutorService e = new ForkJoinPool(1);
- try {
- Future<String> future = e.submit((Callable) null);
- shouldThrow();
- } catch (NullPointerException success) {
- } finally {
- joinPool(e);
+ try (PoolCleaner cleaner = cleaner(e)) {
+ try {
+ Future<String> future = e.submit((Callable) null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -582,13 +571,13 @@
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<Void> awaiter = new CheckedCallable<Void>() {
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<Void> future = p.submit(awaiter);
@@ -596,12 +585,9 @@
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 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 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 testInvokeAny2() throws Throwable {
ExecutorService e = new ForkJoinPool(1);
- try {
- e.invokeAny(new ArrayList<Callable<String>>());
- shouldThrow();
- } catch (IllegalArgumentException success) {
- } finally {
- joinPool(e);
+ try (PoolCleaner cleaner = cleaner(e)) {
+ try {
+ e.invokeAny(new ArrayList<Callable<String>>());
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
}
@@ -656,14 +640,13 @@
*/
public void testInvokeAny3() throws Throwable {
ExecutorService e = new ForkJoinPool(1);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(null);
- try {
- e.invokeAny(l);
- shouldThrow();
- } catch (NullPointerException success) {
- } finally {
- joinPool(e);
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(null);
+ try {
+ e.invokeAny(l);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -673,16 +656,15 @@
public void testInvokeAny4() throws Throwable {
CountDownLatch latch = new CountDownLatch(1);
ExecutorService e = new ForkJoinPool(1);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(latchAwaitingStringTask(latch));
- l.add(null);
- try {
- e.invokeAny(l);
- shouldThrow();
- } catch (NullPointerException success) {
- } finally {
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ 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 testInvokeAny5() throws Throwable {
ExecutorService e = new ForkJoinPool(1);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ try {
+ e.invokeAny(l);
+ shouldThrow();
+ } catch (ExecutionException success) {
+ assertTrue(success.getCause() instanceof NullPointerException);
+ }
}
}
@@ -708,14 +690,12 @@
*/
public void testInvokeAny6() throws Throwable {
ExecutorService e = new ForkJoinPool(1);
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
List<Callable<String>> l = new ArrayList<Callable<String>>();
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 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 testInvokeAll2() throws InterruptedException {
ExecutorService e = new ForkJoinPool(1);
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
List<Future<String>> r
= e.invokeAll(new ArrayList<Callable<String>>());
assertTrue(r.isEmpty());
- } finally {
- joinPool(e);
}
}
@@ -752,15 +729,14 @@
*/
public void testInvokeAll3() throws InterruptedException {
ExecutorService e = new ForkJoinPool(1);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new StringTask());
- l.add(null);
- try {
- e.invokeAll(l);
- shouldThrow();
- } catch (NullPointerException success) {
- } finally {
- joinPool(e);
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ l.add(null);
+ try {
+ e.invokeAll(l);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -770,17 +746,17 @@
*/
public void testInvokeAll4() throws Throwable {
ExecutorService e = new ForkJoinPool(1);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new NPETask());
- List<Future<String>> 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ List<Future<String>> 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 testInvokeAll5() throws Throwable {
ExecutorService e = new ForkJoinPool(1);
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
List<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
@@ -797,8 +773,6 @@
assertEquals(2, futures.size());
for (Future<String> future : futures)
assertSame(TEST_STRING, future.get());
- } finally {
- joinPool(e);
}
}
@@ -807,12 +781,11 @@
*/
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 testTimedInvokeAnyNullTimeUnit() throws Throwable {
ExecutorService e = new ForkJoinPool(1);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ try {
+ e.invokeAny(l, MEDIUM_DELAY_MS, null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -837,13 +809,12 @@
*/
public void testTimedInvokeAny2() throws Throwable {
ExecutorService e = new ForkJoinPool(1);
- try {
- e.invokeAny(new ArrayList<Callable<String>>(),
- MEDIUM_DELAY_MS, MILLISECONDS);
- shouldThrow();
- } catch (IllegalArgumentException success) {
- } finally {
- joinPool(e);
+ try (PoolCleaner cleaner = cleaner(e)) {
+ try {
+ e.invokeAny(new ArrayList<Callable<String>>(),
+ MEDIUM_DELAY_MS, MILLISECONDS);
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
}
@@ -853,16 +824,15 @@
public void testTimedInvokeAny3() throws Throwable {
CountDownLatch latch = new CountDownLatch(1);
ExecutorService e = new ForkJoinPool(1);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ 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 testTimedInvokeAny4() throws Throwable {
ExecutorService e = new ForkJoinPool(1);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ 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 testTimedInvokeAny5() throws Throwable {
ExecutorService e = new ForkJoinPool(1);
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
+ long startTime = System.nanoTime();
List<Callable<String>> l = new ArrayList<Callable<String>>();
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 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 testTimedInvokeAllNullTimeUnit() throws Throwable {
ExecutorService e = new ForkJoinPool(1);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ try {
+ e.invokeAll(l, MEDIUM_DELAY_MS, null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -934,13 +904,11 @@
*/
public void testTimedInvokeAll2() throws InterruptedException {
ExecutorService e = new ForkJoinPool(1);
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
List<Future<String>> r
= e.invokeAll(new ArrayList<Callable<String>>(),
MEDIUM_DELAY_MS, MILLISECONDS);
assertTrue(r.isEmpty());
- } finally {
- joinPool(e);
}
}
@@ -949,15 +917,14 @@
*/
public void testTimedInvokeAll3() throws InterruptedException {
ExecutorService e = new ForkJoinPool(1);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ 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 testTimedInvokeAll4() throws Throwable {
ExecutorService e = new ForkJoinPool(1);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new NPETask());
- List<Future<String>> 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ List<Future<String>> 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 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
List<Future<String>> futures
- = e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS);
+ = e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS);
assertEquals(2, futures.size());
for (Future<String> 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 0000000..6c03348
--- /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); }
+ }
+
+ <T> void checkCompletedNormally(ForkJoinTask<T> a) {
+ checkCompletedNormally(a, null);
+ }
+
+ <T> void checkCompletedNormally(ForkJoinTask<T> 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<Void> {
+
+ 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 3c1fcb7..1616d4f 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 @@
// 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 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 @@
assertFalse(a.isCancelled());
assertNull(a.getException());
assertNull(a.getRawResult());
- } finally {
- joinPool(pool);
}
}
@@ -102,17 +103,17 @@
{
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 @@
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 @@
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 @@
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 @@
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 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 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 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;
@@ -778,6 +780,27 @@
}
/**
+ * 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
*/
public void testInvokeAll2() {
@@ -877,8 +900,10 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 a5d8c46..44d12b3 100644
--- a/jsr166-tests/src/test/java/jsr166/FutureTaskTest.java
+++ b/jsr166-tests/src/test/java/jsr166/FutureTaskTest.java
@@ -38,7 +38,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(FutureTaskTest.class);
// }
void checkIsDone(Future<?> f) {
@@ -272,8 +272,8 @@
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 @@
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 46be906..fc1632c 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 @@
*
* <ol>
*
- * <li> All assertions in code running in generated threads must use
+ * <li>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.</li>
+ * this way, but enough to live with.
*
- * <li> If you override {@link #setUp} or {@link #tearDown}, make sure
+ * <li>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.</li>
+ * assertion failures.
*
* <li>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.</li>
+ * in one spot to rerun tests on slower platforms.
*
- * <li> All threads generated must be joined inside each test case
+ * <li>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.</li>
+ * using Executors.
*
* </ol>
*
* <p><b>Other notes</b>
* <ul>
*
- * <li> Usually, there is one testcase method per JSR166 method
+ * <li>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.</li>
+ * cover multiple methods when they cannot be tested in isolation.
*
- * <li> The documentation style for testcases is to provide as javadoc
+ * <li>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.</li>
+ * the property is tested. To find out, read the code.
*
- * <li> These tests are "conformance tests", and do not attempt to
+ * <li>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.</li>
+ * needed to check basic conformance.
*
* <li>The test classes currently do not declare inclusion in
* any particular package to simplify things for people integrating
- * them in TCK test suites.</li>
+ * them in TCK test suites.
*
- * <li> As a convenience, the {@code main} of this class (JSR166TestCase)
- * runs all JSR166 unit tests.</li>
+ * <li>As a convenience, the {@code main} of this class (JSR166TestCase)
+ * runs all JSR166 unit tests.
*
* </ul>
*/
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 = false;
+ 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 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<Double>() {
+ public Double run() {
+ return Double.valueOf(System.getProperty("java.class.version"));}});
+ JAVA_SPECIFICATION_VERSION = java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction<String>() {
+ 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<String> testMethodNames(Class<?> testClass) {
+ Method[] methods = testClass.getDeclaredMethods();
+ ArrayList<String> names = new ArrayList<String>(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 <ExtraData> Test parameterizedTestSuite
+ (Class<? extends JSR166TestCase> testClass,
+ Class<ExtraData> 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 <ExtraData> Test jdk8ParameterizedTestSuite
+ (Class<? extends JSR166TestCase> testClass,
+ Class<ExtraData> 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 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 @@
}
/**
- * 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 @@
* the same test have no effect.
*/
public void threadRecordFailure(Throwable t) {
+ System.err.println(t);
+ dumpTestThreads();
threadFailure.compareAndSet(null, t);
}
@@ -189,6 +633,13 @@
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 @@
}
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 @@
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 @@
fail(reason);
} catch (AssertionFailedError t) {
threadRecordFailure(t);
- fail(reason);
+ throw t;
}
}
@@ -379,44 +832,148 @@
/**
* 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; }
+
+ /**
+ * 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.
+ */
+ void testInParallel(Action ... actions) {
+ ExecutorService pool = Executors.newCachedThreadPool();
+ try (PoolCleaner cleaner = cleaner(pool)) {
+ ArrayList<Future<?>> 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 all stack traces, as jstack does.
+ * A debugging tool to print stack traces of most threads, as jstack does.
+ * Uninteresting threads are filtered out.
*/
- static void printAllStackTraces() {
+ 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 @@
delay(millis);
assertTrue(thread.isAlive());
} catch (InterruptedException fail) {
- fail("Unexpected InterruptedException");
+ threadFail("Unexpected InterruptedException");
}
}
@@ -458,7 +1015,7 @@
for (Thread thread : threads)
assertTrue(thread.isAlive());
} catch (InterruptedException fail) {
- fail("Unexpected InterruptedException");
+ threadFail("Unexpected InterruptedException");
}
}
@@ -532,6 +1089,12 @@
* 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 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 @@
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 @@
} 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 final String TEST_STRING = "a test string";
public static class StringTask implements Callable<String> {
- 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<String> latchAwaitingStringTask(final CountDownLatch latch) {
@@ -861,24 +1438,50 @@
}};
}
- 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 62802bb..789373d 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 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 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 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 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 @@
}
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 @@
}
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 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<Integer> elements = Arrays.asList(ints);
try {
@@ -419,7 +418,7 @@
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 @@
* 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 @@
* 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 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<Integer> elements = Arrays.asList(ints);
try {
@@ -756,25 +755,23 @@
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 @@
* 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 @@
shouldThrow();
} catch (InterruptedException success) {}
assertFalse(Thread.interrupted());
+ assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS);
}});
await(pleaseInterrupt);
@@ -1251,7 +1250,7 @@
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 @@
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 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 @@
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 @@
* 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 @@
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 @@
shouldThrow();
} catch (InterruptedException success) {}
assertFalse(Thread.interrupted());
+
+ assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS);
}});
barrier.await();
@@ -1463,7 +1469,7 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.remove();
}
}
@@ -1476,7 +1482,7 @@
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 @@
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());
- }});
+ 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());
- }});
-
- joinPool(executor);
+ executor.execute(new CheckedRunnable() {
+ public void realRun() throws InterruptedException {
+ threadsStarted.await();
+ assertSame(one, q.take());
+ }});
+ }
}
/**
@@ -1700,22 +1706,22 @@
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);
- }});
+ 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);
- }});
-
- joinPool(executor);
+ executor.execute(new CheckedRunnable() {
+ public void realRun() throws InterruptedException {
+ threadsStarted.await();
+ q.put(one);
+ }});
+ }
}
/**
@@ -1767,7 +1773,7 @@
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 @@
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 bd37b2a..faf3f18 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 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<Integer> elements = Arrays.asList(ints);
try {
@@ -160,7 +162,7 @@
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 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<Integer> elements = Arrays.asList(ints);
try {
@@ -445,25 +447,23 @@
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 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.remove();
}
}
@@ -592,7 +592,7 @@
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 @@
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());
- }});
+ 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());
- }});
-
- joinPool(executor);
+ executor.execute(new CheckedRunnable() {
+ public void realRun() throws InterruptedException {
+ threadsStarted.await();
+ assertSame(one, q.take());
+ }});
+ }
}
/**
@@ -752,22 +752,22 @@
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);
- }});
+ 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);
- }});
-
- joinPool(executor);
+ executor.execute(new CheckedRunnable() {
+ public void realRun() throws InterruptedException {
+ threadsStarted.await();
+ q.put(one);
+ }});
+ }
}
/**
@@ -819,7 +819,7 @@
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 @@
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 9d9481d..9c971b4 100644
--- a/jsr166-tests/src/test/java/jsr166/LinkedListTest.java
+++ b/jsr166-tests/src/test/java/jsr166/LinkedListTest.java
@@ -25,7 +25,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(LinkedListTest.class);
// }
/**
@@ -91,7 +91,7 @@
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 testOfferNull() {
LinkedList q = new LinkedList();
q.offer(null);
+ assertNull(q.get(0));
+ assertTrue(q.contains(null));
}
/**
@@ -132,8 +134,8 @@
* 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 @@
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 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.remove();
}
}
@@ -324,7 +326,7 @@
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 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 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 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 @@
}
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 @@
}
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 8d3f276..c712592 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 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<Integer> elements = Arrays.asList(ints);
try {
@@ -141,8 +144,8 @@
* 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 @@
* 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 testTimedPoll() throws InterruptedException {
LinkedTransferQueue<Integer> 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 @@
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 @@
final BlockingQueue<Integer> 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 testOfferInExecutor() {
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 {
- threadsStarted.await();
- assertTrue(q.offer(one, LONG_DELAY_MS, MILLISECONDS));
- }});
+ 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);
+ }});
- executor.execute(new CheckedRunnable() {
- public void realRun() throws InterruptedException {
- threadsStarted.await();
- assertSame(one, q.take());
- checkEmpty(q);
- }});
-
- 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 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 {
+ 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);
+ }});
- executor.execute(new CheckedRunnable() {
- public void realRun() throws InterruptedException {
- threadsStarted.await();
- q.put(one);
- }});
-
- joinPool(executor);
+ executor.execute(new CheckedRunnable() {
+ public void realRun() throws InterruptedException {
+ threadsStarted.await();
+ q.put(one);
+ }});
+ }
}
/**
@@ -699,7 +700,7 @@
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 @@
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 @@
assertEquals(0, q.getWaitingConsumerCount());
assertFalse(q.hasWaitingConsumer());
- awaitTermination(t, MEDIUM_DELAY_MS);
+ awaitTermination(t);
}
/**
@@ -782,12 +784,11 @@
}});
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 @@
assertEquals(1, q.size());
assertTrue(q.offer(three));
assertSame(four, q.poll());
- awaitTermination(t, MEDIUM_DELAY_MS);
+ awaitTermination(t);
}
/**
@@ -866,15 +867,15 @@
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 @@
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 @@
assertSame(q.take(), hotPotato);
checkEmpty(q);
- awaitTermination(t, MEDIUM_DELAY_MS);
+ awaitTermination(t);
}
/**
@@ -945,6 +948,7 @@
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 @@
shouldThrow();
} catch (InterruptedException success) {}
assertFalse(Thread.interrupted());
+ assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS);
}});
await(pleaseInterrupt);
@@ -975,10 +980,10 @@
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 @@
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 @@
assertSame(four, q.poll());
assertSame(five, q.poll());
checkEmpty(q);
- awaitTermination(t, MEDIUM_DELAY_MS);
+ awaitTermination(t);
}
/**
@@ -1017,9 +1024,9 @@
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 8347b08..b3a8ed8 100644
--- a/jsr166-tests/src/test/java/jsr166/LockSupportTest.java
+++ b/jsr166-tests/src/test/java/jsr166/LockSupportTest.java
@@ -26,9 +26,15 @@
// 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 0000000..5dd52e9
--- /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 0000000..800a9c8
--- /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 42d72f4..673e556 100644
--- a/jsr166-tests/src/test/java/jsr166/PhaserTest.java
+++ b/jsr166-tests/src/test/java/jsr166/PhaserTest.java
@@ -28,7 +28,7 @@
// 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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
* 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 64c3b3a..41b06f5 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 @@
// 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 @@
PriorityBlockingQueue<Integer> q =
new PriorityBlockingQueue<Integer>(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 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<Integer> elements = Arrays.asList(ints);
try {
@@ -153,7 +155,7 @@
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 @@
* 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 @@
* 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 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 @@
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 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.remove();
}
}
@@ -530,7 +530,7 @@
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 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);
- }});
+ 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);
- }});
-
- joinPool(executor);
+ executor.execute(new CheckedRunnable() {
+ public void realRun() throws InterruptedException {
+ threadsStarted.await();
+ q.put(one);
+ }});
+ }
}
/**
@@ -694,7 +694,7 @@
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 @@
* 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 @@
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 88cdd37..f64ef68 100644
--- a/jsr166-tests/src/test/java/jsr166/PriorityQueueTest.java
+++ b/jsr166-tests/src/test/java/jsr166/PriorityQueueTest.java
@@ -27,7 +27,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(PriorityQueueTest.class);
// }
static class MyReverseComparator implements Comparator {
@@ -43,7 +43,7 @@
private PriorityQueue<Integer> populatedQueue(int n) {
PriorityQueue<Integer> q = new PriorityQueue<Integer>(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 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 @@
* 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 @@
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 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
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 @@
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 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.remove();
}
}
@@ -408,7 +406,7 @@
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 1c3bba8..c8e33be 100644
--- a/jsr166-tests/src/test/java/jsr166/RecursiveActionTest.java
+++ b/jsr166-tests/src/test/java/jsr166/RecursiveActionTest.java
@@ -32,7 +32,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(RecursiveActionTest.class);
// }
private static ForkJoinPool mainPool() {
@@ -50,14 +50,12 @@
}
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 @@
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 7783370..2c07c2a 100644
--- a/jsr166-tests/src/test/java/jsr166/RecursiveTaskTest.java
+++ b/jsr166-tests/src/test/java/jsr166/RecursiveTaskTest.java
@@ -28,7 +28,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(RecursiveTaskTest.class);
// }
private static ForkJoinPool mainPool() {
@@ -46,15 +46,13 @@
}
private <T> T testInvokeOnPool(ForkJoinPool pool, RecursiveTask<T> 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 @@
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 17eaf76..0024ff3 100644
--- a/jsr166-tests/src/test/java/jsr166/ReentrantLockTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ReentrantLockTest.java
@@ -30,8 +30,9 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ReentrantLockTest.class);
// }
+
/**
* A checked runnable calling lockInterruptibly
*/
@@ -150,7 +151,7 @@
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 @@
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 @@
}
for (int i = SIZE; i > 0; i--) {
lock.unlock();
- assertEquals(i-1, lock.getHoldCount());
+ assertEquals(i - 1, lock.getHoldCount());
}
}
@@ -568,11 +570,11 @@
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 7ef8ea3..918f45d 100644
--- a/jsr166-tests/src/test/java/jsr166/ReentrantReadWriteLockTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ReentrantReadWriteLockTest.java
@@ -31,7 +31,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ReentrantReadWriteLockTest.class);
// }
/**
@@ -160,24 +160,26 @@
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 @@
}
for (int i = SIZE; i > 0; i--) {
lock.writeLock().unlock();
- assertEquals(i-1,lock.getWriteHoldCount());
+ assertEquals(i - 1,lock.getWriteHoldCount());
}
}
@@ -258,7 +260,7 @@
}
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 @@
}
for (int i = SIZE; i > 0; i--) {
lock.readLock().unlock();
- assertEquals(i-1,lock.getReadHoldCount());
+ assertEquals(i - 1,lock.getReadHoldCount());
}
}
@@ -972,11 +974,11 @@
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 a93feea..194dd58 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 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ScheduledExecutorSubclassTest.class);
// }
static class CustomTask<V> implements RunnableScheduledFuture<V> {
@@ -101,17 +107,13 @@
* 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 @@
* 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<Boolean>() {
public Boolean realCall() {
done.countDown();
@@ -132,9 +134,6 @@
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 @@
* 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 @@
await(done);
assertNull(f.get(LONG_DELAY_MS, MILLISECONDS));
assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
- } finally {
- joinPool(p);
}
}
@@ -164,10 +161,10 @@
* 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 @@
await(done);
assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
f.cancel(true);
- } finally {
- joinPool(p);
}
}
@@ -188,10 +183,10 @@
* 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 @@
await(done);
assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
f.cancel(true);
- } finally {
- joinPool(p);
}
}
@@ -214,58 +207,77 @@
}
/**
- * 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 @@
* 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 @@
* 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 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 @@
fail("timed out");
Thread.yield();
}
- } finally {
- joinPool(p);
}
}
@@ -436,9 +444,10 @@
* 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 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 @@
* 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 @@
* 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 @@
* 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<Runnable> q = p.getQueue();
assertTrue(q.contains(tasks[tasks.length - 1]));
assertFalse(q.contains(tasks[0]));
- } finally {
- done.countDown();
- joinPool(p);
}
}
@@ -648,20 +656,20 @@
* 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<Runnable> q = p.getQueue();
assertFalse(p.remove((Runnable)tasks[0]));
assertTrue(q.contains((Runnable)tasks[4]));
@@ -672,9 +680,6 @@
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 @@
* 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 @@
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<Runnable> queuedTasks;
try {
- List<Runnable> 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
}
- }
-
- /**
- * In default setting, shutdown cancels periodic but not delayed
- * tasks at shutdown
- */
- 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<Runnable> q = p.getQueue();
- for (ScheduledFuture task : tasks) {
- assertFalse(task.isDone());
- assertFalse(task.isCancelled());
- assertTrue(q.contains(task));
- }
- assertTrue(p.isShutdown());
- assertTrue(p.awaitTermination(SMALL_DELAY_MS, MILLISECONDS));
- assertTrue(p.isTerminated());
- for (ScheduledFuture task : tasks) {
- assertTrue(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.isTerminated());
- for (ScheduledFuture task : tasks) {
- assertTrue(task.isDone());
- assertTrue(task.isCancelled());
- }
- }
-
- /**
- * If setContinueExistingPeriodicTasksAfterShutdownPolicy is set false,
- * periodic tasks are cancelled at shutdown
- */
- 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);
- try { p.shutdown(); } catch (SecurityException ok) { return; }
assertTrue(p.isShutdown());
assertTrue(p.getQueue().isEmpty());
- assertTrue(task.isDone());
- assertTrue(task.isCancelled());
- joinPool(p);
+ assertEquals(count - poolSize, queuedTasks.size());
+ assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS));
+ assertTrue(p.isTerminated());
+ assertEquals(poolSize, ran.get());
+ assertEquals(poolSize, p.getCompletedTaskCount());
}
/**
- * if setContinueExistingPeriodicTasksAfterShutdownPolicy is true,
- * periodic tasks are not cancelled at shutdown
+ * shutdownNow returns a list containing tasks that were not run,
+ * and those tasks are drained from the queue
*/
- public void testShutdown4() throws InterruptedException {
- CustomExecutor p = new CustomExecutor(1);
- final CountDownLatch counter = new CountDownLatch(2);
+ public void testShutdownNow_delayedTasks() throws InterruptedException {
+ final CustomExecutor p = new CustomExecutor(1);
+ List<ScheduledFuture> 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<Runnable> queuedTasks;
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);
+ queuedTasks = p.shutdownNow();
+ } catch (SecurityException ok) {
+ return; // Allowed in case test doesn't have privs
+ }
+ assertTrue(p.isShutdown());
+ assertTrue(p.getQueue().isEmpty());
+ if (testImplementationDetails)
+ assertEquals(new HashSet(tasks), new HashSet(queuedTasks));
+ assertEquals(tasks.size(), queuedTasks.size());
+ for (ScheduledFuture task : tasks) {
+ assertFalse(((CustomTask)task).ran);
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());
}
- finally {
- joinPool(p);
- }
+ assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS));
+ assertTrue(p.isTerminated());
}
/**
+ * 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 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<Future<?>> blockers = new ArrayList<>();
+ List<Future<?>> periodics = new ArrayList<>();
+ List<Future<?>> 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());
+ 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());
+ }
+ }
+ 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<String> future = e.submit(new StringTask());
String result = future.get();
assertSame(TEST_STRING, result);
- } finally {
- joinPool(e);
}
}
@@ -859,13 +893,11 @@
* 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 @@
* 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<String> future = e.submit(new NoOpRunnable(), TEST_STRING);
String result = future.get();
assertSame(TEST_STRING, result);
- } finally {
- joinPool(e);
}
}
@@ -887,13 +917,12 @@
* 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 @@
* invokeAny(empty collection) throws IAE
*/
public void testInvokeAny2() throws Exception {
- ExecutorService e = new CustomExecutor(2);
- try {
- e.invokeAny(new ArrayList<Callable<String>>());
- shouldThrow();
- } catch (IllegalArgumentException success) {
- } finally {
- joinPool(e);
+ final ExecutorService e = new CustomExecutor(2);
+ try (PoolCleaner cleaner = cleaner(e)) {
+ try {
+ e.invokeAny(new ArrayList<Callable<String>>());
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
}
@@ -915,18 +943,17 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(latchAwaitingStringTask(latch));
+ l.add(null);
+ try {
+ e.invokeAny(l);
+ shouldThrow();
+ } catch (NullPointerException success) {}
latch.countDown();
- joinPool(e);
}
}
@@ -934,16 +961,16 @@
* invokeAny(c) throws ExecutionException if no task completes
*/
public void testInvokeAny4() throws Exception {
- ExecutorService e = new CustomExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ try {
+ e.invokeAny(l);
+ shouldThrow();
+ } catch (ExecutionException success) {
+ assertTrue(success.getCause() instanceof NullPointerException);
+ }
}
}
@@ -951,15 +978,13 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
String result = e.invokeAny(l);
assertSame(TEST_STRING, result);
- } finally {
- joinPool(e);
}
}
@@ -967,13 +992,12 @@
* 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 @@
* 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<Future<String>> r = e.invokeAll(new ArrayList<Callable<String>>());
assertTrue(r.isEmpty());
- } finally {
- joinPool(e);
}
}
@@ -994,16 +1016,15 @@
* invokeAll(c) throws NPE if c has null elements
*/
public void testInvokeAll3() throws Exception {
- ExecutorService e = new CustomExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ l.add(null);
+ try {
+ e.invokeAll(l);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1011,18 +1032,18 @@
* get of invokeAll(c) throws exception on failed task
*/
public void testInvokeAll4() throws Exception {
- ExecutorService e = new CustomExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new NPETask());
- List<Future<String>> 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ List<Future<String>> 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 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
@@ -1039,8 +1060,6 @@
assertEquals(2, futures.size());
for (Future<String> future : futures)
assertSame(TEST_STRING, future.get());
- } finally {
- joinPool(e);
}
}
@@ -1048,13 +1067,12 @@
* 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 @@
* timed invokeAny(,,null) throws NPE
*/
public void testTimedInvokeAnyNullTimeUnit() throws Exception {
- ExecutorService e = new CustomExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ try {
+ e.invokeAny(l, MEDIUM_DELAY_MS, null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1078,13 +1095,12 @@
* timed invokeAny(empty collection) throws IAE
*/
public void testTimedInvokeAny2() throws Exception {
- ExecutorService e = new CustomExecutor(2);
- try {
- e.invokeAny(new ArrayList<Callable<String>>(), 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<Callable<String>>(), MEDIUM_DELAY_MS, MILLISECONDS);
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
}
@@ -1093,17 +1109,16 @@
*/
public void testTimedInvokeAny3() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- ExecutorService e = new CustomExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ 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 @@
* timed invokeAny(c) throws ExecutionException if no task completes
*/
public void testTimedInvokeAny4() throws Exception {
- ExecutorService e = new CustomExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ 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 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
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 @@
* 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 @@
* timed invokeAll(,,null) throws NPE
*/
public void testTimedInvokeAllNullTimeUnit() throws Exception {
- ExecutorService e = new CustomExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ try {
+ e.invokeAll(l, MEDIUM_DELAY_MS, null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1174,12 +1189,10 @@
* 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<Future<String>> r = e.invokeAll(new ArrayList<Callable<String>>(), MEDIUM_DELAY_MS, MILLISECONDS);
assertTrue(r.isEmpty());
- } finally {
- joinPool(e);
}
}
@@ -1187,16 +1200,15 @@
* timed invokeAll(c) throws NPE if c has null elements
*/
public void testTimedInvokeAll3() throws Exception {
- ExecutorService e = new CustomExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ l.add(null);
+ try {
+ e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1204,19 +1216,19 @@
* get of element of invokeAll(c) throws exception on failed task
*/
public void testTimedInvokeAll4() throws Exception {
- ExecutorService e = new CustomExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new NPETask());
- List<Future<String>> 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ List<Future<String>> 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 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
List<Future<String>> futures =
- e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS);
+ e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS);
assertEquals(2, futures.size());
for (Future<String> future : futures)
assertSame(TEST_STRING, future.get());
- } finally {
- joinPool(e);
}
}
@@ -1243,21 +1253,37 @@
* timed invokeAll(c) cancels tasks not completed by timeout
*/
public void testTimedInvokeAll6() throws Exception {
- ExecutorService e = new CustomExecutor(2);
- try {
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new StringTask());
- l.add(Executors.callable(new MediumPossiblyInterruptedRunnable(), TEST_STRING));
- l.add(new StringTask());
- List<Future<String>> 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<String> waiter = new CheckedCallable<String>() {
+ 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<Callable<String>> tasks = new ArrayList<>();
+ tasks.add(new StringTask("0"));
+ tasks.add(waiter);
+ tasks.add(new StringTask("2"));
+ long startTime = System.nanoTime();
+ List<Future<String>> 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 a2e83d0..81f7370 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 @@
// 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 @@
* 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<Boolean>() {
public Boolean realCall() {
done.countDown();
@@ -76,8 +78,6 @@
assertSame(Boolean.TRUE, f.get());
assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
assertTrue(done.await(0L, MILLISECONDS));
- } finally {
- joinPool(p);
}
}
@@ -85,10 +85,10 @@
* 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 @@
await(done);
assertNull(f.get(LONG_DELAY_MS, MILLISECONDS));
assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
- } finally {
- joinPool(p);
}
}
@@ -107,10 +105,10 @@
* 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 @@
await(done);
assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
f.cancel(true);
- } finally {
- joinPool(p);
}
}
@@ -131,10 +127,10 @@
* 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 @@
await(done);
assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
f.cancel(true);
- } finally {
- joinPool(p);
}
}
@@ -157,58 +151,77 @@
}
/**
- * 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 @@
* 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 @@
* 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 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 @@
fail("timed out");
Thread.yield();
}
- } finally {
- joinPool(p);
}
}
@@ -382,8 +389,9 @@
*/
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 @@
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 @@
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 @@
* 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 testIsShutdown() {
- ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1);
+ final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1);
try {
assertFalse(p.isShutdown());
}
@@ -517,24 +532,23 @@
*/
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 @@
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<Runnable> q = p.getQueue();
assertTrue(q.contains(tasks[tasks.length - 1]));
assertFalse(q.contains(tasks[0]));
- } finally {
- done.countDown();
- joinPool(p);
}
}
@@ -594,20 +604,20 @@
* 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<Runnable> q = p.getQueue();
assertFalse(p.remove((Runnable)tasks[0]));
assertTrue(q.contains((Runnable)tasks[4]));
@@ -618,9 +628,6 @@
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 @@
* 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 @@
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<Runnable> queuedTasks;
try {
- List<Runnable> 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
}
- }
-
- /**
- * In default setting, shutdown cancels periodic but not delayed
- * tasks at shutdown
- */
- 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<Runnable> q = p.getQueue();
- for (ScheduledFuture task : tasks) {
- assertFalse(task.isDone());
- assertFalse(task.isCancelled());
- assertTrue(q.contains(task));
- }
- assertTrue(p.isShutdown());
- assertTrue(p.awaitTermination(SMALL_DELAY_MS, MILLISECONDS));
- assertTrue(p.isTerminated());
- for (ScheduledFuture task : tasks) {
- assertTrue(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.isTerminated());
- for (ScheduledFuture task : tasks) {
- assertTrue(task.isDone());
- assertTrue(task.isCancelled());
- }
- }
-
- /**
- * If setContinueExistingPeriodicTasksAfterShutdownPolicy is set false,
- * periodic tasks are cancelled at shutdown
- */
- 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);
- try { p.shutdown(); } catch (SecurityException ok) { return; }
assertTrue(p.isShutdown());
assertTrue(p.getQueue().isEmpty());
- assertTrue(task.isDone());
- assertTrue(task.isCancelled());
- joinPool(p);
+ assertEquals(count - poolSize, queuedTasks.size());
+ assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS));
+ assertTrue(p.isTerminated());
+ assertEquals(poolSize, ran.get());
+ assertEquals(poolSize, p.getCompletedTaskCount());
}
/**
- * if setContinueExistingPeriodicTasksAfterShutdownPolicy is true,
- * periodic tasks are not cancelled at shutdown
+ * shutdownNow returns a list containing tasks that were not run,
+ * and those tasks are drained from the queue
*/
- public void testShutdown4() throws InterruptedException {
- ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1);
- final CountDownLatch counter = new CountDownLatch(2);
+ public void testShutdownNow_delayedTasks() throws InterruptedException {
+ final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1);
+ List<ScheduledFuture> 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<Runnable> queuedTasks;
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);
+ queuedTasks = p.shutdownNow();
+ } catch (SecurityException ok) {
+ return; // Allowed in case test doesn't have privs
+ }
+ assertTrue(p.isShutdown());
+ assertTrue(p.getQueue().isEmpty());
+ if (testImplementationDetails)
+ assertEquals(new HashSet(tasks), new HashSet(queuedTasks));
+ assertEquals(tasks.size(), queuedTasks.size());
+ for (ScheduledFuture task : tasks) {
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());
}
- finally {
- joinPool(p);
- }
+ assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS));
+ assertTrue(p.isTerminated());
}
/**
+ * 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 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<Future<?>> blockers = new ArrayList<>();
+ List<Future<?>> periodics = new ArrayList<>();
+ List<Future<?>> 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());
+ 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());
+ }
+ }
+ 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<String> future = e.submit(new StringTask());
String result = future.get();
assertSame(TEST_STRING, result);
- } finally {
- joinPool(e);
}
}
@@ -805,13 +842,11 @@
* 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 @@
* 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<String> future = e.submit(new NoOpRunnable(), TEST_STRING);
String result = future.get();
assertSame(TEST_STRING, result);
- } finally {
- joinPool(e);
}
}
@@ -833,13 +866,12 @@
* 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 @@
* invokeAny(empty collection) throws IAE
*/
public void testInvokeAny2() throws Exception {
- ExecutorService e = new ScheduledThreadPoolExecutor(2);
- try {
- e.invokeAny(new ArrayList<Callable<String>>());
- shouldThrow();
- } catch (IllegalArgumentException success) {
- } finally {
- joinPool(e);
+ final ExecutorService e = new ScheduledThreadPoolExecutor(2);
+ try (PoolCleaner cleaner = cleaner(e)) {
+ try {
+ e.invokeAny(new ArrayList<Callable<String>>());
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
}
@@ -862,17 +893,16 @@
*/
public void testInvokeAny3() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- ExecutorService e = new ScheduledThreadPoolExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(latchAwaitingStringTask(latch));
+ l.add(null);
+ try {
+ e.invokeAny(l);
+ shouldThrow();
+ } catch (NullPointerException success) {}
latch.countDown();
- joinPool(e);
}
}
@@ -880,16 +910,16 @@
* invokeAny(c) throws ExecutionException if no task completes
*/
public void testInvokeAny4() throws Exception {
- ExecutorService e = new ScheduledThreadPoolExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ try {
+ e.invokeAny(l);
+ shouldThrow();
+ } catch (ExecutionException success) {
+ assertTrue(success.getCause() instanceof NullPointerException);
+ }
}
}
@@ -897,15 +927,13 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
String result = e.invokeAny(l);
assertSame(TEST_STRING, result);
- } finally {
- joinPool(e);
}
}
@@ -913,13 +941,12 @@
* 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 @@
* 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<Future<String>> r = e.invokeAll(new ArrayList<Callable<String>>());
assertTrue(r.isEmpty());
- } finally {
- joinPool(e);
}
}
@@ -940,16 +965,15 @@
* invokeAll(c) throws NPE if c has null elements
*/
public void testInvokeAll3() throws Exception {
- ExecutorService e = new ScheduledThreadPoolExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ l.add(null);
+ try {
+ e.invokeAll(l);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -957,18 +981,18 @@
* get of invokeAll(c) throws exception on failed task
*/
public void testInvokeAll4() throws Exception {
- ExecutorService e = new ScheduledThreadPoolExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new NPETask());
- List<Future<String>> 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ List<Future<String>> 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 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
@@ -985,8 +1009,6 @@
assertEquals(2, futures.size());
for (Future<String> future : futures)
assertSame(TEST_STRING, future.get());
- } finally {
- joinPool(e);
}
}
@@ -994,13 +1016,12 @@
* 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 @@
* timed invokeAny(,,null) throws NPE
*/
public void testTimedInvokeAnyNullTimeUnit() throws Exception {
- ExecutorService e = new ScheduledThreadPoolExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ try {
+ e.invokeAny(l, MEDIUM_DELAY_MS, null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1024,13 +1044,12 @@
* timed invokeAny(empty collection) throws IAE
*/
public void testTimedInvokeAny2() throws Exception {
- ExecutorService e = new ScheduledThreadPoolExecutor(2);
- try {
- e.invokeAny(new ArrayList<Callable<String>>(), 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<Callable<String>>(), MEDIUM_DELAY_MS, MILLISECONDS);
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
}
@@ -1039,17 +1058,16 @@
*/
public void testTimedInvokeAny3() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- ExecutorService e = new ScheduledThreadPoolExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ 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 @@
* timed invokeAny(c) throws ExecutionException if no task completes
*/
public void testTimedInvokeAny4() throws Exception {
- ExecutorService e = new ScheduledThreadPoolExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ 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 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
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 @@
* 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 @@
* timed invokeAll(,,null) throws NPE
*/
public void testTimedInvokeAllNullTimeUnit() throws Exception {
- ExecutorService e = new ScheduledThreadPoolExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ try {
+ e.invokeAll(l, MEDIUM_DELAY_MS, null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1120,12 +1138,11 @@
* timed invokeAll(empty collection) returns empty collection
*/
public void testTimedInvokeAll2() throws Exception {
- ExecutorService e = new ScheduledThreadPoolExecutor(2);
- try {
- List<Future<String>> r = e.invokeAll(new ArrayList<Callable<String>>(), MEDIUM_DELAY_MS, MILLISECONDS);
+ final ExecutorService e = new ScheduledThreadPoolExecutor(2);
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Future<String>> r = e.invokeAll(new ArrayList<Callable<String>>(),
+ MEDIUM_DELAY_MS, MILLISECONDS);
assertTrue(r.isEmpty());
- } finally {
- joinPool(e);
}
}
@@ -1133,16 +1150,15 @@
* timed invokeAll(c) throws NPE if c has null elements
*/
public void testTimedInvokeAll3() throws Exception {
- ExecutorService e = new ScheduledThreadPoolExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ l.add(null);
+ try {
+ e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1150,19 +1166,19 @@
* get of element of invokeAll(c) throws exception on failed task
*/
public void testTimedInvokeAll4() throws Exception {
- ExecutorService e = new ScheduledThreadPoolExecutor(2);
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new NPETask());
- List<Future<String>> 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ List<Future<String>> 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 @@
* 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<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
List<Future<String>> futures =
- e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS);
+ e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS);
assertEquals(2, futures.size());
for (Future<String> future : futures)
assertSame(TEST_STRING, future.get());
- } finally {
- joinPool(e);
}
}
@@ -1189,21 +1203,60 @@
* timed invokeAll(c) cancels tasks not completed by timeout
*/
public void testTimedInvokeAll6() throws Exception {
- ExecutorService e = new ScheduledThreadPoolExecutor(2);
- try {
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new StringTask());
- l.add(Executors.callable(new MediumPossiblyInterruptedRunnable(), TEST_STRING));
- l.add(new StringTask());
- List<Future<String>> 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<String> waiter = new CheckedCallable<String>() {
+ 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<Callable<String>> tasks = new ArrayList<>();
+ tasks.add(new StringTask("0"));
+ tasks.add(waiter);
+ tasks.add(new StringTask("2"));
+ long startTime = System.nanoTime();
+ List<Future<String>> 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 db4f4b4..09c82c8 100644
--- a/jsr166-tests/src/test/java/jsr166/SemaphoreTest.java
+++ b/jsr166-tests/src/test/java/jsr166/SemaphoreTest.java
@@ -26,8 +26,9 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(SemaphoreTest.class);
// }
+
/**
* Subclass to expose protected methods
*/
@@ -471,11 +472,16 @@
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 @@
awaitTermination(t);
assertFalse(s.hasQueuedThreads());
assertFalse(clone.hasQueuedThreads());
- } catch (InterruptedException e) { threadUnexpectedException(e); }
+ }
}
/**
@@ -594,7 +600,7 @@
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 0000000..d347c7d
--- /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 605a955..9d3f212 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 @@
// 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 @@
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 @@
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 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);
+ 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 {
+ 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);
+ executor.execute(new CheckedRunnable() {
+ public void realRun() throws InterruptedException {
+ threadsStarted.await();
+ assertSame(one, q.take());
+ }});
+ }
}
/**
@@ -502,22 +505,22 @@
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());
- }});
+ 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);
- }});
-
- joinPool(executor);
+ executor.execute(new CheckedRunnable() {
+ public void realRun() throws InterruptedException {
+ threadsStarted.await();
+ q.put(one);
+ }});
+ }
}
/**
@@ -595,10 +598,12 @@
}});
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 6918374..412ce17 100644
--- a/jsr166-tests/src/test/java/jsr166/SystemTest.java
+++ b/jsr166-tests/src/test/java/jsr166/SystemTest.java
@@ -19,7 +19,7 @@
// 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 0000000..614af83
--- /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 4ae141d..5d9f894 100644
--- a/jsr166-tests/src/test/java/jsr166/ThreadLocalRandomTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ThreadLocalRandomTest.java
@@ -22,7 +22,7 @@
// 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 7f5f072..8bfcf70 100644
--- a/jsr166-tests/src/test/java/jsr166/ThreadLocalTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ThreadLocalTest.java
@@ -19,7 +19,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ThreadLocalTest.class);
// }
static ThreadLocal<Integer> tl = new ThreadLocal<Integer>() {
diff --git a/jsr166-tests/src/test/java/jsr166/ThreadPoolExecutorSubclassTest.java b/jsr166-tests/src/test/java/jsr166/ThreadPoolExecutorSubclassTest.java
index 5f38d39..a502392 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 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ThreadPoolExecutorSubclassTest.class);
// }
static class CustomTask<V> implements RunnableFuture<V> {
@@ -103,11 +106,13 @@
}
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 @@
try {
while (!done)
cond.await();
+ if (cancelled)
+ throw new CancellationException();
if (exception != null)
throw new ExecutionException(exception);
return result;
@@ -127,12 +134,13 @@
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 void testExecute() throws InterruptedException {
final ThreadPoolExecutor p =
new CustomTPE(1, 1,
- LONG_DELAY_MS, MILLISECONDS,
+ 2 * LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
* 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<Runnable>(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 @@
* 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<Runnable>(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<Runnable>(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<Runnable>(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<Runnable>(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 @@
new CustomTPE(2, 2,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
fail("timed out");
Thread.yield();
}
- } finally {
- joinPool(p);
}
}
@@ -339,52 +358,72 @@
* 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<Runnable>(10));
- assertEquals(1, p.getCorePoolSize());
- joinPool(p);
+ final ThreadPoolExecutor p =
+ new CustomTPE(1, 1,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(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<Runnable>(10));
- assertEquals(1, p.getKeepAliveTime(TimeUnit.SECONDS));
- joinPool(p);
+ final ThreadPoolExecutor p =
+ new CustomTPE(2, 2,
+ 1000, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(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<Runnable>(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<Runnable>(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<Runnable>(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<Runnable>(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<Runnable>(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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(p)) {
+ try {
+ p.setThreadFactory(null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -392,10 +431,15 @@
* 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<Runnable>(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<Runnable>(10),
+ handler);
+ try (PoolCleaner cleaner = cleaner(p)) {
+ assertSame(handler, p.getRejectedExecutionHandler());
+ }
}
/**
@@ -403,24 +447,30 @@
* getRejectedExecutionHandler
*/
public void testSetRejectedExecutionHandler() {
- ThreadPoolExecutor p = new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(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<Runnable>(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<Runnable>(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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(p)) {
+ try {
+ p.setRejectedExecutionHandler(null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -430,28 +480,25 @@
*/
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<Runnable>(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 @@
* otherwise set
*/
public void testGetMaximumPoolSize() {
- ThreadPoolExecutor p = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
- assertEquals(2, p.getMaximumPoolSize());
- joinPool(p);
+ final ThreadPoolExecutor p =
+ new CustomTPE(2, 3,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(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 @@
* 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<Runnable>(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 @@
* 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<Runnable>(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<Runnable>(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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(p)) {
+ assertFalse(p.isShutdown());
+ try { p.shutdown(); } catch (SecurityException ok) { return; }
+ assertTrue(p.isShutdown());
+ }
}
/**
@@ -537,25 +604,24 @@
new CustomTPE(1, 1,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
new CustomTPE(1, 1,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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<Runnable> q = new ArrayBlockingQueue<Runnable>(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<Boolean>() {
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 @@
* remove(task) removes queued task, and fails to remove active task
*/
public void testRemove() throws InterruptedException {
+ final CountDownLatch done = new CountDownLatch(1);
BlockingQueue<Runnable> q = new ArrayBlockingQueue<Runnable>(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 @@
assertTrue(q.contains(tasks[3]));
assertTrue(p.remove(tasks[3]));
assertFalse(q.contains(tasks[3]));
- } finally {
- done.countDown();
- joinPool(p);
}
}
@@ -670,19 +729,19 @@
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<Boolean>() {
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 @@
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<Runnable>(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<Runnable>(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<Runnable> 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 testConstructor1() {
try {
- new CustomTPE(-1,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
+ new CustomTPE(-1, 1, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -737,7 +815,8 @@
*/
public void testConstructor2() {
try {
- new CustomTPE(1,-1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
+ new CustomTPE(1, -1, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -747,7 +826,8 @@
*/
public void testConstructor3() {
try {
- new CustomTPE(1,0,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
+ new CustomTPE(1, 0, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -757,7 +837,8 @@
*/
public void testConstructor4() {
try {
- new CustomTPE(1,2,-1L,MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
+ new CustomTPE(1, 2, -1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -767,7 +848,8 @@
*/
public void testConstructor5() {
try {
- new CustomTPE(2,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
+ new CustomTPE(2, 1, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -777,7 +859,7 @@
*/
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 testConstructor6() {
try {
- new CustomTPE(-1,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new SimpleThreadFactory());
+ new CustomTPE(-1, 1, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new SimpleThreadFactory());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -797,7 +881,9 @@
*/
public void testConstructor7() {
try {
- new CustomTPE(1,-1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new SimpleThreadFactory());
+ new CustomTPE(1,-1, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new SimpleThreadFactory());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -807,7 +893,9 @@
*/
public void testConstructor8() {
try {
- new CustomTPE(1,0,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new SimpleThreadFactory());
+ new CustomTPE(1, 0, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new SimpleThreadFactory());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -817,7 +905,9 @@
*/
public void testConstructor9() {
try {
- new CustomTPE(1,2,-1L,MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new SimpleThreadFactory());
+ new CustomTPE(1, 2, -1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new SimpleThreadFactory());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -827,7 +917,9 @@
*/
public void testConstructor10() {
try {
- new CustomTPE(2,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new SimpleThreadFactory());
+ new CustomTPE(2, 1, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new SimpleThreadFactory());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -837,7 +929,7 @@
*/
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 testConstructorNullPointerException3() {
try {
- ThreadFactory f = null;
- new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue<Runnable>(10),f);
+ new CustomTPE(1, 2, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ (ThreadFactory) null);
shouldThrow();
} catch (NullPointerException success) {}
}
@@ -858,7 +951,9 @@
*/
public void testConstructor11() {
try {
- new CustomTPE(-1,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new NoOpREHandler());
+ new CustomTPE(-1, 1, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new NoOpREHandler());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -868,7 +963,9 @@
*/
public void testConstructor12() {
try {
- new CustomTPE(1,-1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new NoOpREHandler());
+ new CustomTPE(1, -1, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new NoOpREHandler());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -878,7 +975,9 @@
*/
public void testConstructor13() {
try {
- new CustomTPE(1,0,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new NoOpREHandler());
+ new CustomTPE(1, 0, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new NoOpREHandler());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -888,7 +987,9 @@
*/
public void testConstructor14() {
try {
- new CustomTPE(1,2,-1L,MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new NoOpREHandler());
+ new CustomTPE(1, 2, -1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new NoOpREHandler());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -898,7 +999,9 @@
*/
public void testConstructor15() {
try {
- new CustomTPE(2,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new NoOpREHandler());
+ new CustomTPE(2, 1, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new NoOpREHandler());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -908,7 +1011,9 @@
*/
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 testConstructorNullPointerException5() {
try {
- RejectedExecutionHandler r = null;
- new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue<Runnable>(10),r);
+ new CustomTPE(1, 2, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ (RejectedExecutionHandler) null);
shouldThrow();
} catch (NullPointerException success) {}
}
@@ -929,7 +1035,10 @@
*/
public void testConstructor16() {
try {
- new CustomTPE(-1,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new SimpleThreadFactory(),new NoOpREHandler());
+ new CustomTPE(-1, 1, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new SimpleThreadFactory(),
+ new NoOpREHandler());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -939,7 +1048,10 @@
*/
public void testConstructor17() {
try {
- new CustomTPE(1,-1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new SimpleThreadFactory(),new NoOpREHandler());
+ new CustomTPE(1, -1, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new SimpleThreadFactory(),
+ new NoOpREHandler());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -949,7 +1061,10 @@
*/
public void testConstructor18() {
try {
- new CustomTPE(1,0,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new SimpleThreadFactory(),new NoOpREHandler());
+ new CustomTPE(1, 0, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new SimpleThreadFactory(),
+ new NoOpREHandler());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -959,7 +1074,10 @@
*/
public void testConstructor19() {
try {
- new CustomTPE(1,2,-1L,MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new SimpleThreadFactory(),new NoOpREHandler());
+ new CustomTPE(1, 2, -1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new SimpleThreadFactory(),
+ new NoOpREHandler());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -969,7 +1087,10 @@
*/
public void testConstructor20() {
try {
- new CustomTPE(2,1,LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),new SimpleThreadFactory(),new NoOpREHandler());
+ new CustomTPE(2, 1, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new SimpleThreadFactory(),
+ new NoOpREHandler());
shouldThrow();
} catch (IllegalArgumentException success) {}
}
@@ -979,7 +1100,10 @@
*/
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 testConstructorNullPointerException7() {
try {
- RejectedExecutionHandler r = null;
- new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue<Runnable>(10),new SimpleThreadFactory(),r);
+ new CustomTPE(1, 2, 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(10),
+ new SimpleThreadFactory(),
+ (RejectedExecutionHandler) null);
shouldThrow();
} catch (NullPointerException success) {}
}
@@ -1000,8 +1126,7 @@
*/
public void testConstructorNullPointerException8() {
try {
- new CustomTPE(1, 2,
- LONG_DELAY_MS, MILLISECONDS,
+ new CustomTPE(1, 2, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
(ThreadFactory) null,
new NoOpREHandler());
@@ -1013,15 +1138,15 @@
* 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<Runnable>(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 @@
} catch (RejectedExecutionException success) {}
assertTrue(p.getTaskCount() <= 2);
}
- } finally {
- done.countDown();
- joinPool(p);
}
}
@@ -1042,24 +1164,26 @@
* 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<Runnable>(1),
- h);
- try {
+ final CountDownLatch done = new CountDownLatch(1);
+ final ThreadPoolExecutor p =
+ new CustomTPE(1, 1,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(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 @@
* 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<Runnable>(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<Runnable>(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<Runnable>(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<Runnable>(1));
+ final ThreadPoolExecutor p =
+ new CustomTPE(1, 1,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(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<Runnable>(1), h);
-
+ final ThreadPoolExecutor p =
+ new CustomTPE(1, 1,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(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 @@
* 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<Runnable>(1), h);
-
+ final ThreadPoolExecutor p =
+ new CustomTPE(1, 1,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(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 @@
* 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<Runnable>(1), h);
+ final ThreadPoolExecutor p =
+ new CustomTPE(1, 1,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(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 @@
* execute(null) throws NPE
*/
public void testExecuteNull() {
- ThreadPoolExecutor p = null;
- try {
- p = new CustomTPE(1,2,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue<Runnable>(10));
- p.execute(null);
- shouldThrow();
- } catch (NullPointerException success) {}
-
- joinPool(p);
+ final ThreadPoolExecutor p =
+ new CustomTPE(1, 2,
+ 1L, SECONDS,
+ new ArrayBlockingQueue<Runnable>(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<Runnable>(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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(p)) {
+ try {
+ p.setCorePoolSize(-1);
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
- joinPool(p);
}
/**
@@ -1210,16 +1348,16 @@
* if given a value less the core pool size
*/
public void testMaximumPoolSizeIllegalArgumentException() {
- ThreadPoolExecutor p =
- new CustomTPE(2,3,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue<Runnable>(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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(p)) {
+ try {
+ p.setMaximumPoolSize(1);
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
- joinPool(p);
}
/**
@@ -1227,16 +1365,16 @@
* if given a negative value
*/
public void testMaximumPoolSizeIllegalArgumentException2() {
- ThreadPoolExecutor p =
- new CustomTPE(2,3,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue<Runnable>(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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(p)) {
+ try {
+ p.setMaximumPoolSize(-1);
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
- joinPool(p);
}
/**
@@ -1244,17 +1382,16 @@
* when given a negative value
*/
public void testKeepAliveTimeIllegalArgumentException() {
- ThreadPoolExecutor p =
- new CustomTPE(2,3,LONG_DELAY_MS, MILLISECONDS,new ArrayBlockingQueue<Runnable>(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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(p)) {
+ try {
+ p.setKeepAliveTime(-1, MILLISECONDS);
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
- joinPool(p);
}
/**
@@ -1262,9 +1399,11 @@
*/
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 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 @@
assertEquals(0, done.getCount());
assertTrue(p.afterCalled());
assertTrue(p.beforeCalled());
- try { p.shutdown(); } catch (SecurityException ok) { return; }
- } finally {
- joinPool(p);
}
}
@@ -1292,13 +1428,14 @@
* completed submit of callable returns result
*/
public void testSubmitCallable() throws Exception {
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
- try {
+ final ExecutorService e =
+ new CustomTPE(2, 2,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
Future<String> future = e.submit(new StringTask());
String result = future.get();
assertSame(TEST_STRING, result);
- } finally {
- joinPool(e);
}
}
@@ -1306,13 +1443,14 @@
* completed submit of runnable returns successfully
*/
public void testSubmitRunnable() throws Exception {
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
- try {
+ final ExecutorService e =
+ new CustomTPE(2, 2,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
Future<?> future = e.submit(new NoOpRunnable());
future.get();
assertTrue(future.isDone());
- } finally {
- joinPool(e);
}
}
@@ -1320,13 +1458,14 @@
* completed submit of (runnable, result) returns result
*/
public void testSubmitRunnable2() throws Exception {
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
- try {
+ final ExecutorService e =
+ new CustomTPE(2, 2,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
Future<String> future = e.submit(new NoOpRunnable(), TEST_STRING);
String result = future.get();
assertSame(TEST_STRING, result);
- } finally {
- joinPool(e);
}
}
@@ -1334,13 +1473,15 @@
* invokeAny(null) throws NPE
*/
public void testInvokeAny1() throws Exception {
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ try {
+ e.invokeAny(null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1348,13 +1489,15 @@
* invokeAny(empty collection) throws IAE
*/
public void testInvokeAny2() throws Exception {
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
- try {
- e.invokeAny(new ArrayList<Callable<String>>());
- shouldThrow();
- } catch (IllegalArgumentException success) {
- } finally {
- joinPool(e);
+ final ExecutorService e =
+ new CustomTPE(2, 2,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ try {
+ e.invokeAny(new ArrayList<Callable<String>>());
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
}
@@ -1363,17 +1506,19 @@
*/
public void testInvokeAny3() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(latchAwaitingStringTask(latch));
+ l.add(null);
+ try {
+ e.invokeAny(l);
+ shouldThrow();
+ } catch (NullPointerException success) {}
latch.countDown();
- joinPool(e);
}
}
@@ -1381,16 +1526,19 @@
* 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<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ try {
+ e.invokeAny(l);
+ shouldThrow();
+ } catch (ExecutionException success) {
+ assertTrue(success.getCause() instanceof NullPointerException);
+ }
}
}
@@ -1398,15 +1546,16 @@
* invokeAny(c) returns result of some task
*/
public void testInvokeAny5() throws Exception {
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
- try {
+ final ExecutorService e =
+ new CustomTPE(2, 2,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
List<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
String result = e.invokeAny(l);
assertSame(TEST_STRING, result);
- } finally {
- joinPool(e);
}
}
@@ -1414,13 +1563,15 @@
* invokeAll(null) throws NPE
*/
public void testInvokeAll1() throws Exception {
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ try {
+ e.invokeAll(null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1428,12 +1579,13 @@
* invokeAll(empty collection) returns empty collection
*/
public void testInvokeAll2() throws Exception {
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
- try {
+ final ExecutorService e =
+ new CustomTPE(2, 2,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
List<Future<String>> r = e.invokeAll(new ArrayList<Callable<String>>());
assertTrue(r.isEmpty());
- } finally {
- joinPool(e);
}
}
@@ -1441,16 +1593,18 @@
* 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<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ l.add(null);
+ try {
+ e.invokeAll(l);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1458,18 +1612,21 @@
* 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<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new NPETask());
- List<Future<String>> 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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ List<Future<String>> 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 @@
* 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<Runnable>(10));
- try {
+ final ExecutorService e =
+ new CustomTPE(2, 2,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
List<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
@@ -1486,8 +1646,6 @@
assertEquals(2, futures.size());
for (Future<String> future : futures)
assertSame(TEST_STRING, future.get());
- } finally {
- joinPool(e);
}
}
@@ -1495,13 +1653,15 @@
* timed invokeAny(null) throws NPE
*/
public void testTimedInvokeAny1() throws Exception {
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ try {
+ e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1509,15 +1669,17 @@
* timed invokeAny(,,null) throws NPE
*/
public void testTimedInvokeAnyNullTimeUnit() throws Exception {
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ try {
+ e.invokeAny(l, MEDIUM_DELAY_MS, null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1525,13 +1687,16 @@
* timed invokeAny(empty collection) throws IAE
*/
public void testTimedInvokeAny2() throws Exception {
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
- try {
- e.invokeAny(new ArrayList<Callable<String>>(), MEDIUM_DELAY_MS, MILLISECONDS);
- shouldThrow();
- } catch (IllegalArgumentException success) {
- } finally {
- joinPool(e);
+ final ExecutorService e =
+ new CustomTPE(2, 2,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ try {
+ e.invokeAny(new ArrayList<Callable<String>>(),
+ MEDIUM_DELAY_MS, MILLISECONDS);
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
}
@@ -1540,17 +1705,19 @@
*/
public void testTimedInvokeAny3() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ 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 @@
* 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<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ long startTime = System.nanoTime();
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ 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 @@
* 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<Runnable>(10));
- try {
+ final ExecutorService e =
+ new CustomTPE(2, 2,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ long startTime = System.nanoTime();
List<Callable<String>> l = new ArrayList<Callable<String>>();
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 @@
* timed invokeAll(null) throws NPE
*/
public void testTimedInvokeAll1() throws Exception {
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ try {
+ e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1605,15 +1782,17 @@
* timed invokeAll(,,null) throws NPE
*/
public void testTimedInvokeAllNullTimeUnit() throws Exception {
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ try {
+ e.invokeAll(l, MEDIUM_DELAY_MS, null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1621,12 +1800,14 @@
* timed invokeAll(empty collection) returns empty collection
*/
public void testTimedInvokeAll2() throws Exception {
- ExecutorService e = new CustomTPE(2, 2, LONG_DELAY_MS, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
- try {
- List<Future<String>> r = e.invokeAll(new ArrayList<Callable<String>>(), MEDIUM_DELAY_MS, MILLISECONDS);
+ final ExecutorService e =
+ new CustomTPE(2, 2,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Future<String>> r = e.invokeAll(new ArrayList<Callable<String>>(),
+ MEDIUM_DELAY_MS, MILLISECONDS);
assertTrue(r.isEmpty());
- } finally {
- joinPool(e);
}
}
@@ -1634,16 +1815,18 @@
* 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<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ l.add(null);
+ try {
+ e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1651,19 +1834,22 @@
* 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<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new NPETask());
- List<Future<String>> 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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ List<Future<String>> 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 @@
* 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<Runnable>(10));
- try {
+ final ExecutorService e =
+ new CustomTPE(2, 2,
+ LONG_DELAY_MS, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(e)) {
List<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
List<Future<String>> futures =
- e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS);
+ e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS);
assertEquals(2, futures.size());
for (Future<String> future : futures)
assertSame(TEST_STRING, future.get());
- } finally {
- joinPool(e);
}
}
@@ -1690,21 +1877,40 @@
* 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<Runnable>(10));
- try {
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new StringTask());
- l.add(Executors.callable(new MediumPossiblyInterruptedRunnable(), TEST_STRING));
- l.add(new StringTask());
- List<Future<String>> 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<String> waiter = new CheckedCallable<String>() {
+ 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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(p, done)) {
+ List<Callable<String>> tasks = new ArrayList<>();
+ tasks.add(new StringTask("0"));
+ tasks.add(waiter);
+ tasks.add(new StringTask("2"));
+ long startTime = System.nanoTime();
+ List<Future<String>> 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 @@
LONG_DELAY_MS, MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
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 @@
done.countDown();
}});
assertTrue(done.await(LONG_DELAY_MS, MILLISECONDS));
- } finally {
- joinPool(e);
}
}
@@ -1736,38 +1940,40 @@
* allowsCoreThreadTimeOut is by default false.
*/
public void testAllowsCoreThreadTimeOut() {
- ThreadPoolExecutor p = new CustomTPE(2, 2, 1000, MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
- assertFalse(p.allowsCoreThreadTimeOut());
- joinPool(p);
+ final ThreadPoolExecutor p =
+ new CustomTPE(2, 2,
+ 1000, MILLISECONDS,
+ new ArrayBlockingQueue<Runnable>(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<Runnable>(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 @@
* 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<Runnable>(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<Runnable>());
+ try (PoolCleaner cleaner = cleaner(e, done)) {
+ final CountDownLatch blockerStarted = new CountDownLatch(1);
+ final List<Future<?>> 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 52a7002..7fe26f4 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 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(ThreadPoolExecutorTest.class);
// }
static class ExtendedTPE extends ThreadPoolExecutor {
@@ -89,16 +92,12 @@
new ThreadPoolExecutor(1, 1,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
* 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<Runnable>(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 testPrestartCoreThread() {
final ThreadPoolExecutor p =
- new ThreadPoolExecutor(2, 2,
+ new ThreadPoolExecutor(2, 6,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 testPrestartAllCoreThreads() {
final ThreadPoolExecutor p =
- new ThreadPoolExecutor(2, 2,
+ new ThreadPoolExecutor(2, 6,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
new ThreadPoolExecutor(2, 2,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
fail("timed out");
Thread.yield();
}
- } finally {
- joinPool(p);
}
}
@@ -207,8 +215,9 @@
new ThreadPoolExecutor(1, 1,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(10));
- assertEquals(1, p.getCorePoolSize());
- joinPool(p);
+ try (PoolCleaner cleaner = cleaner(p)) {
+ assertEquals(1, p.getCorePoolSize());
+ }
}
/**
@@ -219,23 +228,25 @@
new ThreadPoolExecutor(2, 2,
1000, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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<Runnable>(10),
- tf,
+ threadFactory,
new NoOpREHandler());
- assertSame(tf, p.getThreadFactory());
- joinPool(p);
+ try (PoolCleaner cleaner = cleaner(p)) {
+ assertSame(threadFactory, p.getThreadFactory());
+ }
}
/**
@@ -246,10 +257,11 @@
new ThreadPoolExecutor(1, 2,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
new ThreadPoolExecutor(1, 2,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
* 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<Runnable>(10),
- h);
- assertSame(h, p.getRejectedExecutionHandler());
- joinPool(p);
+ handler);
+ try (PoolCleaner cleaner = cleaner(p)) {
+ assertSame(handler, p.getRejectedExecutionHandler());
+ }
}
/**
@@ -292,10 +304,11 @@
new ThreadPoolExecutor(1, 2,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
new ThreadPoolExecutor(1, 2,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 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<Runnable>(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 @@
new ThreadPoolExecutor(2, 3,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
* 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<Runnable>(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 @@
* 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<Runnable>(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 @@
new ThreadPoolExecutor(1, 1,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
new ThreadPoolExecutor(1, 1,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
new ThreadPoolExecutor(1, 1,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
new ThreadPoolExecutor(1, 1,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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<Runnable> q = new ArrayBlockingQueue<Runnable>(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<Boolean>() {
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 @@
* remove(task) removes queued task, and fails to remove active task
*/
public void testRemove() throws InterruptedException {
+ final CountDownLatch done = new CountDownLatch(1);
BlockingQueue<Runnable> q = new ArrayBlockingQueue<Runnable>(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 @@
assertTrue(q.contains(tasks[3]));
assertTrue(p.remove(tasks[3]));
assertFalse(q.contains(tasks[3]));
- } finally {
- done.countDown();
- joinPool(p);
}
}
@@ -595,19 +614,19 @@
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<Boolean>() {
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 @@
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<Runnable>(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<Runnable> 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 testConstructor1() {
try {
- new ThreadPoolExecutor(-1, 1,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(-1, 1, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10));
shouldThrow();
} catch (IllegalArgumentException success) {}
@@ -667,8 +700,7 @@
*/
public void testConstructor2() {
try {
- new ThreadPoolExecutor(1, -1,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(1, -1, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10));
shouldThrow();
} catch (IllegalArgumentException success) {}
@@ -679,8 +711,7 @@
*/
public void testConstructor3() {
try {
- new ThreadPoolExecutor(1, 0,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(1, 0, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10));
shouldThrow();
} catch (IllegalArgumentException success) {}
@@ -691,8 +722,7 @@
*/
public void testConstructor4() {
try {
- new ThreadPoolExecutor(1, 2,
- -1L, MILLISECONDS,
+ new ThreadPoolExecutor(1, 2, -1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10));
shouldThrow();
} catch (IllegalArgumentException success) {}
@@ -703,8 +733,7 @@
*/
public void testConstructor5() {
try {
- new ThreadPoolExecutor(2, 1,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(2, 1, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10));
shouldThrow();
} catch (IllegalArgumentException success) {}
@@ -715,8 +744,7 @@
*/
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 testConstructor6() {
try {
- new ThreadPoolExecutor(-1, 1,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(-1, 1, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new SimpleThreadFactory());
shouldThrow();
@@ -740,8 +767,7 @@
*/
public void testConstructor7() {
try {
- new ThreadPoolExecutor(1, -1,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(1, -1, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new SimpleThreadFactory());
shouldThrow();
@@ -753,8 +779,7 @@
*/
public void testConstructor8() {
try {
- new ThreadPoolExecutor(1, 0,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(1, 0, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new SimpleThreadFactory());
shouldThrow();
@@ -766,8 +791,7 @@
*/
public void testConstructor9() {
try {
- new ThreadPoolExecutor(1, 2,
- -1L, MILLISECONDS,
+ new ThreadPoolExecutor(1, 2, -1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new SimpleThreadFactory());
shouldThrow();
@@ -779,8 +803,7 @@
*/
public void testConstructor10() {
try {
- new ThreadPoolExecutor(2, 1,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(2, 1, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new SimpleThreadFactory());
shouldThrow();
@@ -792,8 +815,7 @@
*/
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 testConstructorNullPointerException3() {
try {
- new ThreadPoolExecutor(1, 2,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(1, 2, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
(ThreadFactory) null);
shouldThrow();
@@ -818,8 +839,7 @@
*/
public void testConstructor11() {
try {
- new ThreadPoolExecutor(-1, 1,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(-1, 1, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new NoOpREHandler());
shouldThrow();
@@ -831,8 +851,7 @@
*/
public void testConstructor12() {
try {
- new ThreadPoolExecutor(1, -1,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(1, -1, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new NoOpREHandler());
shouldThrow();
@@ -844,8 +863,7 @@
*/
public void testConstructor13() {
try {
- new ThreadPoolExecutor(1, 0,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(1, 0, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new NoOpREHandler());
shouldThrow();
@@ -857,8 +875,7 @@
*/
public void testConstructor14() {
try {
- new ThreadPoolExecutor(1, 2,
- -1L, MILLISECONDS,
+ new ThreadPoolExecutor(1, 2, -1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new NoOpREHandler());
shouldThrow();
@@ -870,8 +887,7 @@
*/
public void testConstructor15() {
try {
- new ThreadPoolExecutor(2, 1,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(2, 1, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new NoOpREHandler());
shouldThrow();
@@ -883,8 +899,7 @@
*/
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 testConstructorNullPointerException5() {
try {
- new ThreadPoolExecutor(1, 2,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(1, 2, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
(RejectedExecutionHandler) null);
shouldThrow();
@@ -909,8 +923,7 @@
*/
public void testConstructor16() {
try {
- new ThreadPoolExecutor(-1, 1,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(-1, 1, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new SimpleThreadFactory(),
new NoOpREHandler());
@@ -923,8 +936,7 @@
*/
public void testConstructor17() {
try {
- new ThreadPoolExecutor(1, -1,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(1, -1, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new SimpleThreadFactory(),
new NoOpREHandler());
@@ -937,8 +949,7 @@
*/
public void testConstructor18() {
try {
- new ThreadPoolExecutor(1, 0,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(1, 0, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new SimpleThreadFactory(),
new NoOpREHandler());
@@ -951,8 +962,7 @@
*/
public void testConstructor19() {
try {
- new ThreadPoolExecutor(1, 2,
- -1L, MILLISECONDS,
+ new ThreadPoolExecutor(1, 2, -1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new SimpleThreadFactory(),
new NoOpREHandler());
@@ -965,8 +975,7 @@
*/
public void testConstructor20() {
try {
- new ThreadPoolExecutor(2, 1,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(2, 1, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new SimpleThreadFactory(),
new NoOpREHandler());
@@ -979,8 +988,7 @@
*/
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 testConstructorNullPointerException7() {
try {
- new ThreadPoolExecutor(1, 2,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(1, 2, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new SimpleThreadFactory(),
(RejectedExecutionHandler) null);
@@ -1007,8 +1014,7 @@
*/
public void testConstructorNullPointerException8() {
try {
- new ThreadPoolExecutor(1, 2,
- LONG_DELAY_MS, MILLISECONDS,
+ new ThreadPoolExecutor(1, 2, 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(10),
(ThreadFactory) null,
new NoOpREHandler());
@@ -1020,31 +1026,28 @@
* 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<Runnable>(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<Boolean>() {
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 @@
* 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<Runnable>(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 @@
} catch (RejectedExecutionException success) {}
assertTrue(p.getTaskCount() <= 2);
}
- } finally {
- done.countDown();
- joinPool(p);
}
}
@@ -1081,15 +1081,15 @@
* 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<Runnable>(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 @@
} catch (RejectedExecutionException success) {}
assertTrue(p.getTaskCount() <= 2);
}
- } finally {
- done.countDown();
- joinPool(p);
}
}
@@ -1110,15 +1107,15 @@
* 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<Runnable>(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 @@
} catch (RejectedExecutionException success) {}
assertTrue(p.getTaskCount() <= 2);
}
- } finally {
- done.countDown();
- joinPool(p);
}
}
@@ -1139,26 +1133,28 @@
* 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<Runnable>(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 @@
* 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<Runnable>(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<Runnable>(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<Runnable>(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<Runnable>(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 @@
new ArrayBlockingQueue<Runnable>(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 @@
* 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<Runnable>(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 @@
* 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<Runnable>(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 @@
* execute(null) throws NPE
*/
public void testExecuteNull() {
- ThreadPoolExecutor p =
+ final ThreadPoolExecutor p =
new ThreadPoolExecutor(1, 2,
- LONG_DELAY_MS, MILLISECONDS,
+ 1L, SECONDS,
new ArrayBlockingQueue<Runnable>(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<Runnable>(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 @@
* 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<Runnable>(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 @@
* if given a negative value
*/
public void testMaximumPoolSizeIllegalArgumentException2() {
- ThreadPoolExecutor p =
+ final ThreadPoolExecutor p =
new ThreadPoolExecutor(2, 3,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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);
+ }
+
+ /**
+ * 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<Runnable>(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());
+ }
+ }
}
/**
@@ -1368,18 +1384,16 @@
* when given a negative value
*/
public void testKeepAliveTimeIllegalArgumentException() {
- ThreadPoolExecutor p =
+ final ThreadPoolExecutor p =
new ThreadPoolExecutor(2, 3,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 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 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 @@
assertEquals(0, done.getCount());
assertTrue(p.afterCalled());
assertTrue(p.beforeCalled());
- try { p.shutdown(); } catch (SecurityException ok) { return; }
- } finally {
- joinPool(p);
}
}
@@ -1417,16 +1430,14 @@
* 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<Runnable>(10));
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
Future<String> future = e.submit(new StringTask());
String result = future.get();
assertSame(TEST_STRING, result);
- } finally {
- joinPool(e);
}
}
@@ -1434,16 +1445,14 @@
* 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<Runnable>(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 @@
* 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<Runnable>(10));
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
Future<String> future = e.submit(new NoOpRunnable(), TEST_STRING);
String result = future.get();
assertSame(TEST_STRING, result);
- } finally {
- joinPool(e);
}
}
@@ -1468,16 +1475,15 @@
* invokeAny(null) throws NPE
*/
public void testInvokeAny1() throws Exception {
- ExecutorService e =
+ final ExecutorService e =
new ThreadPoolExecutor(2, 2,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
* 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<Runnable>(10));
- try {
- e.invokeAny(new ArrayList<Callable<String>>());
- shouldThrow();
- } catch (IllegalArgumentException success) {
- } finally {
- joinPool(e);
+ try (PoolCleaner cleaner = cleaner(e)) {
+ try {
+ e.invokeAny(new ArrayList<Callable<String>>());
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
}
@@ -1507,16 +1512,15 @@
new ThreadPoolExecutor(2, 2,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(latchAwaitingStringTask(latch));
- l.add(null);
- try {
- e.invokeAny(l);
- shouldThrow();
- } catch (NullPointerException success) {
- } finally {
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(latchAwaitingStringTask(latch));
+ l.add(null);
+ try {
+ e.invokeAny(l);
+ shouldThrow();
+ } catch (NullPointerException success) {}
latch.countDown();
- joinPool(e);
}
}
@@ -1524,19 +1528,19 @@
* 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<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ try {
+ e.invokeAny(l);
+ shouldThrow();
+ } catch (ExecutionException success) {
+ assertTrue(success.getCause() instanceof NullPointerException);
+ }
}
}
@@ -1544,18 +1548,16 @@
* 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<Runnable>(10));
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
List<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
String result = e.invokeAny(l);
assertSame(TEST_STRING, result);
- } finally {
- joinPool(e);
}
}
@@ -1563,16 +1565,15 @@
* invokeAll(null) throws NPE
*/
public void testInvokeAll1() throws Exception {
- ExecutorService e =
+ final ExecutorService e =
new ThreadPoolExecutor(2, 2,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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 @@
* 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<Runnable>(10));
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
List<Future<String>> r = e.invokeAll(new ArrayList<Callable<String>>());
assertTrue(r.isEmpty());
- } finally {
- joinPool(e);
}
}
@@ -1596,19 +1595,18 @@
* 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<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new StringTask());
- l.add(null);
- try {
- e.invokeAll(l);
- shouldThrow();
- } catch (NullPointerException success) {
- } finally {
- joinPool(e);
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ l.add(null);
+ try {
+ e.invokeAll(l);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1616,11 +1614,11 @@
* 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<Runnable>(10));
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
List<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new NPETask());
List<Future<String>> futures = e.invokeAll(l);
@@ -1631,8 +1629,6 @@
} catch (ExecutionException success) {
assertTrue(success.getCause() instanceof NullPointerException);
}
- } finally {
- joinPool(e);
}
}
@@ -1640,11 +1636,11 @@
* 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<Runnable>(10));
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
List<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
@@ -1652,8 +1648,6 @@
assertEquals(2, futures.size());
for (Future<String> future : futures)
assertSame(TEST_STRING, future.get());
- } finally {
- joinPool(e);
}
}
@@ -1661,16 +1655,15 @@
* 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<Runnable>(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 @@
* 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<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ try {
+ e.invokeAny(l, MEDIUM_DELAY_MS, null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1697,16 +1689,16 @@
* 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<Runnable>(10));
- try {
- e.invokeAny(new ArrayList<Callable<String>>(), MEDIUM_DELAY_MS, MILLISECONDS);
- shouldThrow();
- } catch (IllegalArgumentException success) {
- } finally {
- joinPool(e);
+ try (PoolCleaner cleaner = cleaner(e)) {
+ try {
+ e.invokeAny(new ArrayList<Callable<String>>(),
+ MEDIUM_DELAY_MS, MILLISECONDS);
+ shouldThrow();
+ } catch (IllegalArgumentException success) {}
}
}
@@ -1719,16 +1711,15 @@
new ThreadPoolExecutor(2, 2,
LONG_DELAY_MS, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ 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 @@
* 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<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ 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 @@
* 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<Runnable>(10));
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
+ long startTime = System.nanoTime();
List<Callable<String>> l = new ArrayList<Callable<String>>();
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 @@
* 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<Runnable>(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 @@
* 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<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ try {
+ e.invokeAll(l, MEDIUM_DELAY_MS, null);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1811,15 +1802,14 @@
* 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<Runnable>(10));
- try {
- List<Future<String>> r = e.invokeAll(new ArrayList<Callable<String>>(), MEDIUM_DELAY_MS, MILLISECONDS);
+ try (PoolCleaner cleaner = cleaner(e)) {
+ List<Future<String>> r = e.invokeAll(new ArrayList<Callable<String>>(),
+ MEDIUM_DELAY_MS, MILLISECONDS);
assertTrue(r.isEmpty());
- } finally {
- joinPool(e);
}
}
@@ -1827,19 +1817,18 @@
* 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<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new StringTask());
+ l.add(null);
+ try {
+ e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS);
+ shouldThrow();
+ } catch (NullPointerException success) {}
}
}
@@ -1847,22 +1836,22 @@
* 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<Runnable>(10));
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new NPETask());
- List<Future<String>> 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<Callable<String>> l = new ArrayList<Callable<String>>();
+ l.add(new NPETask());
+ List<Future<String>> 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 @@
* 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<Runnable>(10));
- try {
+ try (PoolCleaner cleaner = cleaner(e)) {
List<Callable<String>> l = new ArrayList<Callable<String>>();
l.add(new StringTask());
l.add(new StringTask());
List<Future<String>> futures =
- e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS);
+ e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS);
assertEquals(2, futures.size());
for (Future<String> future : futures)
assertSame(TEST_STRING, future.get());
- } finally {
- joinPool(e);
}
}
@@ -1892,24 +1879,40 @@
* 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<Runnable>(10));
- try {
- List<Callable<String>> l = new ArrayList<Callable<String>>();
- l.add(new StringTask());
- l.add(Executors.callable(new MediumPossiblyInterruptedRunnable(), TEST_STRING));
- l.add(new StringTask());
- List<Future<String>> 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<String> waiter = new CheckedCallable<String>() {
+ 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<Runnable>(10));
+ try (PoolCleaner cleaner = cleaner(p, done)) {
+ List<Callable<String>> tasks = new ArrayList<>();
+ tasks.add(new StringTask("0"));
+ tasks.add(waiter);
+ tasks.add(new StringTask("2"));
+ long startTime = System.nanoTime();
+ List<Future<String>> 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 @@
LONG_DELAY_MS, MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
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 @@
done.countDown();
}});
assertTrue(done.await(LONG_DELAY_MS, MILLISECONDS));
- } finally {
- joinPool(e);
}
}
@@ -1945,21 +1946,22 @@
new ThreadPoolExecutor(2, 2,
1000, MILLISECONDS,
new ArrayBlockingQueue<Runnable>(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<Runnable>(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 @@
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 @@
* 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<Runnable>(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 @@
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 @@
}
// 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<Runnable>());
+ try (PoolCleaner cleaner = cleaner(e, done)) {
+ final CountDownLatch blockerStarted = new CountDownLatch(1);
+ final List<Future<?>> 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 27f22ca..e69b422 100644
--- a/jsr166-tests/src/test/java/jsr166/ThreadTest.java
+++ b/jsr166-tests/src/test/java/jsr166/ThreadTest.java
@@ -19,7 +19,7 @@
// 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 @@
// 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 2c9529b..b21fa7d 100644
--- a/jsr166-tests/src/test/java/jsr166/TimeUnitTest.java
+++ b/jsr166-tests/src/test/java/jsr166/TimeUnitTest.java
@@ -30,7 +30,7 @@
// 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 @@
* 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 afc73de..e445609 100644
--- a/jsr166-tests/src/test/java/jsr166/TreeMapTest.java
+++ b/jsr166-tests/src/test/java/jsr166/TreeMapTest.java
@@ -29,7 +29,7 @@
// 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 a935637..c3093f6 100644
--- a/jsr166-tests/src/test/java/jsr166/TreeSetTest.java
+++ b/jsr166-tests/src/test/java/jsr166/TreeSetTest.java
@@ -29,7 +29,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(TreeSetTest.class);
// }
static class MyReverseComparator implements Comparator {
@@ -50,7 +50,7 @@
private TreeSet<Integer> populatedSet(int n) {
TreeSet<Integer> q = new TreeSet<Integer>();
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 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 @@
* 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 @@
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 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 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 @@
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 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 @@
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 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.pollFirst();
}
}
@@ -375,7 +374,7 @@
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 @@
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 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 18a9e37..09b809e 100644
--- a/jsr166-tests/src/test/java/jsr166/TreeSubMapTest.java
+++ b/jsr166-tests/src/test/java/jsr166/TreeSubMapTest.java
@@ -27,7 +27,7 @@
// 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 5398c4e..31403be 100644
--- a/jsr166-tests/src/test/java/jsr166/TreeSubSetTest.java
+++ b/jsr166-tests/src/test/java/jsr166/TreeSubSetTest.java
@@ -25,7 +25,7 @@
// main(suite(), args);
// }
// public static Test suite() {
- // return new TestSuite(...);
+ // return new TestSuite(TreeSubSetTest.class);
// }
static class MyReverseComparator implements Comparator {
@@ -42,7 +42,7 @@
TreeSet<Integer> q = new TreeSet<Integer>();
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 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 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 @@
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 @@
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 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.pollFirst();
}
}
@@ -325,7 +325,7 @@
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 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 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 @@
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 @@
}
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 @@
assertTrue(changed);
assertTrue(q.containsAll(p));
- assertEquals(SIZE-i, q.size());
+ assertEquals(SIZE - i, q.size());
p.pollFirst();
}
}
@@ -804,7 +804,7 @@
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 f444b29..213baf2 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 @@
* <p><b>Extension example</b>. Here is a sketch of a class
* that customizes {@link ThreadPoolExecutor} to use
* a {@code CustomTask} class instead of the default {@code FutureTask}:
- * <pre> {@code
+ * <pre> {@code
* public class CustomThreadPoolExecutor extends ThreadPoolExecutor {
*
* static class CustomTask<V> implements RunnableFuture<V> {...}
@@ -118,7 +123,7 @@
int ntasks = tasks.size();
if (ntasks == 0)
throw new IllegalArgumentException();
- ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
+ ArrayList<Future<T>> futures = new ArrayList<>(ntasks);
ExecutorCompletionService<T> ecs =
new ExecutorCompletionService<T>(this);
@@ -151,7 +156,7 @@
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 @@
throw ee;
} finally {
- for (int i = 0, size = futures.size(); i < size; i++)
- futures.get(i).cancel(true);
+ cancelAll(futures);
}
}
@@ -201,8 +205,7 @@
throws InterruptedException {
if (tasks == null)
throw new NullPointerException();
- ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
- boolean done = false;
+ ArrayList<Future<T>> futures = new ArrayList<>(tasks.size());
try {
for (Callable<T> t : tasks) {
RunnableFuture<T> f = newTaskFor(t);
@@ -212,19 +215,15 @@
for (int i = 0, size = futures.size(); i < size; i++) {
Future<T> 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 @@
throws InterruptedException {
if (tasks == null)
throw new NullPointerException();
- long nanos = unit.toNanos(timeout);
- ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
- boolean done = false;
- try {
+ final long nanos = unit.toNanos(timeout);
+ final long deadline = System.nanoTime() + nanos;
+ ArrayList<Future<T>> futures = new ArrayList<>(tasks.size());
+ int j = 0;
+ timedOut: try {
for (Callable<T> 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<T> f = futures.get(i);
+ for (; j < size; j++) {
+ Future<T> 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 <T> void cancelAll(ArrayList<Future<T>> futures) {
+ cancelAll(futures, 0);
+ }
+
+ /** Cancels all futures with index at least j. */
+ private static <T> void cancelAll(ArrayList<Future<T>> 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 9dca1b3..3183c01 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 @@
* 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 @@
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 @@
* @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 @@
* @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 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 @@
lock.lockInterruptibly();
try {
while (count == 0) {
- if (nanos <= 0)
+ if (nanos <= 0L)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
@@ -584,27 +585,7 @@
}
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 @@
* 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 @@
* @throws IllegalArgumentException {@inheritDoc}
*/
public int drainTo(Collection<? super E> 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 @@
// }
}
+ /**
+ * Returns a {@link Spliterator} over the elements in this queue.
+ *
+ * <p>The returned spliterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
+ *
+ * <p>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<E> 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 b1437cc..b52f719 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:
*
- * <p>
* <table BORDER CELLPADDING=3 CELLSPACING=1>
* <caption>Summary of BlockingDeque methods</caption>
* <tr>
@@ -98,7 +103,6 @@
* {@code BlockingQueue} interface are precisely equivalent to
* {@code BlockingDeque} methods as indicated in the following table:
*
- * <p>
* <table BORDER CELLPADDING=3 CELLSPACING=1>
* <caption>Comparison of BlockingQueue and BlockingDeque methods</caption>
* <tr>
@@ -169,7 +173,7 @@
*
* @since 1.6
* @author Doug Lea
- * @param <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this deque
*/
public interface BlockingDeque<E> extends BlockingQueue<E>, Deque<E> {
/*
@@ -375,9 +379,9 @@
* @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
- * (<a href="../Collection.html#optional-restrictions">optional</a>)
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null
- * (<a href="../Collection.html#optional-restrictions">optional</a>)
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
*/
boolean removeFirstOccurrence(Object o);
@@ -393,9 +397,9 @@
* @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
- * (<a href="../Collection.html#optional-restrictions">optional</a>)
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null
- * (<a href="../Collection.html#optional-restrictions">optional</a>)
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
*/
boolean removeLastOccurrence(Object o);
@@ -570,9 +574,9 @@
* @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
- * (<a href="../Collection.html#optional-restrictions">optional</a>)
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null
- * (<a href="../Collection.html#optional-restrictions">optional</a>)
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
*/
boolean remove(Object o);
@@ -585,18 +589,18 @@
* @return {@code true} if this deque contains the specified element
* @throws ClassCastException if the class of the specified element
* is incompatible with this deque
- * (<a href="../Collection.html#optional-restrictions">optional</a>)
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null
- * (<a href="../Collection.html#optional-restrictions">optional</a>)
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
*/
- 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 33d83b7..2a56179 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:
*
- * <p>
* <table BORDER CELLPADDING=3 CELLSPACING=1>
* <caption>Summary of BlockingQueue methods</caption>
* <tr>
@@ -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.
- * <pre> {@code
+ * <pre> {@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 <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this queue
*/
public interface BlockingQueue<E> extends Queue<E> {
/**
@@ -275,9 +275,9 @@
* @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
- * (<a href="../Collection.html#optional-restrictions">optional</a>)
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null
- * (<a href="../Collection.html#optional-restrictions">optional</a>)
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
*/
boolean remove(Object o);
@@ -290,11 +290,11 @@
* @return {@code true} if this queue contains the specified element
* @throws ClassCastException if the class of the specified element
* is incompatible with this queue
- * (<a href="../Collection.html#optional-restrictions">optional</a>)
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null
- * (<a href="../Collection.html#optional-restrictions">optional</a>)
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
*/
- 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 a3b3883..a22ec50 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 <V> the result type of method {@code call}
*/
+@FunctionalInterface
public interface Callable<V> {
/**
* 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 0000000..8383a68
--- /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.
+ *
+ * <p>When two or more threads attempt to
+ * {@link #complete complete},
+ * {@link #completeExceptionally completeExceptionally}, or
+ * {@link #cancel cancel}
+ * a CompletableFuture, only one of them succeeds.
+ *
+ * <p>In addition to these and related methods for directly
+ * manipulating status and results, CompletableFuture implements
+ * interface {@link CompletionStage} with the following policies: <ul>
+ *
+ * <li>Actions supplied for dependent completions of
+ * <em>non-async</em> methods may be performed by the thread that
+ * completes the current CompletableFuture, or by any other caller of
+ * a completion method.
+ *
+ * <li>All <em>async</em> 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.
+ *
+ * <li>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.
+ *
+ * </ul>
+ *
+ * <p>CompletableFuture also implements {@link Future} with the following
+ * policies: <ul>
+ *
+ * <li>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.
+ *
+ * <li>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.
+ * </ul>
+ *
+ * <p>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 <T> The result type returned by this future's {@code join}
+ * and {@code get} methods
+ */
+public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
+
+ /*
+ * 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> 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> 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<Void>
+ 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<T,V> extends Completion {
+ Executor executor; // executor to use (null if none)
+ CompletableFuture<V> dep; // the dependent to complete
+ CompletableFuture<T> src; // source for action
+
+ UniCompletion(Executor executor, CompletableFuture<V> dep,
+ CompletableFuture<T> 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<T> 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<T,V> extends UniCompletion<T,V> {
+ Function<? super T,? extends V> fn;
+ UniApply(Executor executor, CompletableFuture<V> dep,
+ CompletableFuture<T> src,
+ Function<? super T,? extends V> fn) {
+ super(executor, dep, src); this.fn = fn;
+ }
+ final CompletableFuture<V> tryFire(int mode) {
+ CompletableFuture<V> d; CompletableFuture<T> 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 <S> boolean uniApply(CompletableFuture<S> a,
+ Function<? super S,? extends T> f,
+ UniApply<S,T> 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 <V> CompletableFuture<V> uniApplyStage(
+ Executor e, Function<? super T,? extends V> f) {
+ if (f == null) throw new NullPointerException();
+ CompletableFuture<V> d = newIncompleteFuture();
+ if (e != null || !d.uniApply(this, f, null)) {
+ UniApply<T,V> c = new UniApply<T,V>(e, d, this, f);
+ push(c);
+ c.tryFire(SYNC);
+ }
+ return d;
+ }
+
+ @SuppressWarnings("serial")
+ static final class UniAccept<T> extends UniCompletion<T,Void> {
+ Consumer<? super T> fn;
+ UniAccept(Executor executor, CompletableFuture<Void> dep,
+ CompletableFuture<T> src, Consumer<? super T> fn) {
+ super(executor, dep, src); this.fn = fn;
+ }
+ final CompletableFuture<Void> tryFire(int mode) {
+ CompletableFuture<Void> d; CompletableFuture<T> 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 <S> boolean uniAccept(CompletableFuture<S> a,
+ Consumer<? super S> f, UniAccept<S> 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<Void> uniAcceptStage(Executor e,
+ Consumer<? super T> f) {
+ if (f == null) throw new NullPointerException();
+ CompletableFuture<Void> d = newIncompleteFuture();
+ if (e != null || !d.uniAccept(this, f, null)) {
+ UniAccept<T> c = new UniAccept<T>(e, d, this, f);
+ push(c);
+ c.tryFire(SYNC);
+ }
+ return d;
+ }
+
+ @SuppressWarnings("serial")
+ static final class UniRun<T> extends UniCompletion<T,Void> {
+ Runnable fn;
+ UniRun(Executor executor, CompletableFuture<Void> dep,
+ CompletableFuture<T> src, Runnable fn) {
+ super(executor, dep, src); this.fn = fn;
+ }
+ final CompletableFuture<Void> tryFire(int mode) {
+ CompletableFuture<Void> d; CompletableFuture<T> 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<Void> uniRunStage(Executor e, Runnable f) {
+ if (f == null) throw new NullPointerException();
+ CompletableFuture<Void> d = newIncompleteFuture();
+ if (e != null || !d.uniRun(this, f, null)) {
+ UniRun<T> c = new UniRun<T>(e, d, this, f);
+ push(c);
+ c.tryFire(SYNC);
+ }
+ return d;
+ }
+
+ @SuppressWarnings("serial")
+ static final class UniWhenComplete<T> extends UniCompletion<T,T> {
+ BiConsumer<? super T, ? super Throwable> fn;
+ UniWhenComplete(Executor executor, CompletableFuture<T> dep,
+ CompletableFuture<T> src,
+ BiConsumer<? super T, ? super Throwable> fn) {
+ super(executor, dep, src); this.fn = fn;
+ }
+ final CompletableFuture<T> tryFire(int mode) {
+ CompletableFuture<T> d; CompletableFuture<T> 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<T> a,
+ BiConsumer<? super T,? super Throwable> f,
+ UniWhenComplete<T> 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<T> uniWhenCompleteStage(
+ Executor e, BiConsumer<? super T, ? super Throwable> f) {
+ if (f == null) throw new NullPointerException();
+ CompletableFuture<T> d = newIncompleteFuture();
+ if (e != null || !d.uniWhenComplete(this, f, null)) {
+ UniWhenComplete<T> c = new UniWhenComplete<T>(e, d, this, f);
+ push(c);
+ c.tryFire(SYNC);
+ }
+ return d;
+ }
+
+ @SuppressWarnings("serial")
+ static final class UniHandle<T,V> extends UniCompletion<T,V> {
+ BiFunction<? super T, Throwable, ? extends V> fn;
+ UniHandle(Executor executor, CompletableFuture<V> dep,
+ CompletableFuture<T> src,
+ BiFunction<? super T, Throwable, ? extends V> fn) {
+ super(executor, dep, src); this.fn = fn;
+ }
+ final CompletableFuture<V> tryFire(int mode) {
+ CompletableFuture<V> d; CompletableFuture<T> 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 <S> boolean uniHandle(CompletableFuture<S> a,
+ BiFunction<? super S, Throwable, ? extends T> f,
+ UniHandle<S,T> 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 <V> CompletableFuture<V> uniHandleStage(
+ Executor e, BiFunction<? super T, Throwable, ? extends V> f) {
+ if (f == null) throw new NullPointerException();
+ CompletableFuture<V> d = newIncompleteFuture();
+ if (e != null || !d.uniHandle(this, f, null)) {
+ UniHandle<T,V> c = new UniHandle<T,V>(e, d, this, f);
+ push(c);
+ c.tryFire(SYNC);
+ }
+ return d;
+ }
+
+ @SuppressWarnings("serial")
+ static final class UniExceptionally<T> extends UniCompletion<T,T> {
+ Function<? super Throwable, ? extends T> fn;
+ UniExceptionally(CompletableFuture<T> dep, CompletableFuture<T> src,
+ Function<? super Throwable, ? extends T> fn) {
+ super(null, dep, src); this.fn = fn;
+ }
+ final CompletableFuture<T> tryFire(int mode) { // never ASYNC
+ // assert mode != ASYNC;
+ CompletableFuture<T> d; CompletableFuture<T> 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<T> a,
+ Function<? super Throwable, ? extends T> f,
+ UniExceptionally<T> 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<T> uniExceptionallyStage(
+ Function<Throwable, ? extends T> f) {
+ if (f == null) throw new NullPointerException();
+ CompletableFuture<T> d = newIncompleteFuture();
+ if (!d.uniExceptionally(this, f, null)) {
+ UniExceptionally<T> c = new UniExceptionally<T>(d, this, f);
+ push(c);
+ c.tryFire(SYNC);
+ }
+ return d;
+ }
+
+ @SuppressWarnings("serial")
+ static final class UniRelay<T> extends UniCompletion<T,T> { // for Compose
+ UniRelay(CompletableFuture<T> dep, CompletableFuture<T> src) {
+ super(null, dep, src);
+ }
+ final CompletableFuture<T> tryFire(int mode) {
+ CompletableFuture<T> d; CompletableFuture<T> a;
+ if ((d = dep) == null || !d.uniRelay(a = src))
+ return null;
+ src = null; dep = null;
+ return d.postFire(a, mode);
+ }
+ }
+
+ final boolean uniRelay(CompletableFuture<T> 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<T> uniCopyStage() {
+ Object r;
+ CompletableFuture<T> d = newIncompleteFuture();
+ if ((r = result) != null)
+ d.completeRelay(r);
+ else {
+ UniRelay<T> c = new UniRelay<T>(d, this);
+ push(c);
+ c.tryFire(SYNC);
+ }
+ return d;
+ }
+
+ private MinimalStage<T> uniAsMinimalStage() {
+ Object r;
+ if ((r = result) != null)
+ return new MinimalStage<T>(encodeRelay(r));
+ MinimalStage<T> d = new MinimalStage<T>();
+ UniRelay<T> c = new UniRelay<T>(d, this);
+ push(c);
+ c.tryFire(SYNC);
+ return d;
+ }
+
+ @SuppressWarnings("serial")
+ static final class UniCompose<T,V> extends UniCompletion<T,V> {
+ Function<? super T, ? extends CompletionStage<V>> fn;
+ UniCompose(Executor executor, CompletableFuture<V> dep,
+ CompletableFuture<T> src,
+ Function<? super T, ? extends CompletionStage<V>> fn) {
+ super(executor, dep, src); this.fn = fn;
+ }
+ final CompletableFuture<V> tryFire(int mode) {
+ CompletableFuture<V> d; CompletableFuture<T> 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 <S> boolean uniCompose(
+ CompletableFuture<S> a,
+ Function<? super S, ? extends CompletionStage<T>> f,
+ UniCompose<S,T> 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<T> g = f.apply(s).toCompletableFuture();
+ if (g.result == null || !uniRelay(g)) {
+ UniRelay<T> copy = new UniRelay<T>(this, g);
+ g.push(copy);
+ copy.tryFire(SYNC);
+ if (result == null)
+ return false;
+ }
+ } catch (Throwable ex) {
+ completeThrowable(ex);
+ }
+ }
+ return true;
+ }
+
+ private <V> CompletableFuture<V> uniComposeStage(
+ Executor e, Function<? super T, ? extends CompletionStage<V>> f) {
+ if (f == null) throw new NullPointerException();
+ Object r, s; Throwable x;
+ CompletableFuture<V> 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<V> g = f.apply(t).toCompletableFuture();
+ if ((s = g.result) != null)
+ d.completeRelay(s);
+ else {
+ UniRelay<V> c = new UniRelay<V>(d, g);
+ g.push(c);
+ c.tryFire(SYNC);
+ }
+ return d;
+ } catch (Throwable ex) {
+ d.result = encodeThrowable(ex);
+ return d;
+ }
+ }
+ UniCompose<T,V> c = new UniCompose<T,V>(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<T,U,V> extends UniCompletion<T,V> {
+ CompletableFuture<U> snd; // second source for action
+ BiCompletion(Executor executor, CompletableFuture<V> dep,
+ CompletableFuture<T> src, CompletableFuture<U> 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<T> 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<T,U,V> extends BiCompletion<T,U,V> {
+ BiFunction<? super T,? super U,? extends V> fn;
+ BiApply(Executor executor, CompletableFuture<V> dep,
+ CompletableFuture<T> src, CompletableFuture<U> snd,
+ BiFunction<? super T,? super U,? extends V> fn) {
+ super(executor, dep, src, snd); this.fn = fn;
+ }
+ final CompletableFuture<V> tryFire(int mode) {
+ CompletableFuture<V> d;
+ CompletableFuture<T> a;
+ CompletableFuture<U> 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 <R,S> boolean biApply(CompletableFuture<R> a,
+ CompletableFuture<S> b,
+ BiFunction<? super R,? super S,? extends T> f,
+ BiApply<R,S,T> 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 <U,V> CompletableFuture<V> biApplyStage(
+ Executor e, CompletionStage<U> o,
+ BiFunction<? super T,? super U,? extends V> f) {
+ CompletableFuture<U> b;
+ if (f == null || (b = o.toCompletableFuture()) == null)
+ throw new NullPointerException();
+ CompletableFuture<V> d = newIncompleteFuture();
+ if (e != null || !d.biApply(this, b, f, null)) {
+ BiApply<T,U,V> c = new BiApply<T,U,V>(e, d, this, b, f);
+ bipush(b, c);
+ c.tryFire(SYNC);
+ }
+ return d;
+ }
+
+ @SuppressWarnings("serial")
+ static final class BiAccept<T,U> extends BiCompletion<T,U,Void> {
+ BiConsumer<? super T,? super U> fn;
+ BiAccept(Executor executor, CompletableFuture<Void> dep,
+ CompletableFuture<T> src, CompletableFuture<U> snd,
+ BiConsumer<? super T,? super U> fn) {
+ super(executor, dep, src, snd); this.fn = fn;
+ }
+ final CompletableFuture<Void> tryFire(int mode) {
+ CompletableFuture<Void> d;
+ CompletableFuture<T> a;
+ CompletableFuture<U> 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 <R,S> boolean biAccept(CompletableFuture<R> a,
+ CompletableFuture<S> b,
+ BiConsumer<? super R,? super S> f,
+ BiAccept<R,S> 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 <U> CompletableFuture<Void> biAcceptStage(
+ Executor e, CompletionStage<U> o,
+ BiConsumer<? super T,? super U> f) {
+ CompletableFuture<U> b;
+ if (f == null || (b = o.toCompletableFuture()) == null)
+ throw new NullPointerException();
+ CompletableFuture<Void> d = newIncompleteFuture();
+ if (e != null || !d.biAccept(this, b, f, null)) {
+ BiAccept<T,U> c = new BiAccept<T,U>(e, d, this, b, f);
+ bipush(b, c);
+ c.tryFire(SYNC);
+ }
+ return d;
+ }
+
+ @SuppressWarnings("serial")
+ static final class BiRun<T,U> extends BiCompletion<T,U,Void> {
+ Runnable fn;
+ BiRun(Executor executor, CompletableFuture<Void> dep,
+ CompletableFuture<T> src,
+ CompletableFuture<U> snd,
+ Runnable fn) {
+ super(executor, dep, src, snd); this.fn = fn;
+ }
+ final CompletableFuture<Void> tryFire(int mode) {
+ CompletableFuture<Void> d;
+ CompletableFuture<T> a;
+ CompletableFuture<U> 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<Void> biRunStage(Executor e, CompletionStage<?> o,
+ Runnable f) {
+ CompletableFuture<?> b;
+ if (f == null || (b = o.toCompletableFuture()) == null)
+ throw new NullPointerException();
+ CompletableFuture<Void> d = newIncompleteFuture();
+ if (e != null || !d.biRun(this, b, f, null)) {
+ BiRun<T,?> c = new BiRun<>(e, d, this, b, f);
+ bipush(b, c);
+ c.tryFire(SYNC);
+ }
+ return d;
+ }
+
+ @SuppressWarnings("serial")
+ static final class BiRelay<T,U> extends BiCompletion<T,U,Void> { // for And
+ BiRelay(CompletableFuture<Void> dep,
+ CompletableFuture<T> src,
+ CompletableFuture<U> snd) {
+ super(null, dep, src, snd);
+ }
+ final CompletableFuture<Void> tryFire(int mode) {
+ CompletableFuture<Void> d;
+ CompletableFuture<T> a;
+ CompletableFuture<U> 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<Void> andTree(CompletableFuture<?>[] cfs,
+ int lo, int hi) {
+ CompletableFuture<Void> d = new CompletableFuture<Void>();
+ 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<T,U extends T,V> extends BiCompletion<T,U,V> {
+ Function<? super T,? extends V> fn;
+ OrApply(Executor executor, CompletableFuture<V> dep,
+ CompletableFuture<T> src,
+ CompletableFuture<U> snd,
+ Function<? super T,? extends V> fn) {
+ super(executor, dep, src, snd); this.fn = fn;
+ }
+ final CompletableFuture<V> tryFire(int mode) {
+ CompletableFuture<V> d;
+ CompletableFuture<T> a;
+ CompletableFuture<U> 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 <R,S extends R> boolean orApply(CompletableFuture<R> a,
+ CompletableFuture<S> b,
+ Function<? super R, ? extends T> f,
+ OrApply<R,S,T> 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 <U extends T,V> CompletableFuture<V> orApplyStage(
+ Executor e, CompletionStage<U> o,
+ Function<? super T, ? extends V> f) {
+ CompletableFuture<U> b;
+ if (f == null || (b = o.toCompletableFuture()) == null)
+ throw new NullPointerException();
+ CompletableFuture<V> d = newIncompleteFuture();
+ if (e != null || !d.orApply(this, b, f, null)) {
+ OrApply<T,U,V> c = new OrApply<T,U,V>(e, d, this, b, f);
+ orpush(b, c);
+ c.tryFire(SYNC);
+ }
+ return d;
+ }
+
+ @SuppressWarnings("serial")
+ static final class OrAccept<T,U extends T> extends BiCompletion<T,U,Void> {
+ Consumer<? super T> fn;
+ OrAccept(Executor executor, CompletableFuture<Void> dep,
+ CompletableFuture<T> src,
+ CompletableFuture<U> snd,
+ Consumer<? super T> fn) {
+ super(executor, dep, src, snd); this.fn = fn;
+ }
+ final CompletableFuture<Void> tryFire(int mode) {
+ CompletableFuture<Void> d;
+ CompletableFuture<T> a;
+ CompletableFuture<U> 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 <R,S extends R> boolean orAccept(CompletableFuture<R> a,
+ CompletableFuture<S> b,
+ Consumer<? super R> f,
+ OrAccept<R,S> 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 <U extends T> CompletableFuture<Void> orAcceptStage(
+ Executor e, CompletionStage<U> o, Consumer<? super T> f) {
+ CompletableFuture<U> b;
+ if (f == null || (b = o.toCompletableFuture()) == null)
+ throw new NullPointerException();
+ CompletableFuture<Void> d = newIncompleteFuture();
+ if (e != null || !d.orAccept(this, b, f, null)) {
+ OrAccept<T,U> c = new OrAccept<T,U>(e, d, this, b, f);
+ orpush(b, c);
+ c.tryFire(SYNC);
+ }
+ return d;
+ }
+
+ @SuppressWarnings("serial")
+ static final class OrRun<T,U> extends BiCompletion<T,U,Void> {
+ Runnable fn;
+ OrRun(Executor executor, CompletableFuture<Void> dep,
+ CompletableFuture<T> src,
+ CompletableFuture<U> snd,
+ Runnable fn) {
+ super(executor, dep, src, snd); this.fn = fn;
+ }
+ final CompletableFuture<Void> tryFire(int mode) {
+ CompletableFuture<Void> d;
+ CompletableFuture<T> a;
+ CompletableFuture<U> 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<Void> orRunStage(Executor e, CompletionStage<?> o,
+ Runnable f) {
+ CompletableFuture<?> b;
+ if (f == null || (b = o.toCompletableFuture()) == null)
+ throw new NullPointerException();
+ CompletableFuture<Void> d = newIncompleteFuture();
+ if (e != null || !d.orRun(this, b, f, null)) {
+ OrRun<T,?> c = new OrRun<>(e, d, this, b, f);
+ orpush(b, c);
+ c.tryFire(SYNC);
+ }
+ return d;
+ }
+
+ @SuppressWarnings("serial")
+ static final class OrRelay<T,U> extends BiCompletion<T,U,Object> { // for Or
+ OrRelay(CompletableFuture<Object> dep, CompletableFuture<T> src,
+ CompletableFuture<U> snd) {
+ super(null, dep, src, snd);
+ }
+ final CompletableFuture<Object> tryFire(int mode) {
+ CompletableFuture<Object> d;
+ CompletableFuture<T> a;
+ CompletableFuture<U> 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<Object> orTree(CompletableFuture<?>[] cfs,
+ int lo, int hi) {
+ CompletableFuture<Object> d = new CompletableFuture<Object>();
+ 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<T> extends ForkJoinTask<Void>
+ implements Runnable, AsynchronousCompletionTask {
+ CompletableFuture<T> dep; Supplier<? extends T> fn;
+ AsyncSupply(CompletableFuture<T> dep, Supplier<? extends T> 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<T> d; Supplier<? extends T> 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 <U> CompletableFuture<U> asyncSupplyStage(Executor e,
+ Supplier<U> f) {
+ if (f == null) throw new NullPointerException();
+ CompletableFuture<U> d = new CompletableFuture<U>();
+ e.execute(new AsyncSupply<U>(d, f));
+ return d;
+ }
+
+ @SuppressWarnings("serial")
+ static final class AsyncRun extends ForkJoinTask<Void>
+ implements Runnable, AsynchronousCompletionTask {
+ CompletableFuture<Void> dep; Runnable fn;
+ AsyncRun(CompletableFuture<Void> 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<Void> 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<Void> asyncRunStage(Executor e, Runnable f) {
+ if (f == null) throw new NullPointerException();
+ CompletableFuture<Void> d = new CompletableFuture<Void>();
+ 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 <U> the function's return type
+ * @return the new CompletableFuture
+ */
+ public static <U> CompletableFuture<U> supplyAsync(Supplier<U> 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 <U> the function's return type
+ * @return the new CompletableFuture
+ */
+ public static <U> CompletableFuture<U> supplyAsync(Supplier<U> 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<Void> 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<Void> 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 <U> the type of the value
+ * @return the completed CompletableFuture
+ */
+ public static <U> CompletableFuture<U> completedFuture(U value) {
+ return new CompletableFuture<U>((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 <U> CompletableFuture<U> thenApply(
+ Function<? super T,? extends U> fn) {
+ return uniApplyStage(null, fn);
+ }
+
+ public <U> CompletableFuture<U> thenApplyAsync(
+ Function<? super T,? extends U> fn) {
+ return uniApplyStage(defaultExecutor(), fn);
+ }
+
+ public <U> CompletableFuture<U> thenApplyAsync(
+ Function<? super T,? extends U> fn, Executor executor) {
+ return uniApplyStage(screenExecutor(executor), fn);
+ }
+
+ public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
+ return uniAcceptStage(null, action);
+ }
+
+ public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {
+ return uniAcceptStage(defaultExecutor(), action);
+ }
+
+ public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action,
+ Executor executor) {
+ return uniAcceptStage(screenExecutor(executor), action);
+ }
+
+ public CompletableFuture<Void> thenRun(Runnable action) {
+ return uniRunStage(null, action);
+ }
+
+ public CompletableFuture<Void> thenRunAsync(Runnable action) {
+ return uniRunStage(defaultExecutor(), action);
+ }
+
+ public CompletableFuture<Void> thenRunAsync(Runnable action,
+ Executor executor) {
+ return uniRunStage(screenExecutor(executor), action);
+ }
+
+ public <U,V> CompletableFuture<V> thenCombine(
+ CompletionStage<? extends U> other,
+ BiFunction<? super T,? super U,? extends V> fn) {
+ return biApplyStage(null, other, fn);
+ }
+
+ public <U,V> CompletableFuture<V> thenCombineAsync(
+ CompletionStage<? extends U> other,
+ BiFunction<? super T,? super U,? extends V> fn) {
+ return biApplyStage(defaultExecutor(), other, fn);
+ }
+
+ public <U,V> CompletableFuture<V> thenCombineAsync(
+ CompletionStage<? extends U> other,
+ BiFunction<? super T,? super U,? extends V> fn, Executor executor) {
+ return biApplyStage(screenExecutor(executor), other, fn);
+ }
+
+ public <U> CompletableFuture<Void> thenAcceptBoth(
+ CompletionStage<? extends U> other,
+ BiConsumer<? super T, ? super U> action) {
+ return biAcceptStage(null, other, action);
+ }
+
+ public <U> CompletableFuture<Void> thenAcceptBothAsync(
+ CompletionStage<? extends U> other,
+ BiConsumer<? super T, ? super U> action) {
+ return biAcceptStage(defaultExecutor(), other, action);
+ }
+
+ public <U> CompletableFuture<Void> thenAcceptBothAsync(
+ CompletionStage<? extends U> other,
+ BiConsumer<? super T, ? super U> action, Executor executor) {
+ return biAcceptStage(screenExecutor(executor), other, action);
+ }
+
+ public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,
+ Runnable action) {
+ return biRunStage(null, other, action);
+ }
+
+ public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,
+ Runnable action) {
+ return biRunStage(defaultExecutor(), other, action);
+ }
+
+ public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,
+ Runnable action,
+ Executor executor) {
+ return biRunStage(screenExecutor(executor), other, action);
+ }
+
+ public <U> CompletableFuture<U> applyToEither(
+ CompletionStage<? extends T> other, Function<? super T, U> fn) {
+ return orApplyStage(null, other, fn);
+ }
+
+ public <U> CompletableFuture<U> applyToEitherAsync(
+ CompletionStage<? extends T> other, Function<? super T, U> fn) {
+ return orApplyStage(defaultExecutor(), other, fn);
+ }
+
+ public <U> CompletableFuture<U> applyToEitherAsync(
+ CompletionStage<? extends T> other, Function<? super T, U> fn,
+ Executor executor) {
+ return orApplyStage(screenExecutor(executor), other, fn);
+ }
+
+ public CompletableFuture<Void> acceptEither(
+ CompletionStage<? extends T> other, Consumer<? super T> action) {
+ return orAcceptStage(null, other, action);
+ }
+
+ public CompletableFuture<Void> acceptEitherAsync(
+ CompletionStage<? extends T> other, Consumer<? super T> action) {
+ return orAcceptStage(defaultExecutor(), other, action);
+ }
+
+ public CompletableFuture<Void> acceptEitherAsync(
+ CompletionStage<? extends T> other, Consumer<? super T> action,
+ Executor executor) {
+ return orAcceptStage(screenExecutor(executor), other, action);
+ }
+
+ public CompletableFuture<Void> runAfterEither(CompletionStage<?> other,
+ Runnable action) {
+ return orRunStage(null, other, action);
+ }
+
+ public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,
+ Runnable action) {
+ return orRunStage(defaultExecutor(), other, action);
+ }
+
+ public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,
+ Runnable action,
+ Executor executor) {
+ return orRunStage(screenExecutor(executor), other, action);
+ }
+
+ public <U> CompletableFuture<U> thenCompose(
+ Function<? super T, ? extends CompletionStage<U>> fn) {
+ return uniComposeStage(null, fn);
+ }
+
+ public <U> CompletableFuture<U> thenComposeAsync(
+ Function<? super T, ? extends CompletionStage<U>> fn) {
+ return uniComposeStage(defaultExecutor(), fn);
+ }
+
+ public <U> CompletableFuture<U> thenComposeAsync(
+ Function<? super T, ? extends CompletionStage<U>> fn,
+ Executor executor) {
+ return uniComposeStage(screenExecutor(executor), fn);
+ }
+
+ public CompletableFuture<T> whenComplete(
+ BiConsumer<? super T, ? super Throwable> action) {
+ return uniWhenCompleteStage(null, action);
+ }
+
+ public CompletableFuture<T> whenCompleteAsync(
+ BiConsumer<? super T, ? super Throwable> action) {
+ return uniWhenCompleteStage(defaultExecutor(), action);
+ }
+
+ public CompletableFuture<T> whenCompleteAsync(
+ BiConsumer<? super T, ? super Throwable> action, Executor executor) {
+ return uniWhenCompleteStage(screenExecutor(executor), action);
+ }
+
+ public <U> CompletableFuture<U> handle(
+ BiFunction<? super T, Throwable, ? extends U> fn) {
+ return uniHandleStage(null, fn);
+ }
+
+ public <U> CompletableFuture<U> handleAsync(
+ BiFunction<? super T, Throwable, ? extends U> fn) {
+ return uniHandleStage(defaultExecutor(), fn);
+ }
+
+ public <U> CompletableFuture<U> handleAsync(
+ BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) {
+ return uniHandleStage(screenExecutor(executor), fn);
+ }
+
+ /**
+ * Returns this CompletableFuture.
+ *
+ * @return this CompletableFuture
+ */
+ public CompletableFuture<T> 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<T> exceptionally(
+ Function<Throwable, ? extends T> 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}.
+ *
+ * <p>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<Void> 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<Object> 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 <U> the type of the value
+ * @return a new CompletableFuture
+ * @since 9
+ * @hide
+ */
+ // android-changed - hidden
+ public <U> CompletableFuture<U> newIncompleteFuture() {
+ return new CompletableFuture<U>();
+ }
+
+ /**
+ * 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<T> 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<T> 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<T> completeAsync(Supplier<? extends T> supplier,
+ Executor executor) {
+ if (supplier == null || executor == null)
+ throw new NullPointerException();
+ executor.execute(new AsyncSupply<T>(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<T> completeAsync(Supplier<? extends T> 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<T> 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<T> completeOnTimeout(T value, long timeout,
+ TimeUnit unit) {
+ if (unit == null)
+ throw new NullPointerException();
+ if (result == null)
+ whenComplete(new Canceller(Delayer.delay(
+ new DelayedCompleter<T>(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 <U> the type of the value
+ * @return the completed CompletionStage
+ * @since 9
+ * @hide
+ */
+ // android-changed - hidden
+ public static <U> CompletionStage<U> completedStage(U value) {
+ return new MinimalStage<U>((value == null) ? NIL : value);
+ }
+
+ /**
+ * Returns a new CompletableFuture that is already completed
+ * exceptionally with the given exception.
+ *
+ * @param ex the exception
+ * @param <U> the type of the value
+ * @return the exceptionally completed CompletableFuture
+ * @since 9
+ * @hide
+ */
+ // android-changed - hidden
+ public static <U> CompletableFuture<U> failedFuture(Throwable ex) {
+ if (ex == null) throw new NullPointerException();
+ return new CompletableFuture<U>(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 <U> the type of the value
+ * @return the exceptionally completed CompletionStage
+ * @since 9
+ * @hide
+ */
+ // android-changed - hidden
+ public static <U> CompletionStage<U> failedStage(Throwable ex) {
+ if (ex == null) throw new NullPointerException();
+ return new MinimalStage<U>(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<U> implements Runnable {
+ final CompletableFuture<U> f;
+ final U u;
+ DelayedCompleter(CompletableFuture<U> 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<Object, Throwable> {
+ 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<T> extends CompletableFuture<T> {
+ MinimalStage() { }
+ MinimalStage(Object r) { super(r); }
+ @Override public <U> CompletableFuture<U> newIncompleteFuture() {
+ return new MinimalStage<U>(); }
+ @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<T> completeAsync
+ (Supplier<? extends T> supplier, Executor executor) {
+ throw new UnsupportedOperationException(); }
+ @Override public CompletableFuture<T> completeAsync
+ (Supplier<? extends T> supplier) {
+ throw new UnsupportedOperationException(); }
+ @Override public CompletableFuture<T> orTimeout
+ (long timeout, TimeUnit unit) {
+ throw new UnsupportedOperationException(); }
+ @Override public CompletableFuture<T> 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 0000000..9b905d2
--- /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 0000000..ccb1aa4
--- /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:
+ *
+ * <ul>
+ *
+ * <li>The computation performed by a stage may be expressed as a
+ * Function, Consumer, or Runnable (using methods with names including
+ * <em>apply</em>, <em>accept</em>, or <em>run</em>, respectively)
+ * depending on whether it requires arguments and/or produces results.
+ * For example:
+ * <pre> {@code
+ * stage.thenApply(x -> square(x))
+ * .thenAccept(x -> System.out.print(x))
+ * .thenRun(() -> System.out.println());}</pre>
+ *
+ * An additional form (<em>compose</em>) allows the construction of
+ * computation pipelines from functions returning completion stages.
+ *
+ * <p>Any argument to a stage's computation is the outcome of a
+ * triggering stage's computation.
+ *
+ * <li>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 <em>then</em>. Those triggered by completion of
+ * <em>both</em> of two stages may <em>combine</em> their results or
+ * effects, using correspondingly named methods. Those triggered by
+ * <em>either</em> of two stages make no guarantees about which of the
+ * results or effects are used for the dependent stage's computation.
+ *
+ * <li>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 <em>async</em>
+ * 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.
+ *
+ * <li>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 <em>both</em> of two stages, and both
+ * complete exceptionally, then the CompletionException may correspond
+ * to either one of these exceptions. If a stage is dependent on
+ * <em>either</em> 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.
+ *
+ * </ul>
+ *
+ * <p>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.
+ *
+ * <p>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:
+ *
+ * <pre>{@code (result, exception) -> {
+ * if (exception == null) {
+ * // triggering stage completed normally
+ * } else {
+ * // triggering stage completed exceptionally
+ * }
+ * }}</pre>
+ *
+ * <p>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<T> {
+
+ /**
+ * Returns a new CompletionStage that, when this stage completes
+ * normally, is executed with this stage's result as the argument
+ * to the supplied function.
+ *
+ * <p>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.
+ *
+ * <p>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 <U> the function's return type
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> 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 <U> the function's return type
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<U> thenApplyAsync
+ (Function<? super T,? extends U> 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 <U> the function's return type
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<U> thenApplyAsync
+ (Function<? super T,? extends U> 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<Void> thenAccept(Consumer<? super T> 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<Void> thenAcceptAsync(Consumer<? super T> 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<Void> thenAcceptAsync(Consumer<? super T> 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<Void> 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<Void> 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<Void> 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 <U> the type of the other CompletionStage's result
+ * @param <V> the function's return type
+ * @return the new CompletionStage
+ */
+ public <U,V> CompletionStage<V> thenCombine
+ (CompletionStage<? extends U> other,
+ BiFunction<? super T,? super U,? extends V> 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 <U> the type of the other CompletionStage's result
+ * @param <V> the function's return type
+ * @return the new CompletionStage
+ */
+ public <U,V> CompletionStage<V> thenCombineAsync
+ (CompletionStage<? extends U> other,
+ BiFunction<? super T,? super U,? extends V> 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 <U> the type of the other CompletionStage's result
+ * @param <V> the function's return type
+ * @return the new CompletionStage
+ */
+ public <U,V> CompletionStage<V> thenCombineAsync
+ (CompletionStage<? extends U> other,
+ BiFunction<? super T,? super U,? extends V> 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 <U> the type of the other CompletionStage's result
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<Void> thenAcceptBoth
+ (CompletionStage<? extends U> other,
+ BiConsumer<? super T, ? super U> 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 <U> the type of the other CompletionStage's result
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<Void> thenAcceptBothAsync
+ (CompletionStage<? extends U> other,
+ BiConsumer<? super T, ? super U> 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 <U> the type of the other CompletionStage's result
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<Void> thenAcceptBothAsync
+ (CompletionStage<? extends U> other,
+ BiConsumer<? super T, ? super U> 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<Void> 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<Void> 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<Void> 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 <U> the function's return type
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<U> applyToEither
+ (CompletionStage<? extends T> other,
+ Function<? super T, U> 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 <U> the function's return type
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<U> applyToEitherAsync
+ (CompletionStage<? extends T> other,
+ Function<? super T, U> 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 <U> the function's return type
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<U> applyToEitherAsync
+ (CompletionStage<? extends T> other,
+ Function<? super T, U> 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<Void> acceptEither
+ (CompletionStage<? extends T> other,
+ Consumer<? super T> 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<Void> acceptEitherAsync
+ (CompletionStage<? extends T> other,
+ Consumer<? super T> 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<Void> acceptEitherAsync
+ (CompletionStage<? extends T> other,
+ Consumer<? super T> 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<Void> 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<Void> 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<Void> 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.
+ *
+ * <p>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.
+ *
+ * <p>To ensure progress, the supplied function must arrange
+ * eventual completion of its result.
+ *
+ * <p>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.
+ *
+ * <p>See the {@link CompletionStage} documentation for rules
+ * covering exceptional completion.
+ *
+ * @param fn the function to use to compute another CompletionStage
+ * @param <U> the type of the returned CompletionStage's result
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<U> thenCompose
+ (Function<? super T, ? extends CompletionStage<U>> 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.
+ *
+ * <p>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.
+ *
+ * <p>To ensure progress, the supplied function must arrange
+ * eventual completion of its result.
+ *
+ * <p>See the {@link CompletionStage} documentation for rules
+ * covering exceptional completion.
+ *
+ * @param fn the function to use to compute another CompletionStage
+ * @param <U> the type of the returned CompletionStage's result
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<U> thenComposeAsync
+ (Function<? super T, ? extends CompletionStage<U>> 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.
+ *
+ * <p>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.
+ *
+ * <p>To ensure progress, the supplied function must arrange
+ * eventual completion of its result.
+ *
+ * <p>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 <U> the type of the returned CompletionStage's result
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<U> thenComposeAsync
+ (Function<? super T, ? extends CompletionStage<U>> 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.
+ *
+ * <p>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 <U> the function's return type
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<U> handle
+ (BiFunction<? super T, Throwable, ? extends U> 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.
+ *
+ * <p>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 <U> the function's return type
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<U> handleAsync
+ (BiFunction<? super T, Throwable, ? extends U> 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.
+ *
+ * <p>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 <U> the function's return type
+ * @return the new CompletionStage
+ */
+ public <U> CompletionStage<U> handleAsync
+ (BiFunction<? super T, Throwable, ? extends U> 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.
+ *
+ * <p>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.
+ *
+ * <p>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<T> whenComplete
+ (BiConsumer<? super T, ? super Throwable> 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.
+ *
+ * <p>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.
+ *
+ * <p>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<T> whenCompleteAsync
+ (BiConsumer<? super T, ? super Throwable> 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.
+ *
+ * <p>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.
+ *
+ * <p>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<T> whenCompleteAsync
+ (BiConsumer<? super T, ? super Throwable> 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<T> exceptionally
+ (Function<Throwable, ? extends T> 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<T> toCompletableFuture();
+
+}
diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java b/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java
index 3ed54cf..b4fa8aa 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 <em>not</em> 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.
*
+ * <p>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.
+ *
+ * <p>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<String,LongAdder> freqs}, you can use
+ * {@code freqs.computeIfAbsent(key, k -> new LongAdder()).increment();}
+ *
* <p>This class and its views and iterators implement all of the
* <em>optional</em> methods of the {@link Map} and {@link Iterator}
* interfaces.
@@ -93,15 +120,121 @@
* <p>Like {@link Hashtable} but unlike {@link HashMap}, this class
* does <em>not</em> allow {@code null} to be used as a key or value.
*
+ * <p>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}.
+ *
+ * <ul>
+ * <li>forEach: Performs a given action on each element.
+ * A variant form applies a given transformation on each element
+ * before performing the action.
+ *
+ * <li>search: Returns the first available non-null result of
+ * applying a given function on each element; skipping further
+ * search when a result is found.
+ *
+ * <li>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:
+ *
+ * <ul>
+ *
+ * <li>Plain reductions. (There is not a form of this method for
+ * (key, value) function arguments since there is no corresponding
+ * return type.)
+ *
+ * <li>Mapped reductions that accumulate the results of a given
+ * function applied to each element.
+ *
+ * <li>Reductions to scalar doubles, longs, and ints, using a
+ * given basis value.
+ *
+ * </ul>
+ * </ul>
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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)}.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>All arguments to all task methods must be non-null.
+ *
* @since 1.5
* @author Doug Lea
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*/
-// android-note: removed documentation about hidden newKeySet and newKeySet(int) APIs.
-// android-note: Added "extends AbstractMap<K, V>.
-public class ConcurrentHashMap<K,V> extends AbstractMap<K, V>
- implements ConcurrentMap<K,V>, Serializable {
+public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
+ implements ConcurrentMap<K,V>, Serializable {
private static final long serialVersionUID = 7249069246763182397L;
/*
@@ -316,7 +449,7 @@
*
* 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 @@
* bulk operations.
*/
-
/* ---------------- Constants -------------- */
/**
@@ -412,7 +544,7 @@
* 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 @@
/*
* 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 @@
final int hash;
final K key;
volatile V val;
- Node<K,V> next;
+ volatile Node<K,V> next;
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
@@ -466,10 +607,12 @@
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 @@
* 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 <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
- U.putOrderedObject(tab, ((long)i << ASHIFT) + ABASE, v);
+ U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
/* ---------------- Fields -------------- */
@@ -892,6 +1036,8 @@
p.val = value;
}
}
+ else if (f instanceof ReservationNode)
+ throw new IllegalStateException("Recursive update");
}
}
if (binCount != 0) {
@@ -994,6 +1140,8 @@
}
}
}
+ else if (f instanceof ReservationNode)
+ throw new IllegalStateException("Recursive update");
}
}
if (validated) {
@@ -1054,16 +1202,15 @@
* operations. It does not support the {@code add} or
* {@code addAll} operations.
*
- * <p>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.
+ * <p>The view's iterators and spliterators are
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
+ *
+ * <p>The view's {@code spliterator} reports {@link Spliterator#CONCURRENT},
+ * {@link Spliterator#DISTINCT}, and {@link Spliterator#NONNULL}.
*
* @return the set view
*/
- // android-note : changed KeySetView<K,V> to Set<K> to maintain API compatibility.
- public Set<K> keySet() {
+ public KeySetView<K,V> keySet() {
KeySetView<K,V> ks;
return (ks = keySet) != null ? ks : (keySet = new KeySetView<K,V>(this, null));
}
@@ -1078,11 +1225,11 @@
* {@code retainAll}, and {@code clear} operations. It does not
* support the {@code add} or {@code addAll} operations.
*
- * <p>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.
+ * <p>The view's iterators and spliterators are
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
+ *
+ * <p>The view's {@code spliterator} reports {@link Spliterator#CONCURRENT}
+ * and {@link Spliterator#NONNULL}.
*
* @return the collection view
*/
@@ -1100,11 +1247,11 @@
* {@code removeAll}, {@code retainAll}, and {@code clear}
* operations.
*
- * <p>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.
+ * <p>The view's iterators and spliterators are
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
+ *
+ * <p>The view's {@code spliterator} reports {@link Spliterator#CONCURRENT},
+ * {@link Spliterator#DISTINCT}, and {@link Spliterator#NONNULL}.
*
* @return the set view
*/
@@ -1202,7 +1349,7 @@
/**
* 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<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
@@ -1214,9 +1361,10 @@
* 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 @@
}
int segmentShift = 32 - sshift;
int segmentMask = ssize - 1;
- @SuppressWarnings("unchecked") Segment<K,V>[] segments = (Segment<K,V>[])
+ @SuppressWarnings("unchecked")
+ Segment<K,V>[] segments = (Segment<K,V>[])
new Segment<?,?>[DEFAULT_CONCURRENCY_LEVEL];
for (int i = 0; i < segments.length; ++i)
segments[i] = new Segment<K,V>(LOAD_FACTOR);
@@ -1251,12 +1400,14 @@
}
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 @@
long size = 0L;
Node<K,V> 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<K,V>(spread(k.hashCode()), k, v, p);
++size;
@@ -1292,7 +1445,7 @@
n = tableSizeFor(sz + (sz >>> 1) + 1);
}
@SuppressWarnings("unchecked")
- Node<K,V>[] tab = (Node<K,V>[])new Node<?,?>[n];
+ Node<K,V>[] tab = (Node<K,V>[])new Node<?,?>[n];
int mask = n - 1;
long added = 0L;
while (p != null) {
@@ -1400,17 +1553,544 @@
throw new NullPointerException();
return replaceNode(key, value, null);
}
+
+ // Overrides of JDK8+ Map extension method defaults
+
+ /**
+ * Returns the value to which the specified key is mapped, or the
+ * given default value if this map contains no mapping for the
+ * key.
+ *
+ * @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
+ */
+ public V getOrDefault(Object key, V defaultValue) {
+ V v;
+ return (v = get(key)) == null ? defaultValue : v;
+ }
+
+ public void forEach(BiConsumer<? super K, ? super V> action) {
+ if (action == null) throw new NullPointerException();
+ Node<K,V>[] t;
+ if ((t = table) != null) {
+ Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
+ for (Node<K,V> p; (p = it.advance()) != null; ) {
+ action.accept(p.key, p.val);
+ }
+ }
+ }
+
+ public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
+ if (function == null) throw new NullPointerException();
+ Node<K,V>[] t;
+ if ((t = table) != null) {
+ Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
+ for (Node<K,V> 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;
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method for EntrySetView.removeIf.
+ */
+ boolean removeEntryIf(Predicate<? super Entry<K,V>> function) {
+ if (function == null) throw new NullPointerException();
+ Node<K,V>[] t;
+ boolean removed = false;
+ if ((t = table) != null) {
+ Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
+ for (Node<K,V> p; (p = it.advance()) != null; ) {
+ K k = p.key;
+ V v = p.val;
+ Map.Entry<K,V> e = new AbstractMap.SimpleImmutableEntry<>(k, v);
+ if (function.test(e) && replaceNode(k, null, v) != null)
+ removed = true;
+ }
+ }
+ return removed;
+ }
+
+ /**
+ * Helper method for ValuesView.removeIf.
+ */
+ boolean removeValueIf(Predicate<? super V> function) {
+ if (function == null) throw new NullPointerException();
+ Node<K,V>[] t;
+ boolean removed = false;
+ if ((t = table) != null) {
+ Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
+ for (Node<K,V> 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;
+ }
+
+ /**
+ * 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.
+ *
+ * @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 V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+ if (key == null || mappingFunction == null)
+ throw new NullPointerException();
+ int h = spread(key.hashCode());
+ V val = null;
+ int binCount = 0;
+ for (Node<K,V>[] tab = table;;) {
+ Node<K,V> 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<K,V> r = new ReservationNode<K,V>();
+ synchronized (r) {
+ if (casTabAt(tab, i, null, r)) {
+ binCount = 1;
+ Node<K,V> node = null;
+ try {
+ if ((val = mappingFunction.apply(key)) != null)
+ node = new Node<K,V>(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<K,V> e = f;; ++binCount) {
+ K ek;
+ if (e.hash == h &&
+ ((ek = e.key) == key ||
+ (ek != null && key.equals(ek)))) {
+ val = e.val;
+ break;
+ }
+ Node<K,V> 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<K,V>(h, key, val, null);
+ }
+ break;
+ }
+ }
+ }
+ else if (f instanceof TreeBin) {
+ binCount = 2;
+ TreeBin<K,V> t = (TreeBin<K,V>)f;
+ TreeNode<K,V> 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;
+ }
+
+ /**
+ * 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 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 V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> 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<K,V>[] tab = table;;) {
+ Node<K,V> 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<K,V> 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<K,V> 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<K,V> t = (TreeBin<K,V>)f;
+ TreeNode<K,V> 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;
+ }
+ }
+ if (delta != 0)
+ addCount((long)delta, binCount);
+ return val;
+ }
+
+ /**
+ * 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
+ */
+ public V compute(K key,
+ BiFunction<? super K, ? super V, ? extends V> 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<K,V>[] tab = table;;) {
+ Node<K,V> 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<K,V> r = new ReservationNode<K,V>();
+ synchronized (r) {
+ if (casTabAt(tab, i, null, r)) {
+ binCount = 1;
+ Node<K,V> node = null;
+ try {
+ if ((val = remappingFunction.apply(key, null)) != null) {
+ delta = 1;
+ node = new Node<K,V>(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<K,V> 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<K,V> 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<K,V>(h, key, val, null);
+ }
+ break;
+ }
+ }
+ }
+ else if (f instanceof TreeBin) {
+ binCount = 1;
+ TreeBin<K,V> t = (TreeBin<K,V>)f;
+ TreeNode<K,V> 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<? super V, ? super V, ? extends V> 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<K,V>[] tab = table;;) {
+ Node<K,V> 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<K,V>(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<K,V> 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<K,V> 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<K,V>(h, key, val, null);
+ break;
+ }
+ }
+ }
+ else if (f instanceof TreeBin) {
+ binCount = 2;
+ TreeBin<K,V> t = (TreeBin<K,V>)f;
+ TreeNode<K,V> r = t.root;
+ TreeNode<K,V> 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
/**
- * Legacy method testing if some key maps into the specified value
- * in this table.
+ * Tests if some key maps into the specified value in this table.
*
- * This method is identical in functionality to
+ * <p>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.
+ * Java Collections Framework.
*
* @param value a value to search for
* @return {@code true} if and only if some key maps to the
@@ -1419,11 +2099,7 @@
* {@code false} otherwise
* @throws NullPointerException if the specified value 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);
}
@@ -1462,8 +2138,6 @@
*
* @return the number of mappings
* @since 1.8
- *
- * @hide
*/
public long mappingCount() {
long n = sumCount();
@@ -1477,8 +2151,6 @@
* @param <K> the element type of the returned set
* @return the new set
* @since 1.8
- *
- * @hide
*/
public static <K> KeySetView<K,Boolean> newKeySet() {
return new KeySetView<K,Boolean>
@@ -1496,8 +2168,6 @@
* @throws IllegalArgumentException if the initial capacity of
* elements is negative
* @since 1.8
- *
- * @hide
*/
public static <K> KeySetView<K,Boolean> newKeySet(int initialCapacity) {
return new KeySetView<K,Boolean>
@@ -1514,8 +2184,6 @@
* @param mappedValue the mapped value to use for any additions
* @return the set view
* @throws NullPointerException if the mappedValue is null
- *
- * @hide
*/
public KeySetView<K,V> keySet(V mappedValue) {
if (mappedValue == null)
@@ -1536,25 +2204,34 @@
}
Node<K,V> find(int h, Object k) {
- Node<K,V> e; int n;
- Node<K,V>[] tab = nextTable;
- if (k != null && tab != null && (n = tab.length) > 0 &&
- (e = tabAt(tab, (n - 1) & h)) != null) {
- do {
+ // loop to avoid arbitrarily deep recursion on forwarding nodes
+ outer: for (Node<K,V>[] tab = nextTable;;) {
+ Node<K,V> 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)
- return e.find(h, k);
- } while ((e = e.next) != null);
+ if (eh < 0) {
+ if (e instanceof ForwardingNode) {
+ tab = ((ForwardingNode<K,V>)e).nextTable;
+ continue outer;
+ }
+ else
+ return e.find(h, k);
+ }
+ if ((e = e.next) == null)
+ return null;
+ }
}
- return null;
}
}
/**
- * A place-holder node used in computeIfAbsent and compute
+ * A place-holder node used in computeIfAbsent and compute.
*/
static final class ReservationNode<K,V> extends Node<K,V> {
ReservationNode() {
@@ -1616,14 +2293,13 @@
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 @@
break;
else if (tab == table) {
int rs = resizeStamp(n);
- if (sc < 0) {
- Node<K,V>[] 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 @@
}
}
+ /* ---------------- 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 @@
* too small, in which case resizes instead.
*/
private final void treeifyBin(Node<K,V>[] tab, int index) {
- Node<K,V> b; int n, sc;
+ Node<K,V> b; int n;
if (tab != null) {
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1);
@@ -1908,7 +2681,7 @@
/* ---------------- TreeNodes -------------- */
/**
- * Nodes for use in TreeBins
+ * Nodes for use in TreeBins.
*/
static final class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent; // red-black tree links
@@ -1961,7 +2734,6 @@
}
}
-
/* ---------------- TreeBins -------------- */
/**
@@ -2028,7 +2800,7 @@
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
- TreeNode<K,V> xp = p;
+ TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
@@ -2106,13 +2878,9 @@
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;
@@ -2126,10 +2894,6 @@
* Finds or adds a node.
* @return null if added
*/
- /**
- * Finds or adds a node.
- * @return null if added
- */
final TreeNode<K,V> putTreeVal(int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
@@ -2480,7 +3244,7 @@
}
/**
- * Recursive invariant check
+ * Checks invariants recursively for the tree of Nodes rooted at t.
*/
static <K,V> boolean checkInvariants(TreeNode<K,V> t) {
TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
@@ -2504,15 +3268,13 @@
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 @@
}
/**
- * Exported Entry for EntryIterator
+ * Exported Entry for EntryIterator.
*/
static final class MapEntry<K,V> implements Map.Entry<K,V> {
final K key; // non-null
@@ -2741,7 +3503,9 @@
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,6 +3533,866 @@
}
}
+ static final class KeySpliterator<K,V> extends Traverser<K,V>
+ implements Spliterator<K> {
+ long est; // size estimate
+ KeySpliterator(Node<K,V>[] tab, int size, int index, int limit,
+ long est) {
+ super(tab, size, index, limit);
+ this.est = est;
+ }
+
+ public KeySpliterator<K,V> trySplit() {
+ int i, f, h;
+ return (h = ((i = baseIndex) + (f = baseLimit)) >>> 1) <= i ? null :
+ new KeySpliterator<K,V>(tab, baseSize, baseLimit = h,
+ f, est >>>= 1);
+ }
+
+ public void forEachRemaining(Consumer<? super K> action) {
+ if (action == null) throw new NullPointerException();
+ for (Node<K,V> p; (p = advance()) != null;)
+ action.accept(p.key);
+ }
+
+ public boolean tryAdvance(Consumer<? super K> action) {
+ if (action == null) throw new NullPointerException();
+ Node<K,V> p;
+ if ((p = advance()) == null)
+ return false;
+ action.accept(p.key);
+ return true;
+ }
+
+ public long estimateSize() { return est; }
+
+ public int characteristics() {
+ return Spliterator.DISTINCT | Spliterator.CONCURRENT |
+ Spliterator.NONNULL;
+ }
+ }
+
+ static final class ValueSpliterator<K,V> extends Traverser<K,V>
+ implements Spliterator<V> {
+ long est; // size estimate
+ ValueSpliterator(Node<K,V>[] tab, int size, int index, int limit,
+ long est) {
+ super(tab, size, index, limit);
+ this.est = est;
+ }
+
+ public ValueSpliterator<K,V> trySplit() {
+ int i, f, h;
+ return (h = ((i = baseIndex) + (f = baseLimit)) >>> 1) <= i ? null :
+ new ValueSpliterator<K,V>(tab, baseSize, baseLimit = h,
+ f, est >>>= 1);
+ }
+
+ public void forEachRemaining(Consumer<? super V> action) {
+ if (action == null) throw new NullPointerException();
+ for (Node<K,V> p; (p = advance()) != null;)
+ action.accept(p.val);
+ }
+
+ public boolean tryAdvance(Consumer<? super V> action) {
+ if (action == null) throw new NullPointerException();
+ Node<K,V> p;
+ if ((p = advance()) == null)
+ return false;
+ action.accept(p.val);
+ return true;
+ }
+
+ public long estimateSize() { return est; }
+
+ public int characteristics() {
+ return Spliterator.CONCURRENT | Spliterator.NONNULL;
+ }
+ }
+
+ static final class EntrySpliterator<K,V> extends Traverser<K,V>
+ implements Spliterator<Map.Entry<K,V>> {
+ final ConcurrentHashMap<K,V> map; // To export MapEntry
+ long est; // size estimate
+ EntrySpliterator(Node<K,V>[] tab, int size, int index, int limit,
+ long est, ConcurrentHashMap<K,V> map) {
+ super(tab, size, index, limit);
+ this.map = map;
+ this.est = est;
+ }
+
+ public EntrySpliterator<K,V> trySplit() {
+ int i, f, h;
+ return (h = ((i = baseIndex) + (f = baseLimit)) >>> 1) <= i ? null :
+ new EntrySpliterator<K,V>(tab, baseSize, baseLimit = h,
+ f, est >>>= 1, map);
+ }
+
+ public void forEachRemaining(Consumer<? super Map.Entry<K,V>> action) {
+ if (action == null) throw new NullPointerException();
+ for (Node<K,V> p; (p = advance()) != null; )
+ action.accept(new MapEntry<K,V>(p.key, p.val, map));
+ }
+
+ public boolean tryAdvance(Consumer<? super Map.Entry<K,V>> action) {
+ if (action == null) throw new NullPointerException();
+ Node<K,V> p;
+ if ((p = advance()) == null)
+ return false;
+ action.accept(new MapEntry<K,V>(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
+
+ /**
+ * 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<? super K,? super V> action) {
+ if (action == null) throw new NullPointerException();
+ new ForEachMappingTask<K,V>
+ (null, batchFor(parallelismThreshold), 0, 0, table,
+ action).invoke();
+ }
+
+ /**
+ * Performs the given action for each non-null transformation
+ * of each (key, 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 <U> the return type of the transformer
+ * @since 1.8
+ */
+ public <U> void forEach(long parallelismThreshold,
+ BiFunction<? super K, ? super V, ? extends U> transformer,
+ Consumer<? super U> action) {
+ if (transformer == null || action == null)
+ throw new NullPointerException();
+ new ForEachTransformedMappingTask<K,V,U>
+ (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 <U> 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> U search(long parallelismThreshold,
+ BiFunction<? super K, ? super V, ? extends U> searchFunction) {
+ if (searchFunction == null) throw new NullPointerException();
+ return new SearchMappingsTask<K,V,U>
+ (null, batchFor(parallelismThreshold), 0, 0, table,
+ searchFunction, new AtomicReference<U>()).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 <U> the return type of the transformer
+ * @return the result of accumulating the given transformation
+ * of all (key, value) pairs
+ * @since 1.8
+ */
+ public <U> U reduce(long parallelismThreshold,
+ BiFunction<? super K, ? super V, ? extends U> transformer,
+ BiFunction<? super U, ? super U, ? extends U> reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceMappingsTask<K,V,U>
+ (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<? super K, ? super V> transformer,
+ double basis,
+ DoubleBinaryOperator reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceMappingsToDoubleTask<K,V>
+ (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<? super K, ? super V> transformer,
+ long basis,
+ LongBinaryOperator reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceMappingsToLongTask<K,V>
+ (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<? super K, ? super V> transformer,
+ int basis,
+ IntBinaryOperator reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceMappingsToIntTask<K,V>
+ (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<? super K> action) {
+ if (action == null) throw new NullPointerException();
+ new ForEachKeyTask<K,V>
+ (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 <U> the return type of the transformer
+ * @since 1.8
+ */
+ public <U> void forEachKey(long parallelismThreshold,
+ Function<? super K, ? extends U> transformer,
+ Consumer<? super U> action) {
+ if (transformer == null || action == null)
+ throw new NullPointerException();
+ new ForEachTransformedKeyTask<K,V,U>
+ (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 <U> 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> U searchKeys(long parallelismThreshold,
+ Function<? super K, ? extends U> searchFunction) {
+ if (searchFunction == null) throw new NullPointerException();
+ return new SearchKeysTask<K,V,U>
+ (null, batchFor(parallelismThreshold), 0, 0, table,
+ searchFunction, new AtomicReference<U>()).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<? super K, ? super K, ? extends K> reducer) {
+ if (reducer == null) throw new NullPointerException();
+ return new ReduceKeysTask<K,V>
+ (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 <U> the return type of the transformer
+ * @return the result of accumulating the given transformation
+ * of all keys
+ * @since 1.8
+ */
+ public <U> U reduceKeys(long parallelismThreshold,
+ Function<? super K, ? extends U> transformer,
+ BiFunction<? super U, ? super U, ? extends U> reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceKeysTask<K,V,U>
+ (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<? super K> transformer,
+ double basis,
+ DoubleBinaryOperator reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceKeysToDoubleTask<K,V>
+ (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<? super K> transformer,
+ long basis,
+ LongBinaryOperator reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceKeysToLongTask<K,V>
+ (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<? super K> transformer,
+ int basis,
+ IntBinaryOperator reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceKeysToIntTask<K,V>
+ (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<? super V> action) {
+ if (action == null)
+ throw new NullPointerException();
+ new ForEachValueTask<K,V>
+ (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 <U> the return type of the transformer
+ * @since 1.8
+ */
+ public <U> void forEachValue(long parallelismThreshold,
+ Function<? super V, ? extends U> transformer,
+ Consumer<? super U> action) {
+ if (transformer == null || action == null)
+ throw new NullPointerException();
+ new ForEachTransformedValueTask<K,V,U>
+ (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 <U> 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> U searchValues(long parallelismThreshold,
+ Function<? super V, ? extends U> searchFunction) {
+ if (searchFunction == null) throw new NullPointerException();
+ return new SearchValuesTask<K,V,U>
+ (null, batchFor(parallelismThreshold), 0, 0, table,
+ searchFunction, new AtomicReference<U>()).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<? super V, ? super V, ? extends V> reducer) {
+ if (reducer == null) throw new NullPointerException();
+ return new ReduceValuesTask<K,V>
+ (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 <U> the return type of the transformer
+ * @return the result of accumulating the given transformation
+ * of all values
+ * @since 1.8
+ */
+ public <U> U reduceValues(long parallelismThreshold,
+ Function<? super V, ? extends U> transformer,
+ BiFunction<? super U, ? super U, ? extends U> reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceValuesTask<K,V,U>
+ (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<? super V> transformer,
+ double basis,
+ DoubleBinaryOperator reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceValuesToDoubleTask<K,V>
+ (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<? super V> transformer,
+ long basis,
+ LongBinaryOperator reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceValuesToLongTask<K,V>
+ (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<? super V> transformer,
+ int basis,
+ IntBinaryOperator reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceValuesToIntTask<K,V>
+ (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<? super Map.Entry<K,V>> action) {
+ if (action == null) throw new NullPointerException();
+ new ForEachEntryTask<K,V>(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 <U> the return type of the transformer
+ * @since 1.8
+ */
+ public <U> void forEachEntry(long parallelismThreshold,
+ Function<Map.Entry<K,V>, ? extends U> transformer,
+ Consumer<? super U> action) {
+ if (transformer == null || action == null)
+ throw new NullPointerException();
+ new ForEachTransformedEntryTask<K,V,U>
+ (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 <U> 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> U searchEntries(long parallelismThreshold,
+ Function<Map.Entry<K,V>, ? extends U> searchFunction) {
+ if (searchFunction == null) throw new NullPointerException();
+ return new SearchEntriesTask<K,V,U>
+ (null, batchFor(parallelismThreshold), 0, 0, table,
+ searchFunction, new AtomicReference<U>()).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<K,V> reduceEntries(long parallelismThreshold,
+ BiFunction<Map.Entry<K,V>, Map.Entry<K,V>, ? extends Map.Entry<K,V>> reducer) {
+ if (reducer == null) throw new NullPointerException();
+ return new ReduceEntriesTask<K,V>
+ (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 <U> the return type of the transformer
+ * @return the result of accumulating the given transformation
+ * of all entries
+ * @since 1.8
+ */
+ public <U> U reduceEntries(long parallelismThreshold,
+ Function<Map.Entry<K,V>, ? extends U> transformer,
+ BiFunction<? super U, ? super U, ? extends U> reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceEntriesTask<K,V,U>
+ (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<Map.Entry<K,V>> transformer,
+ double basis,
+ DoubleBinaryOperator reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceEntriesToDoubleTask<K,V>
+ (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<Map.Entry<K,V>> transformer,
+ long basis,
+ LongBinaryOperator reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceEntriesToLongTask<K,V>
+ (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<Map.Entry<K,V>> transformer,
+ int basis,
+ IntBinaryOperator reducer) {
+ if (transformer == null || reducer == null)
+ throw new NullPointerException();
+ return new MapReduceEntriesToIntTask<K,V>
+ (null, batchFor(parallelismThreshold), 0, 0, table,
+ null, transformer, basis, reducer).invoke();
+ }
+
+
/* ----------------Views -------------- */
/**
@@ -2798,30 +4422,30 @@
// 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.
+ * Returns an iterator over the elements in this collection.
+ *
+ * <p>The returned iterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
+ *
+ * @return an iterator over the elements in this collection
*/
public abstract Iterator<E> iterator();
public abstract boolean contains(Object o);
public abstract boolean remove(Object o);
- private static final String oomeMsg = "Required array size too large";
+ 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(oomeMsg);
+ 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(oomeMsg);
+ throw new OutOfMemoryError(OOME_MSG);
if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1)
n = MAX_ARRAY_SIZE;
else
@@ -2837,7 +4461,7 @@
public final <T> T[] toArray(T[] a) {
long sz = map.mappingCount();
if (sz > MAX_ARRAY_SIZE)
- throw new OutOfMemoryError(oomeMsg);
+ throw new OutOfMemoryError(OOME_MSG);
int m = (int)sz;
T[] r = (a.length >= m) ? a :
(T[])java.lang.reflect.Array
@@ -2847,7 +4471,7 @@
for (E e : this) {
if (i == n) {
if (n >= MAX_ARRAY_SIZE)
- throw new OutOfMemoryError(oomeMsg);
+ throw new OutOfMemoryError(OOME_MSG);
if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1)
n = MAX_ARRAY_SIZE;
else
@@ -2901,6 +4525,7 @@
}
public final boolean removeAll(Collection<?> c) {
+ if (c == null) throw new NullPointerException();
boolean modified = false;
for (Iterator<E> it = iterator(); it.hasNext();) {
if (c.contains(it.next())) {
@@ -2912,6 +4537,7 @@
}
public final boolean retainAll(Collection<?> c) {
+ if (c == null) throw new NullPointerException();
boolean modified = false;
for (Iterator<E> it = iterator(); it.hasNext();) {
if (!c.contains(it.next())) {
@@ -2930,12 +4556,11 @@
* 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
- *
- * @hide
*/
- // android-note: removed references to hidden APIs.
public static class KeySetView<K,V> extends CollectionView<K,V,K>
implements Set<K>, java.io.Serializable {
private static final long serialVersionUID = 7249069246763182397L;
@@ -3035,6 +4660,23 @@
(containsAll(c) && c.containsAll(this))));
}
+ public Spliterator<K> spliterator() {
+ Node<K,V>[] t;
+ ConcurrentHashMap<K,V> m = map;
+ long n = m.sumCount();
+ int f = (t = m.table) == null ? 0 : t.length;
+ return new KeySpliterator<K,V>(t, f, 0, f, n < 0L ? 0L : n);
+ }
+
+ public void forEach(Consumer<? super K> action) {
+ if (action == null) throw new NullPointerException();
+ Node<K,V>[] t;
+ if ((t = map.table) != null) {
+ Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
+ for (Node<K,V> p; (p = it.advance()) != null; )
+ action.accept(p.key);
+ }
+ }
}
/**
@@ -3076,6 +4718,27 @@
throw new UnsupportedOperationException();
}
+ public boolean removeIf(Predicate<? super V> filter) {
+ return map.removeValueIf(filter);
+ }
+
+ public Spliterator<V> spliterator() {
+ Node<K,V>[] t;
+ ConcurrentHashMap<K,V> m = map;
+ long n = m.sumCount();
+ int f = (t = m.table) == null ? 0 : t.length;
+ return new ValueSpliterator<K,V>(t, f, 0, f, n < 0L ? 0L : n);
+ }
+
+ public void forEach(Consumer<? super V> action) {
+ if (action == null) throw new NullPointerException();
+ Node<K,V>[] t;
+ if ((t = map.table) != null) {
+ Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
+ for (Node<K,V> p; (p = it.advance()) != null; )
+ action.accept(p.val);
+ }
+ }
}
/**
@@ -3128,6 +4791,10 @@
return added;
}
+ public boolean removeIf(Predicate<? super Entry<K,V>> filter) {
+ return map.removeEntryIf(filter);
+ }
+
public final int hashCode() {
int h = 0;
Node<K,V>[] t;
@@ -3147,181 +4814,1532 @@
(containsAll(c) && c.containsAll(this))));
}
- }
+ public Spliterator<Map.Entry<K,V>> spliterator() {
+ Node<K,V>[] t;
+ ConcurrentHashMap<K,V> m = map;
+ long n = m.sumCount();
+ int f = (t = m.table) == null ? 0 : t.length;
+ return new EntrySpliterator<K,V>(t, f, 0, f, n < 0L ? 0L : n, m);
+ }
-
- /* ---------------- 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; }
- }
-
- /**
- * 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;
- }
-
- /**
- * 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<CounterHashCode> threadCounterHashCode =
- new ThreadLocal<CounterHashCode>();
-
- 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;
+ public void forEach(Consumer<? super Map.Entry<K,V>> action) {
+ if (action == null) throw new NullPointerException();
+ Node<K,V>[] t;
+ if ((t = map.table) != null) {
+ Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
+ for (Node<K,V> p; (p = it.advance()) != null; )
+ action.accept(new MapEntry<K,V>(p.key, p.val, map));
}
}
- 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;
- }
- 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 ^= h << 13; // Rehash
- h ^= h >>> 17;
- h ^= h << 5;
+ // -------------------------------------------------------
+
+ /**
+ * 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<K,V,R> extends CountedCompleter<R> {
+ Node<K,V>[] tab; // same as Traverser
+ Node<K,V> next;
+ TableStack<K,V> stack, spare;
+ int index;
+ int baseIndex;
+ int baseLimit;
+ final int baseSize;
+ int batch; // split control
+
+ BulkTask(BulkTask<K,V,?> par, int b, int i, int f, Node<K,V>[] 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;
}
- 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
}
- hc.code = h; // Record index for next time
+
+ /**
+ * Same as Traverser version.
+ */
+ final Node<K,V> advance() {
+ Node<K,V> e;
+ if ((e = next) != null)
+ e = e.next;
+ for (;;) {
+ Node<K,V>[] 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<K,V>)e).nextTable;
+ e = null;
+ pushState(t, i, n);
+ continue;
+ }
+ else if (e instanceof TreeBin)
+ e = ((TreeBin<K,V>)e).first;
+ else
+ e = null;
+ }
+ if (stack != null)
+ recoverState(n);
+ else if ((index = i + baseSize) >= n)
+ index = ++baseIndex;
+ }
+ }
+
+ private void pushState(Node<K,V>[] t, int i, int n) {
+ TableStack<K,V> s = spare;
+ if (s != null)
+ spare = s.next;
+ else
+ s = new TableStack<K,V>();
+ s.tab = t;
+ s.length = n;
+ s.index = i;
+ s.next = stack;
+ stack = s;
+ }
+
+ private void recoverState(int n) {
+ TableStack<K,V> s; int len;
+ while ((s = stack) != null && (index += (len = s.length)) >= n) {
+ n = len;
+ index = s.index;
+ tab = s.tab;
+ s.tab = null;
+ TableStack<K,V> 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<K,V>
+ extends BulkTask<K,V,Void> {
+ final Consumer<? super K> action;
+ ForEachKeyTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ Consumer<? super K> action) {
+ super(p, b, i, f, t);
+ this.action = action;
+ }
+ public final void compute() {
+ final Consumer<? super K> action;
+ if ((action = this.action) != null) {
+ for (int i = baseIndex, f, h; batch > 0 &&
+ (h = ((f = baseLimit) + i) >>> 1) > i;) {
+ addToPendingCount(1);
+ new ForEachKeyTask<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ action).fork();
+ }
+ for (Node<K,V> p; (p = advance()) != null;)
+ action.accept(p.key);
+ propagateCompletion();
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class ForEachValueTask<K,V>
+ extends BulkTask<K,V,Void> {
+ final Consumer<? super V> action;
+ ForEachValueTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ Consumer<? super V> action) {
+ super(p, b, i, f, t);
+ this.action = action;
+ }
+ public final void compute() {
+ final Consumer<? super V> action;
+ if ((action = this.action) != null) {
+ for (int i = baseIndex, f, h; batch > 0 &&
+ (h = ((f = baseLimit) + i) >>> 1) > i;) {
+ addToPendingCount(1);
+ new ForEachValueTask<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ action).fork();
+ }
+ for (Node<K,V> p; (p = advance()) != null;)
+ action.accept(p.val);
+ propagateCompletion();
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class ForEachEntryTask<K,V>
+ extends BulkTask<K,V,Void> {
+ final Consumer<? super Entry<K,V>> action;
+ ForEachEntryTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ Consumer<? super Entry<K,V>> action) {
+ super(p, b, i, f, t);
+ this.action = action;
+ }
+ public final void compute() {
+ final Consumer<? super Entry<K,V>> action;
+ if ((action = this.action) != null) {
+ for (int i = baseIndex, f, h; batch > 0 &&
+ (h = ((f = baseLimit) + i) >>> 1) > i;) {
+ addToPendingCount(1);
+ new ForEachEntryTask<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ action).fork();
+ }
+ for (Node<K,V> p; (p = advance()) != null; )
+ action.accept(p);
+ propagateCompletion();
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class ForEachMappingTask<K,V>
+ extends BulkTask<K,V,Void> {
+ final BiConsumer<? super K, ? super V> action;
+ ForEachMappingTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ BiConsumer<? super K,? super V> action) {
+ super(p, b, i, f, t);
+ this.action = action;
+ }
+ public final void compute() {
+ final BiConsumer<? super K, ? super V> action;
+ if ((action = this.action) != null) {
+ for (int i = baseIndex, f, h; batch > 0 &&
+ (h = ((f = baseLimit) + i) >>> 1) > i;) {
+ addToPendingCount(1);
+ new ForEachMappingTask<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ action).fork();
+ }
+ for (Node<K,V> p; (p = advance()) != null; )
+ action.accept(p.key, p.val);
+ propagateCompletion();
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class ForEachTransformedKeyTask<K,V,U>
+ extends BulkTask<K,V,Void> {
+ final Function<? super K, ? extends U> transformer;
+ final Consumer<? super U> action;
+ ForEachTransformedKeyTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ Function<? super K, ? extends U> transformer, Consumer<? super U> action) {
+ super(p, b, i, f, t);
+ this.transformer = transformer; this.action = action;
+ }
+ public final void compute() {
+ final Function<? super K, ? extends U> transformer;
+ final Consumer<? super U> 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<K,V,U>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ transformer, action).fork();
+ }
+ for (Node<K,V> p; (p = advance()) != null; ) {
+ U u;
+ if ((u = transformer.apply(p.key)) != null)
+ action.accept(u);
+ }
+ propagateCompletion();
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class ForEachTransformedValueTask<K,V,U>
+ extends BulkTask<K,V,Void> {
+ final Function<? super V, ? extends U> transformer;
+ final Consumer<? super U> action;
+ ForEachTransformedValueTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ Function<? super V, ? extends U> transformer, Consumer<? super U> action) {
+ super(p, b, i, f, t);
+ this.transformer = transformer; this.action = action;
+ }
+ public final void compute() {
+ final Function<? super V, ? extends U> transformer;
+ final Consumer<? super U> 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<K,V,U>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ transformer, action).fork();
+ }
+ for (Node<K,V> p; (p = advance()) != null; ) {
+ U u;
+ if ((u = transformer.apply(p.val)) != null)
+ action.accept(u);
+ }
+ propagateCompletion();
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class ForEachTransformedEntryTask<K,V,U>
+ extends BulkTask<K,V,Void> {
+ final Function<Map.Entry<K,V>, ? extends U> transformer;
+ final Consumer<? super U> action;
+ ForEachTransformedEntryTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ Function<Map.Entry<K,V>, ? extends U> transformer, Consumer<? super U> action) {
+ super(p, b, i, f, t);
+ this.transformer = transformer; this.action = action;
+ }
+ public final void compute() {
+ final Function<Map.Entry<K,V>, ? extends U> transformer;
+ final Consumer<? super U> 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<K,V,U>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ transformer, action).fork();
+ }
+ for (Node<K,V> p; (p = advance()) != null; ) {
+ U u;
+ if ((u = transformer.apply(p)) != null)
+ action.accept(u);
+ }
+ propagateCompletion();
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class ForEachTransformedMappingTask<K,V,U>
+ extends BulkTask<K,V,Void> {
+ final BiFunction<? super K, ? super V, ? extends U> transformer;
+ final Consumer<? super U> action;
+ ForEachTransformedMappingTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ BiFunction<? super K, ? super V, ? extends U> transformer,
+ Consumer<? super U> action) {
+ super(p, b, i, f, t);
+ this.transformer = transformer; this.action = action;
+ }
+ public final void compute() {
+ final BiFunction<? super K, ? super V, ? extends U> transformer;
+ final Consumer<? super U> 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<K,V,U>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ transformer, action).fork();
+ }
+ for (Node<K,V> 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<K,V,U>
+ extends BulkTask<K,V,U> {
+ final Function<? super K, ? extends U> searchFunction;
+ final AtomicReference<U> result;
+ SearchKeysTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ Function<? super K, ? extends U> searchFunction,
+ AtomicReference<U> 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<? super K, ? extends U> searchFunction;
+ final AtomicReference<U> 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<K,V,U>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ searchFunction, result).fork();
+ }
+ while (result.get() == null) {
+ U u;
+ Node<K,V> 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<K,V,U>
+ extends BulkTask<K,V,U> {
+ final Function<? super V, ? extends U> searchFunction;
+ final AtomicReference<U> result;
+ SearchValuesTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ Function<? super V, ? extends U> searchFunction,
+ AtomicReference<U> 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<? super V, ? extends U> searchFunction;
+ final AtomicReference<U> 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<K,V,U>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ searchFunction, result).fork();
+ }
+ while (result.get() == null) {
+ U u;
+ Node<K,V> 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<K,V,U>
+ extends BulkTask<K,V,U> {
+ final Function<Entry<K,V>, ? extends U> searchFunction;
+ final AtomicReference<U> result;
+ SearchEntriesTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ Function<Entry<K,V>, ? extends U> searchFunction,
+ AtomicReference<U> 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<Entry<K,V>, ? extends U> searchFunction;
+ final AtomicReference<U> 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<K,V,U>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ searchFunction, result).fork();
+ }
+ while (result.get() == null) {
+ U u;
+ Node<K,V> 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<K,V,U>
+ extends BulkTask<K,V,U> {
+ final BiFunction<? super K, ? super V, ? extends U> searchFunction;
+ final AtomicReference<U> result;
+ SearchMappingsTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ BiFunction<? super K, ? super V, ? extends U> searchFunction,
+ AtomicReference<U> 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<? super K, ? super V, ? extends U> searchFunction;
+ final AtomicReference<U> 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<K,V,U>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ searchFunction, result).fork();
+ }
+ while (result.get() == null) {
+ U u;
+ Node<K,V> 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<K,V>
+ extends BulkTask<K,V,K> {
+ final BiFunction<? super K, ? super K, ? extends K> reducer;
+ K result;
+ ReduceKeysTask<K,V> rights, nextRight;
+ ReduceKeysTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ ReduceKeysTask<K,V> nextRight,
+ BiFunction<? super K, ? super K, ? extends K> 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<? super K, ? super K, ? extends K> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, reducer)).fork();
+ }
+ K r = null;
+ for (Node<K,V> 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<K,V>
+ t = (ReduceKeysTask<K,V>)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<K,V>
+ extends BulkTask<K,V,V> {
+ final BiFunction<? super V, ? super V, ? extends V> reducer;
+ V result;
+ ReduceValuesTask<K,V> rights, nextRight;
+ ReduceValuesTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ ReduceValuesTask<K,V> nextRight,
+ BiFunction<? super V, ? super V, ? extends V> 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<? super V, ? super V, ? extends V> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, reducer)).fork();
+ }
+ V r = null;
+ for (Node<K,V> 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<K,V>
+ t = (ReduceValuesTask<K,V>)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<K,V>
+ extends BulkTask<K,V,Map.Entry<K,V>> {
+ final BiFunction<Map.Entry<K,V>, Map.Entry<K,V>, ? extends Map.Entry<K,V>> reducer;
+ Map.Entry<K,V> result;
+ ReduceEntriesTask<K,V> rights, nextRight;
+ ReduceEntriesTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ ReduceEntriesTask<K,V> nextRight,
+ BiFunction<Entry<K,V>, Map.Entry<K,V>, ? extends Map.Entry<K,V>> reducer) {
+ super(p, b, i, f, t); this.nextRight = nextRight;
+ this.reducer = reducer;
+ }
+ public final Map.Entry<K,V> getRawResult() { return result; }
+ public final void compute() {
+ final BiFunction<Map.Entry<K,V>, Map.Entry<K,V>, ? extends Map.Entry<K,V>> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, reducer)).fork();
+ }
+ Map.Entry<K,V> r = null;
+ for (Node<K,V> 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<K,V>
+ t = (ReduceEntriesTask<K,V>)c,
+ s = t.rights;
+ while (s != null) {
+ Map.Entry<K,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 MapReduceKeysTask<K,V,U>
+ extends BulkTask<K,V,U> {
+ final Function<? super K, ? extends U> transformer;
+ final BiFunction<? super U, ? super U, ? extends U> reducer;
+ U result;
+ MapReduceKeysTask<K,V,U> rights, nextRight;
+ MapReduceKeysTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceKeysTask<K,V,U> nextRight,
+ Function<? super K, ? extends U> transformer,
+ BiFunction<? super U, ? super U, ? extends U> 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<? super K, ? extends U> transformer;
+ final BiFunction<? super U, ? super U, ? extends U> 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<K,V,U>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, reducer)).fork();
+ }
+ U r = null;
+ for (Node<K,V> 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<K,V,U>
+ t = (MapReduceKeysTask<K,V,U>)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<K,V,U>
+ extends BulkTask<K,V,U> {
+ final Function<? super V, ? extends U> transformer;
+ final BiFunction<? super U, ? super U, ? extends U> reducer;
+ U result;
+ MapReduceValuesTask<K,V,U> rights, nextRight;
+ MapReduceValuesTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceValuesTask<K,V,U> nextRight,
+ Function<? super V, ? extends U> transformer,
+ BiFunction<? super U, ? super U, ? extends U> 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<? super V, ? extends U> transformer;
+ final BiFunction<? super U, ? super U, ? extends U> 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<K,V,U>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, reducer)).fork();
+ }
+ U r = null;
+ for (Node<K,V> 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<K,V,U>
+ t = (MapReduceValuesTask<K,V,U>)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 MapReduceEntriesTask<K,V,U>
+ extends BulkTask<K,V,U> {
+ final Function<Map.Entry<K,V>, ? extends U> transformer;
+ final BiFunction<? super U, ? super U, ? extends U> reducer;
+ U result;
+ MapReduceEntriesTask<K,V,U> rights, nextRight;
+ MapReduceEntriesTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceEntriesTask<K,V,U> nextRight,
+ Function<Map.Entry<K,V>, ? extends U> transformer,
+ BiFunction<? super U, ? super U, ? extends U> 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<Map.Entry<K,V>, ? extends U> transformer;
+ final BiFunction<? super U, ? super U, ? extends U> 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<K,V,U>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, reducer)).fork();
+ }
+ U r = null;
+ for (Node<K,V> 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<K,V,U>
+ t = (MapReduceEntriesTask<K,V,U>)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 MapReduceMappingsTask<K,V,U>
+ extends BulkTask<K,V,U> {
+ final BiFunction<? super K, ? super V, ? extends U> transformer;
+ final BiFunction<? super U, ? super U, ? extends U> reducer;
+ U result;
+ MapReduceMappingsTask<K,V,U> rights, nextRight;
+ MapReduceMappingsTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceMappingsTask<K,V,U> nextRight,
+ BiFunction<? super K, ? super V, ? extends U> transformer,
+ BiFunction<? super U, ? super U, ? extends U> 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 BiFunction<? super K, ? super V, ? extends U> transformer;
+ final BiFunction<? super U, ? super U, ? extends U> 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<K,V,U>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, reducer)).fork();
+ }
+ U r = null;
+ for (Node<K,V> 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<K,V,U>
+ t = (MapReduceMappingsTask<K,V,U>)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 MapReduceKeysToDoubleTask<K,V>
+ extends BulkTask<K,V,Double> {
+ final ToDoubleFunction<? super K> transformer;
+ final DoubleBinaryOperator reducer;
+ final double basis;
+ double result;
+ MapReduceKeysToDoubleTask<K,V> rights, nextRight;
+ MapReduceKeysToDoubleTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceKeysToDoubleTask<K,V> nextRight,
+ ToDoubleFunction<? super K> 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<? super K> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, r, reducer)).fork();
+ }
+ for (Node<K,V> 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<K,V>
+ t = (MapReduceKeysToDoubleTask<K,V>)c,
+ s = t.rights;
+ while (s != null) {
+ t.result = reducer.applyAsDouble(t.result, s.result);
+ s = t.rights = s.nextRight;
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class MapReduceValuesToDoubleTask<K,V>
+ extends BulkTask<K,V,Double> {
+ final ToDoubleFunction<? super V> transformer;
+ final DoubleBinaryOperator reducer;
+ final double basis;
+ double result;
+ MapReduceValuesToDoubleTask<K,V> rights, nextRight;
+ MapReduceValuesToDoubleTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceValuesToDoubleTask<K,V> nextRight,
+ ToDoubleFunction<? super V> 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<? super V> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, r, reducer)).fork();
+ }
+ for (Node<K,V> 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<K,V>
+ t = (MapReduceValuesToDoubleTask<K,V>)c,
+ s = t.rights;
+ while (s != null) {
+ t.result = reducer.applyAsDouble(t.result, s.result);
+ s = t.rights = s.nextRight;
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class MapReduceEntriesToDoubleTask<K,V>
+ extends BulkTask<K,V,Double> {
+ final ToDoubleFunction<Map.Entry<K,V>> transformer;
+ final DoubleBinaryOperator reducer;
+ final double basis;
+ double result;
+ MapReduceEntriesToDoubleTask<K,V> rights, nextRight;
+ MapReduceEntriesToDoubleTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceEntriesToDoubleTask<K,V> nextRight,
+ ToDoubleFunction<Map.Entry<K,V>> 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<Map.Entry<K,V>> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, r, reducer)).fork();
+ }
+ for (Node<K,V> 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<K,V>
+ t = (MapReduceEntriesToDoubleTask<K,V>)c,
+ s = t.rights;
+ while (s != null) {
+ t.result = reducer.applyAsDouble(t.result, s.result);
+ s = t.rights = s.nextRight;
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class MapReduceMappingsToDoubleTask<K,V>
+ extends BulkTask<K,V,Double> {
+ final ToDoubleBiFunction<? super K, ? super V> transformer;
+ final DoubleBinaryOperator reducer;
+ final double basis;
+ double result;
+ MapReduceMappingsToDoubleTask<K,V> rights, nextRight;
+ MapReduceMappingsToDoubleTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceMappingsToDoubleTask<K,V> nextRight,
+ ToDoubleBiFunction<? super K, ? super V> 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 ToDoubleBiFunction<? super K, ? super V> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, r, reducer)).fork();
+ }
+ for (Node<K,V> 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<K,V>
+ t = (MapReduceMappingsToDoubleTask<K,V>)c,
+ s = t.rights;
+ while (s != null) {
+ t.result = reducer.applyAsDouble(t.result, s.result);
+ s = t.rights = s.nextRight;
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class MapReduceKeysToLongTask<K,V>
+ extends BulkTask<K,V,Long> {
+ final ToLongFunction<? super K> transformer;
+ final LongBinaryOperator reducer;
+ final long basis;
+ long result;
+ MapReduceKeysToLongTask<K,V> rights, nextRight;
+ MapReduceKeysToLongTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceKeysToLongTask<K,V> nextRight,
+ ToLongFunction<? super K> 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<? super K> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, r, reducer)).fork();
+ }
+ for (Node<K,V> 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<K,V>
+ t = (MapReduceKeysToLongTask<K,V>)c,
+ s = t.rights;
+ while (s != null) {
+ t.result = reducer.applyAsLong(t.result, s.result);
+ s = t.rights = s.nextRight;
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class MapReduceValuesToLongTask<K,V>
+ extends BulkTask<K,V,Long> {
+ final ToLongFunction<? super V> transformer;
+ final LongBinaryOperator reducer;
+ final long basis;
+ long result;
+ MapReduceValuesToLongTask<K,V> rights, nextRight;
+ MapReduceValuesToLongTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceValuesToLongTask<K,V> nextRight,
+ ToLongFunction<? super V> 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<? super V> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, r, reducer)).fork();
+ }
+ for (Node<K,V> 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<K,V>
+ t = (MapReduceValuesToLongTask<K,V>)c,
+ s = t.rights;
+ while (s != null) {
+ t.result = reducer.applyAsLong(t.result, s.result);
+ s = t.rights = s.nextRight;
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class MapReduceEntriesToLongTask<K,V>
+ extends BulkTask<K,V,Long> {
+ final ToLongFunction<Map.Entry<K,V>> transformer;
+ final LongBinaryOperator reducer;
+ final long basis;
+ long result;
+ MapReduceEntriesToLongTask<K,V> rights, nextRight;
+ MapReduceEntriesToLongTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceEntriesToLongTask<K,V> nextRight,
+ ToLongFunction<Map.Entry<K,V>> 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<Map.Entry<K,V>> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, r, reducer)).fork();
+ }
+ for (Node<K,V> 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<K,V>
+ t = (MapReduceEntriesToLongTask<K,V>)c,
+ s = t.rights;
+ while (s != null) {
+ t.result = reducer.applyAsLong(t.result, s.result);
+ s = t.rights = s.nextRight;
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class MapReduceMappingsToLongTask<K,V>
+ extends BulkTask<K,V,Long> {
+ final ToLongBiFunction<? super K, ? super V> transformer;
+ final LongBinaryOperator reducer;
+ final long basis;
+ long result;
+ MapReduceMappingsToLongTask<K,V> rights, nextRight;
+ MapReduceMappingsToLongTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceMappingsToLongTask<K,V> nextRight,
+ ToLongBiFunction<? super K, ? super V> 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<? super K, ? super V> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, r, reducer)).fork();
+ }
+ for (Node<K,V> 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<K,V>
+ t = (MapReduceMappingsToLongTask<K,V>)c,
+ s = t.rights;
+ while (s != null) {
+ t.result = reducer.applyAsLong(t.result, s.result);
+ s = t.rights = s.nextRight;
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class MapReduceKeysToIntTask<K,V>
+ extends BulkTask<K,V,Integer> {
+ final ToIntFunction<? super K> transformer;
+ final IntBinaryOperator reducer;
+ final int basis;
+ int result;
+ MapReduceKeysToIntTask<K,V> rights, nextRight;
+ MapReduceKeysToIntTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceKeysToIntTask<K,V> nextRight,
+ ToIntFunction<? super K> 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<? super K> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, r, reducer)).fork();
+ }
+ for (Node<K,V> 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<K,V>
+ t = (MapReduceKeysToIntTask<K,V>)c,
+ s = t.rights;
+ while (s != null) {
+ t.result = reducer.applyAsInt(t.result, s.result);
+ s = t.rights = s.nextRight;
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class MapReduceValuesToIntTask<K,V>
+ extends BulkTask<K,V,Integer> {
+ final ToIntFunction<? super V> transformer;
+ final IntBinaryOperator reducer;
+ final int basis;
+ int result;
+ MapReduceValuesToIntTask<K,V> rights, nextRight;
+ MapReduceValuesToIntTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceValuesToIntTask<K,V> nextRight,
+ ToIntFunction<? super V> 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<? super V> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, r, reducer)).fork();
+ }
+ for (Node<K,V> 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<K,V>
+ t = (MapReduceValuesToIntTask<K,V>)c,
+ s = t.rights;
+ while (s != null) {
+ t.result = reducer.applyAsInt(t.result, s.result);
+ s = t.rights = s.nextRight;
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class MapReduceEntriesToIntTask<K,V>
+ extends BulkTask<K,V,Integer> {
+ final ToIntFunction<Map.Entry<K,V>> transformer;
+ final IntBinaryOperator reducer;
+ final int basis;
+ int result;
+ MapReduceEntriesToIntTask<K,V> rights, nextRight;
+ MapReduceEntriesToIntTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceEntriesToIntTask<K,V> nextRight,
+ ToIntFunction<Map.Entry<K,V>> 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<Map.Entry<K,V>> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, r, reducer)).fork();
+ }
+ for (Node<K,V> 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<K,V>
+ t = (MapReduceEntriesToIntTask<K,V>)c,
+ s = t.rights;
+ while (s != null) {
+ t.result = reducer.applyAsInt(t.result, s.result);
+ s = t.rights = s.nextRight;
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ static final class MapReduceMappingsToIntTask<K,V>
+ extends BulkTask<K,V,Integer> {
+ final ToIntBiFunction<? super K, ? super V> transformer;
+ final IntBinaryOperator reducer;
+ final int basis;
+ int result;
+ MapReduceMappingsToIntTask<K,V> rights, nextRight;
+ MapReduceMappingsToIntTask
+ (BulkTask<K,V,?> p, int b, int i, int f, Node<K,V>[] t,
+ MapReduceMappingsToIntTask<K,V> nextRight,
+ ToIntBiFunction<? super K, ? super V> 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<? super K, ? super V> 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<K,V>
+ (this, batch >>>= 1, baseLimit = h, f, tab,
+ rights, transformer, r, reducer)).fork();
+ }
+ for (Node<K,V> 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<K,V>
+ t = (MapReduceMappingsToIntTask<K,V>)c,
+ s = t.rights;
+ while (s != null) {
+ t.result = reducer.applyAsInt(t.result, s.result);
+ s = t.rights = s.nextRight;
+ }
+ }
+ }
+ }
}
// 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 @@
// 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 b38d6a5..7084d14 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.
*
- * <p>Iterators are <i>weakly consistent</i>, returning elements
- * reflecting the state of the deque at some point at or since the
- * creation of the iterator. They do <em>not</em> throw {@link
- * java.util.ConcurrentModificationException
- * ConcurrentModificationException}, and may proceed concurrently with
- * other operations.
+ * <p>Iterators and spliterators are
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* <p>Beware that, unlike in most collections, the {@code size} method
* is <em>NOT</em> a constant-time operation. Because of the
@@ -59,7 +59,7 @@
* @since 1.7
* @author Doug Lea
* @author Martin Buchholz
- * @param <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this deque
*/
public class ConcurrentLinkedDeque<E>
extends AbstractCollection<E>
@@ -272,47 +272,45 @@
* 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<E> val) {
- UNSAFE.putOrderedObject(this, nextOffset, val);
+ U.putOrderedObject(this, NEXT, val);
}
boolean casNext(Node<E> cmp, Node<E> val) {
- return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
+ return U.compareAndSwapObject(this, NEXT, cmp, val);
}
void lazySetPrev(Node<E> val) {
- UNSAFE.putOrderedObject(this, prevOffset, val);
+ U.putOrderedObject(this, PREV, val);
}
boolean casPrev(Node<E> cmp, Node<E> 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 @@
* Links e as first element.
*/
private void linkFirst(E e) {
- checkNotNull(e);
- final Node<E> newNode = new Node<E>(e);
+ final Node<E> newNode = new Node<E>(Objects.requireNonNull(e));
restartFromHead:
for (;;)
@@ -355,8 +352,7 @@
* Links e as last element.
*/
private void linkLast(E e) {
- checkNotNull(e);
- final Node<E> newNode = new Node<E>(e);
+ final Node<E> newNode = new Node<E>(Objects.requireNonNull(e));
restartFromTail:
for (;;)
@@ -761,16 +757,6 @@
// 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.
*
@@ -784,22 +770,6 @@
}
/**
- * Creates an array list and fills it with elements of this list.
- * Used by toArray.
- *
- * @return the array list
- */
- private ArrayList<E> toArrayList() {
- ArrayList<E> list = new ArrayList<E>();
- for (Node<E> p = first(); p != null; p = succ(p)) {
- E item = p.item;
- if (item != null)
- list.add(item);
- }
- return list;
- }
-
- /**
* Constructs an empty deque.
*/
public ConcurrentLinkedDeque() {
@@ -819,8 +789,7 @@
// Copy c into a private chain of Nodes
Node<E> h = null, t = null;
for (E e : c) {
- checkNotNull(e);
- Node<E> newNode = new Node<E>(e);
+ Node<E> newNode = new Node<E>(Objects.requireNonNull(e));
if (h == null)
h = t = newNode;
else {
@@ -995,23 +964,42 @@
}
public E poll() { return pollFirst(); }
- public E remove() { return removeFirst(); }
public E peek() { return peekFirst(); }
- public E element() { return getFirst(); }
- public void push(E e) { addFirst(e); }
+
+ /**
+ * @throws NoSuchElementException {@inheritDoc}
+ */
+ public E remove() { return removeFirst(); }
+
+ /**
+ * @throws NoSuchElementException {@inheritDoc}
+ */
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.
+ * @throws NoSuchElementException {@inheritDoc}
+ */
+ public E element() { return getFirst(); }
+
+ /**
+ * @throws NullPointerException {@inheritDoc}
+ */
+ public void push(E e) { addFirst(e); }
+
+ /**
+ * 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<E> 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 @@
}
/**
- * 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<E> 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 @@
}
/**
- * 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<E> p = first(); p != null; p = succ(p)) {
- E item = p.item;
- if (item != null && o.equals(item))
- return true;
+ if (o != null) {
+ for (Node<E> 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 @@
* @return the number of elements in this deque
*/
public int size() {
- int count = 0;
- for (Node<E> 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<E> 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).
+ *
+ * <p>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 @@
// Copy c into a private chain of Nodes
Node<E> beginningOfTheEnd = null, last = null;
for (E e : c) {
- checkNotNull(e);
- Node<E> newNode = new Node<E>(e);
+ Node<E> newNode = new Node<E>(Objects.requireNonNull(e));
if (beginningOfTheEnd == null)
beginningOfTheEnd = last = newNode;
else {
@@ -1180,6 +1181,62 @@
;
}
+ public String toString() {
+ String[] a = null;
+ restartFromHead: for (;;) {
+ int charLength = 0;
+ int size = 0;
+ for (Node<E> 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<E> 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 @@
* @return an array containing all of the elements in this deque
*/
public Object[] toArray() {
- return toArrayList().toArray();
+ return toArrayInternal(null);
}
/**
@@ -1220,7 +1277,7 @@
* The following code can be used to dump the deque into a newly
* allocated array of {@code String}:
*
- * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
+ * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
*
* Note that {@code toArray(new Object[0])} is identical in function to
* {@code toArray()}.
@@ -1234,20 +1291,18 @@
* this deque
* @throws NullPointerException if the specified array is null
*/
+ @SuppressWarnings("unchecked")
public <T> 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).
*
- * <p>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.
+ * <p>The returned iterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* @return an iterator over the elements in this deque in proper sequence
*/
@@ -1260,12 +1315,8 @@
* sequential order. The elements will be returned in order from
* last (tail) to first (head).
*
- * <p>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.
+ * <p>The returned iterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* @return an iterator over the elements in this deque in reverse order
*/
@@ -1310,7 +1361,7 @@
Node<E> 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 @@
Node<E> nextNode(Node<E> p) { return pred(p); }
}
+ /** A customized variant of Spliterators.IteratorSpliterator */
+ static final class CLDSpliterator<E> implements Spliterator<E> {
+ static final int MAX_BATCH = 1 << 25; // max batch array size;
+ final ConcurrentLinkedDeque<E> queue;
+ Node<E> current; // current node; null until initialized
+ int batch; // batch size for splits
+ boolean exhausted; // true when no more nodes
+ CLDSpliterator(ConcurrentLinkedDeque<E> queue) {
+ this.queue = queue;
+ }
+
+ public Spliterator<E> trySplit() {
+ Node<E> p;
+ final ConcurrentLinkedDeque<E> 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<? super E> action) {
+ Node<E> p;
+ if (action == null) throw new NullPointerException();
+ final ConcurrentLinkedDeque<E> 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<? super E> action) {
+ Node<E> p;
+ if (action == null) throw new NullPointerException();
+ final ConcurrentLinkedDeque<E> 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.
+ *
+ * <p>The returned spliterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
+ *
+ * <p>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<E> spliterator() {
+ return new CLDSpliterator<E>(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 @@
/**
* 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 @@
// Read in elements until trailing null sentinel found
Node<E> h = null, t = null;
- Object item;
- while ((item = s.readObject()) != null) {
+ for (Object item; (item = s.readObject()) != null; ) {
@SuppressWarnings("unchecked")
Node<E> newNode = new Node<E>((E) item);
if (h == null)
@@ -1404,31 +1570,29 @@
}
private boolean casHead(Node<E> cmp, Node<E> val) {
- return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val);
+ return U.compareAndSwapObject(this, HEAD, cmp, val);
}
private boolean casTail(Node<E> cmp, Node<E> 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<Object>();
PREV_TERMINATOR.next = PREV_TERMINATOR;
NEXT_TERMINATOR = new Node<Object>();
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 9010cbe..e96b1b8 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 <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this queue
*/
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
implements Queue<E>, java.io.Serializable {
@@ -148,45 +152,28 @@
private static class Node<E> {
volatile E item;
volatile Node<E> 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);
- }
+ /**
+ * Returns a new node holding item. Uses relaxed write because item
+ * can only be seen after piggy-backing publication via casNext.
+ */
+ static <E> Node<E> newNode(E item) {
+ Node<E> node = new Node<E>();
+ U.putObject(node, ITEM, item);
+ return node;
+ }
- boolean casItem(E cmp, E val) {
- return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
- }
+ static <E> boolean casItem(Node<E> node, E cmp, E val) {
+ return U.compareAndSwapObject(node, ITEM, cmp, val);
+ }
- void lazySetNext(Node<E> val) {
- UNSAFE.putOrderedObject(this, nextOffset, val);
- }
+ static <E> void lazySetNext(Node<E> node, Node<E> val) {
+ U.putOrderedObject(node, NEXT, val);
+ }
- boolean casNext(Node<E> cmp, Node<E> val) {
- return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, 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 <E> boolean casNext(Node<E> node, Node<E> cmp, Node<E> val) {
+ return U.compareAndSwapObject(node, NEXT, cmp, val);
}
/**
@@ -201,7 +188,7 @@
* - it is permitted for tail to lag behind head, that is, for tail
* to not be reachable from head!
*/
- private transient volatile Node<E> head;
+ transient volatile Node<E> head;
/**
* A node from which the last node on list (that is, the unique
@@ -221,7 +208,7 @@
* Creates a {@code ConcurrentLinkedQueue} that is initially empty.
*/
public ConcurrentLinkedQueue() {
- head = tail = new Node<E>(null);
+ head = tail = newNode(null);
}
/**
@@ -236,17 +223,16 @@
public ConcurrentLinkedQueue(Collection<? extends E> c) {
Node<E> h = null, t = null;
for (E e : c) {
- checkNotNull(e);
- Node<E> newNode = new Node<E>(e);
+ Node<E> 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<E>(null);
+ h = t = newNode(null);
head = h;
tail = t;
}
@@ -270,8 +256,9 @@
* as sentinel for succ(), below.
*/
final void updateHead(Node<E> h, Node<E> 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 @@
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
- checkNotNull(e);
- final Node<E> newNode = new Node<E>(e);
+ final Node<E> newNode = newNode(Objects.requireNonNull(e));
for (Node<E> t = tail, p = t;;) {
Node<E> 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 @@
for (Node<E> 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 @@
* @return the number of elements in this queue
*/
public int size() {
- int count = 0;
- for (Node<E> 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<E> 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 @@
* @return {@code true} if this queue contains the specified element
*/
public boolean contains(Object o) {
- if (o == null) return false;
- for (Node<E> p = first(); p != null; p = succ(p)) {
- E item = p.item;
- if (item != null && o.equals(item))
- return true;
+ if (o != null) {
+ for (Node<E> 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 @@
* @return {@code true} if this queue changed as a result of the call
*/
public boolean remove(Object o) {
- if (o == null) return false;
- Node<E> pred = null;
- for (Node<E> p = first(); p != null; p = succ(p)) {
- E item = p.item;
- if (item != null &&
- o.equals(item) &&
- p.casItem(item, null)) {
- Node<E> next = succ(p);
- if (pred != null && next != null)
- pred.casNext(p, next);
- return true;
+ if (o != null) {
+ Node<E> next, pred = null;
+ for (Node<E> 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 @@
// Copy c into a private chain of Nodes
Node<E> beginningOfTheEnd = null, last = null;
for (E e : c) {
- checkNotNull(e);
- Node<E> newNode = new Node<E>(e);
+ Node<E> newNode = newNode(Objects.requireNonNull(e));
if (beginningOfTheEnd == null)
beginningOfTheEnd = last = newNode;
else {
- last.lazySetNext(newNode);
+ lazySetNext(last, newNode);
last = newNode;
}
}
@@ -507,7 +503,7 @@
Node<E> 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 @@
}
}
+ public String toString() {
+ String[] a = null;
+ restartFromHead: for (;;) {
+ int charLength = 0;
+ int size = 0;
+ for (Node<E> 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<E> 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 @@
* @return an array containing all of the elements in this queue
*/
public Object[] toArray() {
- // Use ArrayList to deal with resizing.
- ArrayList<E> al = new ArrayList<E>();
- for (Node<E> 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 @@
* The following code can be used to dump the queue into a newly
* allocated array of {@code String}:
*
- * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
+ * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
*
* Note that {@code toArray(new Object[0])} is identical in function to
* {@code toArray()}.
@@ -594,40 +639,16 @@
*/
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
- // try to use sent-in array
- int k = 0;
- Node<E> 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<E> al = new ArrayList<E>();
- for (Node<E> 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).
*
- * <p>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.
+ * <p>The returned iterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* @return an iterator over the elements in this queue in proper sequence
*/
@@ -655,54 +676,47 @@
private Node<E> 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<E> 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;
+ restartFromHead: for (;;) {
+ Node<E> 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;
}
- E item = p.item;
- if (item != null) {
- nextNode = p;
- nextItem = item;
- return x;
- } else {
- // skip over nulls
- Node<E> next = succ(p);
- if (pred != null && next != null)
- pred.casNext(p, next);
- p = next;
- }
+ 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<E> pred = nextNode;
+ if (pred == null) throw new NoSuchElementException();
+ // assert nextItem != null;
+ lastRet = pred;
+ E item = null;
+
+ for (Node<E> 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 @@
/**
* 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 @@
/**
* 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 @@
// Read in elements until trailing null sentinel found
Node<E> h = null, t = null;
- Object item;
- while ((item = s.readObject()) != null) {
+ for (Object item; (item = s.readObject()) != null; ) {
@SuppressWarnings("unchecked")
- Node<E> newNode = new Node<E>((E) item);
+ Node<E> 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<E>(null);
+ h = t = newNode(null);
head = h;
tail = t;
}
+ /** A customized variant of Spliterators.IteratorSpliterator */
+ static final class CLQSpliterator<E> implements Spliterator<E> {
+ static final int MAX_BATCH = 1 << 25; // max batch array size;
+ final ConcurrentLinkedQueue<E> queue;
+ Node<E> current; // current node; null until initialized
+ int batch; // batch size for splits
+ boolean exhausted; // true when no more nodes
+ CLQSpliterator(ConcurrentLinkedQueue<E> queue) {
+ this.queue = queue;
+ }
+
+ public Spliterator<E> trySplit() {
+ Node<E> p;
+ final ConcurrentLinkedQueue<E> 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<? super E> action) {
+ Node<E> p;
+ if (action == null) throw new NullPointerException();
+ final ConcurrentLinkedQueue<E> 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<? super E> action) {
+ Node<E> p;
+ if (action == null) throw new NullPointerException();
+ final ConcurrentLinkedQueue<E> 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.
*
- * @param v the element
+ * <p>The returned spliterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
+ *
+ * <p>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
*/
- private static void checkNotNull(Object v) {
- if (v == null)
- throw new NullPointerException();
+ @Override
+ public Spliterator<E> spliterator() {
+ return new CLQSpliterator<E>(this);
}
private boolean casTail(Node<E> cmp, Node<E> val) {
- return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val);
+ return U.compareAndSwapObject(this, TAIL, cmp, val);
}
private boolean casHead(Node<E> cmp, Node<E> 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 1391f04..ae4d221 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.
+ *
+ * <p>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.
*
* <p>Memory consistency effects: As with other concurrent
* collections, actions in a thread prior to placing an object into a
@@ -29,11 +42,65 @@
* @param <V> the type of mapped values
*/
public interface ConcurrentMap<K,V> extends Map<K,V> {
+
+ /**
+ * {@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
+ * <strong>must</strong> 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}:
+ * <pre> {@code
+ * for (Map.Entry<K,V> entry : map.entrySet()) {
+ * action.accept(entry.getKey(), entry.getValue());
+ * }}</pre>
+ *
+ * @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<? super K, ? super V> action) {
+ Objects.requireNonNull(action);
+ for (Map.Entry<K,V> 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
- * <pre> {@code
+ * with a value, associates it with the given value.
+ * This is equivalent to, for this {@code map}:
+ * <pre> {@code
* if (!map.containsKey(key))
* return map.put(key, value);
* else
@@ -41,6 +108,9 @@
*
* 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 @@
/**
* Removes the entry for a key only if currently mapped to a given value.
- * This is equivalent to
- * <pre> {@code
- * if (map.containsKey(key) && map.get(key).equals(value)) {
+ * This is equivalent to, for this {@code map}:
+ * <pre> {@code
+ * if (map.containsKey(key)
+ * && Objects.equals(map.get(key), value)) {
* map.remove(key);
* return true;
- * } else
- * return false;}</pre>
+ * } else {
+ * return false;
+ * }}</pre>
*
* 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 @@
* is not supported by this map
* @throws ClassCastException if the key or value is of an inappropriate
* type for this map
- * (<a href="../Collection.html#optional-restrictions">optional</a>)
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified key or value is null,
* and this map does not permit null keys or values
- * (<a href="../Collection.html#optional-restrictions">optional</a>)
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
*/
boolean remove(Object key, Object value);
/**
* Replaces the entry for a key only if currently mapped to a given value.
- * This is equivalent to
- * <pre> {@code
- * if (map.containsKey(key) && map.get(key).equals(oldValue)) {
+ * This is equivalent to, for this {@code map}:
+ * <pre> {@code
+ * if (map.containsKey(key)
+ * && Objects.equals(map.get(key), oldValue)) {
* map.put(key, newValue);
* return true;
- * } else
- * return false;}</pre>
+ * } else {
+ * return false;
+ * }}</pre>
*
* 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 @@
/**
* Replaces the entry for a key only if currently mapped to some value.
- * This is equivalent to
- * <pre> {@code
- * if (map.containsKey(key)) {
+ * This is equivalent to, for this {@code map}:
+ * <pre> {@code
+ * if (map.containsKey(key))
* return map.put(key, value);
- * } else
+ * else
* return null;}</pre>
*
* 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 @@
* or value prevents it from being stored in this map
*/
V replace(K key, V value);
+
+ /**
+ * {@inheritDoc}
+ *
+ * @implSpec
+ * <p>The default implementation is equivalent to, for this {@code map}:
+ * <pre> {@code
+ * for (Map.Entry<K,V> entry : map.entrySet()) {
+ * K k;
+ * V v;
+ * do {
+ * k = entry.getKey();
+ * v = entry.getValue();
+ * } while (!map.replace(k, v, function.apply(k, v)));
+ * }}</pre>
+ *
+ * The default implementation may retry these steps when multiple
+ * threads attempt updates including potentially calling the function
+ * repeatedly for a given key.
+ *
+ * <p>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 <strong>must</strong>
+ * override this default implementation.
+ *
+ * @throws UnsupportedOperationException {@inheritDoc}
+ * @throws NullPointerException {@inheritDoc}
+ * @throws ClassCastException {@inheritDoc}
+ * @throws IllegalArgumentException {@inheritDoc}
+ * @since 1.8
+ */
+ @Override
+ default void replaceAll(BiFunction<? super K, ? super V, ? extends V> 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}:
+ *
+ * <pre> {@code
+ * V oldValue, newValue;
+ * return ((oldValue = map.get(key)) == null
+ * && (newValue = mappingFunction.apply(key)) != null
+ * && (oldValue = map.putIfAbsent(key, newValue)) == null)
+ * ? newValue
+ * : oldValue;}</pre>
+ *
+ * <p>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 <strong>must</strong>
+ * 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<? super K, ? extends V> 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}:
+ *
+ * <pre> {@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;}</pre>
+ * When multiple threads attempt updates, map operations and the
+ * remapping function may be called multiple times.
+ *
+ * <p>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 <strong>must</strong>
+ * 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<? super K, ? super V, ? extends V> 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}:
+ *
+ * <pre> {@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;
+ * }
+ * }}</pre>
+ * When multiple threads attempt updates, map operations and the
+ * remapping function may be called multiple times.
+ *
+ * <p>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 <strong>must</strong>
+ * 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<? super K, ? super V, ? extends V> 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}:
+ *
+ * <pre> {@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;
+ * }
+ * }}</pre>
+ * When multiple threads attempt updates, map operations and the
+ * remapping function may be called multiple times.
+ *
+ * <p>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 <strong>must</strong>
+ * 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<? super V, ? super V, ? extends V> 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 17890ff..0d795b4 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 @@
* reflected in the descending map, and vice-versa.
*
* <p>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 @@
* operations. It does not support the {@code add} or {@code addAll}
* operations.
*
- * <p>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.
+ * <p>The view's iterators and spliterators are
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* @return a navigable set view of the keys in this map
*/
- public NavigableSet<K> navigableKeySet();
+ NavigableSet<K> navigableKeySet();
/**
* Returns a {@link NavigableSet} view of the keys contained in this map.
@@ -113,11 +111,8 @@
* operations. It does not support the {@code add} or {@code addAll}
* operations.
*
- * <p>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.
+ * <p>The view's iterators and spliterators are
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* <p>This method is equivalent to method {@code navigableKeySet}.
*
@@ -136,13 +131,10 @@
* operations. It does not support the {@code add} or {@code addAll}
* operations.
*
- * <p>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.
+ * <p>The view's iterators and spliterators are
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* @return a reverse order navigable set view of the keys in this map
*/
- public NavigableSet<K> descendingKeySet();
+ NavigableSet<K> descendingKeySet();
}
diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentSkipListMap.java b/luni/src/main/java/java/util/concurrent/ConcurrentSkipListMap.java
index 0e8b64a..359d4f1 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 <i>weakly consistent</i>, returning
- * elements reflecting the state of the map at some point at or since
- * the creation of the iterator. They do <em>not</em> throw {@link
- * ConcurrentModificationException}, and may proceed concurrently with
- * other operations. Ascending key ordered views and their iterators
- * are faster than descending ones.
+ * multiple threads.
+ *
+ * <p>Iterators and spliterators are
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
+ *
+ * <p>Ascending key ordered views and their iterators are faster than
+ * descending ones.
*
* <p>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 <V> the type of mapped values
* @since 1.6
*/
-@SuppressWarnings("unchecked")
public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
- implements ConcurrentNavigableMap<K,V>,
- Cloneable,
- java.io.Serializable {
+ implements ConcurrentNavigableMap<K,V>, 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 @@
*
* 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 @@
* 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 @@
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 @@
private transient volatile HeadIndex<K,V> 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<? super K> 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<? super K> comparator;
/** Lazily initialized key set */
- private transient KeySet<K> keySet;
+ private transient KeySet<K,V> keySet;
/** Lazily initialized entry set */
private transient EntrySet<K,V> entrySet;
/** Lazily initialized values collection */
- private transient Values<V> values;
+ private transient Values<K,V> values;
/** Lazily initialized descending key set */
private transient ConcurrentNavigableMap<K,V> descendingMap;
@@ -339,27 +359,20 @@
* 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<K,V>(new Node<K,V>(null, BASE_HEADER, null),
null, null, 1);
}
/**
- * compareAndSet head node
+ * compareAndSet head node.
*/
private boolean casHead(HeadIndex<K,V> cmp, HeadIndex<K,V> val) {
- return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val);
+ return U.compareAndSwapObject(this, HEAD, cmp, val);
}
/* ---------------- Nodes -------------- */
@@ -399,17 +412,17 @@
}
/**
- * 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<K,V> cmp, Node<K,V> val) {
- return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
+ return U.compareAndSwapObject(this, NEXT, cmp, val);
}
/**
@@ -457,7 +470,7 @@
*/
if (f == next && this == b.next) {
if (f == null || f.value != f) // not already marked
- appendMarker(f);
+ casNext(f, new Node<K,V>(f));
else
b.casNext(this, f.next);
}
@@ -473,7 +486,8 @@
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 @@
* @return new entry or null
*/
AbstractMap.SimpleImmutableEntry<K,V> createSnapshot() {
- V v = getValidValue();
- if (v == null)
+ Object v = value;
+ if (v == null || v == this || v == BASE_HEADER)
return null;
- return new AbstractMap.SimpleImmutableEntry<K,V>(key, v);
+ @SuppressWarnings("unchecked") V vv = (V)v;
+ return new AbstractMap.SimpleImmutableEntry<K,V>(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 @@
}
/**
- * compareAndSet right field
+ * compareAndSet right field.
*/
final boolean casRight(Index<K,V> cmp, Index<K,V> val) {
- return UNSAFE.compareAndSwapObject(this, rightOffset, cmp, val);
+ return U.compareAndSwapObject(this, RIGHT, cmp, val);
}
/**
@@ -568,19 +581,17 @@
* @return true if successful
*/
final boolean unlink(Index<K,V> 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 @@
/* ---------------- 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<K> implements Comparable<K> {
- final K actualKey;
- final Comparator<? super K> cmp;
- ComparableUsingComparator(K key, Comparator<? super K> 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<? super K> comparable(Object key)
- throws ClassCastException {
- if (key == null)
- throw new NullPointerException();
- if (comparator != null)
- return new ComparableUsingComparator<K>((K)key, comparator);
- else
- return (Comparable<? super K>)key;
- }
-
- /**
- * Compares using comparator or natural ordering. Used when the
- * ComparableUsingComparator approach doesn't apply.
- */
- int compare(K k1, K k2) throws ClassCastException {
- Comparator<? super K> cmp = comparator;
- if (cmp != null)
- return cmp.compare(k1, k2);
- else
- return ((Comparable<? super K>)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 @@
* @param key the key
* @return a predecessor of key
*/
- private Node<K,V> findPredecessor(Comparable<? super K> key) {
+ private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
if (key == null)
throw new NullPointerException(); // don't postpone errors
for (;;) {
- Index<K,V> q = head;
- Index<K,V> r = q.right;
- for (;;) {
+ for (Index<K,V> q = head, r = q.right, d;;) {
if (r != null) {
Node<K,V> n = r.node;
K k = n.key;
@@ -704,18 +645,16 @@
r = q.right; // reread r
continue;
}
- if (key.compareTo(k) > 0) {
+ if (cpr(cmp, key, k) > 0) {
q = r;
r = r.right;
continue;
}
}
- Index<K,V> 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 @@
*
* 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<K,V> findNode(Comparable<? super K> key) {
- for (;;) {
- Node<K,V> b = findPredecessor(key);
- Node<K,V> n = b.next;
- for (;;) {
+ private Node<K,V> findNode(Object key) {
+ if (key == null)
+ throw new NullPointerException(); // don't postpone errors
+ Comparator<? super K> cmp = comparator;
+ outer: for (;;) {
+ for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
+ Object v; int c;
if (n == null)
- return null;
+ break outer;
Node<K,V> 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<? super K> 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<K,V> 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<? super K> cmp = comparator;
+ outer: for (;;) {
+ for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
+ Object v; int c;
+ if (n == null)
+ break outer;
+ Node<K,V> 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 @@
/**
* 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<? super K> key = comparable(kkey);
- for (;;) {
- Node<K,V> b = findPredecessor(key);
- Node<K,V> n = b.next;
- for (;;) {
+ private V doPut(K key, V value, boolean onlyIfAbsent) {
+ Node<K,V> z; // added node
+ if (key == null)
+ throw new NullPointerException();
+ Comparator<? super K> cmp = comparator;
+ outer: for (;;) {
+ for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
if (n != null) {
+ Object v; int c;
Node<K,V> 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<K,V> z = new Node<K,V>(kkey, value, n);
+ z = new Node<K,V>(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<K,V> z, int level) {
- HeadIndex<K,V> 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<K,V> idx = null;
- for (int i = 1; i <= level; ++i)
- idx = new Index<K,V>(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<K,V>[] idxs = (Index<K,V>[])new Index<?,?>[level+1];
- Index<K,V> idx = null;
- for (int i = 1; i <= level; ++i)
- idxs[i] = idx = new Index<K,V>(z, idx, null);
-
- HeadIndex<K,V> oldh;
- int k;
- for (;;) {
- oldh = head;
- int oldLevel = oldh.level;
- if (level <= oldLevel) { // lost race to add level
- k = level;
- break;
- }
- HeadIndex<K,V> newh = oldh;
- Node<K,V> oldbase = oldh.node;
- for (int j = oldLevel+1; j <= level; ++j)
- newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
- if (casHead(oldh, newh)) {
- k = oldLevel;
- break;
+ HeadIndex<K,V> h = head;
+ if (level <= (max = h.level)) {
+ for (int i = 1; i <= level; ++i)
+ idx = new Index<K,V>(z, idx, null);
+ }
+ else { // try to grow by one level
+ level = max + 1; // hold in array and later pick the one to use
+ @SuppressWarnings("unchecked")Index<K,V>[] idxs =
+ (Index<K,V>[])new Index<?,?>[level+1];
+ for (int i = 1; i <= level; ++i)
+ idxs[i] = idx = new Index<K,V>(z, idx, null);
+ for (;;) {
+ h = head;
+ int oldLevel = h.level;
+ if (level <= oldLevel) // lost race to add level
+ break;
+ HeadIndex<K,V> newh = h;
+ Node<K,V> oldbase = h.node;
+ for (int j = oldLevel+1; j <= level; ++j)
+ newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
+ if (casHead(h, newh)) {
+ h = newh;
+ idx = idxs[level = oldLevel];
+ break;
+ }
}
}
- 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<K,V> idx, HeadIndex<K,V> h, int indexLevel) {
- // Track next level to insert in case of retries
- int insertionLevel = indexLevel;
- Comparable<? super K> 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<K,V> q = h;
- Index<K,V> r = q.right;
- Index<K,V> t = idx;
- for (;;) {
- if (r != null) {
- Node<K,V> 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;
+ // find insertion points and splice in
+ splice: for (int insertionLevel = level;;) {
+ int j = h.level;
+ for (Index<K,V> q = h, r = q.right, t = idx;;) {
+ if (q == null || t == null)
+ break splice;
+ if (r != null) {
+ Node<K,V> 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 (c > 0) {
- q = r;
- r = r.right;
- continue;
- }
- }
- if (j == insertionLevel) {
- // Don't insert index if node already deleted
- if (t.indexesDeletedNode()) {
- findNode(key); // cleans up
- return;
- }
- 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 @@
* 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<? super K> key = comparable(okey);
- for (;;) {
- Node<K,V> b = findPredecessor(key);
- Node<K,V> n = b.next;
- for (;;) {
+ final V doRemove(Object key, Object value) {
+ if (key == null)
+ throw new NullPointerException();
+ Comparator<? super K> cmp = comparator;
+ outer: for (;;) {
+ for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
+ Object v; int c;
if (n == null)
- return null;
+ break outer;
Node<K,V> 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 @@
* Specialized variant of findNode to get first valid node.
* @return first node or null if empty
*/
- Node<K,V> findFirst() {
- for (;;) {
- Node<K,V> b = head.node;
- Node<K,V> n = b.next;
- if (n == null)
+ final Node<K,V> findFirst() {
+ for (Node<K,V> b, n;;) {
+ if ((n = (b = head.node).next) == null)
return null;
if (n.value != null)
return n;
@@ -1122,11 +1016,9 @@
* Removes first entry; returns its snapshot.
* @return null if empty, else snapshot of first entry
*/
- Map.Entry<K,V> doRemoveFirstEntry() {
- for (;;) {
- Node<K,V> b = head.node;
- Node<K,V> n = b.next;
- if (n == null)
+ private Map.Entry<K,V> doRemoveFirstEntry() {
+ for (Node<K,V> b, n;;) {
+ if ((n = (b = head.node).next) == null)
return null;
Node<K,V> f = n.next;
if (n != b.next)
@@ -1141,7 +1033,8 @@
if (!n.appendMarker(f) || !b.casNext(n, f))
findFirst(); // retry
clearIndexToFirst();
- return new AbstractMap.SimpleImmutableEntry<K,V>(n.key, (V)v);
+ @SuppressWarnings("unchecked") V vv = (V)v;
+ return new AbstractMap.SimpleImmutableEntry<K,V>(n.key, vv);
}
}
@@ -1150,8 +1043,7 @@
*/
private void clearIndexToFirst() {
for (;;) {
- Index<K,V> q = head;
- for (;;) {
+ for (Index<K,V> q = head;;) {
Index<K,V> r = q.right;
if (r != null && r.indexesDeletedNode() && !q.unlink(r))
break;
@@ -1164,6 +1056,52 @@
}
}
+ /**
+ * Removes last entry; returns its snapshot.
+ * Specialized variant of doRemove.
+ * @return null if empty, else snapshot of last entry
+ */
+ private Map.Entry<K,V> doRemoveLastEntry() {
+ for (;;) {
+ Node<K,V> b = findPredecessorOfLast();
+ Node<K,V> n = b.next;
+ if (n == null) {
+ if (b.isBaseHeader()) // empty
+ return null;
+ else
+ continue; // all b's successors are deleted; retry
+ }
+ for (;;) {
+ Node<K,V> 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<K,V>(key, vv);
+ }
+ }
+ }
/* ---------------- Finding and removing last element -------------- */
@@ -1171,7 +1109,7 @@
* Specialized version of find to get last valid node.
* @return last node or null if empty
*/
- Node<K,V> findLast() {
+ final Node<K,V> findLast() {
/*
* findPredecessor can't be used to traverse index level
* because this doesn't use comparisons. So traversals of
@@ -1190,9 +1128,7 @@
} else if ((d = q.down) != null) {
q = d;
} else {
- Node<K,V> b = q.node;
- Node<K,V> n = b.next;
- for (;;) {
+ for (Node<K,V> b = q.node, n = b.next;;) {
if (n == null)
return b.isBaseHeader() ? null : b;
Node<K,V> f = n.next; // inconsistent read
@@ -1203,7 +1139,7 @@
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 @@
*/
private Node<K,V> findPredecessorOfLast() {
for (;;) {
- Index<K,V> q = head;
- for (;;) {
+ for (Index<K,V> q = head;;) {
Index<K,V> d, r;
if ((r = q.right) != null) {
if (r.indexesDeletedNode()) {
@@ -1244,53 +1179,6 @@
}
}
- /**
- * Removes last entry; returns its snapshot.
- * Specialized variant of doRemove.
- * @return null if empty, else snapshot of last entry
- */
- Map.Entry<K,V> doRemoveLastEntry() {
- for (;;) {
- Node<K,V> b = findPredecessorOfLast();
- Node<K,V> n = b.next;
- if (n == null) {
- if (b.isBaseHeader()) // empty
- return null;
- else
- continue; // all b's successors are deleted; retry
- }
- for (;;) {
- Node<K,V> 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<? super K> 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<K,V>(key, (V)v);
- }
- }
- }
-
/* ---------------- Relational operations -------------- */
// Control values OR'ed as arguments to findNear
@@ -1301,29 +1189,28 @@
/**
* 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<K,V> findNear(K kkey, int rel) {
- Comparable<? super K> key = comparable(kkey);
+ final Node<K,V> findNear(K key, int rel, Comparator<? super K> cmp) {
+ if (key == null)
+ throw new NullPointerException();
for (;;) {
- Node<K,V> b = findPredecessor(key);
- Node<K,V> n = b.next;
- for (;;) {
+ for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
+ Object v;
if (n == null)
return ((rel & LT) == 0 || b.isBaseHeader()) ? null : b;
Node<K,V> 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 @@
* @param rel the relation -- OR'ed combination of EQ, LT, GT
* @return Entry fitting relation, or null if no such
*/
- AbstractMap.SimpleImmutableEntry<K,V> getNear(K key, int rel) {
+ final AbstractMap.SimpleImmutableEntry<K,V> getNear(K key, int rel) {
+ Comparator<? super K> cmp = comparator;
for (;;) {
- Node<K,V> n = findNear(key, rel);
+ Node<K,V> n = findNear(key, rel, cmp);
if (n == null)
return null;
AbstractMap.SimpleImmutableEntry<K,V> e = n.createSnapshot();
@@ -1352,7 +1240,6 @@
}
}
-
/* ---------------- Constructors -------------- */
/**
@@ -1442,7 +1329,7 @@
// Track the current rightmost node at each level. Uses an
// ArrayList to avoid committing to initial or maximum level.
- ArrayList<Index<K,V>> preds = new ArrayList<Index<K,V>>();
+ ArrayList<Index<K,V>> preds = new ArrayList<>();
// initialize
for (int i = 0; i <= h.level; ++i)
@@ -1457,8 +1344,14 @@
map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<? extends K, ? extends V> 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 @@
/**
* 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 @@
/**
* 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 @@
* 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<K,V> h = head;
Node<K,V> basepred = h.node;
- ArrayList<Index<K,V>> preds = new ArrayList<Index<K,V>>();
+ ArrayList<Index<K,V>> preds = new ArrayList<>();
for (int i = 0; i <= h.level; ++i)
preds.add(null);
Index<K,V> q = h;
@@ -1549,8 +1449,14 @@
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<K,V> z = new Node<K,V>(key, val, null);
basepred.next = z;
basepred = z;
@@ -1607,6 +1513,22 @@
}
/**
+ * 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
* value is replaced.
@@ -1702,6 +1624,140 @@
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 <em>NOT</em> 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<? super K, ? extends V> 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 <em>NOT</em> 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<? super K, ? super V, ? extends V> remappingFunction) {
+ if (key == null || remappingFunction == null)
+ throw new NullPointerException();
+ Node<K,V> 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 <em>NOT</em> 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<? super K, ? super V, ? extends V> remappingFunction) {
+ if (key == null || remappingFunction == null)
+ throw new NullPointerException();
+ for (;;) {
+ Node<K,V> 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 <em>NOT</em>
+ * 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<? super V, ? super V, ? extends V> remappingFunction) {
+ if (key == null || value == null || remappingFunction == null)
+ throw new NullPointerException();
+ for (;;) {
+ Node<K,V> 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 @@
/**
* 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
+ *
+ * <p>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.
+ *
+ * <p>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 @@
* operations. It does not support the {@code add} or {@code addAll}
* operations.
*
- * <p>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.
+ * <p>The view's iterators and spliterators are
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* <p>This method is equivalent to method {@code navigableKeySet}.
*
* @return a navigable set view of the keys in this map
*/
public NavigableSet<K> keySet() {
- KeySet<K> ks = keySet;
- return (ks != null) ? ks : (keySet = new KeySet<K>(this));
+ KeySet<K,V> ks = keySet;
+ return (ks != null) ? ks : (keySet = new KeySet<>(this));
}
public NavigableSet<K> navigableKeySet() {
- KeySet<K> ks = keySet;
- return (ks != null) ? ks : (keySet = new KeySet<K>(this));
+ KeySet<K,V> 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
+ * <p>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.
+ *
+ * <p>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 @@
* {@code retainAll} and {@code clear} operations. It does not
* support the {@code add} or {@code addAll} operations.
*
- * <p>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.
+ * <p>The view's iterators and spliterators are
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*/
public Collection<V> values() {
- Values<V> vs = values;
- return (vs != null) ? vs : (values = new Values<V>(this));
+ Values<K,V> 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
+ *
+ * <p>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.
+ *
+ * <p>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 @@
* operations. It does not support the {@code add} or
* {@code addAll} operations.
*
- * <p>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.
+ * <p>The view's iterators and spliterators are
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
- * <p>The {@code Map.Entry} elements returned by
- * {@code iterator.next()} do <em>not</em> support the
- * {@code setValue} operation.
+ * <p>The {@code Map.Entry} elements traversed by the {@code iterator}
+ * or {@code spliterator} do <em>not</em> 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 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 @@
* @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<? super K> k = comparable(key);
for (;;) {
- Node<K,V> n = findNode(k);
- if (n == null)
+ Node<K,V> 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 @@
* @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<? super K> k = comparable(key);
for (;;) {
- Node<K,V> n = findNode(k);
- if (n == null)
+ Node<K,V> 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 @@
* @throws NullPointerException if the specified key is null
*/
public K lowerKey(K key) {
- Node<K,V> n = findNear(key, LT);
+ Node<K,V> n = findNear(key, LT, comparator);
return (n == null) ? null : n.key;
}
@@ -2062,7 +2125,7 @@
* @throws NullPointerException if the specified key is null
*/
public K floorKey(K key) {
- Node<K,V> n = findNear(key, LT|EQ);
+ Node<K,V> n = findNear(key, LT|EQ, comparator);
return (n == null) ? null : n.key;
}
@@ -2084,7 +2147,7 @@
* @throws NullPointerException if the specified key is null
*/
public K ceilingKey(K key) {
- Node<K,V> n = findNear(key, GT|EQ);
+ Node<K,V> n = findNear(key, GT|EQ, comparator);
return (n == null) ? null : n.key;
}
@@ -2108,7 +2171,7 @@
* @throws NullPointerException if the specified key is null
*/
public K higherKey(K key) {
- Node<K,V> n = findNear(key, GT);
+ Node<K,V> n = findNear(key, GT, comparator);
return (n == null) ? null : n.key;
}
@@ -2182,13 +2245,11 @@
/** 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 @@
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 @@
}
}
- // Factory methods for iterators needed by ConcurrentSkipListSet etc
-
- Iterator<K> keyIterator() {
- return new KeyIterator();
- }
-
- Iterator<V> valueIterator() {
- return new ValueIterator();
- }
-
- Iterator<Map.Entry<K,V>> entryIterator() {
- return new EntryIterator();
- }
-
/* ---------------- View Classes -------------- */
/*
@@ -2282,35 +2327,34 @@
return list;
}
- static final class KeySet<E>
- extends AbstractSet<E> implements NavigableSet<E> {
- private final ConcurrentNavigableMap<E,?> m;
- KeySet(ConcurrentNavigableMap<E,?> map) { m = map; }
+ static final class KeySet<K,V>
+ extends AbstractSet<K> implements NavigableSet<K> {
+ final ConcurrentNavigableMap<K,V> m;
+ KeySet(ConcurrentNavigableMap<K,V> 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<? super E> comparator() { return m.comparator(); }
- public E first() { return m.firstKey(); }
- public E last() { return m.lastKey(); }
- public E pollFirst() {
- Map.Entry<E,?> 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<? super K> comparator() { return m.comparator(); }
+ public K first() { return m.firstKey(); }
+ public K last() { return m.lastKey(); }
+ public K pollFirst() {
+ Map.Entry<K,V> e = m.pollFirstEntry();
return (e == null) ? null : e.getKey();
}
- public E pollLast() {
- Map.Entry<E,?> e = m.pollLastEntry();
+ public K pollLast() {
+ Map.Entry<K,V> e = m.pollLastEntry();
return (e == null) ? null : e.getKey();
}
- public Iterator<E> iterator() {
- if (m instanceof ConcurrentSkipListMap)
- return ((ConcurrentSkipListMap<E,Object>)m).keyIterator();
- else
- return ((ConcurrentSkipListMap.SubMap<E,Object>)m).keyIterator();
+ public Iterator<K> iterator() {
+ return (m instanceof ConcurrentSkipListMap)
+ ? ((ConcurrentSkipListMap<K,V>)m).new KeyIterator()
+ : ((SubMap<K,V>)m).new SubMapKeyIterator();
}
public boolean equals(Object o) {
if (o == this)
@@ -2328,81 +2372,99 @@
}
public Object[] toArray() { return toList(this).toArray(); }
public <T> T[] toArray(T[] a) { return toList(this).toArray(a); }
- public Iterator<E> descendingIterator() {
+ public Iterator<K> descendingIterator() {
return descendingSet().iterator();
}
- public NavigableSet<E> subSet(E fromElement,
+ public NavigableSet<K> subSet(K fromElement,
boolean fromInclusive,
- E toElement,
+ K toElement,
boolean toInclusive) {
- return new KeySet<E>(m.subMap(fromElement, fromInclusive,
- toElement, toInclusive));
+ return new KeySet<>(m.subMap(fromElement, fromInclusive,
+ toElement, toInclusive));
}
- public NavigableSet<E> headSet(E toElement, boolean inclusive) {
- return new KeySet<E>(m.headMap(toElement, inclusive));
+ public NavigableSet<K> headSet(K toElement, boolean inclusive) {
+ return new KeySet<>(m.headMap(toElement, inclusive));
}
- public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
- return new KeySet<E>(m.tailMap(fromElement, inclusive));
+ public NavigableSet<K> tailSet(K fromElement, boolean inclusive) {
+ return new KeySet<>(m.tailMap(fromElement, inclusive));
}
- public NavigableSet<E> subSet(E fromElement, E toElement) {
+ public NavigableSet<K> subSet(K fromElement, K toElement) {
return subSet(fromElement, true, toElement, false);
}
- public NavigableSet<E> headSet(E toElement) {
+ public NavigableSet<K> headSet(K toElement) {
return headSet(toElement, false);
}
- public NavigableSet<E> tailSet(E fromElement) {
+ public NavigableSet<K> tailSet(K fromElement) {
return tailSet(fromElement, true);
}
- public NavigableSet<E> descendingSet() {
- return new KeySet<E>(m.descendingMap());
+ public NavigableSet<K> descendingSet() {
+ return new KeySet<>(m.descendingMap());
+ }
+
+ public Spliterator<K> spliterator() {
+ return (m instanceof ConcurrentSkipListMap)
+ ? ((ConcurrentSkipListMap<K,V>)m).keySpliterator()
+ : ((SubMap<K,V>)m).new SubMapKeyIterator();
}
}
- static final class Values<E> extends AbstractCollection<E> {
- private final ConcurrentNavigableMap<?,E> m;
- Values(ConcurrentNavigableMap<?,E> map) {
+ static final class Values<K,V> extends AbstractCollection<V> {
+ final ConcurrentNavigableMap<K,V> m;
+ Values(ConcurrentNavigableMap<K,V> map) {
m = map;
}
- public Iterator<E> iterator() {
- if (m instanceof ConcurrentSkipListMap)
- return ((ConcurrentSkipListMap<?,E>)m).valueIterator();
- else
- return ((SubMap<?,E>)m).valueIterator();
+ public Iterator<V> iterator() {
+ return (m instanceof ConcurrentSkipListMap)
+ ? ((ConcurrentSkipListMap<K,V>)m).new ValueIterator()
+ : ((SubMap<K,V>)m).new SubMapValueIterator();
}
- 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 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> T[] toArray(T[] a) { return toList(this).toArray(a); }
- }
- static final class EntrySet<K1,V1> extends AbstractSet<Map.Entry<K1,V1>> {
- private final ConcurrentNavigableMap<K1, V1> m;
- EntrySet(ConcurrentNavigableMap<K1, V1> map) {
- m = map;
+ public Spliterator<V> spliterator() {
+ return (m instanceof ConcurrentSkipListMap)
+ ? ((ConcurrentSkipListMap<K,V>)m).valueSpliterator()
+ : ((SubMap<K,V>)m).new SubMapValueIterator();
}
- public Iterator<Map.Entry<K1,V1>> iterator() {
+ public boolean removeIf(Predicate<? super V> filter) {
+ if (filter == null) throw new NullPointerException();
if (m instanceof ConcurrentSkipListMap)
- return ((ConcurrentSkipListMap<K1,V1>)m).entryIterator();
- else
- return ((SubMap<K1,V1>)m).entryIterator();
+ return ((ConcurrentSkipListMap<K,V>)m).removeValueIf(filter);
+ // else use iterator
+ Iterator<Map.Entry<K,V>> it =
+ ((SubMap<K,V>)m).new SubMapEntryIterator();
+ boolean removed = false;
+ while (it.hasNext()) {
+ Map.Entry<K,V> e = it.next();
+ V v = e.getValue();
+ if (filter.test(v) && m.remove(e.getKey(), v))
+ removed = true;
+ }
+ return removed;
+ }
+ }
+
+ static final class EntrySet<K,V> extends AbstractSet<Map.Entry<K,V>> {
+ final ConcurrentNavigableMap<K,V> m;
+ EntrySet(ConcurrentNavigableMap<K,V> map) {
+ m = map;
+ }
+ public Iterator<Map.Entry<K,V>> iterator() {
+ return (m instanceof ConcurrentSkipListMap)
+ ? ((ConcurrentSkipListMap<K,V>)m).new EntryIterator()
+ : ((SubMap<K,V>)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 Object[] toArray() { return toList(this).toArray(); }
public <T> T[] toArray(T[] a) { return toList(this).toArray(a); }
+
+ public Spliterator<Map.Entry<K,V>> spliterator() {
+ return (m instanceof ConcurrentSkipListMap)
+ ? ((ConcurrentSkipListMap<K,V>)m).entrySpliterator()
+ : ((SubMap<K,V>)m).new SubMapEntryIterator();
+ }
+ public boolean removeIf(Predicate<? super Entry<K,V>> filter) {
+ if (filter == null) throw new NullPointerException();
+ if (m instanceof ConcurrentSkipListMap)
+ return ((ConcurrentSkipListMap<K,V>)m).removeEntryIf(filter);
+ // else use iterator
+ Iterator<Map.Entry<K,V>> it =
+ ((SubMap<K,V>)m).new SubMapEntryIterator();
+ boolean removed = false;
+ while (it.hasNext()) {
+ Map.Entry<K,V> 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<K,V> extends AbstractMap<K,V>
- implements ConcurrentNavigableMap<K,V>, Cloneable,
- java.io.Serializable {
+ implements ConcurrentNavigableMap<K,V>, Cloneable, Serializable {
private static final long serialVersionUID = -7647078645895051609L;
/** Underlying map */
- private final ConcurrentSkipListMap<K,V> m;
+ final ConcurrentSkipListMap<K,V> 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 @@
/** inclusion flag for hi */
private final boolean hiInclusive;
/** direction */
- private final boolean isDescending;
+ final boolean isDescending;
// Lazily initialized view holders
- private transient KeySet<K> keySetView;
+ private transient KeySet<K,V> keySetView;
private transient Set<Map.Entry<K,V>> entrySetView;
private transient Collection<V> valuesView;
@@ -2481,8 +2563,9 @@
K fromKey, boolean fromInclusive,
K toKey, boolean toInclusive,
boolean isDescending) {
+ Comparator<? super K> 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 @@
/* ---------------- 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<? super K> 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<? super K> 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<? super K> cmp) {
+ return !tooLow(key, cmp) && !tooHigh(key, cmp);
}
- private void checkKeyBounds(K key) throws IllegalArgumentException {
+ void checkKeyBounds(K key, Comparator<? super K> 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<K,V> n) {
+ boolean isBeforeEnd(ConcurrentSkipListMap.Node<K,V> n,
+ Comparator<? super K> cmp) {
if (n == null)
return false;
if (hi == null)
@@ -2534,7 +2612,7 @@
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 @@
* Returns lowest node. This node might not be in range, so
* most usages need to check bounds.
*/
- private ConcurrentSkipListMap.Node<K,V> loNode() {
+ ConcurrentSkipListMap.Node<K,V> loNode(Comparator<? super K> 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<K,V> hiNode() {
+ ConcurrentSkipListMap.Node<K,V> hiNode(Comparator<? super K> 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<K,V> n = loNode();
- if (isBeforeEnd(n))
+ K lowestKey() {
+ Comparator<? super K> cmp = m.comparator;
+ ConcurrentSkipListMap.Node<K,V> n = loNode(cmp);
+ if (isBeforeEnd(n, cmp))
return n.key;
else
throw new NoSuchElementException();
@@ -2580,20 +2659,22 @@
/**
* Returns highest absolute key (ignoring directionality).
*/
- private K highestKey() {
- ConcurrentSkipListMap.Node<K,V> n = hiNode();
+ K highestKey() {
+ Comparator<? super K> cmp = m.comparator;
+ ConcurrentSkipListMap.Node<K,V> 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<K,V> lowestEntry() {
+ Map.Entry<K,V> lowestEntry() {
+ Comparator<? super K> cmp = m.comparator;
for (;;) {
- ConcurrentSkipListMap.Node<K,V> n = loNode();
- if (!isBeforeEnd(n))
+ ConcurrentSkipListMap.Node<K,V> n = loNode(cmp);
+ if (!isBeforeEnd(n, cmp))
return null;
Map.Entry<K,V> e = n.createSnapshot();
if (e != null)
@@ -2601,10 +2682,11 @@
}
}
- private Map.Entry<K,V> highestEntry() {
+ Map.Entry<K,V> highestEntry() {
+ Comparator<? super K> cmp = m.comparator;
for (;;) {
- ConcurrentSkipListMap.Node<K,V> n = hiNode();
- if (n == null || !inBounds(n.key))
+ ConcurrentSkipListMap.Node<K,V> n = hiNode(cmp);
+ if (n == null || !inBounds(n.key, cmp))
return null;
Map.Entry<K,V> e = n.createSnapshot();
if (e != null)
@@ -2612,13 +2694,14 @@
}
}
- private Map.Entry<K,V> removeLowest() {
+ Map.Entry<K,V> removeLowest() {
+ Comparator<? super K> cmp = m.comparator;
for (;;) {
- Node<K,V> n = loNode();
+ Node<K,V> 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<K,V> removeHighest() {
+ Map.Entry<K,V> removeHighest() {
+ Comparator<? super K> cmp = m.comparator;
for (;;) {
- Node<K,V> n = hiNode();
+ Node<K,V> 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 @@
}
/**
- * Submap version of ConcurrentSkipListMap.getNearEntry
+ * Submap version of ConcurrentSkipListMap.getNearEntry.
*/
- private Map.Entry<K,V> getNearEntry(K key, int rel) {
+ Map.Entry<K,V> getNearEntry(K key, int rel) {
+ Comparator<? super K> 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<K,V> n = m.findNear(key, rel);
- if (n == null || !inBounds(n.key))
+ Node<K,V> 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 @@
}
// Almost the same as getNearEntry, except for keys
- private K getNearKey(K key, int rel) {
+ K getNearKey(K key, int rel) {
+ Comparator<? super K> 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<K,V> n = loNode();
- if (isBeforeEnd(n))
+ ConcurrentSkipListMap.Node<K,V> 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<K,V> n = hiNode();
+ ConcurrentSkipListMap.Node<K,V> n = hiNode(cmp);
if (n != null) {
K last = n.key;
- if (inBounds(last))
+ if (inBounds(last, cmp))
return last;
}
}
return null;
}
for (;;) {
- Node<K,V> n = m.findNear(key, rel);
- if (n == null || !inBounds(n.key))
+ Node<K,V> 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 @@
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<? super K> cmp = m.comparator;
long count = 0;
- for (ConcurrentSkipListMap.Node<K,V> n = loNode();
- isBeforeEnd(n);
+ for (ConcurrentSkipListMap.Node<K,V> n = loNode(cmp);
+ isBeforeEnd(n, cmp);
n = n.next) {
if (n.getValidValue() != null)
++count;
@@ -2739,14 +2823,16 @@
}
public boolean isEmpty() {
- return !isBeforeEnd(loNode());
+ Comparator<? super K> cmp = m.comparator;
+ return !isBeforeEnd(loNode(cmp), cmp);
}
public boolean containsValue(Object value) {
if (value == null)
throw new NullPointerException();
- for (ConcurrentSkipListMap.Node<K,V> n = loNode();
- isBeforeEnd(n);
+ Comparator<? super K> cmp = m.comparator;
+ for (ConcurrentSkipListMap.Node<K,V> n = loNode(cmp);
+ isBeforeEnd(n, cmp);
n = n.next) {
V v = n.getValidValue();
if (v != null && value.equals(v))
@@ -2756,8 +2842,9 @@
}
public void clear() {
- for (ConcurrentSkipListMap.Node<K,V> n = loNode();
- isBeforeEnd(n);
+ Comparator<? super K> cmp = m.comparator;
+ for (ConcurrentSkipListMap.Node<K,V> n = loNode(cmp);
+ isBeforeEnd(n, cmp);
n = n.next) {
if (n.getValidValue() != null)
m.remove(n.key);
@@ -2767,22 +2854,21 @@
/* ---------------- 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 @@
* Utility to create submaps, where given bounds override
* unbounded(null) ones and/or are checked against bounded ones.
*/
- private SubMap<K,V> newSubMap(K fromKey,
- boolean fromInclusive,
- K toKey,
- boolean toInclusive) {
+ SubMap<K,V> newSubMap(K fromKey, boolean fromInclusive,
+ K toKey, boolean toInclusive) {
+ Comparator<? super K> cmp = m.comparator;
if (isDescending) { // flip senses
K tk = fromKey;
fromKey = toKey;
@@ -2818,7 +2903,7 @@
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 @@
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 @@
toKey, toInclusive, isDescending);
}
- public SubMap<K,V> subMap(K fromKey,
- boolean fromInclusive,
- K toKey,
- boolean toInclusive) {
+ public SubMap<K,V> 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<K,V> headMap(K toKey,
- boolean inclusive) {
+ public SubMap<K,V> headMap(K toKey, boolean inclusive) {
if (toKey == null)
throw new NullPointerException();
return newSubMap(null, false, toKey, inclusive);
}
- public SubMap<K,V> tailMap(K fromKey,
- boolean inclusive) {
+ public SubMap<K,V> tailMap(K fromKey, boolean inclusive) {
if (fromKey == null)
throw new NullPointerException();
return newSubMap(fromKey, inclusive, null, false);
@@ -2939,18 +3020,18 @@
/* ---------------- Submap Views -------------- */
public NavigableSet<K> keySet() {
- KeySet<K> ks = keySetView;
- return (ks != null) ? ks : (keySetView = new KeySet<K>(this));
+ KeySet<K,V> ks = keySetView;
+ return (ks != null) ? ks : (keySetView = new KeySet<>(this));
}
public NavigableSet<K> navigableKeySet() {
- KeySet<K> ks = keySetView;
- return (ks != null) ? ks : (keySetView = new KeySet<K>(this));
+ KeySet<K,V> ks = keySetView;
+ return (ks != null) ? ks : (keySetView = new KeySet<>(this));
}
public Collection<V> values() {
Collection<V> vs = valuesView;
- return (vs != null) ? vs : (valuesView = new Values<V>(this));
+ return (vs != null) ? vs : (valuesView = new Values<>(this));
}
public Set<Map.Entry<K,V>> entrySet() {
@@ -2962,22 +3043,11 @@
return descendingMap().navigableKeySet();
}
- Iterator<K> keyIterator() {
- return new SubMapKeyIterator();
- }
-
- Iterator<V> valueIterator() {
- return new SubMapValueIterator();
- }
-
- Iterator<Map.Entry<K,V>> entryIterator() {
- return new SubMapEntryIterator();
- }
-
/**
* Variant of main Iter class to traverse through submaps.
+ * Also serves as back-up Spliterator for views.
*/
- abstract class SubMapIter<T> implements Iterator<T> {
+ abstract class SubMapIter<T> implements Iterator<T>, Spliterator<T> {
/** the last node returned by next() */
Node<K,V> lastReturned;
/** the next node to return from next(); */
@@ -2986,16 +3056,19 @@
V nextValue;
SubMapIter() {
+ Comparator<? super K> 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 @@
}
private void ascend() {
+ Comparator<? super K> 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<? super K> 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 @@
lastReturned = null;
}
+ public Spliterator<T> trySplit() {
+ return null;
+ }
+
+ public boolean tryAdvance(Consumer<? super T> action) {
+ if (hasNext()) {
+ action.accept(next());
+ return true;
+ }
+ return false;
+ }
+
+ public void forEachRemaining(Consumer<? super T> action) {
+ while (hasNext())
+ action.accept(next());
+ }
+
+ public long estimateSize() {
+ return Long.MAX_VALUE;
+ }
+
}
final class SubMapValueIterator extends SubMapIter<V> {
@@ -3063,6 +3163,9 @@
advance();
return v;
}
+ public int characteristics() {
+ return 0;
+ }
}
final class SubMapKeyIterator extends SubMapIter<K> {
@@ -3071,6 +3174,13 @@
advance();
return n.key;
}
+ public int characteristics() {
+ return Spliterator.DISTINCT | Spliterator.ORDERED |
+ Spliterator.SORTED;
+ }
+ public final Comparator<? super K> getComparator() {
+ return SubMap.this.comparator();
+ }
}
final class SubMapEntryIterator extends SubMapIter<Map.Entry<K,V>> {
@@ -3080,19 +3190,390 @@
advance();
return new AbstractMap.SimpleImmutableEntry<K,V>(n.key, v);
}
+ public int characteristics() {
+ return Spliterator.DISTINCT;
+ }
+ }
+ }
+
+ // default Map method overrides
+
+ public void forEach(BiConsumer<? super K, ? super V> action) {
+ if (action == null) throw new NullPointerException();
+ V v;
+ for (Node<K,V> n = findFirst(); n != null; n = n.next) {
+ if ((v = n.getValidValue()) != null)
+ action.accept(n.key, v);
+ }
+ }
+
+ public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
+ if (function == null) throw new NullPointerException();
+ V v;
+ for (Node<K,V> 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<? super Entry<K,V>> function) {
+ if (function == null) throw new NullPointerException();
+ boolean removed = false;
+ for (Node<K,V> n = findFirst(); n != null; n = n.next) {
+ V v;
+ if ((v = n.getValidValue()) != null) {
+ K k = n.key;
+ Map.Entry<K,V> 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<? super V> function) {
+ if (function == null) throw new NullPointerException();
+ boolean removed = false;
+ for (Node<K,V> 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<K,V> {
+ final Comparator<? super K> comparator;
+ final K fence; // exclusive upper bound for keys, or null if to end
+ Index<K,V> row; // the level to split out
+ Node<K,V> current; // current traversal node; initialize at origin
+ int est; // pseudo-size estimate
+ CSLMSpliterator(Comparator<? super K> comparator, Index<K,V> row,
+ Node<K,V> 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<K,V> extends CSLMSpliterator<K,V>
+ implements Spliterator<K> {
+ KeySpliterator(Comparator<? super K> comparator, Index<K,V> row,
+ Node<K,V> origin, K fence, int est) {
+ super(comparator, row, origin, fence, est);
+ }
+
+ public KeySpliterator<K,V> trySplit() {
+ Node<K,V> e; K ek;
+ Comparator<? super K> cmp = comparator;
+ K f = fence;
+ if ((e = current) != null && (ek = e.key) != null) {
+ for (Index<K,V> q = row; q != null; q = row = q.down) {
+ Index<K,V> s; Node<K,V> 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<K,V> r = q.down;
+ row = (s.right != null) ? s : s.down;
+ est -= est >>> 2;
+ return new KeySpliterator<K,V>(cmp, r, e, sk, est);
+ }
+ }
+ }
+ return null;
+ }
+
+ public void forEachRemaining(Consumer<? super K> action) {
+ if (action == null) throw new NullPointerException();
+ Comparator<? super K> cmp = comparator;
+ K f = fence;
+ Node<K,V> 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<? super K> action) {
+ if (action == null) throw new NullPointerException();
+ Comparator<? super K> cmp = comparator;
+ K f = fence;
+ Node<K,V> 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<? super K> getComparator() {
+ return comparator;
+ }
+ }
+ // factory method for KeySpliterator
+ final KeySpliterator<K,V> keySpliterator() {
+ Comparator<? super K> cmp = comparator;
+ for (;;) { // ensure h corresponds to origin p
+ HeadIndex<K,V> h; Node<K,V> p;
+ Node<K,V> b = (h = head).node;
+ if ((p = b.next) == null || p.value != null)
+ return new KeySpliterator<K,V>(cmp, h, p, null, (p == null) ?
+ 0 : Integer.MAX_VALUE);
+ p.helpDelete(b, p.next);
+ }
+ }
+
+ static final class ValueSpliterator<K,V> extends CSLMSpliterator<K,V>
+ implements Spliterator<V> {
+ ValueSpliterator(Comparator<? super K> comparator, Index<K,V> row,
+ Node<K,V> origin, K fence, int est) {
+ super(comparator, row, origin, fence, est);
+ }
+
+ public ValueSpliterator<K,V> trySplit() {
+ Node<K,V> e; K ek;
+ Comparator<? super K> cmp = comparator;
+ K f = fence;
+ if ((e = current) != null && (ek = e.key) != null) {
+ for (Index<K,V> q = row; q != null; q = row = q.down) {
+ Index<K,V> s; Node<K,V> 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<K,V> r = q.down;
+ row = (s.right != null) ? s : s.down;
+ est -= est >>> 2;
+ return new ValueSpliterator<K,V>(cmp, r, e, sk, est);
+ }
+ }
+ }
+ return null;
+ }
+
+ public void forEachRemaining(Consumer<? super V> action) {
+ if (action == null) throw new NullPointerException();
+ Comparator<? super K> cmp = comparator;
+ K f = fence;
+ Node<K,V> 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<? super V> action) {
+ if (action == null) throw new NullPointerException();
+ Comparator<? super K> cmp = comparator;
+ K f = fence;
+ Node<K,V> 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<K,V> valueSpliterator() {
+ Comparator<? super K> cmp = comparator;
+ for (;;) {
+ HeadIndex<K,V> h; Node<K,V> p;
+ Node<K,V> b = (h = head).node;
+ if ((p = b.next) == null || p.value != null)
+ return new ValueSpliterator<K,V>(cmp, h, p, null, (p == null) ?
+ 0 : Integer.MAX_VALUE);
+ p.helpDelete(b, p.next);
+ }
+ }
+
+ static final class EntrySpliterator<K,V> extends CSLMSpliterator<K,V>
+ implements Spliterator<Map.Entry<K,V>> {
+ EntrySpliterator(Comparator<? super K> comparator, Index<K,V> row,
+ Node<K,V> origin, K fence, int est) {
+ super(comparator, row, origin, fence, est);
+ }
+
+ public EntrySpliterator<K,V> trySplit() {
+ Node<K,V> e; K ek;
+ Comparator<? super K> cmp = comparator;
+ K f = fence;
+ if ((e = current) != null && (ek = e.key) != null) {
+ for (Index<K,V> q = row; q != null; q = row = q.down) {
+ Index<K,V> s; Node<K,V> 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<K,V> r = q.down;
+ row = (s.right != null) ? s : s.down;
+ est -= est >>> 2;
+ return new EntrySpliterator<K,V>(cmp, r, e, sk, est);
+ }
+ }
+ }
+ return null;
+ }
+
+ public void forEachRemaining(Consumer<? super Map.Entry<K,V>> action) {
+ if (action == null) throw new NullPointerException();
+ Comparator<? super K> cmp = comparator;
+ K f = fence;
+ Node<K,V> 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,V>(k, vv));
+ }
+ }
+ }
+
+ public boolean tryAdvance(Consumer<? super Map.Entry<K,V>> action) {
+ if (action == null) throw new NullPointerException();
+ Comparator<? super K> cmp = comparator;
+ K f = fence;
+ Node<K,V> 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,V>(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<Map.Entry<K,V>> getComparator() {
+ // Adapt or create a key-based comparator
+ if (comparator != null) {
+ return Map.Entry.comparingByKey(comparator);
+ }
+ else {
+ return (Comparator<Map.Entry<K,V>> & Serializable) (e1, e2) -> {
+ @SuppressWarnings("unchecked")
+ Comparable<? super K> k1 = (Comparable<? super K>) e1.getKey();
+ return k1.compareTo(e2.getKey());
+ };
+ }
+ }
+ }
+
+ // Almost the same as keySpliterator()
+ final EntrySpliterator<K,V> entrySpliterator() {
+ Comparator<? super K> cmp = comparator;
+ for (;;) { // almost same as key version
+ HeadIndex<K,V> h; Node<K,V> p;
+ Node<K,V> b = (h = head).node;
+ if ((p = b.next) == null || p.value != null)
+ return new EntrySpliterator<K,V>(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 13f1a43..1719822 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 <i>weakly consistent</i>, returning elements
- * reflecting the state of the set at some point at or since the
- * creation of the iterator. They do <em>not</em> throw {@link
- * ConcurrentModificationException}, and may proceed concurrently with
- * other operations. Ascending ordered views and their iterators are
- * faster than descending ones.
+ *
+ * <p>Iterators and spliterators are
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
+ *
+ * <p>Ascending ordered views and their iterators are faster than
+ * descending ones.
*
* <p>Beware that, unlike in most collections, the {@code size}
* method is <em>not</em> a constant-time operation. Because of the
@@ -285,8 +296,9 @@
*
* @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
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified collection or any
* of its elements are null
*/
@@ -346,20 +358,19 @@
/* ---------------- SortedSet operations -------------- */
-
public Comparator<? super E> 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 @@
return new ConcurrentSkipListSet<E>(m.descendingMap());
}
- // Support for resetting map in clone
- private void setMap(ConcurrentNavigableMap<E,Object> map) {
- UNSAFE.putObjectVolatile(this, mapOffset, map);
+ /**
+ * Returns a {@link Spliterator} over the elements in this set.
+ *
+ * <p>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.
+ *
+ * <p>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<E> spliterator() {
+ return (m instanceof ConcurrentSkipListMap)
+ ? ((ConcurrentSkipListMap<E,?>)m).keySpliterator()
+ : ((ConcurrentSkipListMap.SubMap<E,?>)m).new SubMapKeyIterator();
}
- private static final sun.misc.Unsafe UNSAFE;
- private static final long mapOffset;
+ // Support for resetting map in clone
+ private void setMap(ConcurrentNavigableMap<E,Object> map) {
+ U.putObjectVolatile(this, MAP, map);
+ }
+
+ 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 798eaaf..c8e342e 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.
*
- * <p>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.
+ * <p>This is ordinarily too costly, but may be <em>more</em> 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}.
*
- * <p>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.
+ * <p>All elements are permitted, including {@code null}.
*
- * <p>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}.
+ * <p>Memory consistency effects: As with other concurrent
+ * collections, actions in a thread prior to placing an object into a
+ * {@code CopyOnWriteArrayList}
+ * <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a>
+ * actions subsequent to the access or removal of that element from
+ * the {@code CopyOnWriteArrayList} in another thread.
*
- * <p>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 <E> the type of elements held in this list
*/
-public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
-
+public class CopyOnWriteArrayList<E>
+ implements List<E>, 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.
+ */
+ final Object[] getArray() {
+ return array;
+ }
+
+ /**
+ * Sets the array.
+ */
+ final void setArray(Object[] a) {
+ array = a;
+ }
+
+ /**
+ * Creates an empty list.
*/
public CopyOnWriteArrayList() {
- elements = EmptyArray.OBJECT;
+ setArray(new Object[0]);
}
/**
- * Creates a new instance containing the elements of {@code collection}.
+ * 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
*/
- @SuppressWarnings("unchecked")
- public CopyOnWriteArrayList(Collection<? extends E> collection) {
- this((E[]) collection.toArray());
+ public CopyOnWriteArrayList(Collection<? extends E> 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);
}
/**
- * Creates a new instance containing the elements of {@code array}.
+ * 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[] array) {
- this.elements = Arrays.copyOf(array, array.length, Object[].class);
+ public CopyOnWriteArrayList(E[] toCopyIn) {
+ setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
- @Override public Object clone() {
+ /**
+ * Returns the number of elements in this list.
+ *
+ * @return the number of elements in this list
+ */
+ public int size() {
+ return getArray().length;
+ }
+
+ /**
+ * Returns {@code true} if this list contains no elements.
+ *
+ * @return {@code true} if this list contains no elements
+ */
+ public boolean isEmpty() {
+ return size() == 0;
+ }
+
+ /**
+ * 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 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 -1;
+ }
+
+ /**
+ * 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)}.
+ *
+ * @param o element whose presence in this list is to be tested
+ * @return {@code true} if this list contains the specified element
+ */
+ public boolean contains(Object o) {
+ Object[] elements = getArray();
+ return indexOf(o, elements, 0, elements.length) >= 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int indexOf(Object o) {
+ Object[] elements = getArray();
+ return indexOf(o, elements, 0, elements.length);
+ }
+
+ /**
+ * 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.
+ *
+ * @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 indexOf(E e, int index) {
+ Object[] elements = getArray();
+ return indexOf(e, elements, index, elements.length);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int lastIndexOf(Object o) {
+ Object[] elements = getArray();
+ return lastIndexOf(o, elements, elements.length - 1);
+ }
+
+ /**
+ * 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 a shallow copy of this list. (The elements themselves
+ * are not copied.)
+ *
+ * @return a clone of this list
+ */
+ public Object clone() {
try {
- CopyOnWriteArrayList result = (CopyOnWriteArrayList) super.clone();
- result.elements = result.elements.clone();
- return result;
+ @SuppressWarnings("unchecked")
+ CopyOnWriteArrayList<E> clone =
+ (CopyOnWriteArrayList<E>) super.clone();
+ clone.resetLock();
+ return clone;
} catch (CloneNotSupportedException e) {
- throw new AssertionError(e);
+ // this shouldn't happen, since we are Cloneable
+ throw new InternalError();
}
}
- public int size() {
- return elements.length;
+ /**
+ * Returns an array containing all of the elements in this list
+ * in proper sequence (from first to last element).
+ *
+ * <p>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.
+ *
+ * <p>This method acts as bridge between array-based and collection-based
+ * APIs.
+ *
+ * @return an array containing all the elements in this list
+ */
+ 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.
+ *
+ * <p>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 <i>only</i> if the caller knows that this list does not contain
+ * any null elements.)
+ *
+ * <p>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.
+ *
+ * <p>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}:
+ *
+ * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
+ *
+ * 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> 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;
+ }
+ }
+
+ // 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;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IndexOutOfBoundsException {@inheritDoc}
+ */
public E get(int index) {
- return (E) elements[index];
+ return get(getArray(), index);
}
- public boolean contains(Object o) {
- return indexOf(o) != -1;
- }
+ /**
+ * 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);
- public boolean containsAll(Collection<?> collection) {
- Object[] snapshot = elements;
- return containsAll(collection, snapshot, 0, snapshot.length);
- }
-
- static boolean containsAll(Collection<?> collection, Object[] snapshot, int from, int to) {
- for (Object o : collection) {
- if (indexOf(o, snapshot, from, to) == -1) {
- return false;
+ 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;
+ }
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * 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;
+ }
+ 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;
+ }
+ }
+
+ /**
+ * 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);
+ }
+ }
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * 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;
}
/**
- * Searches this list for {@code object} and returns the index of the first
- * occurrence that is at or after {@code from}.
+ * 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.
*
- * @return the index or -1 if the object was not found.
+ * @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
+ * (<a href="{@docRoot}/../api/java/util/Collection.html#optional-restrictions">optional</a>)
+ * @throws NullPointerException if this list contains a null element and the
+ * specified collection does not permit null elements
+ * (<a href="{@docRoot}/../api/java/util/Collection.html#optional-restrictions">optional</a>),
+ * or if the specified collection is null
+ * @see #remove(Object)
*/
- public int indexOf(E object, int from) {
- Object[] snapshot = elements;
- return indexOf(object, snapshot, from, snapshot.length);
- }
-
- public int indexOf(Object object) {
- Object[] snapshot = elements;
- return indexOf(object, snapshot, 0, snapshot.length);
+ 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;
+ }
}
/**
- * Searches this list for {@code object} and returns the index of the last
- * occurrence that is before {@code to}.
+ * 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.
*
- * @return the index or -1 if the object was not found.
+ * @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
+ * (<a href="{@docRoot}/../api/java/util/Collection.html#optional-restrictions">optional</a>)
+ * @throws NullPointerException if this list contains a null element and the
+ * specified collection does not permit null elements
+ * (<a href="{@docRoot}/../api/java/util/Collection.html#optional-restrictions">optional</a>),
+ * or if the specified collection is null
+ * @see #remove(Object)
*/
- public int lastIndexOf(E object, int to) {
- Object[] snapshot = elements;
- return lastIndexOf(object, snapshot, 0, to);
- }
-
- public int lastIndexOf(Object object) {
- Object[] snapshot = elements;
- return lastIndexOf(object, snapshot, 0, snapshot.length);
- }
-
- public boolean isEmpty() {
- return elements.length == 0;
+ 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;
+ }
}
/**
- * 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}.
+ * 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.
*
- * <p>The returned iterator does not support {@link Iterator#remove()}.
+ * @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 int addAllAbsent(Collection<? extends E> 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;
+ }
+ }
+
+ /**
+ * 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]);
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @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 boolean addAll(Collection<? extends E> 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;
+ }
+ }
+
+ /**
+ * 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<? extends E> 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 void forEach(Consumer<? super E> action) {
+ if (action == null) throw new NullPointerException();
+ for (Object x : getArray()) {
+ @SuppressWarnings("unchecked") E e = (E) x;
+ action.accept(e);
+ }
+ }
+
+ public boolean removeIf(Predicate<? super E> 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
+ }
+ }
+
+ public void replaceAll(UnaryOperator<E> 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 void sort(Comparator<? super E> 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);
+ }
+ }
+
+ /**
+ * 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 void writeObject(java.io.ObjectOutputStream s)
+ throws java.io.IOException {
+
+ s.defaultWriteObject();
+
+ Object[] elements = getArray();
+ // Write out array length
+ s.writeInt(elements.length);
+
+ // 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();
+
+ // 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);
+ }
+
+ /**
+ * 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());
+ }
+
+ /**
+ * 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 <em>equal</em>.
+ * Two elements {@code e1} and {@code e2} are considered
+ * <em>equal</em> 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;
+ }
+
+ /**
+ * Returns the hash code value for this list.
+ *
+ * <p>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;
+ }
+
+ /**
+ * Returns an iterator over the elements in this list in proper sequence.
+ *
+ * <p>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 <em>NOT</em> support the
+ * {@code remove} method.
+ *
+ * @return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
- Object[] snapshot = elements;
- return new CowIterator<E>(snapshot, 0, snapshot.length);
+ return new COWIterator<E>(getArray(), 0);
}
/**
- * 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}.
+ * {@inheritDoc}
*
- * <p>The returned iterator does not support {@link ListIterator#add},
- * {@link ListIterator#set} or {@link Iterator#remove()},
- */
- public ListIterator<E> listIterator(int index) {
- Object[] snapshot = elements;
- if (index < 0 || index > snapshot.length) {
- throw new IndexOutOfBoundsException("index=" + index + ", length=" + snapshot.length);
- }
- CowIterator<E> result = new CowIterator<E>(snapshot, 0, snapshot.length);
- result.index = index;
- return result;
- }
-
- /**
- * Equivalent to {@code listIterator(0)}.
+ * <p>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 <em>NOT</em> support the
+ * {@code remove}, {@code set} or {@code add} methods.
*/
public ListIterator<E> listIterator() {
- Object[] snapshot = elements;
- return new CowIterator<E>(snapshot, 0, snapshot.length);
- }
-
- public List<E> 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);
- }
- return new CowSubList(snapshot, from, to);
- }
-
- public Object[] toArray() {
- return elements.clone();
- }
-
- @SuppressWarnings({"unchecked","SuspiciousSystemArraycopy"})
- public <T> T[] toArray(T[] contents) {
- Object[] snapshot = elements;
- if (snapshot.length > contents.length) {
- return (T[]) Arrays.copyOf(snapshot, snapshot.length, contents.getClass());
- }
- System.arraycopy(snapshot, 0, contents, 0, snapshot.length);
- if (snapshot.length < contents.length) {
- contents[snapshot.length] = null;
- }
- 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;
- }
- }
- return !i.hasNext();
- } else {
- return false;
- }
- }
-
- @Override public int hashCode() {
- return Arrays.hashCode(elements);
- }
-
- @Override public String toString() {
- return Arrays.toString(elements);
- }
-
- 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;
- }
-
- 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;
- }
-
- public synchronized boolean addAll(Collection<? extends E> collection) {
- return addAll(elements.length, collection);
- }
-
- public synchronized boolean addAll(int index, Collection<? extends E> 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;
+ return new COWIterator<E>(getArray(), 0);
}
/**
- * 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.
+ * {@inheritDoc}
*
- * <p>Callers of this method may prefer {@link CopyOnWriteArraySet}, whose
- * API is more appropriate for set operations.
- */
- public synchronized int addAllAbsent(Collection<? extends E> 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;
- }
- }
- if (addedCount < toAdd.length) {
- newElements = Arrays.copyOfRange(
- newElements, 0, elements.length + addedCount); // trim to size
- }
- elements = newElements;
- return addedCount;
- }
-
- /**
- * Adds {@code object} to the end of this list if it is not already present.
+ * <p>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 <em>NOT</em> support the
+ * {@code remove}, {@code set} or {@code add} methods.
*
- * <p>Callers of this method may prefer {@link CopyOnWriteArraySet}, whose
- * API is more appropriate for set operations.
+ * @throws IndexOutOfBoundsException {@inheritDoc}
*/
- public synchronized boolean addIfAbsent(E object) {
- if (contains(object)) {
- return false;
- }
- add(object);
- return true;
- }
+ public ListIterator<E> listIterator(int index) {
+ Object[] elements = getArray();
+ int len = elements.length;
+ if (index < 0 || index > len)
+ throw new IndexOutOfBoundsException(outOfBounds(index, len));
- @Override public synchronized void clear() {
- elements = EmptyArray.OBJECT;
- }
-
- public synchronized E remove(int index) {
- @SuppressWarnings("unchecked")
- E removed = (E) elements[index];
- removeRange(index, index + 1);
- return removed;
- }
-
- public synchronized boolean remove(Object o) {
- int index = indexOf(o);
- if (index == -1) {
- return false;
- }
- remove(index);
- return true;
- }
-
- public synchronized boolean removeAll(Collection<?> collection) {
- return removeOrRetain(collection, false, 0, elements.length) != 0;
- }
-
- public synchronized boolean retainAll(Collection<?> collection) {
- return removeOrRetain(collection, true, 0, elements.length) != 0;
+ return new COWIterator<E>(elements, index);
}
/**
- * Removes or retains the elements in {@code collection}. Returns the number
- * of elements removed.
- */
- 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;
- }
-
- /*
- * 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];
- }
- }
-
- /*
- * 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);
-
- if (newSize < newElements.length) {
- newElements = Arrays.copyOfRange(newElements, 0, newSize); // trim to size
- }
- int removed = elements.length - newElements.length;
- elements = newElements;
- return removed;
- }
-
- // we made it all the way through the loop without making any changes
- return 0;
- }
-
- 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;
- }
-
- 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;
- }
-
- 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;
- }
-
- 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;
- }
-
- final Object[] getArray() {
- // CopyOnWriteArraySet needs this.
- return elements;
- }
-
- /**
- * 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)
+ * Returns a {@link Spliterator} over the elements in this list.
*
- * 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.
+ * <p>The {@code Spliterator} reports {@link Spliterator#IMMUTABLE},
+ * {@link Spliterator#ORDERED}, {@link Spliterator#SIZED}, and
+ * {@link Spliterator#SUBSIZED}.
+ *
+ * <p>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
*/
- class CowSubList extends AbstractList<E> {
-
- /*
- * 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;
-
- public CowSubList(Object[] expectedElements, int from, int to) {
- this.slice = new Slice(expectedElements, from, to);
- }
-
- @Override public int size() {
- Slice slice = this.slice;
- return slice.to - slice.from;
- }
-
- @Override public boolean isEmpty() {
- Slice slice = this.slice;
- return slice.from == slice.to;
- }
-
- @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];
- }
-
- @Override public Iterator<E> iterator() {
- return listIterator(0);
- }
-
- @Override public ListIterator<E> listIterator() {
- return listIterator(0);
- }
-
- @Override public ListIterator<E> listIterator(int index) {
- Slice slice = this.slice;
- Object[] snapshot = elements;
- slice.checkPositionIndex(index);
- slice.checkConcurrentModification(snapshot);
- CowIterator<E> result = new CowIterator<E>(snapshot, slice.from, slice.to);
- result.index = slice.from + index;
- return result;
- }
-
- @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;
- }
-
- @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;
- }
-
- @Override public boolean contains(Object object) {
- return indexOf(object) != -1;
- }
-
- @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);
- }
-
- @Override public List<E> 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());
- }
- 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;
- }
- }
-
- @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);
- }
- }
-
- @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);
- }
- }
-
- @Override public boolean add(E object) {
- synchronized (CopyOnWriteArrayList.this) {
- add(slice.to - slice.from, object);
- return true;
- }
- }
-
- @Override public boolean addAll(int index, Collection<? extends E> 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));
- return result;
- }
- }
-
- @Override public boolean addAll(Collection<? extends E> collection) {
- synchronized (CopyOnWriteArrayList.this) {
- return addAll(size(), collection);
- }
- }
-
- @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;
- }
- }
-
- @Override public boolean remove(Object object) {
- synchronized (CopyOnWriteArrayList.this) {
- int index = indexOf(object);
- if (index == -1) {
- return false;
- }
- remove(index);
- return true;
- }
- }
-
- @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;
- }
- }
-
- @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 Spliterator<E> spliterator() {
+ return Spliterators.spliterator
+ (getArray(), Spliterator.IMMUTABLE | Spliterator.ORDERED);
}
- static class Slice {
- private final Object[] expectedElements;
- private final int from;
- private final int to;
-
- Slice(Object[] expectedElements, int from, int to) {
- this.expectedElements = expectedElements;
- this.from = from;
- this.to = to;
- }
-
- /**
- * 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));
- }
- }
-
- /**
- * 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));
- }
- }
-
- void checkConcurrentModification(Object[] snapshot) {
- if (expectedElements != snapshot) {
- throw new ConcurrentModificationException();
- }
- }
- }
-
- /**
- * Iterates an immutable snapshot of the list.
- */
- static class CowIterator<E> implements ListIterator<E> {
+ static final class COWIterator<E> implements ListIterator<E> {
+ /** Snapshot of the array */
private final Object[] snapshot;
- private final int from;
- private final int to;
- private int index = 0;
+ /** Index of element to be returned by subsequent call to next. */
+ private int cursor;
- CowIterator(Object[] snapshot, int from, int to) {
- this.snapshot = snapshot;
- this.from = from;
- this.to = to;
- this.index = from;
- }
-
- public void add(E object) {
- throw new UnsupportedOperationException();
+ COWIterator(Object[] elements, int initialCursor) {
+ cursor = initialCursor;
+ snapshot = elements;
}
public boolean hasNext() {
- return index < to;
+ return cursor < snapshot.length;
}
public boolean hasPrevious() {
- return index > from;
+ return cursor > 0;
}
@SuppressWarnings("unchecked")
public E next() {
- if (index < to) {
- return (E) snapshot[index++];
- } else {
+ if (! hasNext())
throw new NoSuchElementException();
- }
- }
-
- public int nextIndex() {
- return index;
+ return (E) snapshot[cursor++];
}
@SuppressWarnings("unchecked")
public E previous() {
- if (index > from) {
- return (E) snapshot[--index];
- } else {
+ if (! hasPrevious())
throw new NoSuchElementException();
- }
+ return (E) snapshot[--cursor];
+ }
+
+ public int nextIndex() {
+ return cursor;
}
public int previousIndex() {
- return index - 1;
+ return cursor-1;
+ }
+
+ /**
+ * Not supported. Always throws UnsupportedOperationException.
+ * @throws UnsupportedOperationException always; {@code remove}
+ * is not supported by this iterator.
+ */
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Not supported. Always throws UnsupportedOperationException.
+ * @throws UnsupportedOperationException always; {@code set}
+ * is not supported by this iterator.
+ */
+ public void set(E e) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * 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
+ @SuppressWarnings("unchecked")
+ public void forEachRemaining(Consumer<? super E> action) {
+ Objects.requireNonNull(action);
+ final int size = snapshot.length;
+ for (int i = cursor; i < size; i++) {
+ action.accept((E) snapshot[i]);
+ }
+ cursor = size;
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * <p>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<E> 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<E>(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<E>
+ extends AbstractList<E>
+ implements RandomAccess
+ {
+ private final CopyOnWriteArrayList<E> l;
+ private final int offset;
+ private int size;
+ private Object[] expectedArray;
+
+ // only call this holding l's lock
+ COWSubList(CopyOnWriteArrayList<E> list,
+ int fromIndex, int toIndex) {
+ // assert Thread.holdsLock(list.lock);
+ l = list;
+ expectedArray = l.getArray();
+ offset = fromIndex;
+ size = toIndex - fromIndex;
+ }
+
+ // 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;
+ }
+ }
+
+ public E get(int index) {
+ synchronized (l.lock) {
+ rangeCheck(index);
+ checkForComodification();
+ return l.get(index+offset);
+ }
+ }
+
+ public int size() {
+ synchronized (l.lock) {
+ checkForComodification();
+ return size;
+ }
+ }
+
+ 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++;
+ }
+ }
+
+ public void clear() {
+ synchronized (l.lock) {
+ checkForComodification();
+ l.removeRange(offset, offset+size);
+ expectedArray = l.getArray();
+ size = 0;
+ }
+ }
+
+ public E remove(int index) {
+ synchronized (l.lock) {
+ rangeCheck(index);
+ checkForComodification();
+ E result = l.remove(index+offset);
+ expectedArray = l.getArray();
+ size--;
+ return result;
+ }
+ }
+
+ public boolean remove(Object o) {
+ int index = indexOf(o);
+ if (index == -1)
+ return false;
+ remove(index);
+ return true;
+ }
+
+ public Iterator<E> iterator() {
+ synchronized (l.lock) {
+ checkForComodification();
+ return new COWSubListIterator<E>(l, 0, offset, size);
+ }
+ }
+
+ public ListIterator<E> listIterator(int index) {
+ synchronized (l.lock) {
+ checkForComodification();
+ if (index < 0 || index > size)
+ throw new IndexOutOfBoundsException
+ (outOfBounds(index, size));
+ return new COWSubListIterator<E>(l, index, offset, size);
+ }
+ }
+
+ public List<E> subList(int fromIndex, int toIndex) {
+ synchronized (l.lock) {
+ checkForComodification();
+ if (fromIndex < 0 || toIndex > size || fromIndex > toIndex)
+ throw new IndexOutOfBoundsException();
+ return new COWSubList<E>(l, fromIndex + offset,
+ toIndex + offset);
+ }
+ }
+
+ public void forEach(Consumer<? super E> 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);
+ }
+ }
+
+ public void replaceAll(UnaryOperator<E> 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);
+ }
+ }
+
+ public void sort(Comparator<? super E> 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);
+ }
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ public boolean removeIf(Predicate<? super E> 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;
+ }
+
+ public Spliterator<E> 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);
+ }
+
+ }
+
+ private static class COWSubListIterator<E> implements ListIterator<E> {
+ private final ListIterator<E> it;
+ private final int offset;
+ private final int size;
+
+ COWSubListIterator(List<E> l, int index, int offset, int size) {
+ this.offset = offset;
+ this.size = size;
+ it = l.listIterator(index+offset);
+ }
+
+ public boolean hasNext() {
+ return nextIndex() < size;
+ }
+
+ public E next() {
+ if (hasNext())
+ return it.next();
+ else
+ throw new NoSuchElementException();
+ }
+
+ public boolean hasPrevious() {
+ return previousIndex() >= 0;
+ }
+
+ public E previous() {
+ if (hasPrevious())
+ return it.previous();
+ else
+ throw new NoSuchElementException();
+ }
+
+ public int nextIndex() {
+ return it.nextIndex() - offset;
+ }
+
+ public int previousIndex() {
+ 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<? super E> 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 347ed14..0cf8558 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.
*
- * <pre> {@code
+ * <pre> {@code
* class Handler { void handle(); ... }
*
* class X {
* private final CopyOnWriteArraySet<Handler> handlers
- * = new CopyOnWriteArraySet<Handler>();
+ * = 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 <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this set
*/
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
implements java.io.Serializable {
@@ -79,8 +88,15 @@
* @throws NullPointerException if the specified collection is null
*/
public CopyOnWriteArraySet(Collection<? extends E> c) {
- al = new CopyOnWriteArrayList<E>();
- al.addAllAbsent(c);
+ if (c.getClass() == CopyOnWriteArraySet.class) {
+ @SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc =
+ (CopyOnWriteArraySet<E>)c;
+ al = new CopyOnWriteArrayList<E>(cc.al);
+ }
+ else {
+ al = new CopyOnWriteArrayList<E>();
+ al.addAllAbsent(c);
+ }
}
/**
@@ -104,8 +120,7 @@
/**
* 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
- * <tt>(o==null ? e==null : o.equals(e))</tt>.
+ * 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 @@
* The following code can be used to dump the set into a newly allocated
* array of {@code String}:
*
- * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
+ * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
*
* Note that {@code toArray(new Object[0])} is identical in function to
* {@code toArray()}.
@@ -190,11 +205,10 @@
/**
* Removes the specified element from this set if it is present.
* More formally, removes an element {@code e} such that
- * <tt>(o==null ? e==null : o.equals(e))</tt>,
- * 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 @@
* 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
- * <tt>(e==null ? e2==null : e.equals(e2))</tt>.
+ * {@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 @@
* @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 @@
* @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
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
* @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
+ * (<a href="../Collection.html#optional-restrictions">optional</a>),
* or if the specified collection is null
* @see #remove(Object)
*/
@@ -281,9 +334,11 @@
* @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
+ * (<a href="../Collection.html#optional-restrictions">optional</a>)
* @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
+ * (<a href="../Collection.html#optional-restrictions">optional</a>),
* or if the specified collection is null
* @see #remove(Object)
*/
@@ -310,54 +365,49 @@
* 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<? super E> 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<? super E> 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.
+ *
+ * <p>The {@code Spliterator} reports {@link Spliterator#IMMUTABLE},
+ * {@link Spliterator#DISTINCT}, {@link Spliterator#SIZED}, and
+ * {@link Spliterator#SUBSIZED}.
+ *
+ * <p>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<E> 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 77093f7..680ea16 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.
* </ul>
*
- * <pre> {@code
+ * <pre> {@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}.)
*
- * <pre> {@code
+ * <pre> {@code
* class Driver2 { // ...
* void main() throws InterruptedException {
* CountDownLatch doneSignal = new CountDownLatch(N);
@@ -151,7 +151,7 @@
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 b868037..9c8b1b2 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 <em>pending</em> 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:
*
* <pre> {@code
- * class ForEach<E> ...
+ * class ForEach<E> ... {
+ * ...
* public void compute() { // version 2
* if (hi - lo >= 2) {
* int mid = (lo + hi) >>> 1;
@@ -153,18 +154,19 @@
* tryComplete();
* }
* }
- * }</pre>
+ * }}</pre>
*
- * 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}.
*
* <pre> {@code
- * class ForEach<E> ...
+ * class ForEach<E> ... {
+ * ...
* 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();
* }
- * }</pre>
+ * }}</pre>
*
- * 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 @@
* }}</pre>
*
* 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();
- * }</pre>
+ * new BodyBuilder(p, ...).fork();}</pre>
*
* @since 1.8
- * @hide
* @author Doug Lea
*/
public abstract class CountedCompleter<T> extends ForkJoinTask<T> {
@@ -495,8 +495,7 @@
* @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 @@
* 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 @@
/**
* 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}
*/
@@ -667,6 +666,26 @@
}
/**
+ * 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.
*/
void internalPropagateException(Throwable ex) {
@@ -706,14 +725,13 @@
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 d698501..7219c93 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 <em>barrier action</em> is useful
* for updating shared-state before any of the parties continue.
*
- * <p><b>Sample usage:</b> Here is an example of
- * using a barrier in a parallel decomposition design:
+ * <p><b>Sample usage:</b> Here is an example of using a barrier in a
+ * parallel decomposition design:
*
- * <pre> {@code
+ * <pre> {@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<Thread> 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();
* }
* }}</pre>
*
@@ -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:
- * <pre> {@code
+ * <pre> {@code
* if (barrier.await() == 0) {
* // log the completion of this iteration
* }}</pre>
@@ -117,7 +121,7 @@
* 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 @@
* 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 <em>another</em> 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 e4a715e..d100a9c 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 <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this queue
*/
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> {
@@ -157,10 +162,9 @@
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 @@
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 @@
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 @@
}
/**
- * 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 @@
* unexpired) in this queue. The iterator does not return the
* elements in any particular order.
*
- * <p>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.
+ * <p>The returned iterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* @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 60871b4..5f4c534 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.
- * <pre> {@code
+ * <pre> {@code
* class FillAndEmpty {
- * Exchanger<DataBuffer> exchanger = new Exchanger<DataBuffer>();
+ * Exchanger<DataBuffer> exchanger = new Exchanger<>();
* DataBuffer initialEmptyBuffer = ... a made-up type
* DataBuffer initialFullBuffer = ...
*
@@ -125,9 +125,10 @@
* 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 @@
/**
* 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 @@
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 @@
}
/**
- * Per-thread state
+ * Per-thread state.
*/
private final Participant participant;
@@ -598,37 +596,33 @@
}
// 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"));
- MATCH = U.objectFieldOffset
- (nk.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);
+ (Exchanger.class.getDeclaredField("slot"));
- } catch (Exception e) {
+ MATCH = U.objectFieldOffset
+ (Node.class.getDeclaredField("match"));
+
+ BLOCKER = U.objectFieldOffset
+ (Thread.class.getDeclaredField("parkBlocker"));
+
+ 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 095ebfc..9dd3efb 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:
*
- * <pre>
- * Executor executor = <em>anExecutor</em>;
+ * <pre> {@code
+ * Executor executor = anExecutor();
* executor.execute(new RunnableTask1());
* executor.execute(new RunnableTask2());
- * ...
- * </pre>
+ * ...}</pre>
*
- * 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:
*
- * <pre> {@code
+ * <pre> {@code
* class DirectExecutor implements Executor {
* public void execute(Runnable r) {
* r.run();
* }
* }}</pre>
*
- * 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.
*
- * <pre> {@code
+ * <pre> {@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.
*
- * <pre> {@code
+ * <pre> {@code
* class SerialExecutor implements Executor {
* final Queue<Runnable> 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 9514246..cea0384 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<Callable<Result>> solvers)
* throws InterruptedException, ExecutionException {
- * CompletionService<Result> ecs
- * = new ExecutorCompletionService<Result>(e);
- * for (Callable<Result> 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<Result> ecs
+ * = new ExecutorCompletionService<Result>(e);
+ * for (Callable<Result> 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);
+ * }
* }}</pre>
*
* Suppose instead that you would like to use the first non-null result
@@ -47,32 +47,31 @@
* void solve(Executor e,
* Collection<Callable<Result>> solvers)
* throws InterruptedException {
- * CompletionService<Result> ecs
- * = new ExecutorCompletionService<Result>(e);
- * int n = solvers.size();
- * List<Future<Result>> futures
- * = new ArrayList<Future<Result>>(n);
- * Result result = null;
- * try {
- * for (Callable<Result> 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<Result> ecs
+ * = new ExecutorCompletionService<Result>(e);
+ * int n = solvers.size();
+ * List<Future<Result>> futures = new ArrayList<>(n);
+ * Result result = null;
+ * try {
+ * for (Callable<Result> 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<Result> f : futures)
- * f.cancel(true);
- * }
+ * }
+ * finally {
+ * for (Future<Result> f : futures)
+ * f.cancel(true);
+ * }
*
- * if (result != null)
- * use(result);
+ * if (result != null)
+ * use(result);
* }}</pre>
*/
public class ExecutorCompletionService<V> implements CompletionService<V> {
@@ -81,15 +80,18 @@
private final BlockingQueue<Future<V>> completionQueue;
/**
- * FutureTask extension to enqueue upon completion
+ * FutureTask extension to enqueue upon completion.
*/
- private class QueueingFuture extends FutureTask<Void> {
- QueueingFuture(RunnableFuture<V> task) {
+ private static class QueueingFuture<V> extends FutureTask<Void> {
+ QueueingFuture(RunnableFuture<V> task,
+ BlockingQueue<Future<V>> completionQueue) {
super(task, null);
this.task = task;
+ this.completionQueue = completionQueue;
}
- protected void done() { completionQueue.add(task); }
private final Future<V> task;
+ private final BlockingQueue<Future<V>> completionQueue;
+ protected void done() { completionQueue.add(task); }
}
private RunnableFuture<V> newTaskFor(Callable<V> task) {
@@ -149,14 +151,14 @@
public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task);
- executor.execute(new QueueingFuture(f));
+ executor.execute(new QueueingFuture<V>(f, completionQueue));
return f;
}
public Future<V> submit(Runnable task, V result) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task, result);
- executor.execute(new QueueingFuture(f));
+ executor.execute(new QueueingFuture<V>(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 58a2113..ce7b2c6 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:
*
- * <pre> {@code
+ * <pre> {@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:
*
- * <pre> {@code
+ * <pre> {@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 2068fd7..3d49b82 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:
*
* <ul>
- * <li> Methods that create and return an {@link ExecutorService}
- * set up with commonly useful configuration settings.
- * <li> Methods that create and return a {@link ScheduledExecutorService}
- * set up with commonly useful configuration settings.
- * <li> Methods that create and return a "wrapped" ExecutorService, that
- * disables reconfiguration by making implementation-specific methods
- * inaccessible.
- * <li> Methods that create and return a {@link ThreadFactory}
- * that sets newly created threads to a known state.
- * <li> 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}.
+ * <li>Methods that create and return an {@link ExecutorService}
+ * set up with commonly useful configuration settings.
+ * <li>Methods that create and return a {@link ScheduledExecutorService}
+ * set up with commonly useful configuration settings.
+ * <li>Methods that create and return a "wrapped" ExecutorService, that
+ * disables reconfiguration by making implementation-specific methods
+ * inaccessible.
+ * <li>Methods that create and return a {@link ThreadFactory}
+ * that sets newly created threads to a known state.
+ * <li>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}.
* </ul>
*
* @since 1.5
@@ -78,7 +82,6 @@
* @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 @@
}
/**
- * 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 @@
// 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<T> implements Callable<T> {
- final Runnable task;
- final T result;
+ private static final class RunnableAdapter<T> implements Callable<T> {
+ private final Runnable task;
+ private final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
@@ -427,11 +431,11 @@
}
/**
- * A callable that runs under established access control settings
+ * A callable that runs under established access control settings.
*/
- static final class PrivilegedCallable<T> implements Callable<T> {
- private final Callable<T> task;
- private final AccessControlContext acc;
+ private static final class PrivilegedCallable<T> implements Callable<T> {
+ final Callable<T> task;
+ final AccessControlContext acc;
PrivilegedCallable(Callable<T> task) {
this.task = task;
@@ -454,12 +458,13 @@
/**
* A callable that runs under established access control settings and
- * current ClassLoader
+ * current ClassLoader.
*/
- static final class PrivilegedCallableUsingCurrentClassLoader<T> implements Callable<T> {
- private final Callable<T> task;
- private final AccessControlContext acc;
- private final ClassLoader ccl;
+ private static final class PrivilegedCallableUsingCurrentClassLoader<T>
+ implements Callable<T> {
+ final Callable<T> task;
+ final AccessControlContext acc;
+ final ClassLoader ccl;
PrivilegedCallableUsingCurrentClassLoader(Callable<T> task) {
// BEGIN android-removed
@@ -469,7 +474,7 @@
// // 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
// // 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 @@
* 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 @@
}
}
- static class FinalizableDelegatedExecutorService
- extends DelegatedExecutorService {
+ private static class FinalizableDelegatedExecutorService
+ extends DelegatedExecutorService {
FinalizableDelegatedExecutorService(ExecutorService executor) {
super(executor);
}
@@ -634,7 +640,7 @@
* 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 41dd161..184d07a 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.
*
- * <p>A static {@code commonPool()} is available and appropriate for
+ * <p>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 @@
*
* <p>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}
* <li>{@code java.util.concurrent.ForkJoinPool.common.exceptionHandler}
* - the class name of a {@link UncaughtExceptionHandler}
+ * <li>{@code java.util.concurrent.ForkJoinPool.common.maximumSpares}
+ * - the maximum number of allowed extra threads to maintain target
+ * parallelism (default 256).
* </ul>
+ * 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.
*
* <p><b>Implementation notes</b>: 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 @@
* 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 @@
* (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.)
+ * 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. 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.)
+ * 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.
+ * 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 all the information needed
- * to atomically decide to add, inactivate, enqueue (on an event
+ * 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.
+ * subfields.
*
- * 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.
+ * Field "runState" holds lifetime status, atomically and
+ * monotonically setting STARTED, SHUTDOWN, STOP, and finally
+ * TERMINATED bits.
*
- * 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
+ * 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.
+ * a weak reference mechanism. Thus for example the stack top
+ * subfield of ctl stores indices, not references.
*
- * 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.
+ * 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.
*
- * 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.
+ * 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.
+ * 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 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.
+ * 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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).
+ * 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:
+ * 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 @@
* (7) Exported methods
* (8) Static block initializing statics in minimally dependent order
*/
- // android-note: Removed references to CountedCompleters.
// Static utilities
@@ -517,7 +674,8 @@
* 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 @@
* 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 @@
* in WorkQueue.tryRemoveAndExec. We don't need the proxy to
* actually do anything beyond having a unique identity.
*/
- static final class EmptyTask extends ForkJoinTask<Void> {
+ private static final class EmptyTask extends ForkJoinTask<Void> {
private static final long serialVersionUID = -7721805057305804111L;
EmptyTask() { status = ForkJoinTask.NORMAL; } // force done
public final Void getRawResult() { return null; }
@@ -549,54 +707,54 @@
}
/**
+ * 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 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 @@
* 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 @@
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 @@
* 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 @@
/**
* 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 @@
* 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 @@
* 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 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;
+ }
+ }
+ else
+ break;
+ }
+ }
+
+ /**
+ * Executes the given task and (some) remaining local tasks.
*/
final void runTask(ForkJoinTask<?> task) {
- if ((currentSteal = task) != null) {
+ if (task != null) {
task.doExec();
- ForkJoinTask<?>[] a = array;
- int md = mode;
- ++nsteals;
+ if (config < 0)
+ localPollAndExec();
+ else
+ localPopAndExec();
+ int ns = ++nsteals;
+ ForkJoinWorkerThread thread = owner;
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)
- break;
- if (U.compareAndSwapObject(a, i, t, null)) {
- top = s;
- t.doExec();
- }
- }
+ 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 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.
+ * or any other cancelled task. Used only by awaitJoin.
*
- * @return false if no progress can be made, else true
+ * @return true if queue empty and task not known to be done
*/
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;
+ 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 (base == b) // replace with proxy
- removed = U.compareAndSwapObject(a, j, task,
- new EmptyTask());
- break;
+ else if (t.status < 0 && s + 1 == top) {
+ if (U.compareAndSwapObject(a, offset, t, null)) {
+ top = s;
+ }
+ break; // was cancelled
+ }
+ else if (++d == 0) {
+ if (base != b) // rescan
+ break;
+ return false;
+ }
}
- 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;
+ if (task.status < 0)
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Pops task if in the same CC computation as the given task,
+ * in either shared or owned mode. Used only by helpComplete.
+ */
+ 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 if (U.compareAndSwapObject(a, offset,
+ t, null)) {
+ top = s - 1;
+ return t;
+ }
+ break;
+ }
+ else if ((r = r.completer) == null) // try parent
+ break;
}
}
- if (removed)
- task.doExec();
+ }
+ return null;
+ }
+
+ /**
+ * 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 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
+ }
+ else
+ h = 2; // lost CAS
+ break;
+ }
+ else if ((r = r.completer) == null) {
+ h = -1; // unmatched
+ break;
+ }
+ }
+ }
}
else
- stat = false;
- return stat;
- }
-
- /**
- * Tries to poll for and execute the given task or any other
- * task in its CountedCompleter computation.
- */
- 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();
- }
- return true;
- }
- else if ((r = r.completer) == null)
- break; // not part of root computation
- }
- }
- }
- return false;
- }
-
- /**
- * Tries to pop and execute the given task or any other task
- * in its CountedCompleter computation.
- */
- 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();
- }
- else
- qlock = 0;
- }
- return true;
- }
- else if ((r = r.completer) == null)
- break;
- }
- }
- }
- return false;
- }
-
- /**
- * Internal version
- */
- 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;
- t.doExec();
- }
- return true;
- }
- else if ((r = r.completer) == null)
- break;
- }
- }
- }
- return false;
+ h = b | Integer.MIN_VALUE; // to sense movement on re-poll
+ return h;
}
/**
@@ -1005,34 +1305,28 @@
*/
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);
}
}
@@ -1041,15 +1335,6 @@
// 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<Submitter> submitters;
-
- /**
* Creates a new ForkJoinWorkerThread. This factory is used unless
* overridden in ForkJoinPool constructors.
*/
@@ -1058,9 +1343,9 @@
/**
* 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 @@
* 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 @@
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
+ * 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
*
- * 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 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.
*
- * 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.
+ * 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.
*/
- // bit positions/shifts for fields
+ // 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;
+ 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
- // 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;
+ // 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;
+ final UncaughtExceptionHandler ueh; // per-worker UEH
/**
- * 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.
+ * Instantiates fields upon first submission, or upon shutdown if
+ * no submissions. If checkTermination true, also responds to
+ * termination by external calls submitting tasks.
*/
- 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();
+ 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
+
+ /**
+ * 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 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;
}
/**
- * Unlocks and signals any thread waiting for plock. Called only
- * when CAS of seq value for unlock fails.
+ * 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 releasePlock(int ps) {
- plock = ps;
- synchronized (this) { notifyAll(); }
- }
-
- /**
- * Tries to create and start one worker if fewer than target
- * parallelism level exist. Adjusts counts etc on failure.
- */
- 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,383 +1605,231 @@
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
-
- /**
- * 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.
- */
- 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;
- }
- }
- 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.
- */
- final void incrementActiveCount() {
- long c;
- do {} while (!U.compareAndSwapLong
- (this, CTL, c = ctl, ((c & ~AC_MASK) |
- ((c & AC_MASK) + AC_UNIT))));
- }
+ // Signalling
/**
* Tries to create or activate a worker if too few are active.
- *
- * @param ws the worker array to use to find signallees
- * @param q if non-null, the queue holding tasks to be processed
*/
- final void signalWork(WorkQueue[] ws, WorkQueue q) {
+ final void signalWork() {
for (;;) {
- long c; int e, u, i; WorkQueue w; Thread p;
- if ((u = (int)((c = ctl) >>> 32)) >= 0)
+ long c; int sp, i; WorkQueue v; WorkQueue[] ws;
+ if ((c = ctl) >= 0L) // enough workers
break;
- if ((e = (int)c) <= 0) {
- if ((short)u < 0)
- tryAddWorker();
+ else if ((sp = (int)c) == 0) { // no idle workers
+ if ((c & ADD_WORKER) != 0L) // too few workers
+ tryAddWorker(c);
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) &&
+ 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;
+ }
+ }
+ }
+ }
+
+ /**
+ * 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
+ */
+ 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;
+ }
+
+ /**
+ * 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 w the worker
+ * @param ws the workQueue array to use
+ * @param r random seed
+ */
+ 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;
- }
- if (q != null && q.base >= q.top)
- break;
- }
- }
-
- // Scanning for tasks
-
- /**
- * Top-level runloop for workers, called by ForkJoinWorkerThread.run.
- */
- 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
- }
- }
-
- /**
- * 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.
- *
- * 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 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);
- }
- }
- break;
- }
- 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
- }
- break;
- }
+ v.scanState = ns;
+ LockSupport.unpark(v.parker);
}
}
- return 0;
}
/**
- * 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
+ * If worker w exists and is active, enqueues and sets status to inactive.
+ *
+ * @param w the worker
+ * @param ss current (non-negative) scanState
+ */
+ 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));
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @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 calling worker
- * @param c the ctl value on entry to scan
- * @param ec the worker's eventCount on entry to scan
+ * @return negative if w should terminate
*/
- 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));
- }
- 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;
- }
- 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
+ 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;
+ }
+ } finally {
+ aux.unlock();
}
}
}
@@ -1754,210 +1837,55 @@
}
/**
- * 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.
- */
- 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);
- }
- }
- }
-
- /**
- * Tries to locate and execute tasks for a stealer of the given
- * 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.
+ * 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 joiner the joining worker
- * @param task the task to join
- * @return 0 if no progress can be made, negative if task
- * known complete, else positive
+ * @param w the calling worker -- must be a spare
+ * @return true if dropped (in which case it must not process more tasks)
*/
- 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
- break;
- }
- if (h == origin)
- break restart; // cannot find stealer
- }
- }
- 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;
- }
- }
- }
- 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;
- }
- }
- }
+ 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);
}
- }
- }
- 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.
- *
- * @param c the assumed ctl value
- */
- final boolean tryCompensate(long c) {
- 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
- }
- }
- 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;
+ 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)));
}
- deregisterWorker(wt, ex); // clean up and return false
+ 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;
}
}
}
@@ -1965,97 +1893,366 @@
}
/**
- * Helps and/or blocks until the given task is done.
+ * 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;
+ }
+ }
+ }
+
+ // 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 joiner the joining worker
- * @param task the task
+ * @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 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
+
+ /**
+ * 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
*/
- final int awaitJoin(WorkQueue joiner, ForkJoinTask<?> task) {
- 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))));
+ 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;
}
}
}
- 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.)
+ * Tries to locate and execute tasks for a stealer of the given
+ * 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.
*
- * @param joiner the joining worker
- * @param task the task
+ * @param w caller
+ * @param task the task to join
*/
- 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);
+ 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;
+ }
+ checkSum += v.base;
+ }
+ if (++k > m) // can't find stealer
+ break outer;
+ }
+
+ 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;
+ }
+ }
+ }
+ }
+ 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;
+ }
+ }
+ }
}
- joiner.currentJoin = prevJoin;
}
}
/**
+ * Tries to decrement active count (sometimes implicitly) and
+ * possibly release or create a compensating worker in preparation
+ * for blocking. Returns false (retryable by caller), on
+ * contention, detected staleness, instability, or termination.
+ *
+ * @param w caller
+ */
+ private boolean tryCompensate(WorkQueue w) {
+ boolean canBlock; int wl;
+ long c = ctl;
+ WorkQueue[] ws = workQueues;
+ 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;
+ }
+ }
+ 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 canBlock;
+ }
+
+ /**
+ * Helps and/or blocks until the given task is done or timeout.
+ *
+ * @param w caller
+ * @param task the task
+ * @param deadline for timed waits, if nonzero
+ * @return task status on exit
+ */
+ final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) {
+ int s = 0;
+ 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;
+ }
+ }
+ return s;
+ }
+
+ // Specialized scanning
+
+ /**
* Returns a (probably) non-empty steal queue, if one is found
* during a scan, else null. This method must be retried by
* 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 @@
* 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 @@
*/
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 @@
/**
* 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 @@
* 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 @@
// 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);
+ 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);
}
- 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;
+
+ 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;
+ }
+
+ if ((short)(ctl >>> TC_SHIFT) + (config & SMASK) <= 0) {
+ runState = (STARTED | SHUTDOWN | STOP | TERMINATED); // final write
+ synchronized (this) {
+ notifyAll(); // for awaitTermination
+ }
+ }
+
+ return -1;
+ }
+
+ // External operations
+
+ /**
+ * 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
+ */
+ 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;
+ }
+ }
}
}
- // external operations on common pool
+ /**
+ * 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);
+ }
+ }
/**
- * Returns common pool queue for a thread that has submitted at
- * least one task.
+ * Pushes a possibly-external submission.
+ */
+ private <T> ForkJoinTask<T> externalSubmit(ForkJoinTask<T> 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() {
- 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;
+ 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;
}
/**
- * Tries to pop the given task from submitter's queue in common pool.
+ * Performs tryUnpush for an external submitter.
*/
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;
- }
- joiner.qlock = 0;
- }
- }
- return popped;
+ 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));
}
- 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;
- }
- }
- }
- return s;
+ /**
+ * 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 @@
* 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 @@
* @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 @@
* @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 @@
this(checkParallelism(parallelism),
checkFactory(factory),
handler,
- (asyncMode ? FIFO_QUEUE : LIFO_QUEUE),
+ asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
"ForkJoinPool-" + nextPoolId() + "-worker-");
checkPermission();
}
@@ -2432,8 +2669,7 @@
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 @@
*
* @return the common pool instance
* @since 1.8
- * @hide
*/
public static ForkJoinPool commonPool() {
// assert common != null : "static init error";
@@ -2479,7 +2714,7 @@
public <T> T invoke(ForkJoinTask<T> task) {
if (task == null)
throw new NullPointerException();
- externalPush(task);
+ externalSubmit(task);
return task.join();
}
@@ -2492,9 +2727,7 @@
* scheduled for execution
*/
public void execute(ForkJoinTask<?> task) {
- if (task == null)
- throw new NullPointerException();
- externalPush(task);
+ externalSubmit(task);
}
// AbstractExecutorService methods
@@ -2512,7 +2745,7 @@
job = (ForkJoinTask<?>) task;
else
job = new ForkJoinTask.RunnableExecuteAction(task);
- externalPush(job);
+ externalSubmit(job);
}
/**
@@ -2526,10 +2759,7 @@
* scheduled for execution
*/
public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {
- if (task == null)
- throw new NullPointerException();
- externalPush(task);
- return task;
+ return externalSubmit(task);
}
/**
@@ -2538,9 +2768,7 @@
* scheduled for execution
*/
public <T> ForkJoinTask<T> submit(Callable<T> task) {
- ForkJoinTask<T> job = new ForkJoinTask.AdaptedCallable<T>(task);
- externalPush(job);
- return job;
+ return externalSubmit(new ForkJoinTask.AdaptedCallable<T>(task));
}
/**
@@ -2549,9 +2777,7 @@
* scheduled for execution
*/
public <T> ForkJoinTask<T> submit(Runnable task, T result) {
- ForkJoinTask<T> job = new ForkJoinTask.AdaptedRunnable<T>(task, result);
- externalPush(job);
- return job;
+ return externalSubmit(new ForkJoinTask.AdaptedRunnable<T>(task, result));
}
/**
@@ -2567,8 +2793,7 @@
job = (ForkJoinTask<?>) task;
else
job = new ForkJoinTask.AdaptedRunnableAction(task);
- externalPush(job);
- return job;
+ return externalSubmit(job);
}
/**
@@ -2579,23 +2804,21 @@
// 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<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
+ ArrayList<Future<T>> futures = new ArrayList<>(tasks.size());
- boolean done = false;
try {
for (Callable<T> t : tasks) {
ForkJoinTask<T> f = new ForkJoinTask.AdaptedCallable<T>(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 int getParallelism() {
int par;
- return ((par = parallelism) > 0) ? par : 1;
+ return ((par = config & SMASK) > 0) ? par : 1;
}
/**
@@ -2633,10 +2856,9 @@
*
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
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 @@
}
}
}
- 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 @@
* 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 @@
/**
* 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 @@
* (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<Runnable> shutdownNow() {
checkPermission();
@@ -2930,9 +3164,7 @@
* @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 @@
* @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 @@
* @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 @@
}
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 @@
/**
* 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 @@
* in {@link ForkJoinPool}s.
*
* <p>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 @@
*
* <p>For example, here is a ManagedBlocker based on a
* ReentrantLock:
- * <pre> {@code
+ * <pre> {@code
* class ManagedLocker implements ManagedBlocker {
* final ReentrantLock lock;
* boolean hasLock = false;
@@ -3094,7 +3326,7 @@
*
* <p>Here is a class that possibly blocks waiting for an
* item on a given queue:
- * <pre> {@code
+ * <pre> {@code
* class QueueTaker<E> implements ManagedBlocker {
* final BlockingQueue<E> queue;
* volatile E item = null;
@@ -3132,37 +3364,46 @@
}
/**
- * 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()}.
*
- * <p>If the caller is not a {@link ForkJoinTask}, this method is
+ * <p>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}.
+ *
+ * <p>If not running in a ForkJoinPool, this method is
* behaviorally equivalent to
- * <pre> {@code
+ * <pre> {@code
* while (!blocker.isReleasable())
* if (blocker.block())
- * return;
- * }</pre>
+ * break;}</pre>
*
- * 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 @@
}
// 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<Submitter>();
+ // 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 @@
common = java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction<ForkJoinPool>() {
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 @@
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<ForkJoinWorkerThread>() {
+ 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 3a1a381..a8d97db 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 @@
*
* <p>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 <em>other</em> 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 @@
* <p>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.
*
* <p>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 <em>tagged</em> 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<V> implements Future<V>, Serializable {
/*
@@ -259,15 +264,22 @@
}
/**
- * 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 @@
* @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 @@
*/
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 @@
((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 @@
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 @@
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<Object> exceptionTableRefQueue) {
super(task, exceptionTableRefQueue);
this.ex = ex;
this.next = next;
@@ -428,7 +436,8 @@
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 @@
}
/**
- * 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 @@
Throwable ex;
if (e == null || (ex = e.ex) == null)
return null;
- if (false && e.thrower != Thread.currentThread().getId()) {
- Class<? extends Throwable> 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
}
/**
- * 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.<RuntimeException>uncheckedThrow(ex);
+ ForkJoinTask.<RuntimeException>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 <T extends Throwable>
- 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 @@
/**
* 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 @@
* invocations of {@code join} and related operations.
*
* @since 1.8
- * @hide
*/
public final void quietlyComplete() {
setCompletion(NORMAL);
@@ -956,11 +962,10 @@
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(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
+ s = wt.pool.awaitJoin(wt.workQueue, this, deadline);
}
- else if ((cp = ForkJoinPool.common) != null) {
- if (this instanceof CountedCompleter)
- cp.externalHelpComplete((CountedCompleter<?>)this);
- else if (cp.tryExternalUnpush(this))
- doExec();
- }
- 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 @@
/**
* 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 @@
}
/**
- * 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.
*
- * @see #inForkJoinPool
+ * <p>This method returns {@code null} if and only if {@link
+ * #inForkJoinPool} returns {@code false}.
+ *
* @return the pool, or {@code null} if none
*/
public static ForkJoinPool getPool() {
@@ -1283,6 +1261,25 @@
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 @@
*
* @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 @@
* 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 @@
}
/**
- * Adaptor for Runnables without results
+ * Adapter for Runnables without results.
*/
static final class AdaptedRunnableAction extends ForkJoinTask<Void>
implements RunnableFuture<Void> {
@@ -1376,7 +1370,7 @@
}
/**
- * Adaptor for Runnables in which failure forces worker exception
+ * Adapter for Runnables in which failure forces worker exception.
*/
static final class RunnableExecuteAction extends ForkJoinTask<Void> {
final Runnable runnable;
@@ -1394,7 +1388,7 @@
}
/**
- * Adaptor for Callables
+ * Adapter for Callables.
*/
static final class AdaptedCallable<T> extends ForkJoinTask<T>
implements RunnableFuture<T> {
@@ -1467,6 +1461,8 @@
/**
* 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 @@
/**
* 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 @@
}
// 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 @@
exceptionTableRefQueue = new ReferenceQueue<Object>();
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 ae28700..664d56e 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 @@
* 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
@@ -51,6 +58,18 @@
}
/**
+ * 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.
*
* @return the pool
@@ -70,7 +89,7 @@
* @return the index number
*/
public int getPoolIndex() {
- return workQueue.poolIndex >>> 1; // ignore odd/even tag bit
+ return workQueue.getPoolIndex();
}
/**
@@ -102,21 +121,124 @@
* {@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 32e8145..28d2de5 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 @@
*
* <p>
* <b>Sample Usage</b> (Note that the following classes are all
- * made-up.) <p>
- * <pre> {@code
+ * made-up.)
+ *
+ * <pre> {@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:
- * <pre> {@code
+ * <pre> {@code
* FutureTask<String> future =
- * new FutureTask<String>(new Callable<String>() {
+ * new FutureTask<>(new Callable<String>() {
* 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 5e24fc8..ed42817 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 @@
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 @@
(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 0000000..9051e2f
--- /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 64b0bf1..b1d196d 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 <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this deque
*/
public class LinkedBlockingDeque<E>
extends AbstractQueue<E>
@@ -286,8 +289,8 @@
// 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 @@
}
/**
- * @throws IllegalStateException {@inheritDoc}
+ * @throws IllegalStateException if this deque is full
* @throws NullPointerException {@inheritDoc}
*/
public void addLast(E e) {
@@ -380,7 +383,7 @@
lock.lockInterruptibly();
try {
while (!linkFirst(node)) {
- if (nanos <= 0)
+ if (nanos <= 0L)
return false;
nanos = notFull.awaitNanos(nanos);
}
@@ -403,7 +406,7 @@
lock.lockInterruptibly();
try {
while (!linkLast(node)) {
- if (nanos <= 0)
+ if (nanos <= 0L)
return false;
nanos = notFull.awaitNanos(nanos);
}
@@ -485,7 +488,7 @@
try {
E x;
while ( (x = unlinkFirst()) == null) {
- if (nanos <= 0)
+ if (nanos <= 0L)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
@@ -503,7 +506,7 @@
try {
E x;
while ( (x = unlinkLast()) == null) {
- if (nanos <= 0)
+ if (nanos <= 0L)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
@@ -594,8 +597,7 @@
*
* <p>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 @@
// 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 @@
// * @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<? extends E> c) {
@@ -893,7 +895,7 @@
* The following code can be used to dump the deque into a newly
* allocated array of {@code String}:
*
- * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
+ * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
*
* Note that {@code toArray(new Object[0])} is identical in function to
* {@code toArray()}.
@@ -928,26 +930,7 @@
}
public String toString() {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- Node<E> 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 @@
* 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).
*
- * <p>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.
+ * <p>The returned iterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* @return an iterator over the elements in this deque in proper sequence
*/
@@ -995,12 +974,8 @@
* sequential order. The elements will be returned in order from
* last (tail) to first (head).
*
- * <p>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.
+ * <p>The returned iterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* @return an iterator over the elements in this deque in reverse order
*/
@@ -1009,11 +984,11 @@
}
/**
- * Base class for Iterators for LinkedBlockingDeque
+ * Base class for LinkedBlockingDeque iterators.
*/
private abstract class AbstractItr implements Iterator<E> {
/**
- * The next node to return in next()
+ * The next node to return in next().
*/
Node<E> next;
@@ -1122,9 +1097,149 @@
Node<E> nextNode(Node<E> n) { return n.prev; }
}
+ /** A customized variant of Spliterators.IteratorSpliterator */
+ static final class LBDSpliterator<E> implements Spliterator<E> {
+ static final int MAX_BATCH = 1 << 25; // max batch array size;
+ final LinkedBlockingDeque<E> queue;
+ Node<E> 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<E> queue) {
+ this.queue = queue;
+ this.est = queue.size();
+ }
+
+ public long estimateSize() { return est; }
+
+ public Spliterator<E> trySplit() {
+ Node<E> h;
+ final LinkedBlockingDeque<E> 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<E> 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<? super E> action) {
+ if (action == null) throw new NullPointerException();
+ final LinkedBlockingDeque<E> q = this.queue;
+ final ReentrantLock lock = q.lock;
+ if (!exhausted) {
+ exhausted = true;
+ Node<E> 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<? super E> action) {
+ if (action == null) throw new NullPointerException();
+ final LinkedBlockingDeque<E> 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.
+ *
+ * <p>The returned spliterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
+ *
+ * <p>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<E> spliterator() {
+ return new LBDSpliterator<E>(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 @@
/**
* 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 0719828..86ed04a 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 <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this queue
*/
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
@@ -85,7 +88,7 @@
*/
/**
- * Linked list node class
+ * Linked list node class.
*/
static class Node<E> {
E item;
@@ -348,7 +351,7 @@
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
- if (nanos <= 0)
+ if (nanos <= 0L)
return false;
nanos = notFull.awaitNanos(nanos);
}
@@ -430,7 +433,7 @@
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
- if (nanos <= 0)
+ if (nanos <= 0L)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
@@ -475,11 +478,7 @@
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
- Node<E> 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 @@
* The following code can be used to dump the queue into a newly
* allocated array of {@code String}:
*
- * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
+ * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
*
* Note that {@code toArray(new Object[0])} is identical in function to
* {@code toArray()}.
@@ -633,25 +632,7 @@
}
public String toString() {
- fullyLock();
- try {
- Node<E> 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 @@
* 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).
*
- * <p>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.
+ * <p>The returned iterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* @return an iterator over the elements in this queue in proper sequence
*/
@@ -773,34 +750,26 @@
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<E> nextNode(Node<E> p) {
- for (;;) {
- Node<E> 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<E> 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 @@
}
}
+ /** A customized variant of Spliterators.IteratorSpliterator */
+ static final class LBQSpliterator<E> implements Spliterator<E> {
+ static final int MAX_BATCH = 1 << 25; // max batch array size;
+ final LinkedBlockingQueue<E> queue;
+ Node<E> 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<E> queue) {
+ this.queue = queue;
+ this.est = queue.size();
+ }
+
+ public long estimateSize() { return est; }
+
+ public Spliterator<E> trySplit() {
+ Node<E> h;
+ final LinkedBlockingQueue<E> 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<E> 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<? super E> action) {
+ if (action == null) throw new NullPointerException();
+ final LinkedBlockingQueue<E> q = this.queue;
+ if (!exhausted) {
+ exhausted = true;
+ Node<E> 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<? super E> action) {
+ if (action == null) throw new NullPointerException();
+ final LinkedBlockingQueue<E> 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.
+ *
+ * <p>The returned spliterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
+ *
+ * <p>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<E> spliterator() {
+ return new LBQSpliterator<E>(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 @@
/**
* 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 db48420..185ebfd 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 <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this queue
*/
public class LinkedTransferQueue<E> extends AbstractQueue<E>
implements TransferQueue<E>, java.io.Serializable {
@@ -182,7 +186,7 @@
* 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 @@
// 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 @@
* 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 @@
* 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 @@
* 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 @@
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 @@
// 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 static final int SYNC = 2; // for transfer, take
private static final int TIMED = 3; // for timed poll, tryTransfer
- @SuppressWarnings("unchecked")
- static <E> 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 @@
break; // unless slack < 2
}
LockSupport.unpark(p.waiter);
- return LinkedTransferQueue.<E>cast(item);
+ @SuppressWarnings("unchecked") E itemE = (E) item;
+ return itemE;
}
}
Node n = p.next;
@@ -675,15 +672,15 @@
if (item != e) { // matched
// assert item != s;
s.forgetContents(); // avoid garbage
- return LinkedTransferQueue.<E>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 @@
}
/**
- * 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.<E>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 @@
* 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
- break;
+ 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;
}
- Node n = p.next;
- if (n != p)
- p = n;
- else {
- count = 0;
- p = head;
- }
+ return count;
}
- 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;
+ }
+
+ 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);
+ }
+ }
+
+ /**
+ * Returns an array containing all of the elements in this queue, in
+ * proper sequence.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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}.
+ *
+ * <p>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.
+ *
+ * <p>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}:
+ *
+ * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
+ *
+ * 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> T[] toArray(T[] a) {
+ if (a == null) throw new NullPointerException();
+ return (T[]) toArrayInternal(a);
}
final class Itr implements Iterator<E> {
@@ -833,7 +940,8 @@
Object item = s.item;
if (s.isData) {
if (item != null && item != s) {
- nextItem = LinkedTransferQueue.<E>cast(item);
+ @SuppressWarnings("unchecked") E itemE = (E) item;
+ nextItem = itemE;
nextNode = s;
return;
}
@@ -880,6 +988,111 @@
}
}
+ /** A customized variant of Spliterators.IteratorSpliterator */
+ final class LTQSpliterator<E> implements Spliterator<E> {
+ 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<E> 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<? super E> 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<? super E> 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.
+ *
+ * <p>The returned spliterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
+ *
+ * <p>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<E> spliterator() {
+ return new LTQSpliterator<E>();
+ }
+
/* -------------- Removal methods -------------- */
/**
@@ -891,7 +1104,7 @@
* @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 @@
* 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).
*
- * <p>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.
+ * <p>The returned iterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* @return an iterator over the elements in this queue in proper sequence
*/
@@ -1173,7 +1382,22 @@
}
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 @@
* @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 @@
* @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 @@
/**
* 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 @@
/**
* 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 @@
// 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 c5faf16..9b2a7a1 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 @@
*
* <ul>
*
- * <li> <b>Arrival.</b> Methods {@link #arrive} and
+ * <li><b>Arrival.</b> Methods {@link #arrive} and
* {@link #arriveAndDeregister} record arrival. These methods
* do not block, but return an associated <em>arrival phase
* number</em>; that is, the phase number of the phaser to which
@@ -55,7 +55,7 @@
* flexible than, providing a barrier action to a {@code
* CyclicBarrier}.
*
- * <li> <b>Waiting.</b> Method {@link #awaitAdvance} requires an
+ * <li><b>Waiting.</b> 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.
*
* </ul>
*
@@ -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:
*
- * <pre> {@code
+ * <pre> {@code
* void runTasks(List<Runnable> tasks) {
* final Phaser phaser = new Phaser(1); // "1" to register self
* // create and start threads
@@ -145,7 +146,7 @@
* <p>One way to cause a set of threads to repeatedly perform actions
* for a given number of iterations is to override {@code onAdvance}:
*
- * <pre> {@code
+ * <pre> {@code
* void startTasks(List<Runnable> 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:
- * <pre> {@code
+ * <pre> {@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:
*
- * <pre> {@code
+ * <pre> {@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:
*
- * <pre> {@code
+ * <pre> {@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 @@
}
/**
- * 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 @@
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 @@
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 @@
}
/**
- * 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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
}
/**
- * 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 {
try {
ForkJoinPool.managedBlock(node);
- } catch (InterruptedException ie) {
+ } catch (InterruptedException cantHappen) {
node.wasInterrupted = true;
}
}
@@ -1053,7 +1050,7 @@
}
/**
- * 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 @@
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 40b3510..2044406 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.
*
- * <pre> {@code
+ * <pre> {@code
* class FIFOEntry<E extends Comparable<? super E>>
* implements Comparable<FIFOEntry<E>> {
* static final AtomicLong seq = new AtomicLong(0);
@@ -64,7 +74,7 @@
*
* @since 1.5
* @author Doug Lea
- * @param <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this queue
*/
@SuppressWarnings("unchecked")
public class PriorityBlockingQueue<E> extends AbstractQueue<E>
@@ -122,12 +132,12 @@
private transient Comparator<? super E> 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 @@
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 @@
}
/**
- * 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 @@
}
}
- /**
- * Returns an array containing all of the elements in this queue.
- * The returned array elements are in no particular order.
- *
- * <p>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.
- *
- * <p>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);
}
/**
@@ -769,6 +738,29 @@
}
/**
+ * Returns an array containing all of the elements in this queue.
+ * The returned array elements are in no particular order.
+ *
+ * <p>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.
+ *
+ * <p>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.
* The returned array elements are in no particular order.
@@ -790,7 +782,7 @@
* The following code can be used to dump the queue into a newly
* allocated array of {@code String}:
*
- * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
+ * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
*
* Note that {@code toArray(new Object[0])} is identical in function to
* {@code toArray()}.
@@ -825,12 +817,8 @@
* Returns an iterator over the elements in this queue. The
* iterator does not return the elements in any particular order.
*
- * <p>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.
+ * <p>The returned iterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
*
* @return an iterator over the elements in this queue
*/
@@ -876,6 +864,9 @@
* 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 @@
/**
* 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 @@
}
}
+ // Similar to Collections.ArraySnapshotSpliterator but avoids
+ // commitment to toArray until needed
+ static final class PBQSpliterator<E> implements Spliterator<E> {
+ final PriorityBlockingQueue<E> queue;
+ Object[] array;
+ int index;
+ int fence;
+
+ PBQSpliterator(PriorityBlockingQueue<E> 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<E> trySplit() {
+ int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
+ return (lo >= mid) ? null :
+ new PBQSpliterator<E>(queue, array, lo, index = mid);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void forEachRemaining(Consumer<? super E> 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<? super E> 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.
+ *
+ * <p>The returned spliterator is
+ * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
+ *
+ * <p>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<E> spliterator() {
+ return new PBQSpliterator<E>(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 e3a6340..8631a89 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 @@
* <p><b>Sample Usages.</b> Here is a simple but complete ForkJoin
* sort that sorts a given {@code long[]} array:
*
- * <pre> {@code
+ * <pre> {@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:
- * <pre> {@code
+ * <pre> {@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.
*
- * <pre> {@code
+ * <pre> {@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 d201bd6..5cba1da 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 @@
*
* <p>For a classic example, here is a task computing Fibonacci numbers:
*
- * <pre> {@code
+ * <pre> {@code
* class Fibonacci extends RecursiveTask<Integer> {
* 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 7125233..604f180 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<V> extends RunnableFuture<V>, ScheduledFuture<V> {
/**
- * 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 c07a5b9..eae47b3 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:
*
- * <pre> {@code
+ * <pre> {@code
* import static java.util.concurrent.TimeUnit.*;
* class BeeperControl {
* private final ScheduledExecutorService scheduler =
@@ -100,23 +100,37 @@
/**
* 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.
+ *
+ * <p>The sequence of task executions continues indefinitely until
+ * one of the following exceptional completions occur:
+ * <ul>
+ * <li>The task is {@linkplain Future#cancel explicitly cancelled}
+ * via the returned future.
+ * <li>The executor terminates, also resulting in task cancellation.
+ * <li>An execution of the task throws an exception. In this case
+ * calling {@link Future#get() get} on the returned future will
+ * throw {@link ExecutionException}.
+ * </ul>
+ * Subsequent executions are suppressed. Subsequent calls to
+ * {@link Future#isDone isDone()} on the returned future will
+ * return {@code true}.
+ *
+ * <p>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 @@
* 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.
+ *
+ * <p>The sequence of task executions continues indefinitely until
+ * one of the following exceptional completions occur:
+ * <ul>
+ * <li>The task is {@linkplain Future#cancel explicitly cancelled}
+ * via the returned future.
+ * <li>The executor terminates, also resulting in task cancellation.
+ * <li>An execution of the task throws an exception. In this case
+ * calling {@link Future#get() get} on the returned future will
+ * throw {@link ExecutionException}.
+ * </ul>
+ * 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 @@
* 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 821342d..f9a2000 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.
*
* <p>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.
*
* <p>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 <a
- * href="package-summary.html#MemoryVisibility"><i>happen-before</i></a>
+ * {@link #scheduleAtFixedRate scheduleAtFixedRate} or
+ * {@link #scheduleWithFixedDelay scheduleWithFixedDelay}
+ * do not overlap. While different executions may be performed by
+ * different threads, the effects of prior executions
+ * <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a>
* those of subsequent ones.
*
* <p>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:
*
- * <pre> {@code
+ * <pre> {@code
* public class CustomScheduledExecutor extends ScheduledThreadPoolExecutor {
*
* static class CustomTask<V> implements RunnableScheduledFuture<V> { ... }
@@ -99,11 +105,10 @@
/*
* 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 @@
/**
* 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 @@
*/
private static final AtomicLong sequencer = new AtomicLong();
- /**
- * Returns current nanosecond time.
- */
- final long now() {
- return System.nanoTime();
- }
-
private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> {
/** 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 @@
/**
* 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 @@
* 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<V> callable, long triggerTime) {
+ ScheduledFutureTask(Callable<V> 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 @@
}
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 @@
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 @@
* 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 @@
throw new NullPointerException();
RunnableScheduledFuture<Void> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
- triggerTime(delay, unit)));
+ triggerTime(delay, unit),
+ sequencer.getAndIncrement()));
delayedExecute(t);
return t;
}
@@ -541,7 +545,8 @@
throw new NullPointerException();
RunnableScheduledFuture<V> t = decorateTask(callable,
new ScheduledFutureTask<V>(callable,
- triggerTime(delay, unit)));
+ triggerTime(delay, unit),
+ sequencer.getAndIncrement()));
delayedExecute(t);
return t;
}
@@ -557,13 +562,14 @@
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
- if (period <= 0)
+ if (period <= 0L)
throw new IllegalArgumentException();
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
- unit.toNanos(period));
+ unit.toNanos(period),
+ sequencer.getAndIncrement());
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
@@ -581,13 +587,14 @@
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
- if (delay <= 0)
+ if (delay <= 0L)
throw new IllegalArgumentException();
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
- unit.toNanos(-delay));
+ -unit.toNanos(delay),
+ sequencer.getAndIncrement());
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
@@ -759,7 +766,8 @@
/**
* 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.
*
* <p>This method does not wait for actively executing tasks to
* terminate. Use {@link #awaitTermination awaitTermination} to
@@ -767,7 +775,7 @@
*
* <p>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 @@
* 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<Runnable> shutdownNow() {
@@ -784,12 +792,16 @@
}
/**
- * 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.
+ *
+ * <p>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}.
*
* <p>Iteration over this queue is <em>not</em> guaranteed to traverse
* tasks in the order in which they will execute.
@@ -1061,10 +1073,9 @@
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 @@
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 @@
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 @@
*/
private class Itr implements Iterator<Runnable> {
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 b4b7edd..856b02b 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 @@
* <p>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:
- * <pre> {@code
+ * <pre> {@code
* class Pool {
* private static final int MAX_AVAILABLE = 100;
* private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
@@ -113,8 +113,13 @@
*
* <p>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.
*
* <p>Memory consistency effects: Actions in a thread prior to calling
* a "release" method such as {@code release()}
@@ -405,14 +410,16 @@
*
* <p>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:
*
* <p>If insufficient permits are available then the current thread becomes
* disabled for thread scheduling purposes and lies dormant until
* one of two things happens:
* <ul>
* <li>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
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread.
@@ -445,12 +452,14 @@
*
* <p>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:
*
* <p>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.
*
* <p>If the current thread is {@linkplain Thread#interrupt interrupted}
@@ -512,7 +521,7 @@
* purposes and lies dormant until one of three things happens:
* <ul>
* <li>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
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
@@ -559,7 +568,7 @@
*
* <p>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 @@
* 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 69452af..a46f672 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 <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this queue
*/
public class SynchronousQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
@@ -152,9 +157,6 @@
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 @@
* 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<E> extends Transferer<E> {
@@ -215,7 +218,7 @@
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 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 @@
* 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 @@
}
// 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 casHead(SNode h, SNode nh) {
return h == head &&
- UNSAFE.compareAndSwapObject(this, headOffset, h, nh);
+ U.compareAndSwapObject(this, HEAD, h, nh);
}
/**
@@ -323,7 +324,7 @@
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 @@
*/
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 @@
}
}
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 @@
}
// 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 @@
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 @@
}
// 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 @@
*/
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 advanceTail(QNode t, QNode nt) {
if (tail == t)
- UNSAFE.compareAndSwapObject(this, tailOffset, t, nt);
+ U.compareAndSwapObject(this, TAIL, t, nt);
}
/**
@@ -604,7 +602,7 @@
*/
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 @@
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 @@
/* 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 @@
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 @@
}
}
- 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 @@
*
* @return an empty iterator
*/
- @SuppressWarnings("unchecked")
public Iterator<E> iterator() {
- return (Iterator<E>) EmptyIterator.EMPTY_ITERATOR;
+ return Collections.emptyIterator();
}
- // Replicated from a previous version of Collections
- private static class EmptyIterator<E> implements Iterator<E> {
- static final EmptyIterator<Object> EMPTY_ITERATOR
- = new EmptyIterator<Object>();
-
- 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<E> spliterator() {
+ return Spliterators.emptySpliterator();
}
/**
@@ -1072,6 +1069,14 @@
}
/**
+ * Always returns {@code "[]"}.
+ * @return {@code "[]"}
+ */
+ public String toString() {
+ return "[]";
+ }
+
+ /**
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
@@ -1131,6 +1136,8 @@
/**
* 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 @@
/**
* 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 @@
transferer = new TransferStack<E>();
}
- // 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 d1a4eb6..fdedea3 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 @@
*
* <p>
* The simplest implementation of this interface is just:
- * <pre> {@code
+ * <pre> {@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 b007af2..8c65bde 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 @@
* <p>This class also provides additional commonly used bounded random
* generation methods.
*
+ * <p>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;
+
+ private static long mix64(long z) {
+ z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL;
+ z = (z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L;
+ return z ^ (z >>> 33);
+ }
+
+ private static int mix32(long z) {
+ z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL;
+ return (int)(((z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L) >>> 32);
+ }
/**
- * 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.
+ * Field used only during singleton initialization.
+ * True when constructor completes.
*/
boolean initialized;
- // 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;
+ /** Constructor used only for static singleton */
+ private ThreadLocalRandom() {
+ initialized = true; // false during super() call
+ }
/**
- * The actual ThreadLocal
+ * 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.
*/
- private static final ThreadLocal<ThreadLocalRandom> localRandom =
- new ThreadLocal<ThreadLocalRandom>() {
- protected ThreadLocalRandom initialValue() {
- return new ThreadLocalRandom();
- }
- };
-
-
- /**
- * Constructor called only by localRandom.initialValue.
- */
- 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 @@
* @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 @@
* @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));
}
/**
- * Returns a pseudorandom, uniformly distributed value between the
- * given least value (inclusive) and bound (exclusive).
+ * 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 least the least value returned
- * @param bound the upper bound (exclusive)
- * @return the next value
- * @throws IllegalArgumentException if least greater than or equal
- * to bound
+ * @param origin the least value, unless greater than bound
+ * @param bound the upper bound (exclusive), must not equal origin
+ * @return a pseudorandom value
*/
- public int nextInt(int least, int bound) {
- if (least >= bound)
- throw new IllegalArgumentException();
- return nextInt(bound - least) + least;
- }
-
- /**
- * Returns a pseudorandom, uniformly distributed value
- * between 0 (inclusive) and the specified value (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
- */
- 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;
+ 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 offset + nextInt((int) n);
+ return r;
}
/**
- * Returns a pseudorandom, uniformly distributed value between the
- * given least value (inclusive) and bound (exclusive).
+ * The form of nextInt used by IntStream Spliterators.
+ * Exactly the same as long version, except for types.
*
- * @param least the least value returned
+ * @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 {@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 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 long nextLong(long least, long bound) {
- if (least >= bound)
- throw new IllegalArgumentException();
- return nextLong(bound - least) + least;
+ public int nextInt(int origin, int bound) {
+ if (origin >= bound)
+ throw new IllegalArgumentException(BAD_RANGE);
+ return internalNextInt(origin, bound);
}
/**
- * Returns a pseudorandom, uniformly distributed {@code double} value
- * between 0 (inclusive) and the specified value (exclusive).
+ * Returns a pseudorandom {@code long} value.
*
- * @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 long} value
*/
- public double nextDouble(double n) {
- if (!(n > 0))
- throw new IllegalArgumentException("n must be positive");
- return nextDouble() * n;
+ public long nextLong() {
+ return mix64(nextSeed());
}
/**
- * Returns a pseudorandom, uniformly distributed value between the
- * given least value (inclusive) and bound (exclusive).
+ * Returns a pseudorandom {@code long} value between zero (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 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);
+ 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 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 double nextDouble(double least, double bound) {
- if (least >= bound)
- throw new IllegalArgumentException();
- return nextDouble() * (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 {@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;
+ }
+
+ /**
+ * 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<Double> 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 <clinit> to survive static initialization circularity
+ static {
+ if (java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction<Boolean>() {
+ 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 c484920..e601b6a 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 @@
*
* <dt>Core and maximum pool sizes</dt>
*
- * <dd>A {@code ThreadPoolExecutor} will automatically adjust the
+ * <dd style="font-family:'DejaVu Sans', Arial, Helvetica, sans-serif">
+ * 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 @@
*
* <dt>On-demand construction</dt>
*
- * <dd>By default, even core threads are initially created and
+ * <dd style="font-family:'DejaVu Sans', Arial, Helvetica, sans-serif">
+ * 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 @@
*
* <dt>Creating new threads</dt>
*
- * <dd>New threads are created using a {@link ThreadFactory}. If not
+ * <dd style="font-family:'DejaVu Sans', Arial, Helvetica, sans-serif">
+ * 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.</dd>
+ * 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.</dd>
*
* <dt>Keep-alive times</dt>
*
- * <dd>If the pool currently has more than corePoolSize threads,
+ * <dd style="font-family:'DejaVu Sans', Arial, Helvetica, sans-serif">
+ * 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. </dd>
*
* <dt>Queuing</dt>
*
- * <dd>Any {@link BlockingQueue} may be used to transfer and hold
+ * <dd style="font-family:'DejaVu Sans', Arial, Helvetica, sans-serif">
+ * Any {@link BlockingQueue} may be used to transfer and hold
* submitted tasks. The use of this queue interacts with pool sizing:
*
* <ul>
*
- * <li> If fewer than corePoolSize threads are running, the Executor
+ * <li>If fewer than corePoolSize threads are running, the Executor
* always prefers adding a new thread
- * rather than queuing.</li>
+ * rather than queuing.
*
- * <li> If corePoolSize or more threads are running, the Executor
+ * <li>If corePoolSize or more threads are running, the Executor
* always prefers queuing a request rather than adding a new
- * thread.</li>
+ * thread.
*
- * <li> If a request cannot be queued, a new thread is created unless
+ * <li>If a request cannot be queued, a new thread is created unless
* this would exceed maximumPoolSize, in which case, the task will be
- * rejected.</li>
+ * rejected.
*
* </ul>
*
* There are three general strategies for queuing:
* <ol>
*
- * <li> <em> Direct handoffs.</em> A good default choice for a work
+ * <li><em> Direct handoffs.</em> 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. </li>
+ * arrive on average faster than they can be processed.
*
* <li><em> Unbounded queues.</em> 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. </li>
+ * average faster than they can be processed.
*
* <li><em>Bounded queues.</em> 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. </li>
+ * decreases throughput.
*
* </ol>
*
@@ -169,7 +183,8 @@
*
* <dt>Rejected tasks</dt>
*
- * <dd>New tasks submitted in method {@link #execute(Runnable)} will be
+ * <dd style="font-family:'DejaVu Sans', Arial, Helvetica, sans-serif">
+ * New tasks submitted in method {@link #execute(Runnable)} will be
* <em>rejected</em> 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 @@
*
* <ol>
*
- * <li> In the default {@link ThreadPoolExecutor.AbortPolicy}, the
+ * <li>In the default {@link ThreadPoolExecutor.AbortPolicy}, the
* handler throws a runtime {@link RejectedExecutionException} upon
- * rejection. </li>
+ * rejection.
*
- * <li> In {@link ThreadPoolExecutor.CallerRunsPolicy}, the thread
+ * <li>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. </li>
+ * new tasks are submitted.
*
- * <li> In {@link ThreadPoolExecutor.DiscardPolicy}, a task that
- * cannot be executed is simply dropped. </li>
+ * <li>In {@link ThreadPoolExecutor.DiscardPolicy}, a task that
+ * cannot be executed is simply dropped.
*
* <li>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.) </li>
+ * causing this to be repeated.)
*
* </ol>
*
@@ -206,7 +221,8 @@
*
* <dt>Hook methods</dt>
*
- * <dd>This class provides {@code protected} overridable
+ * <dd style="font-family:'DejaVu Sans', Arial, Helvetica, sans-serif">
+ * 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.
*
- * <p>If hook or callback methods throw exceptions, internal worker
- * threads may in turn fail and abruptly terminate.</dd>
+ * <p>If hook, callback, or BlockingQueue methods throw exceptions,
+ * internal worker threads may in turn fail, abruptly terminate, and
+ * possibly be replaced.</dd>
*
* <dt>Queue maintenance</dt>
*
- * <dd>Method {@link #getQueue()} allows access to the work queue
+ * <dd style="font-family:'DejaVu Sans', Arial, Helvetica, sans-serif">
+ * 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 @@
*
* <dt>Finalization</dt>
*
- * <dd>A pool that is no longer referenced in a program <em>AND</em>
+ * <dd style="font-family:'DejaVu Sans', Arial, Helvetica, sans-serif">
+ * A pool that is no longer referenced in a program <em>AND</em>
* 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:
*
- * <pre> {@code
+ * <pre> {@code
* class PausableThreadPoolExecutor extends ThreadPoolExecutor {
* private boolean isPaused;
* private ReentrantLock pauseLock = new ReentrantLock();
@@ -433,10 +452,10 @@
* Set containing all worker threads in pool. Accessed only when
* holding mainLock.
*/
- private final HashSet<Worker> workers = new HashSet<Worker>();
+ private final HashSet<Worker> workers = new HashSet<>();
/**
- * Wait condition to support awaitTermination
+ * Wait condition to support awaitTermination.
*/
private final Condition termination = mainLock.newCondition();
@@ -512,7 +531,7 @@
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 @@
* (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 @@
*/
private List<Runnable> drainQueue() {
BlockingQueue<Runnable> q = workQueue;
- ArrayList<Runnable> taskList = new ArrayList<Runnable>();
+ ArrayList<Runnable> taskList = new ArrayList<>();
q.drainTo(taskList);
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
@@ -1376,7 +1396,7 @@
*
* <p>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 @@
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 @@
*
* @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 @@
}
/**
- * 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 @@
/**
* 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 @@
*
* <p>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 @@
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 @@
* as in this sample subclass that prints either the direct cause
* or the underlying exception if a task has been aborted:
*
- * <pre> {@code
+ * <pre> {@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 8e6a5f7..fff02d8 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:
*
- * <pre> {@code
+ * <pre> {@code
* Lock lock = ...;
* if (lock.tryLock(50L, TimeUnit.MILLISECONDS)) ...}</pre>
*
* while this code will timeout in 50 seconds:
- * <pre> {@code
+ * <pre> {@code
* Lock lock = ...;
* if (lock.tryLock(50L, TimeUnit.SECONDS)) ...}</pre>
*
@@ -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 @@
},
/**
- * 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 @@
},
/**
- * 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 @@
},
/**
- * 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 @@
},
/**
- * Time unit representing sixty seconds
+ * Time unit representing sixty seconds.
* @since 1.6
*/
MINUTES {
@@ -117,7 +123,7 @@
},
/**
- * Time unit representing sixty minutes
+ * Time unit representing sixty minutes.
* @since 1.6
*/
HOURS {
@@ -133,7 +139,7 @@
},
/**
- * Time unit representing twenty four hours
+ * Time unit representing twenty four hours.
* @since 1.6
*/
DAYS {
@@ -164,7 +170,7 @@
* 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 @@
* method (see {@link BlockingQueue#poll BlockingQueue.poll})
* using:
*
- * <pre> {@code
+ * <pre> {@code
* public synchronized Object poll(long timeout, TimeUnit unit)
* throws InterruptedException {
* while (empty) {
@@ -360,5 +366,4 @@
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 4c2be6f..d4166b5 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 <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this queue
*/
public interface TransferQueue<E> extends BlockingQueue<E> {
/**
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 f51e6af..01e4b07 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 @@
*
* @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 @@
*
* @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 @@
* @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 @@
* @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 8a15298..849fd8a 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 @@
* @since 1.6
*/
public final void lazySet(int newValue) {
- unsafe.putOrderedInt(this, valueOffset, newValue);
+ U.putOrderedInt(this, VALUE, newValue);
}
/**
@@ -87,11 +89,7 @@
* @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 @@
*
* @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 @@
*
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
/**
* Returns the value of this {@code AtomicInteger} as an {@code int}.
+ * Equivalent to {@link #get()}.
*/
public int intValue() {
return get();
@@ -227,6 +282,7 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 fd492d1..7597e53 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 static long byteOffset(int i) {
- return ((long) i << shift) + base;
+ return ((long) i << ASHIFT) + ABASE;
}
/**
@@ -84,7 +86,7 @@
}
private int getRaw(long offset) {
- return unsafe.getIntVolatile(array, offset);
+ return U.getIntVolatile(array, offset);
}
/**
@@ -94,7 +96,7 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
}
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 @@
* @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 @@
* @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 @@
* @return the updated value
*/
public final int incrementAndGet(int i) {
- return addAndGet(i, 1);
+ return getAndAdd(i, 1) + 1;
}
/**
@@ -213,7 +205,7 @@
* @return the updated value
*/
public final int decrementAndGet(int i) {
- return addAndGet(i, -1);
+ return getAndAdd(i, -1) - 1;
}
/**
@@ -224,13 +216,101 @@
* @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 15e8840..9c55491 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 @@
* or the field is inaccessible to the caller according to Java language
* access control
*/
- public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName) {
- return new AtomicIntegerFieldUpdaterImpl<U>(tclass, fieldName);
+ @CallerSensitive
+ public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
+ String fieldName) {
+ return new AtomicIntegerFieldUpdaterImpl<U>
+ (tclass, fieldName, VMStack.getStackClass1()); // android-changed
}
/**
@@ -69,7 +75,7 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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<T> extends AtomicIntegerFieldUpdater<T> {
- private static final Unsafe unsafe = Unsafe.getUnsafe();
- private final long offset;
- private final Class<T> tclass;
- private final Class<?> cclass;
+ 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;
+ }
- AtomicIntegerFieldUpdaterImpl(final Class<T> tclass, final String fieldName) {
+ /**
+ * 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<T>
+ extends AtomicIntegerFieldUpdater<T> {
+ 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<T> tclass;
+
+ AtomicIntegerFieldUpdaterImpl(final Class<T> 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<Field>() {
+ public Field run() throws NoSuchFieldException {
+ return tclass.getDeclaredField(fieldName);
+ }
+ });
modifiers = field.getModifiers();
// BEGIN android-removed
// sun.reflect.misc.ReflectUtil.ensureMemberAccess(
@@ -263,7 +369,7 @@
// 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 @@
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 @@
// * 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 @@
// 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 ab2961a..fdb5f55 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 @@
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 @@
* @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 @@
* @since 1.6
*/
public final void lazySet(long newValue) {
- unsafe.putOrderedLong(this, valueOffset, newValue);
+ U.putOrderedLong(this, VALUE, newValue);
}
/**
@@ -101,11 +105,7 @@
* @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 @@
*
* @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 @@
*
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
/**
* 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 @@
/**
* Returns the value of this {@code AtomicLong} as a {@code long}.
+ * Equivalent to {@link #get()}.
*/
public long longValue() {
return get();
@@ -249,6 +307,7 @@
/**
* 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 @@
/**
* 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 b7f3d1e..8da1501 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 static long byteOffset(int i) {
- return ((long) i << shift) + base;
+ return ((long) i << ASHIFT) + ABASE;
}
/**
@@ -83,7 +85,7 @@
}
private long getRaw(long offset) {
- return unsafe.getLongVolatile(array, offset);
+ return U.getLongVolatile(array, offset);
}
/**
@@ -93,7 +95,7 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
}
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 @@
* @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 @@
* @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 @@
* @return the updated value
*/
public final long incrementAndGet(int i) {
- return addAndGet(i, 1);
+ return getAndAdd(i, 1) + 1;
}
/**
@@ -212,7 +204,7 @@
* @return the updated value
*/
public final long decrementAndGet(int i) {
- return addAndGet(i, -1);
+ return getAndAdd(i, -1) - 1;
}
/**
@@ -223,13 +215,101 @@
* @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 65bd452..c1a02b5 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 @@
* or the field is inaccessible to the caller according to Java language
* access control
*/
- public static <U> AtomicLongFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName) {
+ @CallerSensitive
+ public static <U> AtomicLongFieldUpdater<U> newUpdater(Class<U> tclass,
+ String fieldName) {
+ Class<?> caller = VMStack.getStackClass1(); // android-changed
if (AtomicLong.VM_SUPPORTS_LONG_CAS)
- return new CASUpdater<U>(tclass, fieldName);
+ return new CASUpdater<U>(tclass, fieldName, caller);
else
- return new LockedUpdater<U>(tclass, fieldName);
+ return new LockedUpdater<U>(tclass, fieldName, caller);
}
/**
@@ -69,7 +78,7 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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;
}
- private static class CASUpdater<T> extends AtomicLongFieldUpdater<T> {
- private static final Unsafe unsafe = Unsafe.getUnsafe();
- private final long offset;
- private final Class<T> tclass;
- private final Class<?> cclass;
+ /**
+ * 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;
+ }
- CASUpdater(final Class<T> tclass, final String fieldName) {
+ /**
+ * 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 final class CASUpdater<T> extends AtomicLongFieldUpdater<T> {
+ 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<T> tclass;
+
+ CASUpdater(final Class<T> 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,101 +362,7 @@
// 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
- // } catch (PrivilegedActionException pae) {
- // throw new RuntimeException(pae.getException());
- // END android-removed
- } catch (Exception ex) {
- throw new RuntimeException(ex);
- }
-
- Class<?> fieldt = field.getType();
- if (fieldt != 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.tclass = tclass;
- offset = unsafe.objectFieldOffset(field);
- }
-
- private void fullCheck(T obj) {
- if (!tclass.isInstance(obj))
- throw new ClassCastException();
- if (cclass != null)
- ensureProtectedAccess(obj);
- }
-
- 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 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 void set(T obj, long newValue) {
- if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
- unsafe.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 long get(T obj) {
- if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
- return unsafe.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()
- )
- );
- }
- }
-
-
- private static class LockedUpdater<T> extends AtomicLongFieldUpdater<T> {
- private static final Unsafe unsafe = Unsafe.getUnsafe();
- private final long offset;
- private final Class<T> tclass;
- private final Class<?> cclass;
-
- LockedUpdater(final Class<T> tclass, final String fieldName) {
- 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(
- // 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);
+ // sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
// }
// END android-removed
// BEGIN android-removed
@@ -364,73 +373,206 @@
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))
+ /**
+ * 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, 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);
+ return U.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 final void set(T obj, long newValue) {
+ accessCheck(obj);
+ U.putLongVolatile(obj, offset, newValue);
+ }
+
+ public final void lazySet(T obj, long newValue) {
+ accessCheck(obj);
+ U.putOrderedLong(obj, offset, newValue);
+ }
+
+ public final long get(T obj) {
+ accessCheck(obj);
+ return U.getLongVolatile(obj, offset);
+ }
+
+ 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);
+ }
+
+ 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<T> extends AtomicLongFieldUpdater<T> {
+ 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<T> tclass;
+
+ LockedUpdater(final Class<T> tclass, final String fieldName,
+ final Class<?> caller) {
+ Field field = null;
+ int modifiers = 0;
+ try {
+ field = tclass.getDeclaredField(fieldName); // 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
+ // } catch (PrivilegedActionException pae) {
+ // throw new RuntimeException(pae.getException());
+ // END android-removed
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+
+ 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;
+ this.tclass = tclass;
+ 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))
+ 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 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);
+ return U.getLong(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()
- )
- );
- }
}
// BEGIN android-removed
@@ -439,13 +581,13 @@
// * 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 18d148f..d11be10 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 @@
* @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 @@
* @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 @@
*
* @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<V> current = pair;
@@ -161,23 +161,18 @@
// 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<V> cmp, Pair<V> val) {
- return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
+ private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
+ private static final long PAIR;
+ static {
+ try {
+ PAIR = U.objectFieldOffset
+ (AtomicMarkableReference.class.getDeclaredField("pair"));
+ } catch (ReflectiveOperationException e) {
+ throw new Error(e);
+ }
}
- 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;
- }
+ private boolean casPair(Pair<V> cmp, Pair<V> 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 7ea6066..c3a5ede 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<V> 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 @@
* @since 1.6
*/
public final void lazySet(V newValue) {
- unsafe.putOrderedObject(this, valueOffset, newValue);
+ U.putOrderedObject(this, VALUE, newValue);
}
/**
@@ -79,11 +82,11 @@
* 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 @@
*
* @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 @@
* @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<V> 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<V> 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<V> 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<V> 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 052b839..f5596e8 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<E> 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 static long byteOffset(int i) {
- return ((long) i << shift) + base;
+ return ((long) i << ASHIFT) + ABASE;
}
/**
@@ -97,7 +97,7 @@
@SuppressWarnings("unchecked")
private E getRaw(long offset) {
- return (E) unsafe.getObjectVolatile(array, offset);
+ return (E) U.getObjectVolatile(array, offset);
}
/**
@@ -107,7 +107,7 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
}
private boolean compareAndSetRaw(long offset, E expect, E update) {
- return unsafe.compareAndSwapObject(array, offset, expect, update);
+ return U.compareAndSwapObject(array, offset, expect, update);
}
/**
@@ -167,13 +163,107 @@
* @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<E> 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<E> 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<E> 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<E> 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 @@
/**
* 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 13ad3eb..1dc2eb9 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
*
- * <pre> {@code
+ * <pre> {@code
* class Node {
* private volatile Node left, right;
*
@@ -60,17 +66,19 @@
* @param <U> the type of instances of tclass
* @param <W> 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 <U,W> AtomicReferenceFieldUpdater<U,W> newUpdater(Class<U> tclass,
Class<W> vclass,
String fieldName) {
return new AtomicReferenceFieldUpdaterImpl<U,W>
- (tclass, vclass, fieldName);
+ (tclass, vclass, fieldName, VMStack.getStackClass1()); // android-changed
}
/**
@@ -89,7 +97,7 @@
* @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 @@
* @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 @@
* @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<V> 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<V> 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<V> 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<V> 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<T,V>
extends AtomicReferenceFieldUpdater<T,V> {
- private static final Unsafe unsafe = Unsafe.getUnsafe();
+ private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
private final long offset;
- private final Class<T> tclass;
- private final Class<V> vclass;
+ /**
+ * if field is protected, the subclass constructing updater, else
+ * the same as tclass
+ */
private final Class<?> cclass;
+ /** class holding the field */
+ private final Class<T> tclass;
+ /** field value type */
+ private final Class<V> vclass;
/*
* Internal type checks within all update methods contain
@@ -177,26 +281,25 @@
*/
AtomicReferenceFieldUpdaterImpl(final Class<T> tclass,
- Class<V> vclass,
- final String fieldName) {
+ final Class<V> 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 @@
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 @@
// * 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))
+ /**
+ * 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()));
}
- void updateCheck(T obj, V update) {
- if (!tclass.isInstance(obj) ||
- (update != null && vclass != null && !vclass.isInstance(update)))
- throw new ClassCastException();
- if (cclass != null)
- ensureProtectedAccess(obj);
+ private final void valueCheck(V v) {
+ if (v != null && !(vclass.isInstance(v)))
+ throwCCE();
}
- 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);
+ static void throwCCE() {
+ throw new ClassCastException();
}
- public boolean weakCompareAndSet(T obj, V expect, V update) {
+ public final boolean compareAndSet(T obj, V expect, V update) {
+ accessCheck(obj);
+ valueCheck(update);
+ return U.compareAndSwapObject(obj, offset, expect, 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 1449856..69fab23 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 @@
* @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 @@
* @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 @@
*
* @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<V> current = pair;
@@ -161,23 +161,18 @@
// 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<V> cmp, Pair<V> val) {
- return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
+ private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
+ private static final long PAIR;
+ static {
+ try {
+ PAIR = U.objectFieldOffset
+ (AtomicStampedReference.class.getDeclaredField("pair"));
+ } catch (ReflectiveOperationException e) {
+ throw new Error(e);
+ }
}
- 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;
- }
+ private boolean casPair(Pair<V> cmp, Pair<V> 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 0000000..1ea088d
--- /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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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)}.
+ *
+ * <p>This class extends {@link Number}, but does <em>not</em> 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 <em>NOT</em>
+ * 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
+ * <em>not</em> 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
+ * <a href="../../../../serialized-form.html#java.util.concurrent.atomic.DoubleAccumulator.SerializationProxy">
+ * SerializationProxy</a>
+ * 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 0000000..94844d5
--- /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.
+ *
+ * <p>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.
+ *
+ * <p>This class extends {@link Number}, but does <em>not</em> 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 <em>NOT</em> 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
+ * <em>not</em> 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
+ * <a href="../../../../serialized-form.html#java.util.concurrent.atomic.DoubleAdder.SerializationProxy">
+ * SerializationProxy</a>
+ * 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 d907faf..0000000
--- 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.
- *
- * <p><b>Memory Ordering.</b> 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.
- *
- * <p>Each ordering method accepts a {@code ref} argument, and
- * controls ordering among accesses with respect to this reference.
- * Invocations must be placed <em>between</em> 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.
- *
- * <p>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.
- *
- * <p><b>Reachability.</b> 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.
- *
- * <p><b>Sample Usages</b>
- *
- * <p><b>Safe publication.</b> 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:
- *
- * <pre> {@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);
- * }
- * }}</pre>
- *
- * 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
- * <em>before</em> 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}.
- *
- * <p>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.
- *
- * <pre> {@code
- * class AnotherWidgetHolder {
- * public Widget widget;
- * void publish(Widget w) {
- * this.widget = Fences.orderWrites(w);
- * }
- * // ...
- * }}</pre>
- *
- * 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 <em>only</em> 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.
- *
- * <p><b>Safely updating fields.</b> 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:
- *
- * <pre> {@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);
- * }
- * // ...
- * }}</pre>
- *
- * 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.
- *
- * <p>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.
- *
- * <p><b>Acquire/Release management of threadsafe objects</b>. 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:
- *
- * <pre> {@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);
- * }
- *
- * // ...
- * }}</pre>
- *
- * 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.
- *
- * <p><b>Avoiding premature finalization.</b> 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.
- *
- * <pre> {@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 = ...;
- * }
- * }}</pre>
- *
- * Here, the call to {@code reachabilityFence} is nonintuitively
- * placed <em>after</em> 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.
- *
- * <p>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}:
- *
- * <pre> {@code
- * public void action2() {
- * // ...
- * Resource.update(getExternalResource());
- * }
- * private ExternalResource getExternalResource() {
- * ExternalResource ext = externalResourceArray[myIndex];
- * Fences.reachabilityFence(this);
- * return ext;
- * }}</pre>
- *
- * <p>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.
- *
- * <p><b>Formal Properties.</b>
- *
- * <p>Using the terminology of The Java Language Specification chapter
- * 17, the rules governing the semantics of the methods of this class
- * are as follows:
- *
- * <p>The following is still under construction.
- *
- * <dl>
- *
- * <dt><b>[Definitions]</b>
- * <dd>
- * <ul>
- *
- * <li>Define <em>sequenced(a, b)</em> to be true if <em>a</em>
- * occurs before <em>b</em> in <em>program order</em>.
- *
- * <li>Define <em>accesses(a, p)</em> to be true if
- * <em>a</em> is a read or write of a field (or if an array, an
- * element) of the object referenced by <em>p</em>.
- *
- * <li>Define <em>deeplyAccesses(a, p)</em> to be true if either
- * <em>accesses(a, p)</em> or <em>deeplyAccesses(a, q)</em> where
- * <em>q</em> is the value seen by some read <em>r</em>
- * such that <em>accesses(r, p)</em>.
- *
- * </ul>
- * <dt><b>[Matching]</b>
- * <dd>Given:
- *
- * <ul>
- *
- * <li><em>p</em>, a reference to an object
- *
- * <li><em>wf</em>, an invocation of {@code orderWrites(p)} or
- * {@code orderAccesses(p)}
- *
- * <li><em>w</em>, a write of value <em>p</em>
- *
- * <li> <em>rf</em>, an invocation of {@code orderReads(p)} or
- * {@code orderAccesses(p)}
- *
- * <li> <em>r</em>, a read returning value <em>p</em>
- *
- * </ul>
- * If:
- * <ul>
- * <li>sequenced(wf, w)
- * <li>read <em>r</em> sees write <em>w</em>
- * <li>sequenced(r, rf)
- * </ul>
- * Then:
- * <ul>
- *
- * <li> <em>wf happens-before rf</em>
- *
- * <li> <em>wf</em> precedes <em>rf</em> in the
- * <em>synchronization order</em>
- *
- * <li> If (<em>r1</em>, <em>w1</em>) and (<em>r2</em>,
- * <em>w2</em>) are two pairs of reads and writes, both
- * respectively satisfying the above conditions for <em>p</em>,
- * and sequenced(r1, r2) then it is not the case that <em>w2
- * happens-before w1</em>.
- *
- * </ul>
- * <dt><b>[Initial Reads]</b>
- * <dd>Given:
- *
- * <ul>
- *
- * <li><em>p</em>, a reference to an object
- *
- * <li> <em>a</em>, an access where deeplyAccesses(a, p)
- *
- * <li><em>wf</em>, an invocation of {@code orderWrites(p)} or
- * {@code orderAccesses(p)}
- *
- * <li><em>w</em>, a write of value <em>p</em>
- *
- * <li> <em>r</em>, a read returning value <em>p</em>
- *
- * <li> <em>b</em>, an access where accesses(b, p)
- *
- * </ul>
- * If:
- * <ul>
- * <li>sequenced(a, wf);
- * <li>sequenced(wf, w)
- * <li>read <em>r</em> sees write <em>w</em>, and
- * <em>r</em> is the first read by some thread
- * <em>t</em> that sees value <em>p</em>
- * <li>sequenced(r, b)
- * </ul>
- * Then:
- * <ul>
- * <li> the effects of <em>b</em> are constrained
- * by the relation <em>a happens-before b</em>.
- * </ul>
- * <dt><b>[orderAccesses]</b>
- * <dd>Given:
- *
- * <ul>
- * <li><em>p</em>, a reference to an object
- * <li><em>f</em>, an invocation of {@code orderAccesses(p)}
- * </ul>
- * If:
- * <ul>
- * <li>sequenced(f, w)
- * </ul>
- *
- * Then:
- *
- * <ul>
- *
- * <li> <em>f</em> is an element of the <em>synchronization order</em>.
- *
- * </ul>
- * <dt><b>[Reachability]</b>
- * <dd>Given:
- *
- * <ul>
- *
- * <li><em>p</em>, a reference to an object
- *
- * <li><em>f</em>, an invocation of {@code reachabilityFence(p)}
- *
- * <li><em>a</em>, an access where accesses(a, p)
- *
- * <li><em>b</em>, 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 <em>p</em>
- *
- * </ul>
- *
- * If:
- * <ul>
- * <li>sequenced(a, f)
- * </ul>
- *
- * Then:
- *
- * <ul>
- *
- * <li> <em>a happens-before b</em>.
- *
- * </ul>
- *
- * </dl>
- *
- * @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 <T> the type of the reference
- * @return the given ref, to simplify usage
- */
- public static <T> 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 <T> the type of the reference
- * @return the given ref, to simplify usage
- */
- public static <T> 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 <T> the type of the reference
- * @return the given ref, to simplify usage
- */
- public static <T> T orderAccesses(T ref) {
- theVolatile = 0;
- return ref;
- }
-
- /**
- * Ensures that the object referenced by the given reference
- * remains <em>strongly reachable</em> (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.
- *
- * <p>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 0000000..85e3241
--- /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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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}.
+ *
+ * <p>This class extends {@link Number}, but does <em>not</em> 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 <em>NOT</em>
+ * 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
+ * <em>not</em> 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
+ * <a href="../../../../serialized-form.html#java.util.concurrent.atomic.LongAccumulator.SerializationProxy">
+ * SerializationProxy</a>
+ * 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 0000000..a17d52a
--- /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.
+ *
+ * <p>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.
+ *
+ * <p>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<String,LongAdder> freqs},
+ * initializing if not already present, you can use {@code
+ * freqs.computeIfAbsent(key, k -> new LongAdder()).increment();}
+ *
+ * <p>This class extends {@link Number}, but does <em>not</em> 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 <em>NOT</em> 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
+ * <em>not</em> 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
+ * <a href="../../../../serialized-form.html#java.util.concurrent.atomic.LongAdder.SerializationProxy">
+ * SerializationProxy</a>
+ * 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 0000000..fc88849
--- /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 568d2c6..b19dd49 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:
*
- * <pre> {@code boolean compareAndSet(expectedValue, updateValue);}</pre>
+ * <pre> {@code boolean compareAndSet(expectedValue, updateValue);}</pre>
*
* <p>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:
*
- * <pre> {@code
+ * <pre> {@code
* class Sequencer {
* private final AtomicLong sequenceNumber
* = new AtomicLong(0);
@@ -53,31 +53,31 @@
* <pre> {@code long transform(long input)}</pre>
*
* write your utility method as follows:
- * <pre> {@code
+ * <pre> {@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
* }}</pre>
*
* <p>The memory effects for accesses and updates of atomics generally
* follow the rules for volatiles, as stated in
- * <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4">
- * The Java Language Specification (17.4 Memory Model)</a>:
+ * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4">
+ * Chapter 17 of
+ * <cite>The Java™ Language Specification</cite></a>:
*
* <ul>
*
- * <li> {@code get} has the memory effects of reading a
+ * <li>{@code get} has the memory effects of reading a
* {@code volatile} variable.
*
- * <li> {@code set} has the memory effects of writing (assigning) a
+ * <li>{@code set} has the memory effects of writing (assigning) a
* {@code volatile} variable.
*
- * <li> {@code lazySet} has the memory effects of writing (assigning)
+ * <li>{@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}.
*
- * <li> {@code compareAndSet}
+ * <li>{@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 a74fb24..3c10805 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
@@ -49,221 +49,6 @@
protected AbstractQueuedLongSynchronizer() { }
/**
- * Wait queue node class.
- *
- * <p>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.
- *
- * <p>To enqueue into a CLH lock, you atomically splice it in as new
- * tail. To dequeue, you just set the head field.
- * <pre>
- * +------+ prev +-----+ +-----+
- * head | | <---- | | <---- | | tail
- * +------+ +-----+ +-----+
- * </pre>
- *
- * <p>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.
- *
- * <p>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/
- *
- * <p>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.)
- *
- * <p>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.
- *
- * <p>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.
- *
- * <p>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.
- *
- * <p>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:
* If head exists, its waitStatus is guaranteed not to be
@@ -297,7 +82,9 @@
* @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 @@
*
* @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 @@
* 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 @@
* @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 @@
*/
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 @@
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 @@
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 @@
/*
* 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 @@
* 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 @@
// 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 @@
* 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 @@
}
/**
- * 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 @@
* @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 @@
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 @@
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 @@
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 void doAcquireShared(long arg) {
final Node node = addWaiter(Node.SHARED);
- boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
@@ -707,7 +491,6 @@
p.next = null; // help GC
if (interrupted)
selfInterrupt();
- failed = false;
return;
}
}
@@ -715,9 +498,9 @@
parkAndCheckInterrupt())
interrupted = true;
}
- } finally {
- if (failed)
- cancelAcquire(node);
+ } catch (Throwable t) {
+ cancelAcquire(node);
+ throw t;
}
}
@@ -728,7 +511,6 @@
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 @@
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
- failed = false;
return;
}
}
@@ -745,9 +526,9 @@
parkAndCheckInterrupt())
throw new InterruptedException();
}
- } finally {
- if (failed)
- cancelAcquire(node);
+ } catch (Throwable t) {
+ cancelAcquire(node);
+ throw t;
}
}
@@ -764,7 +545,6 @@
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 @@
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 @@
/**
* 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.
*
* <p>In this implementation, this operation returns in
* constant time.
@@ -1140,7 +921,7 @@
}
/**
- * Version of getFirstQueuedThread called when fastpath fails
+ * Version of getFirstQueuedThread called when fastpath fails.
*/
private Thread fullGetFirstQueuedThread() {
/*
@@ -1167,13 +948,11 @@
* 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 @@
*
* <p>An invocation of this method is equivalent to (but may be
* more efficient than):
- * <pre> {@code
- * getFirstQueuedThread() != Thread.currentThread() &&
- * hasQueuedThreads()}</pre>
+ * <pre> {@code
+ * getFirstQueuedThread() != Thread.currentThread()
+ * && hasQueuedThreads()}</pre>
*
* <p>Note that because cancellations due to interrupts and
* timeouts may occur at any time, a {@code true} return does not
@@ -1240,7 +1019,7 @@
* tryAcquire} method for a fair, reentrant, exclusive mode
* synchronizer might look like this:
*
- * <pre> {@code
+ * <pre> {@code
* protected boolean tryAcquire(int arg) {
* if (isHeldExclusively()) {
* // A reentrant acquire; increment hold count
@@ -1276,8 +1055,7 @@
* 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 @@
* @return the collection of threads
*/
public final Collection<Thread> getQueuedThreads() {
- ArrayList<Thread> list = new ArrayList<Thread>();
+ ArrayList<Thread> list = new ArrayList<>();
for (Node p = tail; p != null; p = p.prev) {
Thread t = p.thread;
if (t != null)
@@ -1320,7 +1098,7 @@
* @return the collection of threads
*/
public final Collection<Thread> getExclusiveQueuedThreads() {
- ArrayList<Thread> list = new ArrayList<Thread>();
+ ArrayList<Thread> list = new ArrayList<>();
for (Node p = tail; p != null; p = p.prev) {
if (!p.isShared()) {
Thread t = p.thread;
@@ -1340,7 +1118,7 @@
* @return the collection of threads
*/
public final Collection<Thread> getSharedQueuedThreads() {
- ArrayList<Thread> list = new ArrayList<Thread>();
+ ArrayList<Thread> list = new ArrayList<>();
for (Node p = tail; p != null; p = p.prev) {
if (p.isShared()) {
Thread t = p.thread;
@@ -1361,10 +1139,9 @@
* @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 @@
* @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 @@
/*
* 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 @@
*/
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 @@
* @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 @@
* @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 @@
* 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 @@
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 @@
/**
* Implements uninterruptible condition wait.
* <ol>
- * <li> Save lock state returned by {@link #getState}.
- * <li> Invoke {@link #release} with saved state as argument,
- * throwing IllegalMonitorStateException if it fails.
- * <li> Block until signalled.
- * <li> Reacquire by invoking specialized version of
- * {@link #acquire} with saved state as argument.
+ * <li>Save lock state returned by {@link #getState}.
+ * <li>Invoke {@link #release} with saved state as argument,
+ * throwing IllegalMonitorStateException if it fails.
+ * <li>Block until signalled.
+ * <li>Reacquire by invoking specialized version of
+ * {@link #acquire} with saved state as argument.
* </ol>
*/
public final void awaitUninterruptibly() {
@@ -1769,14 +1546,14 @@
/**
* Implements interruptible condition wait.
* <ol>
- * <li> If current thread is interrupted, throw InterruptedException.
- * <li> Save lock state returned by {@link #getState}.
- * <li> Invoke {@link #release} with saved state as argument,
- * throwing IllegalMonitorStateException if it fails.
- * <li> Block until signalled or interrupted.
- * <li> Reacquire by invoking specialized version of
- * {@link #acquire} with saved state as argument.
- * <li> If interrupted while blocked in step 4, throw InterruptedException.
+ * <li>If current thread is interrupted, throw InterruptedException.
+ * <li>Save lock state returned by {@link #getState}.
+ * <li>Invoke {@link #release} with saved state as argument,
+ * throwing IllegalMonitorStateException if it fails.
+ * <li>Block until signalled or interrupted.
+ * <li>Reacquire by invoking specialized version of
+ * {@link #acquire} with saved state as argument.
+ * <li>If interrupted while blocked in step 4, throw InterruptedException.
* </ol>
*/
public final void await() throws InterruptedException {
@@ -1801,31 +1578,33 @@
/**
* Implements timed condition wait.
* <ol>
- * <li> If current thread is interrupted, throw InterruptedException.
- * <li> Save lock state returned by {@link #getState}.
- * <li> Invoke {@link #release} with saved state as argument,
- * throwing IllegalMonitorStateException if it fails.
- * <li> Block until signalled, interrupted, or timed out.
- * <li> Reacquire by invoking specialized version of
- * {@link #acquire} with saved state as argument.
- * <li> If interrupted while blocked in step 4, throw InterruptedException.
+ * <li>If current thread is interrupted, throw InterruptedException.
+ * <li>Save lock state returned by {@link #getState}.
+ * <li>Invoke {@link #release} with saved state as argument,
+ * throwing IllegalMonitorStateException if it fails.
+ * <li>Block until signalled, interrupted, or timed out.
+ * <li>Reacquire by invoking specialized version of
+ * {@link #acquire} with saved state as argument.
+ * <li>If interrupted while blocked in step 4, throw InterruptedException.
* </ol>
*/
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 @@
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.
* <ol>
- * <li> If current thread is interrupted, throw InterruptedException.
- * <li> Save lock state returned by {@link #getState}.
- * <li> Invoke {@link #release} with saved state as argument,
- * throwing IllegalMonitorStateException if it fails.
- * <li> Block until signalled, interrupted, or timed out.
- * <li> Reacquire by invoking specialized version of
- * {@link #acquire} with saved state as argument.
- * <li> If interrupted while blocked in step 4, throw InterruptedException.
- * <li> If timed out while blocked in step 4, return false, else true.
+ * <li>If current thread is interrupted, throw InterruptedException.
+ * <li>Save lock state returned by {@link #getState}.
+ * <li>Invoke {@link #release} with saved state as argument,
+ * throwing IllegalMonitorStateException if it fails.
+ * <li>Block until signalled, interrupted, or timed out.
+ * <li>Reacquire by invoking specialized version of
+ * {@link #acquire} with saved state as argument.
+ * <li>If interrupted while blocked in step 4, throw InterruptedException.
+ * <li>If timed out while blocked in step 4, return false, else true.
* </ol>
*/
public final boolean awaitUntil(Date deadline)
@@ -1868,7 +1644,7 @@
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 @@
/**
* Implements timed condition wait.
* <ol>
- * <li> If current thread is interrupted, throw InterruptedException.
- * <li> Save lock state returned by {@link #getState}.
- * <li> Invoke {@link #release} with saved state as argument,
- * throwing IllegalMonitorStateException if it fails.
- * <li> Block until signalled, interrupted, or timed out.
- * <li> Reacquire by invoking specialized version of
- * {@link #acquire} with saved state as argument.
- * <li> If interrupted while blocked in step 4, throw InterruptedException.
- * <li> If timed out while blocked in step 4, return false, else true.
+ * <li>If current thread is interrupted, throw InterruptedException.
+ * <li>Save lock state returned by {@link #getState}.
+ * <li>Invoke {@link #release} with saved state as argument,
+ * throwing IllegalMonitorStateException if it fails.
+ * <li>Block until signalled, interrupted, or timed out.
+ * <li>Reacquire by invoking specialized version of
+ * {@link #acquire} with saved state as argument.
+ * <li>If interrupted while blocked in step 4, throw InterruptedException.
+ * <li>If timed out while blocked in step 4, return false, else true.
* </ol>
*/
public final boolean await(long time, TimeUnit unit)
@@ -1904,9 +1680,11 @@
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 @@
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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 Collection<Thread> getWaitingThreads() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
- ArrayList<Thread> list = new ArrayList<Thread>();
+ ArrayList<Thread> 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 @@
* 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 @@
}
/**
- * 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 8823b6f..2c41000 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}:
*
* <ul>
- * <li> {@link #tryAcquire}
- * <li> {@link #tryRelease}
- * <li> {@link #tryAcquireShared}
- * <li> {@link #tryReleaseShared}
- * <li> {@link #isHeldExclusively}
+ * <li>{@link #tryAcquire}
+ * <li>{@link #tryRelease}
+ * <li>{@link #tryAcquireShared}
+ * <li>{@link #tryReleaseShared}
+ * <li>{@link #isHeldExclusively}
* </ul>
*
* 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 <em>fair</em> 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:
*
- * <pre> {@code
+ * <pre> {@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.
*
- * <pre> {@code
+ * <pre> {@code
* class BooleanLatch {
*
* private static class Sync extends AbstractQueuedSynchronizer {
@@ -359,15 +355,15 @@
/** 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 @@
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());
}
- Node(Thread thread, Node mode) { // Used by addWaiter
- this.nextWaiter = mode;
- this.thread = thread;
+ /** Constructor used by addConditionWaiter. */
+ Node(int waitStatus) {
+ U.putInt(this, WAITSTATUS, waitStatus);
+ U.putObject(this, THREAD, Thread.currentThread());
}
- Node(Thread thread, int waitStatus) { // Used by Condition
- this.waitStatus = waitStatus;
- this.thread = thread;
+ /** CASes waitStatus field. */
+ final boolean compareAndSetWaitStatus(int expect, int update) {
+ return U.compareAndSwapInt(this, WAITSTATUS, expect, update);
+ }
+
+ /** 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 @@
*
* @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 @@
* 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 @@
* @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 @@
*/
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 @@
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 @@
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 @@
/*
* 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 @@
* 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 @@
// 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 @@
* 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 @@
}
/**
- * 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 @@
* @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 @@
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 @@
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 @@
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 void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
- boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
@@ -933,7 +959,6 @@
p.next = null; // help GC
if (interrupted)
selfInterrupt();
- failed = false;
return;
}
}
@@ -941,9 +966,9 @@
parkAndCheckInterrupt())
interrupted = true;
}
- } finally {
- if (failed)
- cancelAcquire(node);
+ } catch (Throwable t) {
+ cancelAcquire(node);
+ throw t;
}
}
@@ -954,7 +979,6 @@
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 @@
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
- failed = false;
return;
}
}
@@ -971,9 +994,9 @@
parkAndCheckInterrupt())
throw new InterruptedException();
}
- } finally {
- if (failed)
- cancelAcquire(node);
+ } catch (Throwable t) {
+ cancelAcquire(node);
+ throw t;
}
}
@@ -990,7 +1013,6 @@
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 @@
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 @@
* {@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 @@
* 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 @@
* 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 @@
/**
* 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.
*
* <p>In this implementation, this operation returns in
* constant time.
@@ -1366,7 +1392,7 @@
}
/**
- * Version of getFirstQueuedThread called when fastpath fails
+ * Version of getFirstQueuedThread called when fastpath fails.
*/
private Thread fullGetFirstQueuedThread() {
/*
@@ -1393,13 +1419,11 @@
* 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 @@
*
* <p>An invocation of this method is equivalent to (but may be
* more efficient than):
- * <pre> {@code
- * getFirstQueuedThread() != Thread.currentThread() &&
- * hasQueuedThreads()}</pre>
+ * <pre> {@code
+ * getFirstQueuedThread() != Thread.currentThread()
+ * && hasQueuedThreads()}</pre>
*
* <p>Note that because cancellations due to interrupts and
* timeouts may occur at any time, a {@code true} return does not
@@ -1466,7 +1490,7 @@
* tryAcquire} method for a fair, reentrant, exclusive mode
* synchronizer might look like this:
*
- * <pre> {@code
+ * <pre> {@code
* protected boolean tryAcquire(int arg) {
* if (isHeldExclusively()) {
* // A reentrant acquire; increment hold count
@@ -1502,8 +1526,7 @@
* 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 @@
* @return the collection of threads
*/
public final Collection<Thread> getQueuedThreads() {
- ArrayList<Thread> list = new ArrayList<Thread>();
+ ArrayList<Thread> list = new ArrayList<>();
for (Node p = tail; p != null; p = p.prev) {
Thread t = p.thread;
if (t != null)
@@ -1546,7 +1569,7 @@
* @return the collection of threads
*/
public final Collection<Thread> getExclusiveQueuedThreads() {
- ArrayList<Thread> list = new ArrayList<Thread>();
+ ArrayList<Thread> list = new ArrayList<>();
for (Node p = tail; p != null; p = p.prev) {
if (!p.isShared()) {
Thread t = p.thread;
@@ -1566,7 +1589,7 @@
* @return the collection of threads
*/
public final Collection<Thread> getSharedQueuedThreads() {
- ArrayList<Thread> list = new ArrayList<Thread>();
+ ArrayList<Thread> list = new ArrayList<>();
for (Node p = tail; p != null; p = p.prev) {
if (p.isShared()) {
Thread t = p.thread;
@@ -1587,10 +1610,9 @@
* @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 @@
* @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 @@
/*
* 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 @@
*/
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 @@
* @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 @@
* @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 @@
* 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 @@
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 @@
/**
* Implements uninterruptible condition wait.
* <ol>
- * <li> Save lock state returned by {@link #getState}.
- * <li> Invoke {@link #release} with saved state as argument,
- * throwing IllegalMonitorStateException if it fails.
- * <li> Block until signalled.
- * <li> Reacquire by invoking specialized version of
- * {@link #acquire} with saved state as argument.
+ * <li>Save lock state returned by {@link #getState}.
+ * <li>Invoke {@link #release} with saved state as argument,
+ * throwing IllegalMonitorStateException if it fails.
+ * <li>Block until signalled.
+ * <li>Reacquire by invoking specialized version of
+ * {@link #acquire} with saved state as argument.
* </ol>
*/
public final void awaitUninterruptibly() {
@@ -1993,14 +2015,14 @@
/**
* Implements interruptible condition wait.
* <ol>
- * <li> If current thread is interrupted, throw InterruptedException.
- * <li> Save lock state returned by {@link #getState}.
- * <li> Invoke {@link #release} with saved state as argument,
- * throwing IllegalMonitorStateException if it fails.
- * <li> Block until signalled or interrupted.
- * <li> Reacquire by invoking specialized version of
- * {@link #acquire} with saved state as argument.
- * <li> If interrupted while blocked in step 4, throw InterruptedException.
+ * <li>If current thread is interrupted, throw InterruptedException.
+ * <li>Save lock state returned by {@link #getState}.
+ * <li>Invoke {@link #release} with saved state as argument,
+ * throwing IllegalMonitorStateException if it fails.
+ * <li>Block until signalled or interrupted.
+ * <li>Reacquire by invoking specialized version of
+ * {@link #acquire} with saved state as argument.
+ * <li>If interrupted while blocked in step 4, throw InterruptedException.
* </ol>
*/
public final void await() throws InterruptedException {
@@ -2025,31 +2047,33 @@
/**
* Implements timed condition wait.
* <ol>
- * <li> If current thread is interrupted, throw InterruptedException.
- * <li> Save lock state returned by {@link #getState}.
- * <li> Invoke {@link #release} with saved state as argument,
- * throwing IllegalMonitorStateException if it fails.
- * <li> Block until signalled, interrupted, or timed out.
- * <li> Reacquire by invoking specialized version of
- * {@link #acquire} with saved state as argument.
- * <li> If interrupted while blocked in step 4, throw InterruptedException.
+ * <li>If current thread is interrupted, throw InterruptedException.
+ * <li>Save lock state returned by {@link #getState}.
+ * <li>Invoke {@link #release} with saved state as argument,
+ * throwing IllegalMonitorStateException if it fails.
+ * <li>Block until signalled, interrupted, or timed out.
+ * <li>Reacquire by invoking specialized version of
+ * {@link #acquire} with saved state as argument.
+ * <li>If interrupted while blocked in step 4, throw InterruptedException.
* </ol>
*/
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 @@
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.
* <ol>
- * <li> If current thread is interrupted, throw InterruptedException.
- * <li> Save lock state returned by {@link #getState}.
- * <li> Invoke {@link #release} with saved state as argument,
- * throwing IllegalMonitorStateException if it fails.
- * <li> Block until signalled, interrupted, or timed out.
- * <li> Reacquire by invoking specialized version of
- * {@link #acquire} with saved state as argument.
- * <li> If interrupted while blocked in step 4, throw InterruptedException.
- * <li> If timed out while blocked in step 4, return false, else true.
+ * <li>If current thread is interrupted, throw InterruptedException.
+ * <li>Save lock state returned by {@link #getState}.
+ * <li>Invoke {@link #release} with saved state as argument,
+ * throwing IllegalMonitorStateException if it fails.
+ * <li>Block until signalled, interrupted, or timed out.
+ * <li>Reacquire by invoking specialized version of
+ * {@link #acquire} with saved state as argument.
+ * <li>If interrupted while blocked in step 4, throw InterruptedException.
+ * <li>If timed out while blocked in step 4, return false, else true.
* </ol>
*/
public final boolean awaitUntil(Date deadline)
@@ -2092,7 +2113,7 @@
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 @@
/**
* Implements timed condition wait.
* <ol>
- * <li> If current thread is interrupted, throw InterruptedException.
- * <li> Save lock state returned by {@link #getState}.
- * <li> Invoke {@link #release} with saved state as argument,
- * throwing IllegalMonitorStateException if it fails.
- * <li> Block until signalled, interrupted, or timed out.
- * <li> Reacquire by invoking specialized version of
- * {@link #acquire} with saved state as argument.
- * <li> If interrupted while blocked in step 4, throw InterruptedException.
- * <li> If timed out while blocked in step 4, return false, else true.
+ * <li>If current thread is interrupted, throw InterruptedException.
+ * <li>Save lock state returned by {@link #getState}.
+ * <li>Invoke {@link #release} with saved state as argument,
+ * throwing IllegalMonitorStateException if it fails.
+ * <li>Block until signalled, interrupted, or timed out.
+ * <li>Reacquire by invoking specialized version of
+ * {@link #acquire} with saved state as argument.
+ * <li>If interrupted while blocked in step 4, throw InterruptedException.
+ * <li>If timed out while blocked in step 4, return false, else true.
* </ol>
*/
public final boolean await(long time, TimeUnit unit)
@@ -2128,9 +2149,11 @@
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 @@
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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 Collection<Thread> getWaitingThreads() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
- ArrayList<Thread> list = new ArrayList<Thread>();
+ ArrayList<Thread> 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 @@
* 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 @@
}
/**
- * 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 11a7090..b3132e7 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 @@
* <p>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 @@
* condition still does not hold. Typical uses of this method take
* the following form:
*
- * <pre> {@code
+ * <pre> {@code
* boolean aMethod(long timeout, TimeUnit unit) {
* long nanos = unit.toNanos(timeout);
* lock.lock();
@@ -333,7 +333,7 @@
* Causes the current thread to wait until it is signalled or interrupted,
* or the specified waiting time elapses. This method is behaviorally
* equivalent to:
- * <pre> {@code awaitNanos(unit.toNanos(time)) > 0}</pre>
+ * <pre> {@code awaitNanos(unit.toNanos(time)) > 0}</pre>
*
* @param time the maximum time to wait
* @param unit the time unit of the {@code time} argument
@@ -382,7 +382,7 @@
*
* <p>The return value indicates whether the deadline has elapsed,
* which can be used as follows:
- * <pre> {@code
+ * <pre> {@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 a7ca001..a2d7b48 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:
*
- * <pre> {@code
+ * <pre> {@code
* Lock l = ...;
* l.lock();
* try {
@@ -93,8 +93,9 @@
* <p>All {@code Lock} implementations <em>must</em> enforce the same
* memory synchronization semantics as provided by the built-in monitor
* lock, as described in
- * <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4">
- * The Java Language Specification (17.4 Memory Model)</a>:
+ * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4">
+ * Chapter 17 of
+ * <cite>The Java™ Language Specification</cite></a>:
* <ul>
* <li>A successful {@code lock} operation has the same memory
* synchronization effects as a successful <em>Lock</em> action.
@@ -212,7 +213,7 @@
* immediately with the value {@code false}.
*
* <p>A typical usage idiom for this method would be:
- * <pre> {@code
+ * <pre> {@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 089d818..694f4ca 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 <em>may</em> 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.
*
* <p>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}.
*
* <p>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:
*
- * <pre> {@code
- * while (!canProceed()) { ... LockSupport.park(this); }}</pre>
+ * <pre> {@code
+ * while (!canProceed()) {
+ * // ensure request to unpark is visible to other threads
+ * ...
+ * LockSupport.park(this);
+ * }}</pre>
*
- * 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").
*
* <p><b>Sample Usage.</b> Here is a sketch of a first-in-first-out
* non-reentrant lock class:
- * <pre> {@code
+ * <pre> {@code
* class FIFOMutex {
* private final AtomicBoolean locked = new AtomicBoolean(false);
* private final Queue<Thread> waiters
- * = new ConcurrentLinkedQueue<Thread>();
+ * = 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;
+ * }
* }}</pre>
*/
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 @@
*/
public static void unpark(Thread thread) {
if (thread != null)
- unsafe.unpark(thread);
+ U.unpark(thread);
}
/**
@@ -155,7 +158,7 @@
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 @@
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 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 Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
- return unsafe.getObjectVolatile(t, parkBlockerOffset);
+ return U.getObjectVolatile(t, PARKBLOCKER);
}
/**
@@ -284,7 +287,7 @@
* 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 parkNanos(long nanos) {
if (nanos > 0)
- unsafe.park(false, nanos);
+ U.park(false, nanos);
}
/**
@@ -352,6 +355,40 @@
* 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 8690355..a1a211a 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.
*
* <p>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 3654248..5fedcaa 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:
*
- * <pre> {@code
+ * <pre> {@code
* class X {
* private final ReentrantLock lock = new ReentrantLock();
* // ...
@@ -98,6 +99,7 @@
* 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 @@
return false;
}
+/// OPENJDK-9 @ReservedStackAccess
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
@@ -174,6 +177,7 @@
* 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 @@
* 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 @@
* 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:
*
- * <pre> {@code
+ * <pre> {@code
* if (lock.tryLock() ||
* lock.tryLock(timeout, unit)) {
* ...
@@ -456,7 +461,7 @@
* InterruptedException} will be thrown, and the thread's
* interrupted status will be cleared.
*
- * <li> Waiting threads are signalled in FIFO order.
+ * <li>Waiting threads are signalled in FIFO order.
*
* <li>The ordering of lock reacquisition for threads returning
* from waiting methods is the same as for threads initially
@@ -483,7 +488,7 @@
* not be entered with the lock already held then we can assert that
* fact:
*
- * <pre> {@code
+ * <pre> {@code
* class X {
* ReentrantLock lock = new ReentrantLock();
* // ...
@@ -508,12 +513,12 @@
/**
* Queries if this lock is held by the current thread.
*
- * <p>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:
+ * <p>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:
*
- * <pre> {@code
+ * <pre> {@code
* class X {
* ReentrantLock lock = new ReentrantLock();
* // ...
@@ -527,7 +532,7 @@
* <p>It can also be used to ensure that a reentrant lock is used
* in a non-reentrant manner, for example:
*
- * <pre> {@code
+ * <pre> {@code
* class X {
* ReentrantLock lock = new ReentrantLock();
* // ...
@@ -618,12 +623,11 @@
}
/**
- * 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 cc7ba4c..8969a54 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 @@
*
* <dl>
* <dt><b><i>Non-fair mode (default)</i></b>
- * <dd>When constructed as non-fair (the default), the order of entry
+ * <dd style="font-family:'DejaVu Sans', Arial, Helvetica, sans-serif">
+ * 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.
- * <p>
*
* <dt><b><i>Fair mode</i></b>
- * <dd>When constructed as fair, threads contend for entry using an
+ * <dd style="font-family:'DejaVu Sans', Arial, Helvetica, sans-serif">
+ * 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.)
- * <p>
* </dl>
*
* <li><b>Reentrancy</b>
@@ -147,9 +147,9 @@
* is a class using a TreeMap that is expected to be large and
* concurrently accessed.
*
- * <pre> {@code
+ * <pre> {@code
* class RWDictionary {
- * private final Map<String, Data> m = new TreeMap<String, Data>();
+ * private final Map<String, Data> 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<String> 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 @@
* 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 @@
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 @@
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 @@
} 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 @@
} 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 @@
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 @@
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 @@
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 @@
* permit barging on a fair lock then combine the timed and
* un-timed forms together:
*
- * <pre> {@code
+ * <pre> {@code
* if (lock.tryLock() ||
* lock.tryLock(timeout, unit)) {
* ...
@@ -848,7 +848,12 @@
* Attempts to release this lock.
*
* <p>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 @@
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 @@
* 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 @@
* that does permit barging on a fair lock then combine the
* timed and un-timed forms together:
*
- * <pre> {@code
+ * <pre> {@code
* if (lock.tryLock() ||
* lock.tryLock(timeout, unit)) {
* ...
@@ -1135,7 +1140,7 @@
* InterruptedException} will be thrown, and the thread's
* interrupted status will be cleared.
*
- * <li> Waiting threads are signalled in FIFO order.
+ * <li>Waiting threads are signalled in FIFO order.
*
* <li>The ordering of lock reacquisition for threads returning
* from waiting methods is the same as for threads initially
@@ -1343,7 +1348,7 @@
* 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 @@
"[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 0000000..1f4d54f
--- /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:
+ *
+ * <ul>
+ *
+ * <li><b>Writing.</b> 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.
+ *
+ * <li><b>Reading.</b> 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.
+ *
+ * <li><b>Optimistic Reading.</b> 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.
+ *
+ * </ul>
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p><b>Sample Usage.</b> 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.<br>
+ *
+ * <pre> {@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);
+ * }
+ * }
+ * }}</pre>
+ *
+ * @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 afc8ca4..5dc1228 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.
*
- * <p>Most concurrent Collection implementations (including most
- * Queues) also differ from the usual java.util conventions in that
- * their Iterators provide <em>weakly consistent</em> 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.
+ * <p id="Weakly">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
+ * <em>weakly consistent</em> rather than fast-fail traversal:
+ * <ul>
+ * <li>they may proceed concurrently with other operations
+ * <li>they will never throw {@link java.util.ConcurrentModificationException
+ * ConcurrentModificationException}
+ * <li>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.
+ * </ul>
*
* <h2 id="MemoryVisibility">Memory Consistency Properties</h2>
*
- * <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5">
- * Chapter 17 of the Java Language Specification</a> defines the
+ * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.5">
+ * Chapter 17 of
+ * <cite>The Java™ Language Specification</cite></a> defines the
* <i>happens-before</i> 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 0c5502b..104a6f3 100644
--- a/non_openjdk_java_files.mk
+++ b/non_openjdk_java_files.mk
@@ -88,7 +88,10 @@
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 @@
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 @@
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 @@
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 2afeccf..c14022b 100755
--- a/ojluni/src/main/java/java/lang/Thread.java
+++ b/ojluni/src/main/java/java/lang/Thread.java
@@ -2050,6 +2050,25 @@
}
}
+
+ // 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 ebe2700..44e11f7 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 <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this queue
*/
public abstract class AbstractQueue<E>
extends AbstractCollection<E>
diff --git a/ojluni/src/main/java/java/util/ArrayDeque.java b/ojluni/src/main/java/java/util/ArrayDeque.java
index eb31bcc..83ae076 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 <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this deque
*/
public class ArrayDeque<E> extends AbstractCollection<E>
- implements Deque<E>, Cloneable, java.io.Serializable
+ implements Deque<E>, Cloneable, Serializable
{
/**
* The array in which the elements of the deque are stored.
@@ -96,20 +96,20 @@
* 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 @@
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 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 @@
* @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 @@
* @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 @@
}
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 @@
*
* @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 @@
* @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 @@
* Returns {@code true} if this deque contained the specified element
* (or equivalently, if this deque changed as a result of the call).
*
- * <p>This method is equivalent to {@link #removeFirstOccurrence}.
+ * <p>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 @@
* The following code can be used to dump the deque into a newly
* allocated array of {@code String}:
*
- * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
+ * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
*
* Note that {@code toArray(new Object[0])} is identical in function to
* {@code toArray()}.
@@ -856,6 +852,8 @@
/**
* 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 @@
/**
* 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 @@
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<E> deq, int origin, int fence) {
this.deq = deq;
this.index = origin;
@@ -935,7 +937,7 @@
if (h > t)
t += n;
int m = ((h + t) >>> 1) & (n - 1);
- return new DeqSpliterator<>(deq, h, index = m);
+ return new DeqSpliterator<E>(deq, h, index = m);
}
return null;
}
@@ -960,7 +962,7 @@
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 0000000..d3b5614
--- /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<T> extends CountedCompleter<Void> {
+ final T[] array;
+ final BinaryOperator<T> function;
+ CumulateTask<T> left, right;
+ T in, out;
+ final int lo, hi, origin, fence, threshold;
+
+ /** Root task constructor */
+ public CumulateTask(CumulateTask<T> parent,
+ BinaryOperator<T> 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<T> parent, BinaryOperator<T> 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<T> 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> t = this;
+ outer: while ((l = t.lo) >= 0 && (h = t.hi) <= a.length) {
+ if (h - l > th) {
+ CumulateTask<T> lt = t.left, rt = t.right, f;
+ if (lt == null) { // first pass
+ int mid = (l + h) >>> 1;
+ f = rt = t.right =
+ new CumulateTask<T>(t, fn, a, org, fnc, th, mid, h);
+ t = lt = t.left =
+ new CumulateTask<T>(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<T> par;;) { // propagate
+ @SuppressWarnings("unchecked") CumulateTask<T> partmp
+ = (CumulateTask<T>)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<T> 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<Void> {
+ 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<Void> {
+ 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<Void> {
+ 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 f230677..9fec73b 100644
--- a/ojluni/src/main/java/java/util/Deque.java
+++ b/ojluni/src/main/java/java/util/Deque.java
@@ -59,7 +59,6 @@
* <p>The twelve methods described above are summarized in the
* following table:
*
- * <p>
* <table BORDER CELLPADDING=3 CELLSPACING=1>
* <caption>Summary of Deque methods</caption>
* <tr>
@@ -103,7 +102,6 @@
* inherited from the {@code Queue} interface are precisely equivalent to
* {@code Deque} methods as indicated in the following table:
*
- * <p>
* <table BORDER CELLPADDING=3 CELLSPACING=1>
* <caption>Comparison of Queue and Deque methods</caption>
* <tr>
@@ -142,7 +140,6 @@
* beginning of the deque. Stack methods are precisely equivalent to
* {@code Deque} methods as indicated in the table below:
*
- * <p>
* <table BORDER CELLPADDING=3 CELLSPACING=1>
* <caption>Comparison of Stack and Deque methods</caption>
* <tr>
@@ -190,7 +187,7 @@
* @author Doug Lea
* @author Josh Bloch
* @since 1.6
- * @param <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this deque
*/
public interface Deque<E> extends Queue<E> {
/**
@@ -346,17 +343,18 @@
* 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
- * <tt>(o==null ? e==null : o.equals(e))</tt>
- * (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
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements (optional)
+ * deque does not permit null elements
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
*/
boolean removeFirstOccurrence(Object o);
@@ -364,17 +362,18 @@
* 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
- * <tt>(o==null ? e==null : o.equals(e))</tt>
- * (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
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements (optional)
+ * deque does not permit null elements
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
*/
boolean removeLastOccurrence(Object o);
@@ -519,8 +518,7 @@
* 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
- * <tt>(o==null ? e==null : o.equals(e))</tt>
- * (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 @@
* @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
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements (optional)
+ * deque does not permit null elements
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
*/
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
- * <tt>(o==null ? e==null : o.equals(e))</tt>.
+ * 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
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements (optional)
+ * deque does not permit null elements
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
*/
boolean contains(Object o);
@@ -555,7 +556,7 @@
*
* @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 f59b5a9..74ba701
--- 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.
*
- * <p>This interface takes the place of the <tt>Dictionary</tt> class, which
+ * <p>This interface takes the place of the {@code Dictionary} class, which
* was a totally abstract class rather than an interface.
*
- * <p>The <tt>Map</tt> interface provides three <i>collection views</i>, which
+ * <p>The {@code Map} interface provides three <i>collection views</i>, which
* allow a map's contents to be viewed as a set of keys, collection of values,
* or set of key-value mappings. The <i>order</i> 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 <tt>TreeMap</tt> class, make
- * specific guarantees as to their order; others, like the <tt>HashMap</tt>
+ * elements. Some map implementations, like the {@code TreeMap} class, make
+ * specific guarantees as to their order; others, like the {@code HashMap}
* class, do not.
*
* <p>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 <tt>equals</tt> 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 <tt>equals</tt> and <tt>hashCode</tt> methods are no longer
+ * advised: the {@code equals} and {@code hashCode} methods are no longer
* well defined on such a map.
*
* <p>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 <tt>Map</tt>,
+ * 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 @@
*
* <p>The "destructive" methods contained in this interface, that is, the
* methods that modify the map on which they operate, are specified to throw
- * <tt>UnsupportedOperationException</tt> 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 <tt>UnsupportedOperationException</tt> 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 <tt>NullPointerException</tt> or <tt>ClassCastException</tt>.
+ * 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.
*
- * <p>This interface is a member of the
- * <a href="{@docRoot}/../technotes/guides/collections/index.html">
- * Java Collections Framework</a>.
- *
* <p>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 <tt>true</tt> if and
- * only if this map contains a mapping for a key <tt>k</tt> such that
- * <tt>(key==null ? k==null : key.equals(k))</tt>." This specification should
- * <i>not</i> be construed to imply that invoking <tt>Map.containsKey</tt>
- * with a non-null argument <tt>key</tt> will cause <tt>key.equals(k)</tt> to
- * be invoked for any key <tt>k</tt>. Implementations are free to
- * implement optimizations whereby the <tt>equals</tt> 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
+ * <i>not</i> 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.
*
+ * <p>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 <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*
@@ -121,34 +127,34 @@
* @see Set
* @since 1.2
*/
-public interface Map<K,V> {
+public interface Map<K, V> {
// Query Operations
/**
* Returns the number of key-value mappings in this map. If the
- * map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
- * <tt>Integer.MAX_VALUE</tt>.
+ * 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 <tt>true</tt> if this map contains no key-value mappings.
+ * Returns {@code true} if this map contains no key-value mappings.
*
- * @return <tt>true</tt> if this map contains no key-value mappings
+ * @return {@code true} if this map contains no key-value mappings
*/
boolean isEmpty();
/**
- * Returns <tt>true</tt> if this map contains a mapping for the specified
- * key. More formally, returns <tt>true</tt> if and only if
- * this map contains a mapping for a key <tt>k</tt> such that
- * <tt>(key==null ? k==null : key.equals(k))</tt>. (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 <tt>true</tt> 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 @@
boolean containsKey(Object key);
/**
- * Returns <tt>true</tt> if this map maps one or more keys to the
- * specified value. More formally, returns <tt>true</tt> if and only if
- * this map contains at least one mapping to a value <tt>v</tt> such that
- * <tt>(value==null ? v==null : value.equals(v))</tt>. 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 <tt>Map</tt> interface.
+ * implementations of the {@code Map} interface.
*
* @param value value whose presence in this map is to be tested
- * @return <tt>true</tt> 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 @@
* or {@code null} if this map contains no mapping for the key.
*
* <p>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.)
*
* <p>If this map permits null values, then a return value of
@@ -212,18 +219,18 @@
* 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
- * <tt>m</tt> is said to contain a mapping for a key <tt>k</tt> 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
- * <tt>true</tt>.)
+ * {@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 <tt>key</tt>, or
- * <tt>null</tt> if there was no mapping for <tt>key</tt>.
- * (A <tt>null</tt> return can also indicate that the map
- * previously associated <tt>null</tt> with <tt>key</tt>,
- * if the implementation supports <tt>null</tt> values.)
- * @throws UnsupportedOperationException if the <tt>put</tt> 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 @@
/**
* 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 <tt>k</tt> to value <tt>v</tt> such that
- * <code>(key==null ? k==null : key.equals(k))</code>, 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.)
*
* <p>Returns the value to which this map previously associated the key,
- * or <tt>null</tt> if the map contained no mapping for the key.
+ * or {@code null} if the map contained no mapping for the key.
*
* <p>If this map permits null values, then a return value of
- * <tt>null</tt> does not <i>necessarily</i> indicate that the map
+ * {@code null} does not <i>necessarily</i> indicate that the map
* contained no mapping for the key; it's also possible that the map
- * explicitly mapped the key to <tt>null</tt>.
+ * explicitly mapped the key to {@code null}.
*
* <p>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 <tt>key</tt>, or
- * <tt>null</tt> if there was no mapping for <tt>key</tt>.
- * @throws UnsupportedOperationException if the <tt>remove</tt> 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 @@
* 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 <tt>k</tt> to value <tt>v</tt> 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 <tt>putAll</tt> 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 @@
* Removes all of the mappings from this map (optional operation).
* The map will be empty after this call returns.
*
- * @throws UnsupportedOperationException if the <tt>clear</tt> operation
+ * @throws UnsupportedOperationException if the {@code clear} operation
* is not supported by this map
*/
void clear();
@@ -307,12 +314,12 @@
* 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 <tt>remove</tt> 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
- * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
- * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
- * operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
+ * {@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 @@
* 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 <tt>remove</tt> 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 <tt>Iterator.remove</tt>,
- * <tt>Collection.remove</tt>, <tt>removeAll</tt>,
- * <tt>retainAll</tt> and <tt>clear</tt> operations. It does not
- * support the <tt>add</tt> or <tt>addAll</tt> 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 @@
* 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 <tt>remove</tt> operation, or through the
- * <tt>setValue</tt> 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 <tt>Iterator.remove</tt>,
- * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt> and
- * <tt>clear</tt> operations. It does not support the
- * <tt>add</tt> or <tt>addAll</tt> 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<Map.Entry<K, V>> entrySet();
/**
- * A map entry (key-value pair). The <tt>Map.entrySet</tt> 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
* <i>only</i> way to obtain a reference to a map entry is from the
- * iterator of this collection-view. These <tt>Map.Entry</tt> objects are
+ * iterator of this collection-view. These {@code Map.Entry} objects are
* valid <i>only</i> 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 <tt>setValue</tt> operation on the map entry.
+ * the {@code setValue} operation on the map entry.
*
* @see Map#entrySet()
* @since 1.2
*/
- interface Entry<K,V> {
+ interface Entry<K, V> {
/**
* Returns the key corresponding to this entry.
*
@@ -381,7 +388,7 @@
/**
* Returns the value corresponding to this entry. If the mapping
* has been removed from the backing map (by the iterator's
- * <tt>remove</tt> 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 @@
* 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 <tt>remove</tt> 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 <tt>put</tt> 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 @@
/**
* Compares the specified object with this entry for equality.
- * Returns <tt>true</tt> 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 <tt>e1</tt> and <tt>e2</tt> represent the same mapping
+ * entries {@code e1} and {@code e2} represent the same mapping
* if<pre>
* (e1.getKey()==null ?
* e2.getKey()==null : e1.getKey().equals(e2.getKey())) &&
* (e1.getValue()==null ?
* e2.getValue()==null : e1.getValue().equals(e2.getValue()))
* </pre>
- * This ensures that the <tt>equals</tt> method works properly across
- * different implementations of the <tt>Map.Entry</tt> 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 <tt>true</tt> 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 <tt>e</tt> is defined to be: <pre>
+ * of a map entry {@code e} is defined to be: <pre>
* (e.getKey()==null ? 0 : e.getKey().hashCode()) ^
* (e.getValue()==null ? 0 : e.getValue().hashCode())
* </pre>
- * This ensures that <tt>e1.equals(e2)</tt> implies that
- * <tt>e1.hashCode()==e2.hashCode()</tt> for any two Entries
- * <tt>e1</tt> and <tt>e2</tt>, as required by the general
- * contract of <tt>Object.hashCode</tt>.
+ * 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,12 +469,29 @@
* @see Comparable
* @since 1.8
*/
- public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
+ public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K, V>> comparingByKey() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
/**
+ * Returns a comparator that compares {@link Map.Entry} in natural order on value.
+ *
+ * <p>The returned comparator is serializable and throws {@link
+ * NullPointerException} when comparing an entry with null values.
+ *
+ * @param <K> the type of the map keys
+ * @param <V> 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 <K, V extends Comparable<? super V>> Comparator<Map.Entry<K, V>> comparingByValue() {
+ return (Comparator<Map.Entry<K, V>> & 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 @@
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
}
+
+ /**
+ * Returns a comparator that compares {@link Map.Entry} by value using the given
+ * {@link Comparator}.
+ *
+ * <p>The returned comparator is serializable if the specified comparator
+ * is also serializable.
+ *
+ * @param <K> the type of the map keys
+ * @param <V> 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 <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
+ Objects.requireNonNull(cmp);
+ return (Comparator<Map.Entry<K, V>> & Serializable)
+ (c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
+ }
}
// Comparison and hashing
/**
* Compares the specified object with this map for equality. Returns
- * <tt>true</tt> if the given object is also a map and the two maps
- * represent the same mappings. More formally, two maps <tt>m1</tt> and
- * <tt>m2</tt> represent the same mappings if
- * <tt>m1.entrySet().equals(m2.entrySet())</tt>. This ensures that the
- * <tt>equals</tt> method works properly across different implementations
- * of the <tt>Map</tt> 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 <tt>true</tt> 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
- * <tt>entrySet()</tt> view. This ensures that <tt>m1.equals(m2)</tt>
- * implies that <tt>m1.hashCode()==m2.hashCode()</tt> for any two maps
- * <tt>m1</tt> and <tt>m2</tt>, 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 @@
*/
int hashCode();
+ // Defaultable methods
+
+ /**
+ * Returns the value to which the specified key is mapped, or
+ * {@code defaultValue} if this map contains no mapping for the key.
+ *
+ * @implSpec
+ * 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 the key whose associated value is to be returned
+ * @param defaultValue the default mapping of the key
+ * @return the value to which the specified key is mapped, or
+ * {@code defaultValue} if this map contains no mapping for the key
+ * @throws ClassCastException if the key is of an inappropriate type for
+ * this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws NullPointerException if the specified key is null and this map
+ * does not permit null keys
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @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 @@
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
+ * <p>The default implementation is equivalent to, for this {@code map}:
+ * <pre> {@code
+ * for (Map.Entry<K, V> entry : map.entrySet())
+ * entry.setValue(function.apply(entry.getKey(), entry.getValue()));
+ * }</pre>
+ *
+ * <p>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
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws NullPointerException if function or a replacement value is null,
+ * and this map does not permit null keys or values
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws IllegalArgumentException if some property of a replacement value
+ * prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ConcurrentModificationException if an entry is found to be
+ * removed during iteration
+ * @since 1.8
+ */
+ default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
+ Objects.requireNonNull(function);
+ for (Map.Entry<K, V> 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}:
+ *
+ * <pre> {@code
+ * V v = map.get(key);
+ * if (v == null)
+ * v = map.put(key, value);
+ *
+ * return v;
+ * }</pre>
+ *
+ * <p>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
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the key or value is of an inappropriate
+ * type for this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws NullPointerException if the specified key or value is null,
+ * and this map does not permit null keys or values
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws IllegalArgumentException if some property of the specified key
+ * or value prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @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}:
+ *
+ * <pre> {@code
+ * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
+ * map.remove(key);
+ * return true;
+ * } else
+ * return false;
+ * }</pre>
+ *
+ * <p>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
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the key or value is of an inappropriate
+ * type for this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws NullPointerException if the specified key or value is null,
+ * and this map does not permit null keys or values
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @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}:
+ *
+ * <pre> {@code
+ * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
+ * map.put(key, newValue);
+ * return true;
+ * } else
+ * return false;
+ * }</pre>
+ *
+ * The default implementation does not throw NullPointerException
+ * for maps that do not support null values if oldValue is null unless
+ * newValue is also null.
+ *
+ * <p>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
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @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
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @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}:
+ *
+ * <pre> {@code
+ * if (map.containsKey(key)) {
+ * return map.put(key, value);
+ * } else
+ * return null;
+ * }</pre>
+ *
+ * <p>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
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @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}.
+ *
+ * <p>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:
+ *
+ * <pre> {@code
+ * map.computeIfAbsent(key, k -> new Value(f(k)));
+ * }</pre>
+ *
+ * <p>Or to implement a multi-value map, {@code Map<K,Collection<V>>},
+ * supporting multiple values per key:
+ *
+ * <pre> {@code
+ * map.computeIfAbsent(key, k -> new HashSet<V>()).add(v);
+ * }</pre>
+ *
+ * <p>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:
+ *
+ * <pre> {@code
+ * if (map.get(key) == null) {
+ * V newValue = mappingFunction.apply(key);
+ * if (newValue != null)
+ * map.put(key, newValue);
+ * }
+ * }</pre>
+ *
+ * <p>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.
+ *
+ * <p>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
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws IllegalArgumentException if some property of the specified key
+ * or value prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @since 1.8
+ */
+ default V computeIfAbsent(K key,
+ Function<? super K, ? extends V> 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.
+ *
+ * <p>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.
+ *
+ * <p>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:
+ *
+ * <pre> {@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);
+ * }
+ * }</pre>
+ *
+ * <p>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.
+ *
+ * <p>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
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws IllegalArgumentException if some property of the specified key
+ * or value prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @since 1.8
+ */
+ default V computeIfPresent(K key,
+ BiFunction<? super K, ? super V, ? extends V> 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:
+ *
+ * <pre> {@code
+ * map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))}</pre>
+ * (Method {@link #merge merge()} is often simpler to use for such purposes.)
+ *
+ * <p>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.
+ *
+ * <p>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:
+ *
+ * <pre> {@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;
+ * }
+ * }</pre>
+ *
+ * <p>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.
+ *
+ * <p>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
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws IllegalArgumentException if some property of the specified key
+ * or value prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @since 1.8
+ */
+ default V compute(K key,
+ BiFunction<? super K, ? super V, ? extends V> 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:
+ *
+ * <pre> {@code
+ * map.merge(key, msg, String::concat)
+ * }</pre>
+ *
+ * <p>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.
+ *
+ * <p>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:
+ *
+ * <pre> {@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);
+ * }</pre>
+ *
+ * <p>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.
+ *
+ * <p>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
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws IllegalArgumentException if some property of the specified key
+ * or value prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @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<? super V, ? super V, ? extends V> 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 b0d9453..fdbc0b5 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.
*
* <p>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.
*
- * <p>This interface additionally defines methods {@code firstEntry},
- * {@code pollFirstEntry}, {@code lastEntry}, and
- * {@code pollLastEntry} that return and/or remove the least and
+ * <p>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}.
*
* <p>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 @@
* 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 11a9bdb..412a07d 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.
+ *
+ * <p>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.
*
* <p>The return values of navigation methods may be ambiguous in
* implementations that permit {@code null} elements. However, even
@@ -191,7 +195,7 @@
* 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 b42647e..7c929bd
--- 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 <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this queue
*/
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
@@ -99,7 +99,7 @@
/**
* 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 @@
* The number of times this priority queue has been
* <i>structurally modified</i>. 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 @@
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 @@
* @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 @@
* The following code can be used to dump the queue into a newly
* allocated array of {@code String}:
*
- * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
+ * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
*
* Note that {@code toArray(new Object[0])} is identical in function to
* {@code toArray()}.
@@ -489,7 +489,7 @@
* 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 @@
* We expect that most iterations, even those involving removals,
* will not need to store elements in this field.
*/
- private ArrayDeque<E> forgetMeNot = null;
+ private ArrayDeque<E> 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 @@
* 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 @@
* 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 @@
* (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 @@
* @since 1.8
*/
public final Spliterator<E> spliterator() {
- return new PriorityQueueSpliterator<E>(this, 0, -1, 0);
+ return new PriorityQueueSpliterator<>(this, 0, -1, 0);
}
static final class PriorityQueueSpliterator<E> implements Spliterator<E> {
@@ -822,9 +826,9 @@
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<E> pq, int origin, int fence,
- int expectedModCount) {
+ int expectedModCount) {
this.pq = pq;
this.index = origin;
this.fence = fence;
@@ -843,8 +847,8 @@
public PriorityQueueSpliterator<E> trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid) ? null :
- new PriorityQueueSpliterator<E>(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 b43c6c1..6520337 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.
*
- * <p>
* <table BORDER CELLPADDING=3 CELLSPACING=1>
* <caption>Summary of Queue methods</caption>
* <tr>
@@ -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 <E> the type of elements held in this collection
+ * @param <E> the type of elements held in this queue
*/
public interface Queue<E> extends Collection<E> {
/**
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 0000000..b2a07ec
--- /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:
+ *
+ * <ul>
+ *
+ * <li>Series of generated values pass the DieHarder suite testing
+ * independence and uniformity properties of random number generators.
+ * (Most recently validated with <a
+ * href="http://www.phy.duke.edu/~rgb/General/dieharder.php"> version
+ * 3.31.1</a>.) 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 <em>period</em>
+ * (length of any series of generated values before it repeats) is at
+ * least 2<sup>64</sup>.
+ *
+ * <li>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.
+ *
+ * <li>Instances of SplittableRandom are <em>not</em> 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()}.
+ *
+ * <li>This class provides additional methods for generating random
+ * streams, that employ the above techniques when used in {@code
+ * stream.parallel()} mode.
+ *
+ * </ul>
+ *
+ * <p>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 <clinit> to survive static initialization circularity
+ static {
+ if (java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction<Boolean>() {
+ 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 (2<sup>64</sup>) 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);
+ }
+ }
+ }
+
+}