From c0d178d5f111688542b064e66a9de74d2ce77d88 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 7 Mar 2014 09:08:19 -0700 Subject: [PATCH] implement ConcurrentHashMap and AtomicReferenceArray This is the simplest possible ConcurrentHashMap I could come up with that works and is actually concurrent in the way one would expect. It's pretty unconventional, being based on a persistent red-black tree, and not particularly memory-efficient or cache-friendly. I think this is a good place to start, though, and it should perform reasonably well for most workloads. Patches for a more efficient implementation are welcome! I also implemented AtomicReferenceArray, since I was using it in my first, naive attempt to implement ConcurrentHashMap. I had to do a bit of refactoring, including moving some non-standard stuff from java.util.Collections to avian.Data so I could make it available to code outside the java.util package, which is why I had to modify several unrelated files. --- classpath/avian/Data.java | 310 +++++++++++++ classpath/java/util/AbstractCollection.java | 4 +- classpath/java/util/ArrayList.java | 2 +- classpath/java/util/Collections.java | 92 +--- classpath/java/util/HashMap.java | 184 ++------ classpath/java/util/HashSet.java | 2 +- classpath/java/util/LinkedHashMap.java | 10 +- classpath/java/util/LinkedHashSet.java | 2 +- classpath/java/util/LinkedList.java | 2 +- classpath/java/util/TreeMap.java | 10 +- classpath/java/util/TreeSet.java | 2 +- .../util/concurrent/ConcurrentHashMap.java | 430 ++++++++++++++++++ .../java/util/concurrent/ConcurrentMap.java | 23 + .../concurrent/atomic/AtomicReference.java | 4 +- .../atomic/AtomicReferenceArray.java | 69 +++ classpath/sun/misc/Unsafe.java | 2 + src/builtin.cpp | 22 + src/classpath-openjdk.cpp | 22 - test/Concurrent.java | 166 +++++++ 19 files changed, 1074 insertions(+), 284 deletions(-) create mode 100644 classpath/avian/Data.java create mode 100644 classpath/java/util/concurrent/ConcurrentHashMap.java create mode 100644 classpath/java/util/concurrent/ConcurrentMap.java create mode 100644 classpath/java/util/concurrent/atomic/AtomicReferenceArray.java create mode 100644 test/Concurrent.java diff --git a/classpath/avian/Data.java b/classpath/avian/Data.java new file mode 100644 index 0000000000..a725d8150e --- /dev/null +++ b/classpath/avian/Data.java @@ -0,0 +1,310 @@ +/* Copyright (c) 2008-2014, Avian Contributors + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + There is NO WARRANTY for this software. See license.txt for + details. */ + +package avian; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Collections; + +public class Data { + public static int nextPowerOfTwo(int n) { + int r = 1; + while (r < n) r <<= 1; + return r; + } + + public static boolean equal(V a, V b) { + return a == null ? b == null : a.equals(b); + } + + public static T[] toArray(Collection collection, T[] array) { + Class c = array.getClass().getComponentType(); + + if (array.length < collection.size()) { + array = (T[]) java.lang.reflect.Array.newInstance(c, collection.size()); + } + + int i = 0; + for (Object o: collection) { + if (c.isInstance(o)) { + array[i++] = (T) o; + } else { + throw new ArrayStoreException(); + } + } + + return array; + } + + public static String toString(Collection c) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (Iterator it = c.iterator(); it.hasNext();) { + sb.append(it.next()); + if (it.hasNext()) { + sb.append(","); + } + } + sb.append("]"); + return sb.toString(); + } + + public static String toString(Map m) { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + for (Iterator it = m.entrySet().iterator(); it.hasNext();) { + Entry e = it.next(); + sb.append(e.getKey()) + .append("=") + .append(e.getValue()); + if (it.hasNext()) { + sb.append(","); + } + } + sb.append("}"); + return sb.toString(); + } + + public interface EntryMap { + public int size(); + + public Entry find(Object key); + + public Entry remove(Object key); + + public void clear(); + + public Iterator> iterator(); + } + + public static class EntrySet extends AbstractSet> { + private final EntryMap map; + + public EntrySet(EntryMap map) { + this.map = map; + } + + public int size() { + return map.size(); + } + + public boolean isEmpty() { + return map.size() == 0; + } + + public boolean contains(Object o) { + return (o instanceof Entry) + && map.find(((Entry)o).getKey()) != null; + } + + public boolean add(Entry e) { + throw new UnsupportedOperationException(); + } + + public boolean remove(Object o) { + return (o instanceof Entry) + && map.remove(((Entry) o).getKey()) != null; + } + + public boolean remove(Entry e) { + return map.remove(e.getKey()) != null; + } + + public Object[] toArray() { + return toArray(new Object[size()]); + } + + public T[] toArray(T[] array) { + return toArray(this, array); + } + + public void clear() { + map.clear(); + } + + public Iterator> iterator() { + return map.iterator(); + } + } + + public static class KeySet extends AbstractSet { + private final EntryMap map; + + public KeySet(EntryMap map) { + this.map = map; + } + + public int size() { + return map.size(); + } + + public boolean isEmpty() { + return map.size() == 0; + } + + public boolean contains(Object key) { + return map.find(key) != null; + } + + public boolean add(K key) { + throw new UnsupportedOperationException(); + } + + public boolean remove(Object key) { + return map.remove(key) != null; + } + + public Object[] toArray() { + return toArray(new Object[size()]); + } + + public T[] toArray(T[] array) { + return toArray(this, array); + } + + public void clear() { + map.clear(); + } + + public Iterator iterator() { + return new KeyIterator(map.iterator()); + } + } + + public static class Values implements Collection { + private final EntryMap map; + + public Values(EntryMap map) { + this.map = map; + } + + public int size() { + return map.size(); + } + + public boolean isEmpty() { + return map.size() == 0; + } + + public boolean contains(Object value) { + for (Iterator> it = map.iterator(); it.hasNext();) { + if (equal(it.next().getValue(), value)) { + return true; + } + } + return false; + } + + public boolean containsAll(Collection c) { + if (c == null) { + throw new NullPointerException("collection is null"); + } + + for (Iterator it = c.iterator(); it.hasNext();) { + if (! contains(it.next())) { + return false; + } + } + + return true; + } + + public boolean add(V value) { + throw new UnsupportedOperationException(); + } + + public boolean addAll(Collection collection) { + throw new UnsupportedOperationException(); + } + + public boolean remove(Object value) { + for (Iterator> it = map.iterator(); + it.hasNext();) + { + if (equal(it.next().getValue(), value)) { + it.remove(); + return true; + } + } + return false; + } + + public boolean removeAll(Collection c) { + boolean changed = false; + for (Iterator> it = map.iterator(); it.hasNext();) { + if (c.contains(it.next().getValue())) { + it.remove(); + changed = true; + } + } + return changed; + } + + public Object[] toArray() { + return toArray(new Object[size()]); + } + + public T[] toArray(T[] array) { + return toArray(this, array); + } + + public void clear() { + map.clear(); + } + + public Iterator iterator() { + return new ValueIterator(map.iterator()); + } + } + + public static class KeyIterator implements Iterator { + private final Iterator> it; + + public KeyIterator(Iterator> it) { + this.it = it; + } + + public K next() { + return it.next().getKey(); + } + + public boolean hasNext() { + return it.hasNext(); + } + + public void remove() { + it.remove(); + } + } + + public static class ValueIterator implements Iterator { + private final Iterator> it; + + public ValueIterator(Iterator> it) { + this.it = it; + } + + public V next() { + return it.next().getValue(); + } + + public boolean hasNext() { + return it.hasNext(); + } + + public void remove() { + it.remove(); + } + } +} diff --git a/classpath/java/util/AbstractCollection.java b/classpath/java/util/AbstractCollection.java index b577c3bf35..e63b1af8c1 100644 --- a/classpath/java/util/AbstractCollection.java +++ b/classpath/java/util/AbstractCollection.java @@ -97,12 +97,12 @@ public abstract class AbstractCollection implements Collection { } public S[] toArray(S[] array) { - return Collections.toArray(this, array); + return avian.Data.toArray(this, array); } public abstract Iterator iterator(); public String toString() { - return Collections.toString(this); + return avian.Data.toString(this); } } diff --git a/classpath/java/util/ArrayList.java b/classpath/java/util/ArrayList.java index 5cea64b54d..9b3f3f2169 100644 --- a/classpath/java/util/ArrayList.java +++ b/classpath/java/util/ArrayList.java @@ -185,7 +185,7 @@ public class ArrayList extends AbstractList implements java.io.Serializabl } public String toString() { - return Collections.toString(this); + return avian.Data.toString(this); } private void writeObject(ObjectOutputStream out) throws IOException { diff --git a/classpath/java/util/Collections.java b/classpath/java/util/Collections.java index 23db1872ac..3a0608f94f 100644 --- a/classpath/java/util/Collections.java +++ b/classpath/java/util/Collections.java @@ -10,12 +10,14 @@ package java.util; +import avian.Data; + public class Collections { private Collections() { } public static void shuffle(List list, Random random) { - Object[] array = toArray(list, new Object[list.size()]); + Object[] array = Data.toArray(list, new Object[list.size()]); for (int i = 0; i < array.length; ++i) { int j = random.nextInt(array.length); Object tmp = array[i]; @@ -173,25 +175,6 @@ public class Collections { } } - static T[] toArray(Collection collection, T[] array) { - Class c = array.getClass().getComponentType(); - - if (array.length < collection.size()) { - array = (T[]) java.lang.reflect.Array.newInstance(c, collection.size()); - } - - int i = 0; - for (Object o: collection) { - if (c.isInstance(o)) { - array[i++] = (T) o; - } else { - throw new ArrayStoreException(); - } - } - - return array; - } - public static final List EMPTY_LIST = new UnmodifiableList(new ArrayList(0)); @@ -208,35 +191,6 @@ public class Collections { return (Set) new UnmodifiableSet( new HashSet(0)); } - - static String toString(Collection c) { - StringBuilder sb = new StringBuilder(); - sb.append("["); - for (Iterator it = c.iterator(); it.hasNext();) { - sb.append(it.next()); - if (it.hasNext()) { - sb.append(","); - } - } - sb.append("]"); - return sb.toString(); - } - - static String toString(Map m) { - StringBuilder sb = new StringBuilder(); - sb.append("{"); - for (Iterator it = m.entrySet().iterator(); it.hasNext();) { - Map.Entry e = it.next(); - sb.append(e.getKey()) - .append("=") - .append(e.getValue()); - if (it.hasNext()) { - sb.append(","); - } - } - sb.append("}"); - return sb.toString(); - } public static Enumeration enumeration(Collection c) { return new IteratorEnumeration (c.iterator()); @@ -746,46 +700,6 @@ public class Collections { public static Set unmodifiableSet(Set hs) { return new UnmodifiableSet(hs); } - - static class KeyIterator implements Iterator { - private final Iterator> it; - - public KeyIterator(Iterator> it) { - this.it = it; - } - - public K next() { - return it.next().getKey(); - } - - public boolean hasNext() { - return it.hasNext(); - } - - public void remove() { - it.remove(); - } - } - - static class ValueIterator implements Iterator { - private final Iterator> it; - - public ValueIterator(Iterator> it) { - this.it = it; - } - - public V next() { - return it.next().getValue(); - } - - public boolean hasNext() { - return it.hasNext(); - } - - public void remove() { - it.remove(); - } - } private static final class ReverseComparator implements Comparator { diff --git a/classpath/java/util/HashMap.java b/classpath/java/util/HashMap.java index 146a4eb2e1..485cc0436a 100644 --- a/classpath/java/util/HashMap.java +++ b/classpath/java/util/HashMap.java @@ -10,6 +10,8 @@ package java.util; +import avian.Data; + public class HashMap implements Map { private static final int MinimumCapacity = 16; @@ -19,7 +21,7 @@ public class HashMap implements Map { public HashMap(int capacity, Helper helper) { if (capacity > 0) { - array = new Cell[nextPowerOfTwo(capacity)]; + array = new Cell[Data.nextPowerOfTwo(capacity)]; } this.helper = helper; } @@ -40,13 +42,7 @@ public class HashMap implements Map { } public String toString() { - return Collections.toString(this); - } - - private static int nextPowerOfTwo(int n) { - int r = 1; - while (r < n) r <<= 1; - return r; + return avian.Data.toString(this); } public boolean isEmpty() { @@ -72,7 +68,7 @@ public class HashMap implements Map { private void resize(int capacity) { Cell[] newArray = null; if (capacity != 0) { - capacity = nextPowerOfTwo(capacity); + capacity = Data.nextPowerOfTwo(capacity); if (array != null && array.length == capacity) { return; } @@ -220,21 +216,43 @@ public class HashMap implements Map { } public Set> entrySet() { - return new EntrySet(); + return new Data.EntrySet(new MyEntryMap()); } public Set keySet() { - return new KeySet(); + return new Data.KeySet(new MyEntryMap()); } public Collection values() { - return new Values(); + return new Data.Values(new MyEntryMap()); } Iterator> iterator() { return new MyIterator(); } + private class MyEntryMap implements Data.EntryMap { + public int size() { + return HashMap.this.size(); + } + + public Entry find(Object key) { + return HashMap.this.find(key); + } + + public Entry remove(Object key) { + return removeCell(key); + } + + public void clear() { + HashMap.this.clear(); + } + + public Iterator> iterator() { + return HashMap.this.iterator(); + } + } + interface Cell extends Entry { public HashMap.Cell next(); @@ -303,148 +321,6 @@ public class HashMap implements Map { } } - private class EntrySet extends AbstractSet> { - public int size() { - return HashMap.this.size(); - } - - public boolean isEmpty() { - return HashMap.this.isEmpty(); - } - - public boolean contains(Object o) { - return (o instanceof Entry) - && containsKey(((Entry)o).getKey()); - } - - public boolean add(Entry e) { - return putCell(e.getKey(), e.getValue()) != null; - } - - public boolean remove(Object o) { - return (o instanceof Entry) && remove((Entry)o); - } - - public boolean remove(Entry e) { - return removeCell(e.getKey()) != null; - } - - public Object[] toArray() { - return toArray(new Object[size()]); - } - - public T[] toArray(T[] array) { - return Collections.toArray(this, array); - } - - public void clear() { - HashMap.this.clear(); - } - - public Iterator> iterator() { - return new MyIterator(); - } - } - - private class KeySet extends AbstractSet { - public int size() { - return HashMap.this.size(); - } - - public boolean isEmpty() { - return HashMap.this.isEmpty(); - } - - public boolean contains(Object key) { - return containsKey(key); - } - - public boolean add(K key) { - return putCell(key, null) != null; - } - - public boolean remove(Object key) { - return removeCell(key) != null; - } - - public Object[] toArray() { - return toArray(new Object[size()]); - } - - public T[] toArray(T[] array) { - return Collections.toArray(this, array); - } - - public void clear() { - HashMap.this.clear(); - } - - public Iterator iterator() { - return new Collections.KeyIterator(new MyIterator()); - } - } - - private class Values implements Collection { - public int size() { - return HashMap.this.size(); - } - - public boolean isEmpty() { - return HashMap.this.isEmpty(); - } - - public boolean contains(Object value) { - return containsValue(value); - } - - public boolean containsAll(Collection c) { - if (c == null) { - throw new NullPointerException("collection is null"); - } - - Iterator it = c.iterator(); - while (it.hasNext()) { - if (! contains(it.next())) { - return false; - } - } - - return true; - } - - public boolean add(V value) { - throw new UnsupportedOperationException(); - } - - public boolean addAll(Collection collection) { - throw new UnsupportedOperationException(); - } - - public boolean remove(Object value) { - throw new UnsupportedOperationException(); - } - - public boolean removeAll(Collection c) { - throw new UnsupportedOperationException(); - } - - public Object[] toArray() { - return toArray(new Object[size()]); - } - - public T[] toArray(T[] array) { - return Collections.toArray(this, array); - } - - public void clear() { - HashMap.this.clear(); - } - - public Iterator iterator() { - return new Collections.ValueIterator(new MyIterator()); - } - } - private class MyIterator implements Iterator> { private int currentIndex = -1; private int nextIndex = -1; diff --git a/classpath/java/util/HashSet.java b/classpath/java/util/HashSet.java index c445e377cb..de08741ec2 100644 --- a/classpath/java/util/HashSet.java +++ b/classpath/java/util/HashSet.java @@ -63,7 +63,7 @@ public class HashSet extends AbstractSet implements Set { } public String toString() { - return Collections.toString(this); + return avian.Data.toString(this); } private static class MyIterator implements Iterator { diff --git a/classpath/java/util/LinkedHashMap.java b/classpath/java/util/LinkedHashMap.java index ba5b9c09d9..6d94df83ba 100644 --- a/classpath/java/util/LinkedHashMap.java +++ b/classpath/java/util/LinkedHashMap.java @@ -131,7 +131,7 @@ public class LinkedHashMap extends HashMap { } public T[] toArray(T[] array) { - return Collections.toArray(this, array); + return avian.Data.toArray(this, array); } public void clear() { @@ -169,7 +169,7 @@ public class LinkedHashMap extends HashMap { } public T[] toArray(T[] array) { - return Collections.toArray(this, array); + return avian.Data.toArray(this, array); } public void clear() { @@ -177,7 +177,7 @@ public class LinkedHashMap extends HashMap { } public Iterator iterator() { - return new Collections.KeyIterator(new MyIterator()); + return new avian.Data.KeyIterator(new MyIterator()); } } @@ -230,7 +230,7 @@ public class LinkedHashMap extends HashMap { } public T[] toArray(T[] array) { - return Collections.toArray(this, array); + return avian.Data.toArray(this, array); } public void clear() { @@ -238,7 +238,7 @@ public class LinkedHashMap extends HashMap { } public Iterator iterator() { - return new Collections.ValueIterator(new MyIterator()); + return new avian.Data.ValueIterator(new MyIterator()); } } diff --git a/classpath/java/util/LinkedHashSet.java b/classpath/java/util/LinkedHashSet.java index 31efc6c26d..c368dee207 100644 --- a/classpath/java/util/LinkedHashSet.java +++ b/classpath/java/util/LinkedHashSet.java @@ -63,7 +63,7 @@ public class LinkedHashSet extends AbstractSet implements Set { } public String toString() { - return Collections.toString(this); + return avian.Data.toString(this); } private static class MyIterator implements Iterator { diff --git a/classpath/java/util/LinkedList.java b/classpath/java/util/LinkedList.java index d42c6961b2..b56d877139 100644 --- a/classpath/java/util/LinkedList.java +++ b/classpath/java/util/LinkedList.java @@ -360,7 +360,7 @@ public class LinkedList extends AbstractSequentialList implements Deque @Override public String toString() { - return Collections.toString(this); + return avian.Data.toString(this); } @Override diff --git a/classpath/java/util/TreeMap.java b/classpath/java/util/TreeMap.java index 3734e49ba6..4a0462df6b 100644 --- a/classpath/java/util/TreeMap.java +++ b/classpath/java/util/TreeMap.java @@ -42,7 +42,7 @@ public class TreeMap implements Map { } public String toString() { - return Collections.toString(this); + return avian.Data.toString(this); } public V get(Object key) { @@ -164,7 +164,7 @@ public class TreeMap implements Map { } public T[] toArray(T[] array) { - return Collections.toArray(this, array); + return avian.Data.toArray(this, array); } public void clear() { @@ -172,7 +172,7 @@ public class TreeMap implements Map { } public Iterator iterator() { - return new Collections.KeyIterator(set.iterator()); + return new avian.Data.KeyIterator(set.iterator()); } } @@ -225,7 +225,7 @@ public class TreeMap implements Map { } public T[] toArray(T[] array) { - return Collections.toArray(this, array); + return avian.Data.toArray(this, array); } public void clear() { @@ -233,7 +233,7 @@ public class TreeMap implements Map { } public Iterator iterator() { - return new Collections.ValueIterator(set.iterator()); + return new avian.Data.ValueIterator(set.iterator()); } } diff --git a/classpath/java/util/TreeSet.java b/classpath/java/util/TreeSet.java index dd37e8b762..4d4858afe4 100644 --- a/classpath/java/util/TreeSet.java +++ b/classpath/java/util/TreeSet.java @@ -61,7 +61,7 @@ public class TreeSet extends AbstractSet implements Collection { } public String toString() { - return Collections.toString(this); + return avian.Data.toString(this); } public boolean add(T value) { diff --git a/classpath/java/util/concurrent/ConcurrentHashMap.java b/classpath/java/util/concurrent/ConcurrentHashMap.java new file mode 100644 index 0000000000..9eb673bc8d --- /dev/null +++ b/classpath/java/util/concurrent/ConcurrentHashMap.java @@ -0,0 +1,430 @@ +/* Copyright (c) 2008-2014, Avian Contributors + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + There is NO WARRANTY for this software. See license.txt for + details. */ + +package java.util.concurrent; + +import avian.Data; +import avian.PersistentSet; +import avian.PersistentSet.Path; + +import sun.misc.Unsafe; +import java.util.AbstractMap; +import java.util.Map; +import java.util.Set; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class ConcurrentHashMap + extends AbstractMap + implements ConcurrentMap +{ + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long Content; + private static final Content Empty = new Content(new PersistentSet(), 0); + + static { + try { + Content = unsafe.objectFieldOffset + (ConcurrentHashMap.class.getDeclaredField("content")); + } catch (NoSuchFieldException e) { + throw new Error(e); + } + } + + private volatile Content content; + + public ConcurrentHashMap() { + content = Empty; + } + + public ConcurrentHashMap(int initialCapacity) { + this(); + } + + public boolean isEmpty() { + return content.size == 0; + } + + public int size() { + return content.size; + } + + public boolean containsKey(Object key) { + return find(key) != null; + } + + public boolean containsValue(Object value) { + for (V v: values()) { + if (value.equals(v)) { + return true; + } + } + return false; + } + + public V get(Object key) { + Cell cell = find(key); + return cell == null ? null : cell.value; + } + + private Cell find(Object key) { + Content c = content; + + Path>> path = c.set.find(new Node(key.hashCode())); + for (Cell cell = path.value().value; + cell != null; + cell = cell.next) + { + if (key.equals(cell.key)) { + return cell; + } + } + return null; + } + + public V putIfAbsent(K key, V value) { + Cell cell = put(key, value, PutCondition.IfAbsent, null); + return cell == null ? null : cell.value; + } + + public boolean remove(K key, V value) { + Cell cell = remove(key, RemoveCondition.IfEqual, value); + return cell != null && cell.value.equals(value); + } + + public V replace(K key, V value) { + Cell cell = put(key, value, PutCondition.IfPresent, null); + return cell == null ? null : cell.value; + } + + public boolean replace(K key, V oldValue, V newValue) { + Cell cell = put(key, newValue, PutCondition.IfEqual, oldValue); + return cell != null && cell.value.equals(oldValue); + } + + public V put(K key, V value) { + Cell cell = put(key, value, PutCondition.Always, null); + return cell == null ? null : cell.value; + } + + public V remove(Object key) { + Cell cell = remove(key, RemoveCondition.Always, null); + return cell == null ? null : cell.value; + } + + private enum PutCondition { + Always() { + public boolean addIfAbsent() { return true; } + public boolean addIfPresent(V a, V b) { return true; } + }, IfAbsent() { + public boolean addIfAbsent() { return true; } + public boolean addIfPresent(V a, V b) { return false; } + }, IfPresent() { + public boolean addIfAbsent() { return false; } + public boolean addIfPresent(V a, V b) { return true; } + }, IfEqual() { + public boolean addIfAbsent() { return false; } + public boolean addIfPresent(V a, V b) { return a.equals(b); } + }; + + public boolean addIfAbsent() { throw new AssertionError(); } + public boolean addIfPresent(V a, V b) { throw new AssertionError(); } + } + + private enum RemoveCondition { + Always() { + public boolean remove(V a, V b) { return true; } + }, IfEqual() { + public boolean remove(V a, V b) { return a.equals(b); } + }; + + public boolean remove(V a, V b) { throw new AssertionError(); } + } + + private Cell put(K key, V value, PutCondition condition, V oldValue) { + Node> node = new Node(key.hashCode()); + + loop: while (true) { + node.value = null; + Content content = this.content; + Path>> path = content.set.find(node); + for (Cell cell = path.value().value; + cell != null; + cell = cell.next) + { + if (key.equals(cell.key)) { + if (! condition.addIfPresent(cell.value, oldValue)) { + return cell; + } + + Cell start = null; + Cell last = null; + for (Cell cell2 = path.value().value; + true; + cell2 = cell2.next) + { + Cell c; + c = cell2.clone(); + + if (last == null) { + last = start = c; + } else { + last.next = c; + last = c; + } + + if (cell2 == cell) { + c.value = value; + break; + } + } + + node.value = start; + if (unsafe.compareAndSwapObject + (this, Content, content, new Content + (path.replaceWith(node), content.size))) + { + return cell; + } else { + continue loop; + } + } + } + + // no mapping found -- add a new one if appropriate + if (! condition.addIfAbsent()) { + return null; + } + + node.value = new Cell(key, value, null); + if (unsafe.compareAndSwapObject + (this, Content, content, new Content + (path.fresh() ? path.add() : path.replaceWith(node), + content.size + 1))) + { + return null; + } + } + } + + public void putAll(Map map) { + for (Map.Entry e: map.entrySet()) { + put(e.getKey(), e.getValue()); + } + } + + private Cell remove(Object key, RemoveCondition condition, + V oldValue) + { + Node> node = new Node(key.hashCode()); + + loop: while (true) { + node.value = null; + Content content = this.content; + Path>> path = content.set.find(node); + for (Cell cell = path.value().value; + cell != null; + cell = cell.next) + { + if (key.equals(cell.key)) { + if (! condition.remove(cell.value, oldValue)) { + return cell; + } + + Cell start = null; + Cell last = null; + for (Cell cell2 = path.value().value; + cell2 != cell; + cell2 = cell2.next) + { + Cell c = cell2.clone(); + if (last == null) { + last = start = c; + } else { + last.next = c; + last = c; + } + } + + if (last == null) { + start = last = cell.next; + } else { + last.next = cell.next; + } + + node.value = start; + if (unsafe.compareAndSwapObject + (this, Content, content, new Content + (start == null ? path.remove() : path.replaceWith(node), + content.size - 1))) + { + return cell; + } else { + continue loop; + } + } + } + + return null; + } + } + + public void clear() { + content = Empty; + } + + public Set> entrySet() { + return new Data.EntrySet(new MyEntryMap()); + } + + public Set keySet() { + return new Data.KeySet(new MyEntryMap()); + } + + public Collection values() { + return new Data.Values(new MyEntryMap()); + } + + private class MyEntryMap implements Data.EntryMap { + public int size() { + return ConcurrentHashMap.this.size(); + } + + public Map.Entry find(Object key) { + return new MyEntry(ConcurrentHashMap.this.find(key)); + } + + public Map.Entry remove(Object key) { + return new MyEntry + (ConcurrentHashMap.this.remove(key, RemoveCondition.Always, null)); + } + + public void clear() { + ConcurrentHashMap.this.clear(); + } + + public Iterator> iterator() { + return new MyIterator(content); + } + } + + private static class Content { + private final PersistentSet>> set; + private final int size; + + public Content(PersistentSet>> set, + int size) + { + this.set = set; + this.size = size; + } + } + + private static class Cell implements Cloneable { + public final K key; + public V value; + public Cell next; + + public Cell(K key, V value, Cell next) { + this.key = key; + this.value = value; + this.next = next; + } + + public Cell clone() { + try { + return (Cell) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + } + + private static class Node implements Comparable> { + public final int key; + public T value; + + public Node(int key) { + this.key = key; + } + + public int compareTo(Node n) { + return key - n.key; + } + } + + private class MyEntry implements Map.Entry { + private final K key; + private V value; + + public MyEntry(Cell cell) { + key = cell.key; + value = cell.value; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V value) { + V v = value; + this.value = value; + put(key, value); + return v; + } + } + + private class MyIterator implements Iterator> { + private final Content content; + private final Iterator>> iterator; + private Cell currentCell; + private Cell nextCell; + + public MyIterator(Content content) { + this.content = content; + this.iterator = content.set.iterator(); + hasNext(); + } + + public Map.Entry next() { + if (hasNext()) { + currentCell = nextCell; + + nextCell = nextCell.next; + + return new MyEntry(currentCell); + } else { + throw new NoSuchElementException(); + } + } + + public boolean hasNext() { + if (nextCell == null && iterator.hasNext()) { + nextCell = iterator.next().value; + } + return nextCell != null; + } + + public void remove() { + if (currentCell != null) { + ConcurrentHashMap.this.remove + (currentCell.key, RemoveCondition.Always, null); + currentCell = null; + } else { + throw new IllegalStateException(); + } + } + } +} diff --git a/classpath/java/util/concurrent/ConcurrentMap.java b/classpath/java/util/concurrent/ConcurrentMap.java new file mode 100644 index 0000000000..5732a5474d --- /dev/null +++ b/classpath/java/util/concurrent/ConcurrentMap.java @@ -0,0 +1,23 @@ +/* Copyright (c) 2008-2014, Avian Contributors + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + There is NO WARRANTY for this software. See license.txt for + details. */ + +package java.util.concurrent; + +import java.util.Map; + +public interface ConcurrentMap extends Map { + public V putIfAbsent(K key, V value); + + public boolean remove(K key, V value); + + public V replace(K key, V value); + + public boolean replace(K key, V oldValue, V newValue); +} diff --git a/classpath/java/util/concurrent/atomic/AtomicReference.java b/classpath/java/util/concurrent/atomic/AtomicReference.java index b7f3c95121..977ed60926 100644 --- a/classpath/java/util/concurrent/atomic/AtomicReference.java +++ b/classpath/java/util/concurrent/atomic/AtomicReference.java @@ -32,9 +32,9 @@ public class AtomicReference implements java.io.Serializable { public T get() { return value; } - + public void set(T newValue) { - this.value = newValue; + value = newValue; } public void lazySet(T newValue) { diff --git a/classpath/java/util/concurrent/atomic/AtomicReferenceArray.java b/classpath/java/util/concurrent/atomic/AtomicReferenceArray.java new file mode 100644 index 0000000000..cbb3aa0da8 --- /dev/null +++ b/classpath/java/util/concurrent/atomic/AtomicReferenceArray.java @@ -0,0 +1,69 @@ +/* Copyright (c) 2008-2014, Avian Contributors + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + There is NO WARRANTY for this software. See license.txt for + details. */ + +package java.util.concurrent.atomic; + +import java.util.Arrays; + +import sun.misc.Unsafe; + +public class AtomicReferenceArray { + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long arrayOffset = unsafe.arrayBaseOffset(Object.class); + private static final long arrayScale = unsafe.arrayIndexScale(Object.class); + + private final Object[] array; + + public AtomicReferenceArray(int length) { + array = new Object[length]; + } + + public T get(int index) { + return (T) unsafe.getObjectVolatile + (array, arrayOffset + (index * arrayScale)); + } + + public void set(int index, T newValue) { + unsafe.putObjectVolatile + (array, arrayOffset + (index * arrayScale), newValue); + } + + public void lazySet(int index, T newValue) { + unsafe.putOrderedObject + (array, arrayOffset + (index * arrayScale), newValue); + } + + public boolean compareAndSet(int index, T expect, T update) { + return unsafe.compareAndSwapObject + (array, arrayOffset + (index * arrayScale), expect, update); + } + + public boolean weakCompareAndSet(int index, T expect, T update) { + return compareAndSet(index, expect, update); + } + + public final T getAndSet(int index, T newValue) { + while (true) { + T current = get(index); + if (compareAndSet(index, current, newValue)) { + return current; + } + } + } + + public int length() { + return array.length; + } + + @Override + public String toString() { + return Arrays.toString(array); + } +} diff --git a/classpath/sun/misc/Unsafe.java b/classpath/sun/misc/Unsafe.java index b581aea0ed..9bae7e60c0 100644 --- a/classpath/sun/misc/Unsafe.java +++ b/classpath/sun/misc/Unsafe.java @@ -74,6 +74,8 @@ public final class Unsafe { public native int arrayBaseOffset(Class arrayClass); + public native int arrayIndexScale(Class arrayClass); + public native long objectFieldOffset(Field field); public native void park(boolean absolute, long time); diff --git a/src/builtin.cpp b/src/builtin.cpp index 4fe71630e9..54b2e73076 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -663,6 +663,28 @@ Avian_sun_misc_Unsafe_arrayBaseOffset return ArrayBody; } +extern "C" AVIAN_EXPORT int64_t JNICALL +Avian_sun_misc_Unsafe_arrayIndexScale +(Thread* t, object, uintptr_t* arguments) +{ + object c = jclassVmClass(t, reinterpret_cast(arguments[1])); + + if (c == type(t, Machine::JbooleanType) + || c == type(t, Machine::JbyteType)) + return 1; + else if (c == type(t, Machine::JshortType) + || c == type(t, Machine::JcharType)) + return 2; + else if (c == type(t, Machine::JintType) + || c == type(t, Machine::JfloatType)) + return 4; + else if (c == type(t, Machine::JlongType) + || c == type(t, Machine::JdoubleType)) + return 8; + else + return BytesPerWord; +} + extern "C" AVIAN_EXPORT int64_t JNICALL Avian_java_nio_FixedArrayByteBuffer_allocateFixed (Thread* t, object, uintptr_t* arguments) diff --git a/src/classpath-openjdk.cpp b/src/classpath-openjdk.cpp index d51a11eb84..812b69f0c5 100644 --- a/src/classpath-openjdk.cpp +++ b/src/classpath-openjdk.cpp @@ -2558,28 +2558,6 @@ Avian_sun_misc_Unsafe_staticFieldOffset (t, jclassVmClass(t, jfieldClazz(t, jfield))), jfieldSlot(t, jfield))); } -extern "C" AVIAN_EXPORT int64_t JNICALL -Avian_sun_misc_Unsafe_arrayIndexScale -(Thread* t, object, uintptr_t* arguments) -{ - switch (byteArrayBody - (t, className - (t, jclassVmClass(t, reinterpret_cast(arguments[1]))), 1)) - { - case 'Z': - case 'B': return 1; - case 'S': - case 'C': return 2; - case 'I': - case 'F': return 4; - case 'J': - case 'D': return 8; - case '[': - case 'L': return BytesPerWord; - default: abort(t); - } -} - extern "C" AVIAN_EXPORT int64_t JNICALL Avian_sun_misc_Unsafe_staticFieldBase (Thread* t, object, uintptr_t* arguments) diff --git a/test/Concurrent.java b/test/Concurrent.java new file mode 100644 index 0000000000..74bf109730 --- /dev/null +++ b/test/Concurrent.java @@ -0,0 +1,166 @@ +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentHashMap; + +public class Concurrent { + private static final int ThreadCount = 4; + private static final int IterationCount = 100; + private static final int Range = 10; + private static final int CommonBase = -Range; + + private static void expect(boolean v) { + if (! v) throw new RuntimeException(); + } + + public static void main(String[] args) throws Throwable { + final ConcurrentMap map = new ConcurrentHashMap(); + final int[] counter = new int[1]; + final int[] step = new int[1]; + final Throwable[] exception = new Throwable[1]; + + synchronized (map) { + for (int i = 0; i < ThreadCount; ++i) { + final int index = i; + new Thread() { + public void run() { + try { + synchronized (map) { + ++ counter[0]; + map.notifyAll(); + while (exception[0] == null && step[0] == 0) { + map.wait(); + } + } + + for (int i = 0; i < IterationCount; ++i) { + populateCommon(map); + populate(map, index * Range); + } + + synchronized (map) { + -- counter[0]; + map.notifyAll(); + while (exception[0] == null && step[0] == 1) { + map.wait(); + } + } + + for (int i = 0; i < IterationCount; ++i) { + populate(map, index * Range); + depopulate(map, index * Range); + } + + synchronized (map) { + ++ counter[0]; + map.notifyAll(); + } + } catch (Throwable e) { + synchronized (map) { + exception[0] = e; + map.notifyAll(); + } + e.printStackTrace(); + } + } + }.start(); + } + + try { + while (exception[0] == null && counter[0] < ThreadCount) { + map.wait(); + } + + step[0] = 1; + map.notifyAll(); + + while (exception[0] == null && counter[0] > 0) { + map.wait(); + } + + if (map.size() != ThreadCount * Range) { + System.err.println + ("expected " + (ThreadCount * Range) + " got " + map.size()); + } + expect(map.size() == ThreadCount * Range); + for (int i = CommonBase, j = CommonBase + Range; i < j; ++i) { + expect(! map.containsKey(i)); + } + + step[0] = 2; + map.notifyAll(); + + while (exception[0] == null && counter[0] < ThreadCount) { + map.wait(); + } + + expect(map.isEmpty()); + expect(exception[0] == null); + } catch (Throwable e) { + exception[0] = e; + throw e; + } finally { + map.notifyAll(); + } + } + } + + private static void populateCommon(ConcurrentMap map) { + Object value = new Object(); + for (int i = CommonBase, j = CommonBase + Range; i < j; ++i) { + map.remove(i); + map.put(i, value); + map.remove(i); + } + } + + private static void populate(ConcurrentMap map, int base) { + for (int i = base, j = base + Range; i < j; ++i) { + map.remove(i); + Object value = new Object(); + expect(map.put(i, value) == null); + expect(map.containsKey(i)); + expect(map.get(i).equals(value)); + expect(map.putIfAbsent(i, new Object()) == value); + expect(map.get(i).equals(value)); + expect(! map.remove(i, new Object())); + expect(map.remove(i, value)); + expect(map.replace(i, value) == null); + expect(! map.containsKey(i)); + expect(map.get(i) == null); + expect(map.putIfAbsent(i, value) == null); + expect(map.containsKey(i)); + expect(map.get(i) == value); + Object newValue = new Object(); + expect(map.replace(i, newValue) == value); + expect(map.get(i) == newValue); + + boolean found = false; + for (Iterator> it = map.entrySet().iterator(); + it.hasNext();) + { + Map.Entry e = it.next(); + if (e.getKey() == i) { + expect(! found); + expect(e.getValue() == newValue); + found = true; + it.remove(); + } + } + + expect(found); + expect(! map.containsKey(i)); + expect(map.putIfAbsent(i, value) == null); + expect(map.containsKey(i)); + expect(map.get(i) == value); + } + } + + private static void depopulate(ConcurrentMap map, int base) + { + for (int i = base, j = base + Range; i < j; ++i) { + expect(map.containsKey(i)); + expect(map.remove(i) != null); + } + } +}