Java同步迭代器?

时间:2015-10-09 02:59:47

标签: java synchronization

我正在寻找一种方法来调用从被调用对象返回的方法,一个可迭代的集合,源(被调用对象中的集合实例)将阻止其他线程同时访问。

我认为这个可迭代的集合将被包装在一个类中,在该类中提供了同步机制,这样当调用者完成集合时,它可能会调用一个名为finished()的方法来释放对底层集合的锁定宾语。尽管API具有返回公共集合的同步版本的方法,但似乎无法通过调用finished方法以编程方式释放锁定; AFAICT当包装器被垃圾收集时,锁似乎被释放。

在Java SE API中是否有任何功能可以解决这个问题,或者仅仅为了自己滚动它是否足够简单?

感谢您的建设性意见。

3 个答案:

答案 0 :(得分:1)

将对迭代器的引用传递回调用者并尝试在内部阻塞其他方法直到调用者完成迭代器是不切实际的。如果调用线程由于某些错误而永远不会完成迭代怎么办?如果调用线程1然后将ierator引用传递给另一个线程2,那么该线程2可能会永远阻塞,因为线程1给它一个线程1持有锁的对象!

调用线程需要是主动获取和释放锁的参与者,或者您需要反转控制而不是返回迭代器,接收您计划迭代的代码以便您可以控制你的锁边界。

如,

public class TestIt {

    public static class ListWrapper<K> {
        private List<K> list = new ArrayList<>();

        public synchronized void add(K value) {
            list.add(value);
        }

        public synchronized void executeOperation(Operation<K> operation) {
            for (K k: list) {
                operation.execute(k);
            }
        }

    }

    public static interface Operation<K> {
        void execute(K value);
    }

    public static void main(String[] args) {
        ListWrapper<String> strings = new ListWrapper<>();
        strings.add("FOO");
        strings.add("BAR");
        strings.executeOperation(new Operation<String>() {
            @Override
            public void execute(String value) {
                System.out.println(value);
            }
        });

    }

}

答案 1 :(得分:0)

Java SDK确实有Collections.synchronizedXXX方法,它们围绕每个方法包装一个synchronized块。如果你想让多个方法被同一个锁保护,直到你说你明确完成,那将无济于事。

您可以使用RenentrantLock获得相同的效果,并使用Collection方法使用扩展的finished界面:

public interface ExtendedLockCollection<T> extends Collection<T> {
    public void finished();
}

然后你可以创建一个实用工具方法(比如各种Collections方法)来创建代理实现,如下所示:

public static <T> ExtendedLockCollection<T> extendedLockCollection(final Collection<T> wrapped) {
    return new ExtendedLockCollection<T>() {
        RentrantLock extendedLock = new ReentrantLock();

        public void add(T value) {
            start();
            wrapped.add(value);
        }

        // ... etc for the remaining Collection methods ...

        private void start() {
            synchronized(extendedLock) {
                if(!extendedLock.isHeldByCurrentThread()) {
                    extendedLock.lock();
                }
            }
        }

        public void finished() {
            if(extendedLock.isHeldByCurrentThread()) {
                extendedLock.unlock();
            }
        }
    }
}

除非我遗漏了某些东西,否则应该这样做。您可以为要支持的其他集合类型创建这样的方法,例如Collections.synchronizedXXX方法。

编辑:这仅适用于集合。对会员方法的访问不同步。

答案 2 :(得分:0)

我想这可能就是这样。它是你所要求的Iterable的包装器。只需将它想要包装的Collection传递给它。它将允许访问尝试使用它的第一个线程。当该线程完成后,它将调用finish,并且Iterable可以被另一个线程自由使用。这里的关键是跟踪哪个线程拥有它,并同步访问该线程的引用。

public class LockedIterable implements Iterable {

    private final Collection collection;
    private final Object lock = new Object();
    private Thread threadWithAccess;
    private final MyIterator iterator;

    public LockedIterable(Collection collection) {
        this.collection = collection;
        this.iterator = new MyIterator(collection);
    }

    @Override
    public Iterator iterator() {
        tryLock();
        return iterator;
    }

    public void finished() {
        synchronized (lock) {
            threadWithAccess = null;
        }
    }

    private static class SimultanousAccessException extends RuntimeException {

        public SimultanousAccessException() {
        }
    }

    private class MyIterator implements Iterator {

        Iterator it;

        private MyIterator(Collection collection) {
            it = collection.iterator();
        }

        @Override
        public boolean hasNext() {
            tryLock();
            return it.hasNext();
        }

        @Override
        public Object next() {
            tryLock();
            return it.next();
        }

    }

    private void tryLock() {
        synchronized (lock) {
            if (threadWithAccess == null) {
                threadWithAccess = Thread.currentThread();
            } else if (!threadWithAccess.equals(Thread.currentThread())) {
                throw new SimultanousAccessException();
            }
        }
    }

}