/* * Copyright (c) 2006, 2007, 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. * * 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. */ /* * @test * @bug 6360946 6360948 * @summary Test various operations on concurrently mutating collections * @author Martin Buchholz */ import static java.util.Collections.*; import java.util.*; import java.util.concurrent.*; public class RacingCollections { /** * How long to run each "race" (in milliseconds). * Turn this up to some higher value like 1000 for stress testing: * java -Dmillis=1000 RacingCollections */ static final long defaultWorkTimeMillis = Long.getLong("millis", 10L); /** * Whether to print debug information. */ static final boolean debug = Boolean.getBoolean("debug"); static final Integer one = 1; static final Integer two = 2; /** * A thread that mutates an object forever, alternating between * being empty and containing singleton "two" */ static class Frobber extends CheckedThread { volatile boolean done = false; boolean keepGoing(int i) { return (i % 128 != 0) || ! done; } final Object elLoco; Frobber(Object elLoco) { this.elLoco = elLoco; this.start(); } @SuppressWarnings("unchecked") void clear(Object o) { if (o instanceof Collection) ((Collection)o).clear(); else ((Map)o).clear(); } @SuppressWarnings("unchecked") void realRun() { // Mutate elLoco wildly forever, checking occasionally for "done" clear(elLoco); if (elLoco instanceof List) { List l = (List) elLoco; for (int i = 0; keepGoing(i); i++) { switch (i%2) { case 0: l.add(two); break; case 1: l.add(0, two); break; } switch (i%2) { case 0: l.remove(two); break; case 1: l.remove(0); break; }}} else if (elLoco instanceof Deque) { Deque q = (Deque) elLoco; for (int i = 0; keepGoing(i); i++) { switch (i%6) { case 0: q.add(two); break; case 1: q.addFirst(two); break; case 2: q.addLast(two); break; case 3: q.offer(two); break; case 4: q.offerFirst(two); break; case 5: q.offerLast(two); break; } switch (i%6) { case 0: q.remove(two); break; case 1: q.removeFirst(); break; case 2: q.removeLast(); break; case 3: q.poll(); break; case 4: q.pollFirst(); break; case 5: q.pollLast(); break; }}} else if (elLoco instanceof Queue) { Queue q = (Queue) elLoco; for (int i = 0; keepGoing(i); i++) { switch (i%2) { case 0: q.add(two); break; case 1: q.offer(two); break; } switch (i%2) { case 0: q.remove(two); break; case 1: q.poll(); break; }}} else if (elLoco instanceof Map) { Map m = (Map) elLoco; for (int i = 0; keepGoing(i); i++) { m.put(two, true); m.remove(two); }} else if (elLoco instanceof Collection) { Collection c = (Collection) elLoco; for (int i = 0; keepGoing(i); i++) { c.add(two); c.remove(two); }} else { throw new Error("Huh? " + elLoco); } } void enoughAlready() { done = true; try { join(); } catch (Throwable t) { unexpected(t); } } } private static void checkEqualSanity(Object theRock, Object elLoco) { //equal(theRock, theRock); equal(elLoco, elLoco); // It would be nice someday to have theRock and elLoco never "equal", // although the meaning of "equal" for mutating collections // is a bit fuzzy. Uncomment when/if we fix: // 6374942: Improve thread safety of collection .equals() methods //notEqual(theRock, elLoco); //notEqual(elLoco, theRock); notEqual(theRock.toString(), elLoco.toString()); } static class Looper { final long quittingTime; int i = 0; Looper() { this(defaultWorkTimeMillis); } Looper(long workTimeMillis) { quittingTime = System.nanoTime() + workTimeMillis * 1024 * 1024; } boolean keepGoing() { return (i++ % 128 != 0) || (System.nanoTime() - quittingTime < 0); } } private static void frob(Object theRock, Object elLoco) { Frobber frobber = new Frobber(elLoco); try { if (theRock instanceof Collection) { @SuppressWarnings("unchecked") Collection c = (Collection) theRock; if (! c.contains(one)) c.add(one); } else { @SuppressWarnings("unchecked") Map m = (Map) theRock; if (! m.containsKey(one)) m.put(one, true); } for (Looper looper = new Looper(); looper.keepGoing(); ) checkEqualSanity(theRock, elLoco); } catch (Throwable t) { unexpected(t); } finally { frobber.enoughAlready(); } } private static List> newConcurrentMaps() { List> list = new ArrayList>(); list.add(new ConcurrentHashMap()); list.add(new ConcurrentSkipListMap()); return list; } private static List> maps() { List> list = newConcurrentMaps(); list.add(new Hashtable()); list.add(new HashMap()); list.add(new TreeMap()); Comparator cmp = new Comparator() { public int compare(Integer x, Integer y) { return x - y; }}; list.add(new TreeMap(Collections.reverseOrder(cmp))); return list; } private static List> newConcurrentSets() { List> list = new ArrayList>(); list.add(new ConcurrentSkipListSet()); list.add(new CopyOnWriteArraySet()); return list; } private static List> newSets() { List> list = newConcurrentSets(); list.add(new HashSet()); list.add(new TreeSet()); list.add(new TreeSet(Collections.reverseOrder())); return list; } private static List> newConcurrentLists() { List> list = new ArrayList>(); list.add(new CopyOnWriteArrayList()); return list; } private static List> newLists() { List> list = newConcurrentLists(); list.add(new Vector()); list.add(new ArrayList()); return list; } private static List> newConcurrentQueues() { List> list = new ArrayList>(newConcurrentDeques()); list.add(new ArrayBlockingQueue(10)); list.add(new LinkedBlockingQueue(10)); list.add(new LinkedTransferQueue()); list.add(new ConcurrentLinkedQueue()); return list; } private static List> newQueues() { List> list = new ArrayList>(newDeques()); list.add(new LinkedBlockingQueue(10)); return list; } private static List> newConcurrentDeques() { List> list = new ArrayList>(); list.add(new LinkedBlockingDeque(10)); list.add(new ConcurrentLinkedDeque()); return list; } private static List> newDeques() { List> list = newConcurrentDeques(); list.add(new ArrayDeque()); list.add(new LinkedList()); return list; } private static void describe(Class k, Object x, Object y) { if (debug) System.out.printf("%s: %s, %s%n", k.getSimpleName(), x.getClass().getSimpleName(), y.getClass().getSimpleName()); } private static void realMain(String[] args) { for (Map x : maps()) for (Map y : newConcurrentMaps()) { describe(Map.class, x, y); x.put(one, true); frob(x, y); frob(unmodifiableMap(x), y); frob(synchronizedMap(x), y); frob(x, synchronizedMap(y)); frob(checkedMap(x, Integer.class, Boolean.class), y); frob(x, checkedMap(y, Integer.class, Boolean.class)); x.clear(); frob(newSetFromMap(x), newSetFromMap(y)); frob(x.keySet(), newSetFromMap(y)); } for (Set x : newSets()) for (Set y : newConcurrentSets()) { describe(Set.class, x, y); frob(x, y); frob(unmodifiableSet(x), y); frob(synchronizedSet(x), y); frob(x, synchronizedSet(y)); frob(checkedSet(x, Integer.class), y); frob(x, checkedSet(y, Integer.class)); } for (List x : newLists()) for (List y : newConcurrentLists()) { describe(List.class, x, y); frob(x, y); frob(unmodifiableList(x), y); frob(synchronizedList(x), y); frob(x, synchronizedList(y)); frob(checkedList(x, Integer.class), y); frob(x, checkedList(y, Integer.class)); } for (Queue x : newQueues()) for (Queue y : newConcurrentQueues()) { describe(Queue.class, x, y); frob(x, y); } for (Deque x : newDeques()) for (Deque y : newConcurrentDeques()) { describe(Deque.class, x, y); frob(asLifoQueue(x), y); frob(x, asLifoQueue(y)); } } //--------------------- Infrastructure --------------------------- static volatile int passed = 0, failed = 0; static void pass() {passed++;} static void fail() {failed++; Thread.dumpStack();} static void fail(String msg) {System.out.println(msg); fail();} static void unexpected(Throwable t) {failed++; t.printStackTrace();} static void check(boolean cond) {if (cond) pass(); else fail();} static String toString(Object x) { return ((x instanceof Collection) || (x instanceof Map)) ? x.getClass().getName() : x.toString();} static void equal(Object x, Object y) { if (x == null ? y == null : x.equals(y)) pass(); else fail(toString(x) + " not equal to " + toString(y));} static void notEqual(Object x, Object y) { if (x == null ? y == null : x.equals(y)) fail(toString(x) + " equal to " + toString(y)); else pass();} public static void main(String[] args) throws Throwable { try {realMain(args);} catch (Throwable t) {unexpected(t);} System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); if (failed > 0) throw new AssertionError("Some tests failed");} private abstract static class CheckedThread extends Thread { abstract void realRun() throws Throwable; public void run() { try { realRun(); } catch (Throwable t) { unexpected(t); }}} }