这也将保证有序迭代并且应该在一个多线程环境。
首先,让我们从单线程实现开始,然后对其进行扩展。
免责声明:我不认为这个问题有实际应用,而是作为一个从教育角度来看具有价值的项目。
如果我们将这个自定义集合可用的操作的时间复杂度与TreeMap:
-
iterator() -O(n * log n)(比TreeMap差);
-
remove() -上)(比TreeMap差);
-
poll() -O(log n);
-
put() -O(log n);
-
peek() -O(1);
-
get() -O(1)(优于TreeMap)。
操作与Iterator:
-
next() -O(log n)(比TreeSet 返回的迭代器差);
-
hasNext() -O(1);
- 遍历所有键 -O(n * log n).
get() -是个只要此集合中的方法执行得比相应的方法更好TreeMap。由于受到维护PriorityQueue它对于入队和出队方法具有对数时间复杂度。
偏好组合胜于继承
当您需要能够在另一个类中使用特定类的功能时,继承不是您必须考虑的第一种方法,而是最后的手段。
为什么?继承创造紧耦合,就像在您提供的代码中一样,您的新集合恰好完全依赖于HashMap 的实现。
实际上,您的自定义类已损坏。您没有覆盖所有允许在结构上修改地图的方法,例如 compute()、merge() 等。但还有更多,因为它是 HashMap 的子类,它可以通过删除元素来更改从通过values() 获得的值集合或通过从条目集中删除条目,在这种情况下,将无法相应地更改PriorityQueue。
当然,values() 和 entrySet() 可以通过让它们返回不可修改的集合来覆盖。所以事实证明你几乎需要重写每一个方法。然而,将来可以引入的新功能或现有方法的更改可能会破坏您的代码。
有关这方面的更多信息,请参阅项目“优先组合优于继承”“有效的 Java”经过约书亚·布洛赫(前 Sun 员工,开发了 Java 的许多功能,包括 Collections 框架)。
更好的方法是使用组合,即替换IS-A有关系有个通过创建使用HashMap 的类来建立关系,即具有HashMap 类型的字段,而不是HashMap 本身。这将是一个Loose coupling,因为您的集合不再依赖于HashMap 实现,您的集合的方法会将调用委托给HashMap,允许它在内部执行任何操作。
您不受任何合约的约束,并且只能公开有限数量的允许与您的 Collection 交互的方法。
笔记不幸的是,您无法实现Map 接口,因为如果扩展接口可能会导致问题。
它可能如何实现:
class HashPriorityQueueNonSync<K, V> implements Iterable<Map.Entry<K, V>> {
private final PriorityQueue<K> queue;
private final Map<K, V> map = new HashMap<>();
private int modCount = 0;
/* CONSTRUCTORS */
public HashPriorityQueueNonSync(Comparator<K> comparator) {
queue = new PriorityQueue<>(comparator);
}
public HashPriorityQueueNonSync() {
queue = new PriorityQueue<>();
}
/* QUEUE METHODS */
public Map.Entry<K, V> poll() {
modCount++;
K key = queue.poll();
V val = remove(key);
return Map.entry(key, val);
}
public Map.Entry<K, V> peek() {
K key = queue.peek();
V val = map.get(key);
return Map.entry(key, val);
}
/* MAP METHODS */
public V get(Object key) {
return map.get(key);
}
public V put(K key, V value) {
modCount++;
queue.add(key);
return map.put(key, value);
}
public V remove(Object key) {
modCount++;
queue.remove(key);
return map.remove(key);
}
public V remove(Map.Entry<K, V> entry) {
modCount++;
queue.remove(entry.getKey());
return map.remove(entry.getKey());
}
@Override
public Iterator<Map.Entry<K, V>> iterator() {
return new PriorityIterator();
}
private class PriorityIterator implements Iterator<Map.Entry<K, V>> {
private PriorityQueue<K> keys;
private K cursor;
private final int expectedModCount;
public PriorityIterator() {
this.keys = new PriorityQueue<>(HashPriorityQueueNonSync.this.queue);
this.expectedModCount = HashPriorityQueueNonSync.this.modCount;
}
@Override
public boolean hasNext() {
return !keys.isEmpty();
}
@Override
public Map.Entry<K, V> next() {
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
cursor = keys.poll();
V v = HashPriorityQueueNonSync.this.get(cursor);
return Map.entry(cursor, v);
}
@Override
public void remove() {
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
HashPriorityQueueNonSync.this.remove(cursor);
}
}
}
如何防止迭代期间的结构修改
确保迭代器反映集合的实际状态的常见做法是引入两个修改计数器:一个作为集合的实例字段(上面代码中的modCount),另一个在Iterator(@987654357 @)。
当这些变量不相等时,这意味着在创建迭代器之后进行了除迭代器手段之外的修改。在这种情况下,JDK 中所有Iterator 的实现都会抛出ConcurrentModificationException。
并发实现
首先,有两个概念需要考虑:
-
原子性- 当一个线程正在修改一个对象的状态时(在这种情况下改变了自定义集合的底层队列和映射),所有其他线程都无法观察到中间的变化,他们可以看到要么两个都队列和地图是否完全修改。我们可以通过在此自定义 Collection 的方法上使用 synchronized 关键字来确保队列和映射上的所有操作都以原子方式发生。下面显示的方法非常基本,可以在 Vector 和 Hashtable 等遗留集合中观察到,但是因为在底层我们有两个需要原子访问的独立底层集合,所以很难应用比 synchronized 更简洁的任何东西.
-
Happens-before Order - 描述后续更改的可见性。如果一个动作发生在另一个动作之前,那么第一个动作对第二个动作可见并在第二个动作之前排序。确保这一点的方法之一是使用 volatile 关键字。
笔记迭代器的方法不需要同步(这意味着只能在单个线程中使用),但是我们需要同步负责获取迭代器的集合的方法。
这就是并发实现的样子:
public class HashPriorityQueue<K, V> implements Iterable<Map.Entry<K, V>> {
private final PriorityQueue<K> queue;
private final Map<K, V> map = new HashMap<>();
private volatile int modCount = 0;
/* CONSTRUCTORS */
public HashPriorityQueue(Comparator<K> comparator) {
queue = new PriorityQueue<>(comparator);
}
public HashPriorityQueue() {
queue = new PriorityQueue<>();
}
/* QUEUE METHODS */
public synchronized Map.Entry<K, V> poll() {
modCount++;
K key = queue.poll();
V val = remove(key);
return Map.entry(key, val);
}
public synchronized Map.Entry<K, V> peek() {
K key = queue.peek();
V val = map.get(key);
return Map.entry(key, val);
}
/* MAP METHODS */
public synchronized V get(Object key) {
return map.get(key);
}
public synchronized V put(K key, V value) {
modCount++;
queue.add(key);
return map.put(key, value);
}
public synchronized V remove(Object key) {
modCount++;
queue.remove(key);
return map.remove(key);
}
public synchronized V remove(Map.Entry<K, V> entry) {
modCount++;
queue.remove(entry.getKey());
return map.remove(entry.getKey());
}
@Override
public synchronized Iterator<Map.Entry<K, V>> iterator() {
return new PriorityIterator();
}
private class PriorityIterator implements Iterator<Map.Entry<K, V>> {
private PriorityQueue<K> keys;
private K cursor;
private final int expectedModCount;
public PriorityIterator() {
this.keys = new PriorityQueue<>(HashPriorityQueue.this.queue);
this.expectedModCount = HashPriorityQueue.this.modCount;
}
@Override
public boolean hasNext() {
return !keys.isEmpty();
}
@Override
public Map.Entry<K, V> next() {
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
cursor = keys.poll();
V v = HashPriorityQueue.this.get(cursor);
return Map.entry(cursor, v);
}
@Override
public void remove() {
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
HashPriorityQueue.this.remove(cursor);
}
}
}
一个非常小的测试
main()
public static void main(String[] args) {
HashPriorityQueue<Integer, Integer> hpq = new HashPriorityQueue<>();
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> { for (int i = 3; i < 7; i++) hpq.put(i, 1 << i); });
executor.submit(() -> { for (int i = 0; i < 3; i++) hpq.put(i, 1 << i); });
executor.submit(() -> { for (int i = 7; i < 10; i++) hpq.put(i, 1 << i); });
try {
executor.awaitTermination(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdown();
hpq.forEach(System.out::println);
}
输出:
0=1
1=2
2=4
3=8
4=16
5=32
6=64
7=128
8=256
9=512