Warm tip: This article is reproduced from serverfault.com, please click

android-并发访问列表

(android - Concurrent access to lists)

发布于 2016-09-15 10:45:02

我在项目中需要一些可以将调用分组的服务器,因为有些“批处理”路由接受ID列表而不是简单的ID。

我的想法是建立一个带有池的东西,该池有时会被清空。

所以我创建了这个AbstractRepository(不确定它的名字是否正确):

public abstract class AbstractRepository<T>{

    protected Context c;

    //interval between 2 queue emptying
    private final int POOL_DELAY = 200;
    protected int downloadingTaskCount = 0;

    final protected ArrayMap<String, T> memCache = new ArrayMap<>();
    final protected HashSet<String> queue = new HashSet<>();

    final protected ArrayMap<String, List<FetchedInterface<T>>> callbackMap = new ArrayMap<>();

    final protected List<PoolEmptiedInterface> emptinessWatchers = new ArrayList<>();


    protected AbstractRepository(Context c) {
        handler.postDelayed(downloadRoutine, POOL_DELAY);
        this.c = c;
    }

    public void cache(String id) {
        if (!memCache.containsKey(memCache)) {
            synchronized (queue) {
                queue.add(id);
            }
        }
    }

    public void getCache(String id, FetchedInterface<T> callback) {

        if (memCache.containsKey(id)) {
            callback.fetched(memCache.get(id));
        } else {

            synchronized (callbackMap) {
                if (!callbackMap.containsKey(id)) {
                    callbackMap.put(id, new ArrayList<FetchedInterface<T>>());
                }
                callbackMap.get(id).add(callback);
            }

            synchronized (queue) {
                queue.add(id);
            }

        }
    }

    public void getCacheIdObj(List<IdObject> idsObj, final ListFetchedInterface<T> callback) {
        ArrayList<String> ids = new ArrayList<>();
        for (IdObject idObj : idsObj) {
            ids.add(idObj.getId());
        }
        getCache(ids, callback);
    }

    public void getCache(List<String> ids, final ListFetchedInterface<T> callback) {
        final CountDownLatch countDownLatch = new CountDownLatch(ids.size());
        final ArrayList<T> array = new ArrayList<>();
        for (String id : ids) {
            getCache(id, new FetchedInterface<T>() {
                @Override
                public void fetched(T item) {
                    array.add(item);
                    countDownLatch.countDown();
                }
            });

        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    countDownLatch.await();
                    callback.fetched(array);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }


    /**
     * Exists for threads that want to be notified that the user queue has been flushed.
     */
    public void getNotifiedWhenQueueIsEmptied(PoolEmptiedInterface<T> callback) {
        if (downloadingTaskCount == 0 && queue.isEmpty()) {
            callback.poolEmpty();
        } else {
            synchronized (emptinessWatchers) {
                emptinessWatchers.add(callback);
            }
        }
    }

    protected void doIt(
            final HashSet<String> processingQueue) {
    }

    /**
     * Pool Loop
     */
    Handler handler = new Handler();

    private Runnable downloadRoutine = new Runnable() {
        @Override
        public void run() {

            if (!queue.isEmpty()) {
                final HashSet<String> processingQueue = new HashSet<>();

                synchronized (queue) {
                    processingQueue.addAll(queue);
                    queue.clear();
                }
                downloadingTaskCount++;
                doIt(processingQueue);
            }

            handler.postDelayed(downloadRoutine, POOL_DELAY);
        }
    };

还有一个孩子 UserRepository

public class UserRepository extends AbstractRepository<UserCache> {

    private static volatile UserRepository instance;

    public static UserRepository getInstance(Context c) {
        synchronized (UserRepository.class) {
            if (instance == null) {
                instance = new UserRepository(c);
            }
            return instance;
        }
    }

    private UserRepository(Context c) {
        super(c);
    }

    @Override
    protected void doIt(final HashSet<String> processingQueue) {
        Api.getInstance().backend.getUsersCache(new IdListArguments(new ArrayList<>(processingQueue)))
                .enqueue(new Callback<Map<String, UserCache>>() {
                    @Override
                    public void onResponse(Call<Map<String, UserCache>> call, Response<Map<String, UserCache>> responseParent) {
                        Map<String, UserCache> response = responseParent.body();
                        Iterator<String> it = processingQueue.iterator();
                        while (it.hasNext()) {

                            String id = it.next();
                            if (response.containsKey(id)) {
                                memCache.put(id, response.get(id));
                                if (callbackMap.containsKey(id)) {
                                    for (FetchedInterface callback : callbackMap.get(id)) {
                                        callback.fetched(response.get(id));
                                    }
                                }
                                it.remove();
                            }
                        }

                        for (PoolEmptiedInterface watcher : emptinessWatchers) {
                            watcher.poolEmpty();
                        }
                        downloadingTaskCount--;
                        queue.addAll(processingQueue);
                    }

                    @Override
                    public void onFailure(Call<Map<String, UserCache>> call, Throwable t) {
                        queue.addAll(processingQueue);
                    }
                });
    }
}

我的例外:

Java.util.ConcurrentModificationException
  at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:573)
  at com.m360.android.domain_layer.interactors.MemberInteractor.constructPagerMemberUsers(MemberInteractor.java:116)
  at com.m360.android.domain_layer.interactors.MemberInteractor.access$000(MemberInteractor.java:29)
  at com.m360.android.domain_layer.interactors.MemberInteractor$2$1.fetched(MemberInteractor.java:64)
  at com.m360.android.datalayer.repositories.AbstractRepository$2.run(AbstractRepository.java:99)

而且MemberInteractor仅包含静态方法,使用以下2种方法可以显示崩溃:

public static void getGroupUsers(String id, final Context c, final ErrorDisplayerInterface i, final MemberInteractorCallback callback) {
    GroupRepository.getInstance(c).getCache(id, new AbstractRepository.FetchedInterface<Group>() {
        @Override
        public void fetched(Group item) {
            UserRepository userRepositoryNew = UserRepository.getInstance(c);
            userRepositoryNew.getCache(new ArrayList<>(item.getUsers()), new AbstractRepository.ListFetchedInterface<UserCache>() {
                @Override
                public void fetched(List<UserCache> items) {
                    callback.onFinish(constructPagerMemberUsers(items));
                }
            });
        }
    });
}


private static List<PagerMemberUser> constructPagerMemberUsers(final List<UserCache> items) {

    final List<PagerMemberUser> users = new ArrayList<>();
    for (UserCache item : items) {
        users.add(new PagerMemberUser(item));
    }
    return users;
}

抱歉,有很多代码,但是我认为所有代码都与我的问题有关。

那么那里发生了什么?我没看到问题。

Questioner
Renaud Favier
Viewed
0
GoneUp 2016-09-15 19:17:31

你会收到一个ConcurrentModificationException,用于更改你迭代的列表(通过for每个循环)。

因此,在这种情况下,最简单的解决方法是在迭代之前,更改foreach以创建列表的副本:

 for (UserCache item : new LinkedList<UserCache>(items)) {
    users.add(new PagerMemberUser(item));
}