Move Guava concurrent emulation to JRE.

GWT traditionally avoided concurrency related constructs in JRE
since there is no multithreaded programming in JavaScript. However a
lot of cross-platform app used these APIs to abstract asynchronicity in
JavaScript and share code in different platforms. Since Guava supported
those customers, most of emulation traditionally ended up there.
However now this prevents us to adding new ones to GWT proper and causes
confusion for users.

This patch moves most of such emulation to GWT proper with minor cleanups.

Note that, this patch is carefully prepared to not introduce any blocking
APIs since they cannot be properly emulated and we should rather force
compilation errors on their usage instead of causing runtime errors.
Being said there is need for some exceptions, in this case Future API,
where I documented the rationale behind not removing blocking API.

We need to pay attention to future patches to ensure they follow the
same principles.

Change-Id: I107926d7f3795db9f2fad5f0b24922de4874bf33
Review-Link: https://gwt-review.googlesource.com/#/c/19560/
diff --git a/user/super/com/google/gwt/emul/java/lang/InterruptedException.java b/user/super/com/google/gwt/emul/java/lang/InterruptedException.java
new file mode 100644
index 0000000..68101d2
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/lang/InterruptedException.java
@@ -0,0 +1,31 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2012 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.lang;
+
+/**
+ * Minimal emulation of {@link java.lang.InterruptedException}, that should
+ * only be used in method signatures.
+ */
+public class InterruptedException extends Exception {
+  public InterruptedException() { }
+
+  public InterruptedException(String message) {
+    super(message);
+  }
+}
diff --git a/user/super/com/google/gwt/emul/java/lang/ThreadLocal.java b/user/super/com/google/gwt/emul/java/lang/ThreadLocal.java
new file mode 100644
index 0000000..f2c362f
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/lang/ThreadLocal.java
@@ -0,0 +1,49 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2017 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.lang;
+
+import java.util.function.Supplier;
+
+/**
+ * Provides an implementation of {@link java.lang.ThreadLocal} for GWT.
+ *
+ * @param <T> value type.
+ */
+public class ThreadLocal<T> {
+
+  private T value;
+
+  public T get() {
+    return value;
+  }
+
+  public void set(T value) {
+    this.value = value;
+  }
+
+  public void remove() {
+    value = null;
+  }
+
+  public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
+    ThreadLocal<S> threadLocal = new ThreadLocal<>();
+    threadLocal.set(supplier.get());
+    return threadLocal;
+  }
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/Callable.java b/user/super/com/google/gwt/emul/java/util/concurrent/Callable.java
new file mode 100644
index 0000000..9ab1f44
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/Callable.java
@@ -0,0 +1,28 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2011 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent;
+
+/**
+ * Emulation of Callable.
+ *
+ * @param <V> result of call
+ */
+public interface Callable<V> {
+  V call() throws Exception;
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/CancellationException.java b/user/super/com/google/gwt/emul/java/util/concurrent/CancellationException.java
new file mode 100644
index 0000000..4a8eddc
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/CancellationException.java
@@ -0,0 +1,31 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2015 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent;
+
+/**
+ * Emulation of CancellationException.
+ */
+public class CancellationException extends IllegalStateException {
+
+  public CancellationException() { }
+
+  public CancellationException(String message) {
+    super(message);
+  }
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/ConcurrentHashMap.java b/user/super/com/google/gwt/emul/java/util/concurrent/ConcurrentHashMap.java
new file mode 100644
index 0000000..d8a0371
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/ConcurrentHashMap.java
@@ -0,0 +1,145 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2009 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent;
+
+import java.util.AbstractMap;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Minimal emulation of {@link java.util.concurrent.ConcurrentHashMap}.
+ *
+ * <p>Note that the javascript is single-threaded, it is essentially a {@link java.util.HashMap},
+ * implementing the new methods introduced by {@link ConcurrentMap}.
+ *
+ * @param <K> key type
+ * @param <V> value type
+ */
+public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
+
+  private final Map<K, V> backingMap;
+
+  public ConcurrentHashMap() {
+    this.backingMap = new HashMap<K, V>();
+  }
+
+  public ConcurrentHashMap(int initialCapacity) {
+    this.backingMap = new HashMap<K, V>(initialCapacity);
+  }
+
+  public ConcurrentHashMap(int initialCapacity, float loadFactor) {
+    this.backingMap = new HashMap<K, V>(initialCapacity, loadFactor);
+  }
+
+  public ConcurrentHashMap(Map<? extends K, ? extends V> t) {
+    this.backingMap = new HashMap<K, V>(t);
+  }
+
+  public V putIfAbsent(K key, V value) {
+    if (!containsKey(key)) {
+      return put(key, value);
+    } else {
+      return get(key);
+    }
+  }
+
+  public boolean remove(Object key, Object value) {
+    if (containsKey(key) && get(key).equals(value)) {
+      remove(key);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  public boolean replace(K key, V oldValue, V newValue) {
+    if (oldValue == null || newValue == null) {
+      throw new NullPointerException();
+    } else if (containsKey(key) && get(key).equals(oldValue)) {
+      put(key, newValue);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  public V replace(K key, V value) {
+    if (value == null) {
+      throw new NullPointerException();
+    } else if (containsKey(key)) {
+      return put(key, value);
+    } else {
+      return null;
+    }
+  }
+
+  @Override public boolean containsKey(Object key) {
+    if (key == null) {
+      throw new NullPointerException();
+    }
+    return backingMap.containsKey(key);
+  }
+
+  @Override public V get(Object key) {
+    if (key == null) {
+      throw new NullPointerException();
+    }
+    return backingMap.get(key);
+  }
+
+  @Override public V put(K key, V value) {
+    if (key == null || value == null) {
+      throw new NullPointerException();
+    }
+    return backingMap.put(key, value);
+  }
+
+  @Override public boolean containsValue(Object value) {
+    if (value == null) {
+      throw new NullPointerException();
+    }
+    return backingMap.containsValue(value);
+  }
+
+  @Override public V remove(Object key) {
+    if (key == null) {
+      throw new NullPointerException();
+    }
+    return backingMap.remove(key);
+  }
+
+  @Override public Set<Entry<K, V>> entrySet() {
+    return backingMap.entrySet();
+  }
+
+  public boolean contains(Object value) {
+    return containsValue(value);
+  }
+
+  public Enumeration<V> elements() {
+    return Collections.enumeration(values());
+  }
+
+  public Enumeration<K> keys() {
+    return Collections.enumeration(keySet());
+  }
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/ConcurrentMap.java b/user/super/com/google/gwt/emul/java/util/concurrent/ConcurrentMap.java
new file mode 100644
index 0000000..835186e
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/ConcurrentMap.java
@@ -0,0 +1,38 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2009 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent;
+
+import java.util.Map;
+
+/**
+ * Minimal GWT emulation of a map providing atomic operations.
+ *
+ * @param <K> key type
+ * @param <V> value type
+ */
+public interface ConcurrentMap<K, V> extends Map<K, V> {
+
+  V putIfAbsent(K key, V value);
+
+  boolean remove(Object key, Object value);
+
+  V replace(K key, V value);
+
+  boolean replace(K key, V oldValue, V newValue);
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/Delayed.java b/user/super/com/google/gwt/emul/java/util/concurrent/Delayed.java
new file mode 100644
index 0000000..7dd87c0
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/Delayed.java
@@ -0,0 +1,20 @@
+// CHECKSTYLE_OFF: Copyrighted to members of JCP JSR-166 Expert Group.
+/*
+ * This file is a modified version of
+ * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/Delayed.java?revision=1.11
+ * which contained the following notice:
+ *
+ * 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/
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent;
+
+/**
+ * Emulation of Delayed.
+ */
+public interface Delayed extends Comparable<Delayed> {
+ long getDelay(TimeUnit unit);
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/ExecutionException.java b/user/super/com/google/gwt/emul/java/util/concurrent/ExecutionException.java
new file mode 100644
index 0000000..f861e9c
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/ExecutionException.java
@@ -0,0 +1,38 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2011 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent;
+
+/**
+ * Emulation of ExecutionException.
+ */
+public class ExecutionException extends Exception {
+  protected ExecutionException() { }
+
+  protected ExecutionException(String message) {
+    super(message);
+  }
+
+  public ExecutionException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public ExecutionException(Throwable cause) {
+    super(cause);
+  }
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/Executor.java b/user/super/com/google/gwt/emul/java/util/concurrent/Executor.java
new file mode 100644
index 0000000..19e1edb
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/Executor.java
@@ -0,0 +1,26 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2015 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent;
+
+/**
+ * Emulation of Executor.
+ */
+public interface Executor {
+  void execute(Runnable command);
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/Executors.java b/user/super/com/google/gwt/emul/java/util/concurrent/Executors.java
new file mode 100644
index 0000000..c758c67
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/Executors.java
@@ -0,0 +1,52 @@
+// CHECKSTYLE_OFF: Copyrighted to members of JCP JSR-166 Expert Group.
+/*
+ * This file is a modified version of
+ * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/Executors.java?revision=1.90
+ * which contained the following notice:
+ *
+ * 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/
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent;
+
+/**
+ * Emulation of executors.
+ */
+public class Executors {
+
+  public static <T> Callable<T> callable(Runnable task, T result) {
+    if (task == null) {
+      throw new NullPointerException();
+    }
+    return new RunnableAdapter<T>(task, result);
+  }
+
+  public static Callable<Object> callable(Runnable task) {
+    if (task == null) {
+      throw new NullPointerException();
+    }
+    return new RunnableAdapter<Object>(task, null);
+  }
+
+  private static final class RunnableAdapter<T> implements Callable<T> {
+
+    final Runnable task;
+    final T result;
+
+    RunnableAdapter(Runnable task, T result) {
+      this.task = task;
+      this.result = result;
+    }
+
+    public T call() {
+      task.run();
+      return result;
+    }
+  }
+
+  private Executors() {
+  }
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/Future.java b/user/super/com/google/gwt/emul/java/util/concurrent/Future.java
new file mode 100644
index 0000000..f572719
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/Future.java
@@ -0,0 +1,44 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2015 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent;
+
+/**
+ * Emulation of Future. Since GWT environment is single threaded, attempting to block on the future
+ * by calling {@link #get()} or {@link #get(long, TimeUnit)} when the it is not yet done is
+ * considered illegal because it would lead to a deadlock. Future implementations must throw {@link
+ * IllegalStateException} to avoid a deadlock.
+ *
+ * @param <V> value type returned by the future.
+ */
+public interface Future<V> {
+   boolean cancel(boolean mayInterruptIfRunning);
+
+   boolean isCancelled();
+
+   boolean isDone();
+
+  // Even though the 'get' methods below are blocking, they are the only built-in APIs to get the
+  // result of the {@code Future}, hence they are not removed. The implementation must throw {@link
+  // IllegalStateException} if the {@code Future} is not done yet (see the class javadoc).
+
+   V get() throws InterruptedException, ExecutionException;
+
+   V get(long timeout, TimeUnit unit)
+       throws InterruptedException, ExecutionException, TimeoutException;
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/RejectedExecutionException.java b/user/super/com/google/gwt/emul/java/util/concurrent/RejectedExecutionException.java
new file mode 100644
index 0000000..6f13414
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/RejectedExecutionException.java
@@ -0,0 +1,39 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2015 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent;
+
+/**
+ * GWT emulation of RejectedExecutionException.
+ */
+public class RejectedExecutionException extends RuntimeException {
+  public RejectedExecutionException() {
+  }
+
+  public RejectedExecutionException(String message) {
+    super(message);
+  }
+
+  public RejectedExecutionException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public RejectedExecutionException(Throwable cause) {
+    super(cause);
+  }
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/RunnableFuture.java b/user/super/com/google/gwt/emul/java/util/concurrent/RunnableFuture.java
new file mode 100644
index 0000000..88a7219
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/RunnableFuture.java
@@ -0,0 +1,26 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2015 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent;
+
+/**
+ * Emulation of RunnableFuture.
+ *
+ * @param <V> value type returned by the future.
+ */
+public interface RunnableFuture<V> extends Runnable, Future<V> { }
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/ScheduledFuture.java b/user/super/com/google/gwt/emul/java/util/concurrent/ScheduledFuture.java
new file mode 100644
index 0000000..8acfac5
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/ScheduledFuture.java
@@ -0,0 +1,21 @@
+// CHECKSTYLE_OFF: Copyrighted to members of JCP JSR-166 Expert Group.
+/*
+ * This file is a modified version of
+ * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/ScheduledFuture.java?revision=1.6
+ * which contained the following notice:
+ *
+ * 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/
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent;
+
+/**
+ * Emulation of ScheduleFuture.
+ *
+ * @param <V> value type returned by the future.
+ */
+public interface ScheduledFuture<V> extends Delayed, Future<V> {
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/TimeUnit.java b/user/super/com/google/gwt/emul/java/util/concurrent/TimeUnit.java
new file mode 100644
index 0000000..24dc2a5
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/TimeUnit.java
@@ -0,0 +1,177 @@
+// CHECKSTYLE_OFF: Copyrighted to members of JCP JSR-166 Expert Group.
+/*
+ * This file is a modified version of
+ * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/TimeUnit.java
+ * which contained the following notice:
+ *
+ * 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/
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent;
+
+/**
+ * GWT emulation of TimeUnit, created by removing unsupported operations from
+ * Doug Lea's public domain version.
+ */
+public enum TimeUnit {
+  NANOSECONDS {
+    public long toNanos(long d)   { return d; }
+    public long toMicros(long d)  { return d / C1_C0; }
+    public long toMillis(long d)  { return d / C2_C0; }
+    public long toSeconds(long d) { return d / C3_C0; }
+    public long toMinutes(long d) { return d / C4_C0; }
+    public long toHours(long d)   { return d / C5_C0; }
+    public long toDays(long d)    { return d / C6_C0; }
+    public long convert(long d, TimeUnit u) { return u.toNanos(d); }
+  },
+  MICROSECONDS {
+    public long toNanos(long d)   { return x(d, C1_C0, MAX_C1_C0); }
+    public long toMicros(long d)  { return d; }
+    public long toMillis(long d)  { return d / C2_C1; }
+    public long toSeconds(long d) { return d / C3_C1; }
+    public long toMinutes(long d) { return d / C4_C1; }
+    public long toHours(long d)   { return d / C5_C1; }
+    public long toDays(long d)    { return d / C6_C1; }
+    public long convert(long d, TimeUnit u) { return u.toMicros(d); }
+  },
+  MILLISECONDS {
+    public long toNanos(long d)   { return x(d, C2_C0, MAX_C2_C0); }
+    public long toMicros(long d)  { return x(d, C2_C1, MAX_C2_C1); }
+    public long toMillis(long d)  { return d; }
+    public long toSeconds(long d) { return d / C3_C2; }
+    public long toMinutes(long d) { return d / C4_C2; }
+    public long toHours(long d)   { return d / C5_C2; }
+    public long toDays(long d)    { return d / C6_C2; }
+    public long convert(long d, TimeUnit u) { return u.toMillis(d); }
+  },
+  SECONDS {
+    public long toNanos(long d)   { return x(d, C3_C0, MAX_C3_C0); }
+    public long toMicros(long d)  { return x(d, C3_C1, MAX_C3_C1); }
+    public long toMillis(long d)  { return x(d, C3_C2, MAX_C3_C2); }
+    public long toSeconds(long d) { return d; }
+    public long toMinutes(long d) { return d / C4_C3; }
+    public long toHours(long d)   { return d / C5_C3; }
+    public long toDays(long d)    { return d / C6_C3; }
+    public long convert(long d, TimeUnit u) { return u.toSeconds(d); }
+  },
+  MINUTES {
+    public long toNanos(long d)   { return x(d, C4_C0, MAX_C4_C0); }
+    public long toMicros(long d)  { return x(d, C4_C1, MAX_C4_C1); }
+    public long toMillis(long d)  { return x(d, C4_C2, MAX_C4_C2); }
+    public long toSeconds(long d) { return x(d, C4_C3, MAX_C4_C3); }
+    public long toMinutes(long d) { return d; }
+    public long toHours(long d)   { return d / C5_C4; }
+    public long toDays(long d)    { return d / C6_C4; }
+    public long convert(long d, TimeUnit u) { return u.toMinutes(d); }
+  },
+  HOURS {
+    public long toNanos(long d)   { return x(d, C5_C0, MAX_C5_C0); }
+    public long toMicros(long d)  { return x(d, C5_C1, MAX_C5_C1); }
+    public long toMillis(long d)  { return x(d, C5_C2, MAX_C5_C2); }
+    public long toSeconds(long d) { return x(d, C5_C3, MAX_C5_C3); }
+    public long toMinutes(long d) { return x(d, C5_C4, MAX_C5_C4); }
+    public long toHours(long d)   { return d; }
+    public long toDays(long d)    { return d / C6_C5; }
+    public long convert(long d, TimeUnit u) { return u.toHours(d); }
+  },
+  DAYS {
+    public long toNanos(long d)   { return x(d, C6_C0, MAX_C6_C0); }
+    public long toMicros(long d)  { return x(d, C6_C1, MAX_C6_C1); }
+    public long toMillis(long d)  { return x(d, C6_C2, MAX_C6_C2); }
+    public long toSeconds(long d) { return x(d, C6_C3, MAX_C6_C3); }
+    public long toMinutes(long d) { return x(d, C6_C4, MAX_C6_C4); }
+    public long toHours(long d)   { return x(d, C6_C5, MAX_C6_C5); }
+    public long toDays(long d)    { return d; }
+    public long convert(long d, TimeUnit u) { return u.toDays(d); }
+  };
+
+  // Handy constants for conversion methods
+  private static final long C0 = 1L;
+  private static final long C1 = C0 * 1000L;
+  private static final long C2 = C1 * 1000L;
+  private static final long C3 = C2 * 1000L;
+  private static final long C4 = C3 * 60L;
+  private static final long C5 = C4 * 60L;
+  private static final long C6 = C5 * 24L;
+
+  private static final long MAX = Long.MAX_VALUE;
+
+  private static final long C6_C0 = C6 / C0;
+  private static final long C6_C1 = C6 / C1;
+  private static final long C6_C2 = C6 / C2;
+  private static final long C6_C3 = C6 / C3;
+  private static final long C6_C4 = C6 / C4;
+  private static final long C6_C5 = C6 / C5;
+
+  private static final long C5_C0 = C5 / C0;
+  private static final long C5_C1 = C5 / C1;
+  private static final long C5_C2 = C5 / C2;
+  private static final long C5_C3 = C5 / C3;
+  private static final long C5_C4 = C5 / C4;
+
+  private static final long C4_C0 = C4 / C0;
+  private static final long C4_C1 = C4 / C1;
+  private static final long C4_C2 = C4 / C2;
+  private static final long C4_C3 = C4 / C3;
+
+  private static final long C3_C0 = C3 / C0;
+  private static final long C3_C1 = C3 / C1;
+  private static final long C3_C2 = C3 / C2;
+
+  private static final long C2_C0 = C2 / C0;
+  private static final long C2_C1 = C2 / C1;
+
+  private static final long C1_C0 = C1 / C0;
+
+  private static final long MAX_C6_C0 = MAX / C6_C0;
+  private static final long MAX_C6_C1 = MAX / C6_C1;
+  private static final long MAX_C6_C2 = MAX / C6_C2;
+  private static final long MAX_C6_C3 = MAX / C6_C3;
+  private static final long MAX_C6_C4 = MAX / C6_C4;
+  private static final long MAX_C6_C5 = MAX / C6_C5;
+
+  private static final long MAX_C5_C0 = MAX / C5_C0;
+  private static final long MAX_C5_C1 = MAX / C5_C1;
+  private static final long MAX_C5_C2 = MAX / C5_C2;
+  private static final long MAX_C5_C3 = MAX / C5_C3;
+  private static final long MAX_C5_C4 = MAX / C5_C4;
+
+  private static final long MAX_C4_C0 = MAX / C4_C0;
+  private static final long MAX_C4_C1 = MAX / C4_C1;
+  private static final long MAX_C4_C2 = MAX / C4_C2;
+  private static final long MAX_C4_C3 = MAX / C4_C3;
+
+  private static final long MAX_C3_C0 = MAX / C3_C0;
+  private static final long MAX_C3_C1 = MAX / C3_C1;
+  private static final long MAX_C3_C2 = MAX / C3_C2;
+
+  private static final long MAX_C2_C0 = MAX / C2_C0;
+  private static final long MAX_C2_C1 = MAX / C2_C1;
+
+  private static final long MAX_C1_C0 = MAX / C1_C0;
+
+  static long x(long d, long m, long over) {
+    if (d >  over) return Long.MAX_VALUE;
+    if (d < -over) return Long.MIN_VALUE;
+    return d * m;
+  }
+
+  public abstract long convert(long sourceDuration, TimeUnit sourceUnit);
+
+  public abstract long toNanos(long duration);
+
+  public abstract long toMicros(long duration);
+
+  public abstract long toMillis(long duration);
+
+  public abstract long toSeconds(long duration);
+
+  public abstract long toMinutes(long duration);
+
+  public abstract long toHours(long duration);
+
+  public abstract long toDays(long duration);
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/TimeoutException.java b/user/super/com/google/gwt/emul/java/util/concurrent/TimeoutException.java
new file mode 100644
index 0000000..4c98b67
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/TimeoutException.java
@@ -0,0 +1,30 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2015 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent;
+
+/**
+ * Emulation of TimeoutException.
+ */
+public class TimeoutException extends Exception {
+  public TimeoutException() { }
+
+  public TimeoutException(String message) {
+    super(message);
+  }
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/atomic/AtomicBoolean.java b/user/super/com/google/gwt/emul/java/util/concurrent/atomic/AtomicBoolean.java
new file mode 100644
index 0000000..18defde
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/atomic/AtomicBoolean.java
@@ -0,0 +1,68 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2015 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent.atomic;
+
+/**
+ * GWT emulation of AtomicBoolean.
+ */
+public class AtomicBoolean implements java.io.Serializable {
+  private boolean value;
+
+  public AtomicBoolean(boolean initialValue) {
+    value = initialValue;
+  }
+
+  public AtomicBoolean() {
+  }
+
+  public final boolean get() {
+    return value;
+  }
+
+  public final boolean compareAndSet(boolean expect, boolean update) {
+    if (get() == expect) {
+      set(update);
+      return true;
+    }
+
+    return false;
+  }
+
+  public boolean weakCompareAndSet(boolean expect, boolean update) {
+    return compareAndSet(expect, update);
+  }
+
+  public final void set(boolean newValue) {
+    value = newValue;
+  }
+
+  public final void lazySet(boolean newValue) {
+    set(newValue);
+  }
+
+  public final boolean getAndSet(boolean newValue) {
+    boolean current = get();
+    set(newValue);
+    return current;
+  }
+
+  public String toString() {
+    return Boolean.toString(get());
+  }
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/atomic/AtomicInteger.java b/user/super/com/google/gwt/emul/java/util/concurrent/atomic/AtomicInteger.java
new file mode 100644
index 0000000..d167f08
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/atomic/AtomicInteger.java
@@ -0,0 +1,109 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2009 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent.atomic;
+
+/**
+ * GWT emulated version of {@link AtomicInteger}.  It's a thin wrapper
+ * around the primitive {@code int}.
+ */
+public class AtomicInteger extends Number implements java.io.Serializable {
+
+  private int value;
+
+  public AtomicInteger(int initialValue) {
+    value = initialValue;
+  }
+
+  public AtomicInteger() {
+  }
+
+  public final int get() {
+    return value;
+  }
+
+  public final void set(int newValue) {
+    value = newValue;
+  }
+
+  public final void lazySet(int newValue) {
+    set(newValue);
+  }
+
+  public final int getAndSet(int newValue) {
+    int current = value;
+    value = newValue;
+    return current;
+  }
+
+  public final boolean compareAndSet(int expect, int update) {
+    if (value == expect) {
+      value = update;
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  public final int getAndIncrement() {
+    return value++;
+  }
+
+  public final int getAndDecrement() {
+    return value--;
+  }
+
+  public final int getAndAdd(int delta) {
+    int current = value;
+    value += delta;
+    return current;
+  }
+
+  public final int incrementAndGet() {
+    return ++value;
+  }
+
+  public final int decrementAndGet() {
+    return --value;
+  }
+
+  public final int addAndGet(int delta) {
+    value += delta;
+    return value;
+  }
+
+  @Override public String toString() {
+    return Integer.toString(value);
+  }
+
+  public int intValue() {
+    return value;
+  }
+
+  public long longValue() {
+    return (long) value;
+  }
+
+  public float floatValue() {
+    return (float) value;
+  }
+
+  public double doubleValue() {
+    return (double) value;
+  }
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/atomic/AtomicLong.java b/user/super/com/google/gwt/emul/java/util/concurrent/atomic/AtomicLong.java
new file mode 100644
index 0000000..2e2a5f5
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/atomic/AtomicLong.java
@@ -0,0 +1,109 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2011 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent.atomic;
+
+/**
+ * GWT emulated version of {@link AtomicLong}.  It's a thin wrapper
+ * around the primitive {@code long}.
+ */
+public class AtomicLong extends Number implements java.io.Serializable {
+
+  private long value;
+
+  public AtomicLong(long initialValue) {
+    this.value = initialValue;
+  }
+
+  public AtomicLong() {
+  }
+
+  public final long get() {
+    return value;
+  }
+
+  public final void set(long newValue) {
+    value = newValue;
+  }
+
+  public final void lazySet(long newValue) {
+    set(newValue);
+  }
+
+  public final long getAndSet(long newValue) {
+    long current = value;
+    value = newValue;
+    return current;
+  }
+
+  public final boolean compareAndSet(long expect, long update) {
+    if (value == expect) {
+      value = update;
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  public final long getAndIncrement() {
+    return value++;
+  }
+
+  public final long getAndDecrement() {
+    return value--;
+  }
+
+  public final long getAndAdd(long delta) {
+    long current = value;
+    value += delta;
+    return current;
+  }
+
+  public final long incrementAndGet() {
+    return ++value;
+  }
+
+  public final long decrementAndGet() {
+    return --value;
+  }
+
+  public final long addAndGet(long delta) {
+    value += delta;
+    return value;
+  }
+
+  @Override public String toString() {
+    return Long.toString(value);
+  }
+
+  public int intValue() {
+    return (int) value;
+  }
+
+  public long longValue() {
+    return value;
+  }
+
+  public float floatValue() {
+    return (float) value;
+  }
+
+  public double doubleValue() {
+    return (double) value;
+  }
+}
diff --git a/user/super/com/google/gwt/emul/java/util/concurrent/atomic/AtomicReferenceArray.java b/user/super/com/google/gwt/emul/java/util/concurrent/atomic/AtomicReferenceArray.java
new file mode 100644
index 0000000..08516d2
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/concurrent/atomic/AtomicReferenceArray.java
@@ -0,0 +1,79 @@
+// CHECKSTYLE_OFF: Copyrighted to Guava Authors.
+/*
+ * Copyright (C) 2015 The Guava Authors
+ *
+ * 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
+ *
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.util.concurrent.atomic;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * GWT emulated version of {@link AtomicReferenceArray}.
+ *
+ * @param <V> the element type.
+ */
+public class AtomicReferenceArray<V> {
+
+  private final List<V> values;
+
+  public AtomicReferenceArray(V[] array) {
+    values = new ArrayList<V>(Arrays.asList(array));
+  }
+
+  public AtomicReferenceArray(int length) {
+    values = new ArrayList<V>(Collections.<V>nCopies(length, null));
+  }
+
+  public boolean compareAndSet(int i, V expect, V update) {
+    if (values.get(i) == expect) {
+      values.set(i, update);
+      return true;
+    }
+    return false;
+  }
+
+  public V get(int i) {
+    return values.get(i);
+  }
+
+  public V getAndSet(int i, V x) {
+    return values.set(i, x);
+  }
+
+  public void lazySet(int i, V x) {
+    values.set(i, x);
+  }
+
+  public int length() {
+    return values.size();
+  }
+
+  public void set(int i, V x) {
+    values.set(i, x);
+  }
+
+  public boolean weakCompareAndSet(int i, V expect, V update) {
+    return compareAndSet(i, expect, update);
+  }
+
+  @Override
+  public String toString() {
+    return values.toString();
+  }
+}
diff --git a/user/test/com/google/gwt/emultest/ConcurrentSuite.java b/user/test/com/google/gwt/emultest/ConcurrentSuite.java
new file mode 100644
index 0000000..d8671f3
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/ConcurrentSuite.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * 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
+ *
+ * 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.
+ */
+package com.google.gwt.emultest;
+
+import com.google.gwt.emultest.java.util.concurrent.ConcurrentHashMapTest;
+import com.google.gwt.emultest.java.util.concurrent.TimeUnitTest;
+import com.google.gwt.emultest.java.util.concurrent.atomic.AtomicIntegerTest;
+import com.google.gwt.emultest.java.util.concurrent.atomic.AtomicLongTest;
+import com.google.gwt.emultest.java.util.concurrent.atomic.AtomicReferenceArrayTest;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+/** Test JRE emulation of java.util.concurrent. */
+@RunWith(Suite.class)
+@SuiteClasses({
+  ConcurrentHashMapTest.class,
+  TimeUnitTest.class,
+  AtomicIntegerTest.class,
+  AtomicLongTest.class,
+  AtomicReferenceArrayTest.class,
+})
+public class ConcurrentSuite { }
diff --git a/user/test/com/google/gwt/emultest/EmulSuite.java b/user/test/com/google/gwt/emultest/EmulSuite.java
index 155d866..632f4b8 100644
--- a/user/test/com/google/gwt/emultest/EmulSuite.java
+++ b/user/test/com/google/gwt/emultest/EmulSuite.java
@@ -38,6 +38,7 @@
 import com.google.gwt.emultest.java.lang.StringBufferTest;
 import com.google.gwt.emultest.java.lang.StringTest;
 import com.google.gwt.emultest.java.lang.SystemTest;
+import com.google.gwt.emultest.java.lang.ThreadLocalTest;
 import com.google.gwt.emultest.java.lang.ThrowableStackTraceEmulTest;
 import com.google.gwt.emultest.java.lang.ThrowableTest;
 import com.google.gwt.emultest.java.lang.TypeTest;
@@ -56,14 +57,12 @@
 import org.junit.runners.Suite;
 import org.junit.runners.Suite.SuiteClasses;
 
-/**
- * Test JRE emulations.
- */
+/** Test JRE emulations. */
 @RunWith(Suite.class)
 @SuiteClasses({
   CoercionsTest.class,
 
-  //-- java.io
+  // -- java.io
   ByteArrayInputStreamTest.class,
   ByteArrayOutputStreamTest.class,
   FilterInputStreamTest.class,
@@ -71,7 +70,7 @@
   InputStreamTest.class,
   OutputStreamTest.class,
 
-  //-- java.lang
+  // -- java.lang
   BooleanTest.class,
   ByteTest.class,
   CharacterTest.class,
@@ -88,6 +87,7 @@
   StringBufferTest.class,
   StringTest.class,
   SystemTest.class,
+  ThreadLocalTest.class,
   ThrowableTest.class,
   ThrowableStackTraceEmulTest.class,
   TypeTest.class,
@@ -95,20 +95,20 @@
   // java.lang.reflect
   ArrayTest.class,
 
-  //-- java.math
+  // -- java.math
   // BigDecimal is tested in {@link BigDecimalSuite}
   // BigInteger is tested in {@link BigIntegerSuite}
   RoundingModeTest.class,
   MathContextTest.class,
 
-  //-- java.nio
+  // -- java.nio
   CharsetTest.class,
   StandardCharsetsTest.class,
 
-  //-- java.security
+  // -- java.security
   MessageDigestTest.class,
 
-  //-- java.sql
+  // -- java.sql
   SqlDateTest.class,
   SqlTimeTest.class,
   SqlTimestampTest.class,
diff --git a/user/test/com/google/gwt/emultest/java/lang/ThreadLocalTest.java b/user/test/com/google/gwt/emultest/java/lang/ThreadLocalTest.java
new file mode 100644
index 0000000..5fa2d81
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/lang/ThreadLocalTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * 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
+ *
+ * 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.
+ */
+package com.google.gwt.emultest.java.lang;
+
+import com.google.gwt.emultest.java.util.EmulTestBase;
+
+/**
+ * Tests for {@link java.lang.ThreadLocal}.
+ */
+public class ThreadLocalTest extends EmulTestBase {
+
+  public void testGetSet() {
+    ThreadLocal<String> threadLocal = new ThreadLocal<>();
+    threadLocal.set("testString");
+    assertEquals("testString", threadLocal.get());
+  }
+
+  public void testRemove() {
+    ThreadLocal<String> threadLocal = new ThreadLocal<>();
+    threadLocal.set("testString");
+    assertEquals("testString", threadLocal.get());
+    threadLocal.remove();
+    assertNull(threadLocal.get());
+    threadLocal.remove(); // This shouldn't throw any exception
+  }
+}
diff --git a/user/test/com/google/gwt/emultest/java/util/concurrent/ConcurrentHashMapTest.java b/user/test/com/google/gwt/emultest/java/util/concurrent/ConcurrentHashMapTest.java
new file mode 100644
index 0000000..35d8cca
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/util/concurrent/ConcurrentHashMapTest.java
@@ -0,0 +1,498 @@
+// CHECKSTYLE_OFF: Copyrighted to members of JCP JSR-166 Expert Group.
+/*
+ * 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/licenses/publicdomain
+ * Other contributors include Andrew Wright, Jeffrey Hayes,
+ * Pat Fisher, Mike Judd.
+ */
+// CHECKSTYLE_ON
+package com.google.gwt.emultest.java.util.concurrent;
+
+import com.google.gwt.emultest.java.util.EmulTestBase;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Tests for {@link java.util.concurrent.ConcurrentHashMap}.
+ * It's adopted from tests from {@code
+ * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/test/tck/ConcurrentHashMapTest.java?view=markup}
+ */
+// TODO(hhchan): Fix the type parameters (missing from the original test).
+@SuppressWarnings({"unchecked", "MismatchedQueryAndUpdateOfCollection"})
+public class ConcurrentHashMapTest extends EmulTestBase {
+
+  private static final int ZERO = 0;
+  private static final int ONE = 1;
+  private static final int TWO = 2;
+  private static final int THREE = 3;
+  private static final int FOUR = 4;
+  private static final int FIVE = 5;
+  private static final int SIX = 6;
+
+  /** Create a 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;
+  }
+
+  /** clear removes all pairs */
+  public void testClear() {
+    ConcurrentHashMap map = map5();
+    map.clear();
+    assertEquals(0, map.size());
+  }
+
+  /** Maps with same contents are equal */
+  public void testEquals() {
+    ConcurrentHashMap map1 = map5();
+    ConcurrentHashMap map2 = map5();
+    assertEquals(map1, map2);
+    assertEquals(map2, map1);
+    map1.clear();
+    assertFalse(map1.equals(map2));
+    assertFalse(map2.equals(map1));
+  }
+
+  /** contains returns true for contained value */
+  public void testContains() {
+    ConcurrentHashMap map = map5();
+    assertTrue(map.containsValue("A"));
+    assertFalse(map.containsValue("Z"));
+  }
+
+  /** containsKey returns true for contained key */
+  public void testContainsKey() {
+    ConcurrentHashMap map = map5();
+    assertTrue(map.containsKey(ONE));
+    assertFalse(map.containsKey(ZERO));
+  }
+
+  /** containsValue returns true for held values */
+  public void testContainsValue() {
+    ConcurrentHashMap map = map5();
+    assertTrue(map.containsValue("A"));
+    assertFalse(map.containsValue("Z"));
+  }
+
+  /** enumeration returns an enumeration containing the correct elements */
+  public void testEnumeration() {
+    ConcurrentHashMap map = map5();
+    Enumeration e = map.elements();
+    int count = 0;
+    while (e.hasMoreElements()) {
+      count++;
+      e.nextElement();
+    }
+    assertEquals(5, count);
+  }
+
+  /** get returns the correct element at the given key, or null if not present */
+  public void testGet() {
+    ConcurrentHashMap map = map5();
+    assertEquals("A", (String) map.get(ONE));
+    assertNull(map.get("anything"));
+  }
+
+  /** isEmpty is true of empty map and false for non-empty */
+  public void testIsEmpty() {
+    ConcurrentHashMap empty = new ConcurrentHashMap();
+    ConcurrentHashMap map = map5();
+    assertTrue(empty.isEmpty());
+    assertFalse(map.isEmpty());
+  }
+
+  /** keys returns an enumeration containing all the keys from the map */
+  public void testKeys() {
+    ConcurrentHashMap map = map5();
+    Enumeration e = map.keys();
+    int count = 0;
+    while (e.hasMoreElements()) {
+      count++;
+      e.nextElement();
+    }
+    assertEquals(5, count);
+  }
+
+  /** keySet returns a Set containing all the keys */
+  public void testKeySet() {
+    ConcurrentHashMap map = map5();
+    Set s = map.keySet();
+    assertEquals(5, s.size());
+    assertTrue(s.contains(ONE));
+    assertTrue(s.contains(TWO));
+    assertTrue(s.contains(THREE));
+    assertTrue(s.contains(FOUR));
+    assertTrue(s.contains(FIVE));
+  }
+
+  /** keySet.toArray returns contains all keys */
+  public void testKeySetToArray() {
+    ConcurrentHashMap map = map5();
+    Set s = map.keySet();
+    Object[] ar = s.toArray();
+    assertTrue(s.containsAll(Arrays.asList(ar)));
+    assertEquals(5, ar.length);
+    ar[0] = 10;
+    assertFalse(s.containsAll(Arrays.asList(ar)));
+  }
+
+  /** Values.toArray contains all values */
+  public void testValuesToArray() {
+    ConcurrentHashMap map = map5();
+    Collection v = map.values();
+    Object[] ar = v.toArray();
+    ArrayList s = new ArrayList(Arrays.asList(ar));
+    assertEquals(5, ar.length);
+    assertTrue(s.contains("A"));
+    assertTrue(s.contains("B"));
+    assertTrue(s.contains("C"));
+    assertTrue(s.contains("D"));
+    assertTrue(s.contains("E"));
+  }
+
+  /** entrySet.toArray contains all entries */
+  public void testEntrySetToArray() {
+    ConcurrentHashMap map = map5();
+    Set s = map.entrySet();
+    Object[] ar = s.toArray();
+    assertEquals(5, ar.length);
+    for (int i = 0; i < 5; ++i) {
+      assertTrue(map.containsKey(((Map.Entry) (ar[i])).getKey()));
+      assertTrue(map.containsValue(((Map.Entry) (ar[i])).getValue()));
+    }
+  }
+
+  /** values collection contains all values */
+  public void testValues() {
+    ConcurrentHashMap map = map5();
+    Collection s = map.values();
+    assertEquals(5, s.size());
+    assertTrue(s.contains("A"));
+    assertTrue(s.contains("B"));
+    assertTrue(s.contains("C"));
+    assertTrue(s.contains("D"));
+    assertTrue(s.contains("E"));
+  }
+
+  /** entrySet contains all pairs */
+  public void testEntrySet() {
+    ConcurrentHashMap map = map5();
+    Set s = map.entrySet();
+    assertEquals(5, s.size());
+    for (Object value : s) {
+      Entry e = (Entry) value;
+      assertTrue(
+          (e.getKey().equals(ONE) && e.getValue().equals("A"))
+              || (e.getKey().equals(TWO) && e.getValue().equals("B"))
+              || (e.getKey().equals(THREE) && e.getValue().equals("C"))
+              || (e.getKey().equals(FOUR) && e.getValue().equals("D"))
+              || (e.getKey().equals(FIVE) && e.getValue().equals("E")));
+    }
+  }
+
+  /** putAll adds all key-value pairs from the given map */
+  public void testPutAll() {
+    ConcurrentHashMap empty = new ConcurrentHashMap();
+    ConcurrentHashMap map = map5();
+    empty.putAll(map);
+    assertEquals(5, empty.size());
+    assertTrue(empty.containsKey(ONE));
+    assertTrue(empty.containsKey(TWO));
+    assertTrue(empty.containsKey(THREE));
+    assertTrue(empty.containsKey(FOUR));
+    assertTrue(empty.containsKey(FIVE));
+  }
+
+  /** putIfAbsent works when the given key is not present */
+  public void testPutIfAbsent() {
+    ConcurrentHashMap map = map5();
+    map.putIfAbsent(SIX, "Z");
+    assertTrue(map.containsKey(SIX));
+  }
+
+  /** putIfAbsent does not add the pair if the key is already present */
+  public void testPutIfAbsent2() {
+    ConcurrentHashMap map = map5();
+    assertEquals("A", map.putIfAbsent(ONE, "Z"));
+  }
+
+  /** replace fails when the given key is not present */
+  public void testReplace() {
+    ConcurrentHashMap map = map5();
+    assertNull(map.replace(SIX, "Z"));
+    assertFalse(map.containsKey(SIX));
+  }
+
+  /** replace succeeds if the key is already present */
+  public void testReplace2() {
+    ConcurrentHashMap map = map5();
+    assertNotNull(map.replace(ONE, "Z"));
+    assertEquals("Z", map.get(ONE));
+  }
+
+  /** replace value fails when the given key not mapped to expected value */
+  public void testReplaceValue() {
+    ConcurrentHashMap map = map5();
+    assertEquals("A", map.get(ONE));
+    assertFalse(map.replace(ONE, "Z", "Z"));
+    assertEquals("A", map.get(ONE));
+  }
+
+  /** replace value succeeds when the given key mapped to expected value */
+  public void testReplaceValue2() {
+    ConcurrentHashMap map = map5();
+    assertEquals("A", map.get(ONE));
+    assertTrue(map.replace(ONE, "A", "Z"));
+    assertEquals("Z", map.get(ONE));
+  }
+
+  /** remove removes the correct key-value pair from the map */
+  public void testRemove() {
+    ConcurrentHashMap map = map5();
+    map.remove(FIVE);
+    assertEquals(4, map.size());
+    assertFalse(map.containsKey(FIVE));
+  }
+
+  /** remove(key,value) removes only if pair present */
+  public void testRemove2() {
+    ConcurrentHashMap map = map5();
+    map.remove(FIVE, "E");
+    assertEquals(4, map.size());
+    assertFalse(map.containsKey(FIVE));
+    map.remove(FOUR, "A");
+    assertEquals(4, map.size());
+    assertTrue(map.containsKey(FOUR));
+  }
+
+  /** size returns the correct values */
+  public void testSize() {
+    ConcurrentHashMap map = map5();
+    ConcurrentHashMap empty = new ConcurrentHashMap();
+    assertEquals(0, empty.size());
+    assertEquals(5, map.size());
+  }
+
+  /** toString contains toString of elements */
+  public void testToString() {
+    ConcurrentHashMap map = map5();
+    String s = map.toString();
+    for (int i = 1; i <= 5; ++i) {
+      assertTrue(s.indexOf(String.valueOf(i)) >= 0);
+    }
+  }
+
+  // Exception tests
+
+  /** Cannot create with only negative capacity */
+  public void testConstructor() {
+    try {
+      new ConcurrentHashMap(-1);
+      fail("Exception expected");
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  /** get(null) throws NPE */
+  public void testGet_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.get(null);
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** containsKey(null) throws NPE */
+  public void testContainsKey_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.containsKey(null);
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** containsValue(null) throws NPE */
+  public void testContainsValue_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.containsValue(null);
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** contains(null) throws NPE */
+  public void testContains_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.containsValue(null);
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** put(null,x) throws NPE */
+  public void testPut1_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.put(null, "whatever");
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** put(x, null) throws NPE */
+  public void testPut2_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.put("whatever", null);
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** putIfAbsent(null, x) throws NPE */
+  public void testPutIfAbsent1_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.putIfAbsent(null, "whatever");
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** replace(null, x) throws NPE */
+  public void testReplace_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.replace(null, "whatever");
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** replace(null, x, y) throws NPE */
+  public void testReplaceValue_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.replace(null, ONE, "whatever");
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** putIfAbsent(x, null) throws NPE */
+  public void testPutIfAbsent2_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.putIfAbsent("whatever", null);
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** replace(x, null) throws NPE */
+  public void testReplace2_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.replace("whatever", null);
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** replace(x, null, y) throws NPE */
+  public void testReplaceValue2_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.replace("whatever", null, "A");
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** replace(x, y, null) throws NPE */
+  public void testReplaceValue3_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.replace("whatever", ONE, null);
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** remove(null) throws NPE */
+  public void testRemove1_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.put("sadsdf", "asdads");
+      c.remove(null);
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** remove(null, x) throws NPE */
+  public void testRemove2_NullPointerException() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.put("sadsdf", "asdads");
+      c.remove(null, "whatever");
+      fail("Exception expected");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  /** remove(x, null) returns false */
+  public void testRemove3() {
+    try {
+      ConcurrentHashMap c = new ConcurrentHashMap(5);
+      c.put("sadsdf", "asdads");
+      assertFalse(c.remove("sadsdf", null));
+    } catch (NullPointerException e) {
+      fail();
+    }
+  }
+
+  /** SetValue of an EntrySet entry sets value in the map. */
+  public void testSetValueWriteThrough() {
+    // Adapted from a bug report by Eric Zoerner
+    ConcurrentHashMap map = new ConcurrentHashMap(2);
+    assertTrue(map.isEmpty());
+    for (int i = 0; i < 20; i++) {
+      map.put(new Integer(i), new Integer(i));
+    }
+    assertFalse(map.isEmpty());
+    Map.Entry entry1 = (Map.Entry) map.entrySet().iterator().next();
+
+    if (entry1.getKey().equals(new Integer(16))) {
+      // can't perform the test this time
+      // TODO(cpovirk): Could we just pick a different key?
+      return;
+    }
+
+    // remove 16 (a different key) from map
+    // which just happens to cause entry1 to be cloned in map
+    map.remove(new Integer(16));
+    entry1.setValue("XYZ");
+    assertTrue(map.containsValue("XYZ")); // fails
+  }
+}
diff --git a/user/test/com/google/gwt/emultest/java/util/concurrent/TimeUnitTest.java b/user/test/com/google/gwt/emultest/java/util/concurrent/TimeUnitTest.java
new file mode 100644
index 0000000..39fbdd2
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/util/concurrent/TimeUnitTest.java
@@ -0,0 +1,204 @@
+// CHECKSTYLE_OFF: Copyrighted to members of JCP JSR-166 Expert Group.
+/*
+ * 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/licenses/publicdomain
+ * Other contributors include Andrew Wright, Jeffrey Hayes,
+ * Pat Fisher, Mike Judd.
+ */
+// CHECKSTYLE_ON
+package com.google.gwt.emultest.java.util.concurrent;
+
+import com.google.gwt.emultest.java.util.EmulTestBase;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for TimeUnit.
+ */
+public class TimeUnitTest extends EmulTestBase {
+
+  private static final long SECS_IN_MIN = 60L;
+  private static final long SECS_IN_HOUR = SECS_IN_MIN * 60L;
+  private static final long SECS_IN_DAY = SECS_IN_HOUR * 24L;
+
+  private static final long MILLIS_IN_SEC = 1000L;
+  private static final long MILLIS_IN_MIN = SECS_IN_MIN * MILLIS_IN_SEC;
+  private static final long MILLIS_IN_HOUR = SECS_IN_HOUR * MILLIS_IN_SEC;
+  private static final long MILLIS_IN_DAY = SECS_IN_DAY * MILLIS_IN_SEC;
+
+  private static final long MICROS_IN_SEC = 1000000L;
+  private static final long MICROS_IN_MILLI = 1000L;
+  private static final long MICROS_IN_MIN = SECS_IN_MIN * MICROS_IN_SEC;
+  private static final long MICROS_IN_HOUR = SECS_IN_HOUR * MICROS_IN_SEC;
+  private static final long MICROS_IN_DAY = SECS_IN_DAY * MICROS_IN_SEC;
+
+  private static final long NANOS_IN_SEC = 1000000000L;
+  private static final long NANOS_IN_MILLI = 1000000L;
+  private static final long NANOS_IN_MICRO = 1000L;
+  private static final long NANOS_IN_MIN = SECS_IN_MIN * NANOS_IN_SEC;
+  private static final long NANOS_IN_HOUR = SECS_IN_HOUR * NANOS_IN_SEC;
+  private static final long NANOS_IN_DAY = SECS_IN_DAY * NANOS_IN_SEC;
+
+  // (loops to 88888 check increments at all time divisions.)
+
+  /** convert correctly converts sample values across the units */
+  public void testConvert() {
+    for (long t = 0; t < 88888; t += 500) {
+      assertEquals(t * SECS_IN_DAY, TimeUnit.SECONDS.convert(t, TimeUnit.DAYS));
+      assertEquals(t * SECS_IN_HOUR, TimeUnit.SECONDS.convert(t, TimeUnit.HOURS));
+      assertEquals(t * SECS_IN_MIN, TimeUnit.SECONDS.convert(t, TimeUnit.MINUTES));
+      assertEquals(t, TimeUnit.SECONDS.convert(t, TimeUnit.SECONDS));
+      assertEquals(t, TimeUnit.SECONDS.convert(t * MILLIS_IN_SEC, TimeUnit.MILLISECONDS));
+      assertEquals(t, TimeUnit.SECONDS.convert(t * MICROS_IN_SEC, TimeUnit.MICROSECONDS));
+      assertEquals(t, TimeUnit.SECONDS.convert(t * NANOS_IN_SEC, TimeUnit.NANOSECONDS));
+
+      assertEquals(t * MILLIS_IN_DAY, TimeUnit.MILLISECONDS.convert(t, TimeUnit.DAYS));
+      assertEquals(t * MILLIS_IN_HOUR, TimeUnit.MILLISECONDS.convert(t, TimeUnit.HOURS));
+      assertEquals(t * MILLIS_IN_MIN, TimeUnit.MILLISECONDS.convert(t, TimeUnit.MINUTES));
+      assertEquals(t * MILLIS_IN_SEC, TimeUnit.MILLISECONDS.convert(t, TimeUnit.SECONDS));
+      assertEquals(t, TimeUnit.MILLISECONDS.convert(t, TimeUnit.MILLISECONDS));
+      assertEquals(t, TimeUnit.MILLISECONDS.convert(t * MICROS_IN_MILLI, TimeUnit.MICROSECONDS));
+      assertEquals(t, TimeUnit.MILLISECONDS.convert(t * NANOS_IN_MILLI, TimeUnit.NANOSECONDS));
+
+      assertEquals(t * MICROS_IN_DAY, TimeUnit.MICROSECONDS.convert(t, TimeUnit.DAYS));
+      assertEquals(t * MICROS_IN_HOUR, TimeUnit.MICROSECONDS.convert(t, TimeUnit.HOURS));
+      assertEquals(t * MICROS_IN_MIN, TimeUnit.MICROSECONDS.convert(t, TimeUnit.MINUTES));
+      assertEquals(t * MICROS_IN_SEC, TimeUnit.MICROSECONDS.convert(t, TimeUnit.SECONDS));
+      assertEquals(t * MICROS_IN_MILLI, TimeUnit.MICROSECONDS.convert(t, TimeUnit.MILLISECONDS));
+      assertEquals(t, TimeUnit.MICROSECONDS.convert(t, TimeUnit.MICROSECONDS));
+      assertEquals(t, TimeUnit.MICROSECONDS.convert(t * NANOS_IN_MICRO, TimeUnit.NANOSECONDS));
+
+      assertEquals(t * NANOS_IN_DAY, TimeUnit.NANOSECONDS.convert(t, TimeUnit.DAYS));
+      assertEquals(t * NANOS_IN_HOUR, TimeUnit.NANOSECONDS.convert(t, TimeUnit.HOURS));
+      assertEquals(t * NANOS_IN_MIN, TimeUnit.NANOSECONDS.convert(t, TimeUnit.MINUTES));
+      assertEquals(t * NANOS_IN_SEC, TimeUnit.NANOSECONDS.convert(t, TimeUnit.SECONDS));
+      assertEquals(t * NANOS_IN_MILLI, TimeUnit.NANOSECONDS.convert(t, TimeUnit.MILLISECONDS));
+      assertEquals(t * NANOS_IN_MICRO, TimeUnit.NANOSECONDS.convert(t, TimeUnit.MICROSECONDS));
+      assertEquals(t, TimeUnit.NANOSECONDS.convert(t, TimeUnit.NANOSECONDS));
+    }
+  }
+
+  /** toNanos correctly converts sample values in different units to nanoseconds */
+  public void testToNanos() {
+    for (long t = 0; t < 88888; t += 100) {
+      assertEquals(t * NANOS_IN_DAY, TimeUnit.DAYS.toNanos(t));
+      assertEquals(t * NANOS_IN_HOUR, TimeUnit.HOURS.toNanos(t));
+      assertEquals(t * NANOS_IN_MIN, TimeUnit.MINUTES.toNanos(t));
+      assertEquals(t * NANOS_IN_SEC, TimeUnit.SECONDS.toNanos(t));
+      assertEquals(t * NANOS_IN_MILLI, TimeUnit.MILLISECONDS.toNanos(t));
+      assertEquals(t * NANOS_IN_MICRO, TimeUnit.MICROSECONDS.toNanos(t));
+      assertEquals(t, TimeUnit.NANOSECONDS.toNanos(t));
+    }
+  }
+
+  /** toMicros correctly converts sample values in different units to microseconds */
+  public void testToMicros() {
+    for (long t = 0; t < 88888; t += 100) {
+      assertEquals(t * MICROS_IN_DAY, TimeUnit.DAYS.toMicros(t));
+      assertEquals(t * MICROS_IN_HOUR, TimeUnit.HOURS.toMicros(t));
+      assertEquals(t * MICROS_IN_MIN, TimeUnit.MINUTES.toMicros(t));
+      assertEquals(t * MICROS_IN_SEC, TimeUnit.SECONDS.toMicros(t));
+      assertEquals(t * MICROS_IN_MILLI, TimeUnit.MILLISECONDS.toMicros(t));
+      assertEquals(t, TimeUnit.MICROSECONDS.toMicros(t));
+      assertEquals(t, TimeUnit.NANOSECONDS.toMicros(t * NANOS_IN_MICRO));
+    }
+  }
+
+  /** toMillis correctly converts sample values in different units to milliseconds */
+  public void testToMillis() {
+    for (long t = 0; t < 88888; t += 100) {
+      assertEquals(t * MILLIS_IN_DAY, TimeUnit.DAYS.toMillis(t));
+      assertEquals(t * MILLIS_IN_HOUR, TimeUnit.HOURS.toMillis(t));
+      assertEquals(t * MILLIS_IN_MIN, TimeUnit.MINUTES.toMillis(t));
+      assertEquals(t * MILLIS_IN_SEC, TimeUnit.SECONDS.toMillis(t));
+      assertEquals(t, TimeUnit.MILLISECONDS.toMillis(t));
+      assertEquals(t, TimeUnit.MICROSECONDS.toMillis(t * MICROS_IN_MILLI));
+      assertEquals(t, TimeUnit.NANOSECONDS.toMillis(t * NANOS_IN_MILLI));
+    }
+  }
+
+  /** toSeconds correctly converts sample values in different units to seconds */
+  public void testToSeconds() {
+    for (long t = 0; t < 88888; t += 100) {
+      assertEquals(t * SECS_IN_DAY, TimeUnit.DAYS.toSeconds(t));
+      assertEquals(t * SECS_IN_HOUR, TimeUnit.HOURS.toSeconds(t));
+      assertEquals(t * SECS_IN_MIN, TimeUnit.MINUTES.toSeconds(t));
+      assertEquals(t, TimeUnit.SECONDS.toSeconds(t));
+      assertEquals(t, TimeUnit.MILLISECONDS.toSeconds(t * MILLIS_IN_SEC));
+      assertEquals(t, TimeUnit.MICROSECONDS.toSeconds(t * MICROS_IN_SEC));
+      assertEquals(t, TimeUnit.NANOSECONDS.toSeconds(t * NANOS_IN_SEC));
+    }
+  }
+
+  /** toMinutes correctly converts sample values in different units to minutes */
+  public void testToMinutes() {
+    for (long t = 0; t < 88888; t += 100) {
+      assertEquals(t * SECS_IN_MIN * 24, TimeUnit.DAYS.toMinutes(t));
+      assertEquals(t * SECS_IN_MIN, TimeUnit.HOURS.toMinutes(t));
+      assertEquals(t, TimeUnit.MINUTES.toMinutes(t));
+      assertEquals(t, TimeUnit.SECONDS.toMinutes(t * SECS_IN_MIN));
+      assertEquals(t, TimeUnit.MILLISECONDS.toMinutes(t * MILLIS_IN_MIN));
+      assertEquals(t, TimeUnit.MICROSECONDS.toMinutes(t * MICROS_IN_MIN));
+      assertEquals(t, TimeUnit.NANOSECONDS.toMinutes(t * NANOS_IN_MIN));
+    }
+  }
+
+  /** toHours correctly converts sample values in different units to hours */
+  public void testToHours() {
+    for (long t = 0; t < 88888; t += 100) {
+      assertEquals(t * 24, TimeUnit.DAYS.toHours(t));
+      assertEquals(t, TimeUnit.HOURS.toHours(t));
+      assertEquals(t, TimeUnit.MINUTES.toHours(t * SECS_IN_MIN));
+      assertEquals(t, TimeUnit.SECONDS.toHours(t * SECS_IN_HOUR));
+      assertEquals(t, TimeUnit.MILLISECONDS.toHours(t * MILLIS_IN_HOUR));
+      assertEquals(t, TimeUnit.MICROSECONDS.toHours(t * MICROS_IN_HOUR));
+      assertEquals(t, TimeUnit.NANOSECONDS.toHours(t * NANOS_IN_HOUR));
+    }
+  }
+
+  /** toDays correctly converts sample values in different units to days */
+  public void testToDays() {
+    for (long t = 0; t < 88888; t += 100) {
+      assertEquals(t, TimeUnit.DAYS.toDays(t));
+      assertEquals(t, TimeUnit.HOURS.toDays(t * 24));
+      assertEquals(t, TimeUnit.MINUTES.toDays(t * SECS_IN_MIN * 24));
+      assertEquals(t, TimeUnit.SECONDS.toDays(t * SECS_IN_DAY));
+      assertEquals(t, TimeUnit.MILLISECONDS.toDays(t * MILLIS_IN_DAY));
+      assertEquals(t, TimeUnit.MICROSECONDS.toDays(t * MICROS_IN_DAY));
+      assertEquals(t, TimeUnit.NANOSECONDS.toDays(t * NANOS_IN_DAY));
+    }
+  }
+
+  /**
+   * convert saturates positive too-large values to Long.MAX_VALUE and negative to LONG.MIN_VALUE
+   */
+  public void testConvertSaturate() {
+    assertEquals(
+        Long.MAX_VALUE, TimeUnit.NANOSECONDS.convert(Long.MAX_VALUE / 2, TimeUnit.SECONDS));
+    assertEquals(
+        Long.MIN_VALUE, TimeUnit.NANOSECONDS.convert(-Long.MAX_VALUE / 4, TimeUnit.SECONDS));
+    assertEquals(
+        Long.MAX_VALUE, TimeUnit.NANOSECONDS.convert(Long.MAX_VALUE / 2, TimeUnit.MINUTES));
+    assertEquals(
+        Long.MIN_VALUE, TimeUnit.NANOSECONDS.convert(-Long.MAX_VALUE / 4, TimeUnit.MINUTES));
+    assertEquals(Long.MAX_VALUE, TimeUnit.NANOSECONDS.convert(Long.MAX_VALUE / 2, TimeUnit.HOURS));
+    assertEquals(Long.MIN_VALUE, TimeUnit.NANOSECONDS.convert(-Long.MAX_VALUE / 4, TimeUnit.HOURS));
+    assertEquals(Long.MAX_VALUE, TimeUnit.NANOSECONDS.convert(Long.MAX_VALUE / 2, TimeUnit.DAYS));
+    assertEquals(Long.MIN_VALUE, TimeUnit.NANOSECONDS.convert(-Long.MAX_VALUE / 4, TimeUnit.DAYS));
+  }
+
+  /**
+   * toNanos saturates positive too-large values to Long.MAX_VALUE and negative to LONG.MIN_VALUE
+   */
+  public void testToNanosSaturate() {
+    assertEquals(Long.MAX_VALUE, TimeUnit.MILLISECONDS.toNanos(Long.MAX_VALUE / 2));
+    assertEquals(Long.MIN_VALUE, TimeUnit.MILLISECONDS.toNanos(-Long.MAX_VALUE / 3));
+  }
+
+  /** toString returns string containing common name of unit */
+  public void testToString() {
+    String s = TimeUnit.SECONDS.toString();
+    assertTrue(s.indexOf("ECOND") >= 0);
+  }
+}
diff --git a/user/test/com/google/gwt/emultest/java/util/concurrent/atomic/AtomicIntegerTest.java b/user/test/com/google/gwt/emultest/java/util/concurrent/atomic/AtomicIntegerTest.java
new file mode 100644
index 0000000..52f01c9
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/util/concurrent/atomic/AtomicIntegerTest.java
@@ -0,0 +1,167 @@
+// CHECKSTYLE_OFF: Copyrighted to members of JCP JSR-166 Expert Group.
+/*
+ * 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/licenses/publicdomain
+ * Other contributors include Andrew Wright, Jeffrey Hayes,
+ * Pat Fisher, Mike Judd.
+ */
+// CHECKSTYLE_ON
+package com.google.gwt.emultest.java.util.concurrent.atomic;
+
+import com.google.gwt.emultest.java.util.EmulTestBase;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Tests for {@link java.util.concurrent.atomic.AtomicInteger}.
+ * It's adopted from tests from {@code git://android.git.kernel.org/platform/dalvik.git}:
+ */
+public class AtomicIntegerTest extends EmulTestBase {
+
+  /** constructor initializes to given value */
+  public void testConstructor() {
+    AtomicInteger ai = new AtomicInteger(1);
+    assertEquals(1, ai.get());
+  }
+
+  /** default constructed initializes to zero */
+  public void testConstructor2() {
+    AtomicInteger ai = new AtomicInteger();
+    assertEquals(0, ai.get());
+  }
+
+  /** get returns the last value set */
+  public void testGetSet() {
+    AtomicInteger ai = new AtomicInteger(1);
+    assertEquals(1, ai.get());
+    ai.set(2);
+    assertEquals(2, ai.get());
+    ai.set(-3);
+    assertEquals(-3, ai.get());
+  }
+
+  /** compareAndSet succeeds in changing value if equal to expected else fails */
+  public void testCompareAndSet() {
+    AtomicInteger ai = new AtomicInteger(1);
+    assertTrue(ai.compareAndSet(1, 2));
+    assertTrue(ai.compareAndSet(2, -4));
+    assertEquals(-4, ai.get());
+    assertFalse(ai.compareAndSet(-5, 7));
+    assertFalse((7 == ai.get()));
+    assertTrue(ai.compareAndSet(-4, 7));
+    assertEquals(7, ai.get());
+  }
+
+  /** getAndSet returns previous value and sets to given value */
+  public void testGetAndSet() {
+    AtomicInteger ai = new AtomicInteger(1);
+    assertEquals(1, ai.getAndSet(0));
+    assertEquals(0, ai.getAndSet(-10));
+    assertEquals(-10, ai.getAndSet(1));
+  }
+
+  /** getAndAdd returns previous value and adds given value */
+  public void testGetAndAdd() {
+    AtomicInteger ai = new AtomicInteger(1);
+    assertEquals(1, ai.getAndAdd(2));
+    assertEquals(3, ai.get());
+    assertEquals(3, ai.getAndAdd(-4));
+    assertEquals(-1, ai.get());
+  }
+
+  /** getAndDecrement returns previous value and decrements */
+  public void testGetAndDecrement() {
+    AtomicInteger ai = new AtomicInteger(1);
+    assertEquals(1, ai.getAndDecrement());
+    assertEquals(0, ai.getAndDecrement());
+    assertEquals(-1, ai.getAndDecrement());
+  }
+
+  /** getAndIncrement returns previous value and increments */
+  public void testGetAndIncrement() {
+    AtomicInteger ai = new AtomicInteger(1);
+    assertEquals(1, ai.getAndIncrement());
+    assertEquals(2, ai.get());
+    ai.set(-2);
+    assertEquals(-2, ai.getAndIncrement());
+    assertEquals(-1, ai.getAndIncrement());
+    assertEquals(0, ai.getAndIncrement());
+    assertEquals(1, ai.get());
+  }
+
+  /** addAndGet adds given value to current, and returns current value */
+  public void testAddAndGet() {
+    AtomicInteger ai = new AtomicInteger(1);
+    assertEquals(3, ai.addAndGet(2));
+    assertEquals(3, ai.get());
+    assertEquals(-1, ai.addAndGet(-4));
+    assertEquals(-1, ai.get());
+  }
+
+  /** decrementAndGet decrements and returns current value */
+  public void testDecrementAndGet() {
+    AtomicInteger ai = new AtomicInteger(1);
+    assertEquals(0, ai.decrementAndGet());
+    assertEquals(-1, ai.decrementAndGet());
+    assertEquals(-2, ai.decrementAndGet());
+    assertEquals(-2, ai.get());
+  }
+
+  /** incrementAndGet increments and returns current value */
+  public void testIncrementAndGet() {
+    AtomicInteger ai = new AtomicInteger(1);
+    assertEquals(2, ai.incrementAndGet());
+    assertEquals(2, ai.get());
+    ai.set(-2);
+    assertEquals(-1, ai.incrementAndGet());
+    assertEquals(0, ai.incrementAndGet());
+    assertEquals(1, ai.incrementAndGet());
+    assertEquals(1, ai.get());
+  }
+
+  /** toString returns current value. */
+  public void testToString() {
+    AtomicInteger ai = new AtomicInteger();
+    for (int i = -12; i < 6; ++i) {
+      ai.set(i);
+      assertEquals(ai.toString(), Integer.toString(i));
+    }
+  }
+
+  /** intValue returns current value. */
+  public void testIntValue() {
+    AtomicInteger ai = new AtomicInteger();
+    for (int i = -12; i < 6; ++i) {
+      ai.set(i);
+      assertEquals(i, ai.intValue());
+    }
+  }
+
+  /** longValue returns current value. */
+  public void testLongValue() {
+    AtomicInteger ai = new AtomicInteger();
+    for (int i = -12; i < 6; ++i) {
+      ai.set(i);
+      assertEquals(i, ai.longValue());
+    }
+  }
+
+  /** floatValue returns current value. */
+  public void testFloatValue() {
+    AtomicInteger ai = new AtomicInteger();
+    for (int i = -12; i < 6; ++i) {
+      ai.set(i);
+      assertEquals((float) i, ai.floatValue(), 0.0);
+    }
+  }
+
+  /** doubleValue returns current value. */
+  public void testDoubleValue() {
+    AtomicInteger ai = new AtomicInteger();
+    for (int i = -12; i < 6; ++i) {
+      ai.set(i);
+      assertEquals((double) i, ai.doubleValue(), 0.0);
+    }
+  }
+}
diff --git a/user/test/com/google/gwt/emultest/java/util/concurrent/atomic/AtomicLongTest.java b/user/test/com/google/gwt/emultest/java/util/concurrent/atomic/AtomicLongTest.java
new file mode 100644
index 0000000..6662cf2
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/util/concurrent/atomic/AtomicLongTest.java
@@ -0,0 +1,167 @@
+// CHECKSTYLE_OFF: Copyrighted to members of JCP JSR-166 Expert Group.
+/*
+ * 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/licenses/publicdomain
+ * Other contributors include Andrew Wright, Jeffrey Hayes,
+ * Pat Fisher, Mike Judd
+ */
+// CHECKSTYLE_ON
+package com.google.gwt.emultest.java.util.concurrent.atomic;
+
+import com.google.gwt.emultest.java.util.EmulTestBase;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Tests for {@link java.util.concurrent.atomic.AtomicLong}.
+ * It's adopted from tests from {@code git://android.git.kernel.org/platform/dalvik.git}.
+ */
+public class AtomicLongTest extends EmulTestBase {
+
+  /** constructor initializes to given value */
+  public void testConstructor() {
+    AtomicLong ai = new AtomicLong(1);
+    assertEquals(1, ai.get());
+  }
+
+  /** default constructed initializes to zero */
+  public void testConstructor2() {
+    AtomicLong ai = new AtomicLong();
+    assertEquals(0, ai.get());
+  }
+
+  /** get returns the last value set */
+  public void testGetSet() {
+    AtomicLong ai = new AtomicLong(1);
+    assertEquals(1, ai.get());
+    ai.set(2);
+    assertEquals(2, ai.get());
+    ai.set(-3);
+    assertEquals(-3, ai.get());
+  }
+
+  /** compareAndSet succeeds in changing value if equal to expected else fails */
+  public void testCompareAndSet() {
+    AtomicLong ai = new AtomicLong(1);
+    assertTrue(ai.compareAndSet(1, 2));
+    assertTrue(ai.compareAndSet(2, -4));
+    assertEquals(-4, ai.get());
+    assertFalse(ai.compareAndSet(-5, 7));
+    assertFalse((7 == ai.get()));
+    assertTrue(ai.compareAndSet(-4, 7));
+    assertEquals(7, ai.get());
+  }
+
+  /** getAndSet returns previous value and sets to given value */
+  public void testGetAndSet() {
+    AtomicLong ai = new AtomicLong(1);
+    assertEquals(1, ai.getAndSet(0));
+    assertEquals(0, ai.getAndSet(-10));
+    assertEquals(-10, ai.getAndSet(1));
+  }
+
+  /** getAndAdd returns previous value and adds given value */
+  public void testGetAndAdd() {
+    AtomicLong ai = new AtomicLong(1);
+    assertEquals(1, ai.getAndAdd(2));
+    assertEquals(3, ai.get());
+    assertEquals(3, ai.getAndAdd(-4));
+    assertEquals(-1, ai.get());
+  }
+
+  /** getAndDecrement returns previous value and decrements */
+  public void testGetAndDecrement() {
+    AtomicLong ai = new AtomicLong(1);
+    assertEquals(1, ai.getAndDecrement());
+    assertEquals(0, ai.getAndDecrement());
+    assertEquals(-1, ai.getAndDecrement());
+  }
+
+  /** getAndIncrement returns previous value and increments */
+  public void testGetAndIncrement() {
+    AtomicLong ai = new AtomicLong(1);
+    assertEquals(1, ai.getAndIncrement());
+    assertEquals(2, ai.get());
+    ai.set(-2);
+    assertEquals(-2, ai.getAndIncrement());
+    assertEquals(-1, ai.getAndIncrement());
+    assertEquals(0, ai.getAndIncrement());
+    assertEquals(1, ai.get());
+  }
+
+  /** addAndGet adds given value to current, and returns current value */
+  public void testAddAndGet() {
+    AtomicLong ai = new AtomicLong(1);
+    assertEquals(3, ai.addAndGet(2));
+    assertEquals(3, ai.get());
+    assertEquals(-1, ai.addAndGet(-4));
+    assertEquals(-1, ai.get());
+  }
+
+  /** decrementAndGet decrements and returns current value */
+  public void testDecrementAndGet() {
+    AtomicLong ai = new AtomicLong(1);
+    assertEquals(0, ai.decrementAndGet());
+    assertEquals(-1, ai.decrementAndGet());
+    assertEquals(-2, ai.decrementAndGet());
+    assertEquals(-2, ai.get());
+  }
+
+  /** incrementAndGet increments and returns current value */
+  public void testIncrementAndGet() {
+    AtomicLong ai = new AtomicLong(1);
+    assertEquals(2, ai.incrementAndGet());
+    assertEquals(2, ai.get());
+    ai.set(-2);
+    assertEquals(-1, ai.incrementAndGet());
+    assertEquals(0, ai.incrementAndGet());
+    assertEquals(1, ai.incrementAndGet());
+    assertEquals(1, ai.get());
+  }
+
+  /** toString returns current value. */
+  public void testToString() {
+    AtomicLong ai = new AtomicLong();
+    for (long i = -12; i < 6; ++i) {
+      ai.set(i);
+      assertEquals(ai.toString(), Long.toString(i));
+    }
+  }
+
+  /** intValue returns current value. */
+  public void testIntValue() {
+    AtomicLong ai = new AtomicLong();
+    for (long i = -12; i < 6; ++i) {
+      ai.set(i);
+      assertEquals(i, ai.intValue());
+    }
+  }
+
+  /** longValue returns current value. */
+  public void testLongValue() {
+    AtomicLong ai = new AtomicLong();
+    for (long i = -12; i < 6; ++i) {
+      ai.set(i);
+      assertEquals(i, ai.longValue());
+    }
+  }
+
+  /** floatValue returns current value. */
+  public void testFloatValue() {
+    AtomicLong ai = new AtomicLong();
+    for (long i = -12; i < 6; ++i) {
+      ai.set(i);
+      assertEquals((float) i, ai.floatValue(), 0.0);
+    }
+  }
+
+  /** doubleValue returns current value. */
+  public void testDoubleValue() {
+    AtomicLong ai = new AtomicLong();
+    for (long i = -12; i < 6; ++i) {
+      ai.set(i);
+      assertEquals((double) i, ai.doubleValue(), 0.0);
+    }
+  }
+}
diff --git a/user/test/com/google/gwt/emultest/java/util/concurrent/atomic/AtomicReferenceArrayTest.java b/user/test/com/google/gwt/emultest/java/util/concurrent/atomic/AtomicReferenceArrayTest.java
new file mode 100644
index 0000000..19960b7
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/util/concurrent/atomic/AtomicReferenceArrayTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * 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
+ *
+ * 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.
+ */
+package com.google.gwt.emultest.java.util.concurrent.atomic;
+
+import com.google.gwt.emultest.java.util.EmulTestBase;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ * Tests for {@link java.util.concurrent.atomic.AtomicReferenceArray}.
+ */
+public class AtomicReferenceArrayTest extends EmulTestBase {
+
+  public void testArrayConstructor() {
+    Object object = new Object();
+
+    AtomicReferenceArray<Object> refArray = new AtomicReferenceArray<>(new Object[] {object});
+
+    assertSame(object, refArray.get(0));
+    assertEquals(1, refArray.length());
+  }
+
+  public void testLengthConstructor() {
+    AtomicReferenceArray<Object> refArray = new AtomicReferenceArray<>(1);
+
+    assertNull(refArray.get(0));
+    assertSame(1, refArray.length());
+  }
+
+  public void testCompareAndSet() {
+    Object expect = new ArrayList<Object>();
+    // This object is the same by |.equals| equality, but |compareAndSet| should use |==|.
+    Object notExpect = new ArrayList<Object>();
+    Object update = new Object();
+
+    AtomicReferenceArray<Object> refArray = new AtomicReferenceArray<>(new Object[] {expect});
+
+    assertFalse(refArray.compareAndSet(0, notExpect, new Object()));
+    assertSame(expect, refArray.get(0));
+    assertTrue(refArray.compareAndSet(0, expect, update));
+    assertSame(update, refArray.get(0));
+  }
+
+  public void testGet() {
+    Object expect = new Object();
+
+    AtomicReferenceArray<Object> refArray = new AtomicReferenceArray<>(new Object[] {expect});
+
+    assertSame(expect, refArray.get(0));
+  }
+
+  public void testGetAndSet() {
+    Object expect = new Object();
+    Object update = new Object();
+
+    AtomicReferenceArray<Object> refArray = new AtomicReferenceArray<>(new Object[] {expect});
+
+    assertSame(expect, refArray.getAndSet(0, update));
+    assertSame(update, refArray.get(0));
+  }
+
+  public void testLazySet() {
+    Object update = new Object();
+
+    AtomicReferenceArray<Object> refArray = new AtomicReferenceArray<>(new Object[] {new Object()});
+    refArray.lazySet(0, update);
+
+    assertSame(update, refArray.get(0));
+  }
+
+  public void testZeroLength() {
+    AtomicReferenceArray<Object> refArray = new AtomicReferenceArray<>(new Object[] {});
+
+    assertEquals(0, refArray.length());
+  }
+
+  public void testZeroLengthWithLengthConstructor() {
+    AtomicReferenceArray<Object> refArray = new AtomicReferenceArray<>(0);
+
+    assertEquals(0, refArray.length());
+  }
+
+  public void testSet() {
+    Object update = new Object();
+
+    AtomicReferenceArray<Object> refArray = new AtomicReferenceArray<>(new Object[] {new Object()});
+    refArray.lazySet(0, update);
+
+    assertSame(update, refArray.get(0));
+  }
+
+  public void testWeakCompareAndSet() {
+    Object expect = new ArrayList<Object>();
+    // This object is the same by |.equals| equality, but |compareAndSet| should use |==|.
+    Object notExpect = new ArrayList<Object>();
+    Object update = new Object();
+
+    AtomicReferenceArray<Object> refArray = new AtomicReferenceArray<>(new Object[] {expect});
+
+    assertFalse(refArray.compareAndSet(0, notExpect, new Object()));
+    assertTrue(refArray.compareAndSet(0, expect, update));
+    assertSame(update, refArray.get(0));
+  }
+}