1

I'd like a way to copy the content of a HashSet into a collection, while not blocking new inserts.

BlockingQueue has this functionality in the drainTo method.

How to do it with a HashSet? Thanks.

* I'm open to using "concurrent HashSet" structures like ConcurrentHashMap.newKeySet().

AlikElzin-kilaka
  • 34,335
  • 35
  • 194
  • 277
  • Note that the JavaDoc `BlockingQueue.drainTo()` states: "Further, the behavior of this operation is undefined if the specified collection is _modified_ while the operation is in progress." Inserts would be modifications in that scenario. – Thomas Nov 27 '18 at 13:19
  • 1
    @Thomas, I think they mean the collection that's passed as a param to the `drainTo` method. – AlikElzin-kilaka Nov 27 '18 at 13:21
  • You're right, that's true. One other question: `drainTo()` would copy and remove elements, do you only want to copy? – Thomas Nov 27 '18 at 13:24
  • 1
    I think this would be difficult to implement in a non-blocking way for a collection based on a hashtable. It would be easier to atomically swap the reference to the `HashSet` (e.g. using `atomicReference`) to another (empty) `HashSet`. Now you "just" have to ensure that noone else has references to your "old" set. – Hulk Nov 27 '18 at 13:27
  • 1
    @Hulk, I think that in order to "ensure that no one else has references to your "old" set" a lock is required when on swapping the reference. – AlikElzin-kilaka Nov 27 '18 at 13:51
  • @Thomas, I'd like to remove as well - not just copy. – AlikElzin-kilaka Nov 27 '18 at 14:02
  • 1
    @AlikElzin-kilaka you are right, a way of making the two steps of reading of the reference and executing the method on the wrapped set atomic would be required. So far, I have not yet come up with a lock-free solution to that one. Still, it could be done with something like a read-write lock, where the write lock is only needed for the swapping. – Hulk Nov 28 '18 at 04:58
  • @Hulk, My thoughts exactly. Added an SO question on the subject: https://stackoverflow.com/questions/53482673/how-to-replace-a-concurrently-instance-without-losing-data – AlikElzin-kilaka Nov 28 '18 at 11:44

1 Answers1

1

How about a method like this:

public <T> int drainTo(Set<? extends T> source, Collection<T> target) {
    Iterator<? extends T> it = source.iterator();
    int count = 0;
    while (it.hasNext()) {
        target.add(it.next());
        it.remove();
        count++;
    }
    return count;
}

public static void main(String[] args) throws Exception {
    Collection<String> list = new ArrayList<>();

    // HashSet<String> set = new HashSet<>();
    Set<String> set = ConcurrentHashMap.newKeySet();
    set.add("1");
    set.add("2");
    set.add("3");

    new Thread(() -> {
        set.add("4");
        set.add("5");
    }).start();

    drainTo(set, list);

    // could print [1, 2, 3] , [1, 2, 3, 4], or [1, 2, 3, 4, 5]
    // since there's no guarantee that the thread finished putting all elements in yet 
    System.out.println(list);
}
xtratic
  • 4,600
  • 2
  • 14
  • 32