javamultithreadingconcurrency

How to ensure an iterator with a read lock will unlock even if it doesn't finish?


I have a collection. Many threads should be able to read from it at a time, but only one thread should be able to write to it at a time, and only when it's not being read. Java's ReentrantReadWriteLock seems prefect for this.

However, I am confused about how to write the iterator for the collection. The iterator should obtain the read lock when it starts. But I can't figure out how to ensure that it will unlock in the case when the iterator never finished.

Here's some example code that is just a wrapper around a normal iterator that locks and unlocks:

import java.util.Iterator;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;

public class ReadLockIterator<T> implements Iterator<T> {
    
    private final Lock lock;
    private final Iterator<T> iterator;
    
    public ReadLockIterator(ReadWriteLock lock, Iterator<T> iterator) {
        this.lock = lock.readLock();
        this.iterator = iterator;
        this.lock.lock();
    }
    
    @Override
    public boolean hasNext() {
        return iterator.hasNext();
    }

    @Override
    public T next() {
        try {
            return iterator.next();
        }
        finally {
            if(!hasNext())
                lock.unlock();
        }
    }
}

This will work fine as long as the user gets every element from the iterator. But what happens if the user doesn't do that? How can I ensure that the lock will eventually be released, even if some elements are never read from the iterator?

My first thought was to put a second check in the iterator's finalize() method, but then I read that finalize should not be used for unlocking.

What's the best way to handle this?


Solution

  • You should make a lock class for your collection that implements AutoCloseable and Iterable.

    Then you can use try-with-resources to make sure the lock is closed, and your iterator implementations can fail if the lock is not open:

    try(MyLock lock = myCollection.getReadLock()) {
        for(Item item: lock) {
            ...
        }
    }