尝试迭代List时出现ConcurrentModificationException

时间:2016-08-30 21:21:22

标签: java android synchronization

我正在尝试使用IntentService实现下载队列,该队列由同步ArrayList支持。实际下载由类Downloader执行,该类是AsyncTask的子类。但是,当队列中的第一次下载完成时,downloadQueue的迭代总是失败。这是我尝试同步的。

public class DownloadIntentService extends IntentService {

............
............

    private final List<Downloader> downloadQueue
            = Collections.synchronizedList(new ArrayList<Downloader>());

    public DownloadIntentService() {
        super(TAG);
    }

............
............

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_DOWNLOAD.equals(action)) {
                .............
                .............

                handleActionAddDownload(id, url, title, videoId, formatId, extension, thumb);
            } else if (ACTION_PAUSE_DOWNLOAD.equals(action)) {
                final String[] ids = intent.getStringArrayExtra(EXTRA_ID);
                handleActionBaz(ids);
            }
        }
    }

    /**
     * Handle action Foo in the provided background thread with the provided
     * parameters.
     */
    private void handleActionAddDownload(final String id,
                                         final String url,
                                         final String title,
                                         final String videoId,
                                         final String formatId,
                                         final String extension,
                                         final String thumb) {
        ..............
        ..............
        resumeDownload(id);
        realm.close();
    }

    /**
     * Takes a download id, adds it to the queue if possible and resumes the download.
     *
     * @param id The id.
     */
    private void resumeDownload(String id) {
        //noinspection StatementWithEmptyBody
        if (isQueueOpen()) {
            // URL already exists, try to resume it.
            // FIXME: 21/8/16
            if (!isInQueue(id)) {
                synchronized (downloadQueue) {
                    downloadQueue.add(new Downloader(id));
                }
            } else {
                // Download is already there in the queue. Multiple clicks?
                Log.e(TAG, "resumeDownload: Download is already there in the queue. " +
                        "Multiple clicks?");

            }
            if (!ensureSdCard()) {
                Log.e(TAG, "resumeDownload: " + "Cannot ensure sdcard" );
                Toast.makeText(this, R.string.sd_not_available,
                        Toast.LENGTH_LONG).show();
                return;
            }
            checkDownloadQueue();
        } else {
            // do nothing.
        }
    }

    /**
     * Check the database and clean queue, add downloads to queue if possible, start downloads in
     * queue if not already started.
     */
    private void checkDownloadQueue() {
        Realm realm = Realm.getDefaultInstance();
        // Clean queue
        synchronized (downloadQueue) {
            for (Iterator<Downloader> it = downloadQueue.iterator(); it.hasNext();) {
                Downloader downloader = it.next();
                final Download download = Utils.getDownloadById(realm, downloader.getId());
                switch (download.getStatus()) {
                    case Download.DONE:
                    case Download.FAILED:
                    case Download.PAUSED:
                        // Remove processed downloads
                        if (!downloader.isCancelled()) {
                            downloader.cancel(true);
                        }
                        downloadQueue.remove(downloader);
                        break;
                    case Download.WAITING_FOR_QUEUE:
                        // Start pending downloads.
                        realm.executeTransaction(new Realm.Transaction() {
                            @Override
                            public void execute(Realm realm) {
                                download.setStatus(Download.DOWNLOADING);
                                realm.copyToRealmOrUpdate(download);
                            }
                        });
                        startDownloaderInQueue(downloader.getId());
                        break;
                    case Download.DOWNLOADING:
                        if (downloader.getStatus().equals(AsyncTask.Status.PENDING)) {
                            startDownloaderInQueue(downloader.getId());
                        } else if (downloader.getStatus().equals(AsyncTask.Status.FINISHED)) {
                            if (!downloader.isCancelled()) {
                                downloader.cancel(true);
                            }
                            realm.executeTransaction(new Realm.Transaction() {
                                @Override
                                public void execute(Realm realm) {
                                    download.setStatus(Download.FAILED);
                                    realm.copyToRealmOrUpdate(download);
                                }
                            });
                            downloadQueue.remove(downloader);
                        } else {
                            // Do nothing, task is running.
                        }
                        break;
                }
            }

            // Add new downloads to queue if any.
            if (isQueueOpen()) {
                int diff = QUEUE_SIZE - downloadQueue.size();
                RealmResults<Download> downloads = realm
                        .where(Download.class)
                        .findAll()
                        .sort("position", Sort.ASCENDING);
                for (int i = 0; (diff > 0 && i < downloads.size()); i++) {
                    final Download download = downloads.get(i);
                    if (download.getStatus() == Download.WAITING_FOR_QUEUE) {
                        realm.executeTransaction(new Realm.Transaction() {
                            @Override
                            public void execute(Realm realm) {
                                download.setStatus(Download.DOWNLOADING);
                                realm.copyToRealmOrUpdate(download);
                            }
                        });
                        Downloader downloader = new Downloader(download.getId());
                        downloadQueue.add(downloader);
                        startDownloaderInQueue(download.getId());
                        diff--;
                    } else if (download.getStatus() == Download.DOWNLOADING) {
                        if (isInQueue(download.getId())) {
                            Downloader downloader = new Downloader(download.getId());
                            downloadQueue.add(downloader);
                            startDownloaderInQueue(download.getId());
                            diff--;
                        }
                    }
                }
            }
        }
        realm.close();
    }

    /**
     * Start a download in queue
     *
     * @param id an id that exists in download queue.
     */
    private void startDownloaderInQueue(@NonNull String id) {
        if (!isInQueue(id)) throw new IllegalArgumentException("URL is not in queue");
        Downloader downloader = getDownloaderFromQueue(id);
        Log.d(TAG, "startDownloaderInQueue: " + downloader.getStatus());
        if (AsyncTask.Status.PENDING.equals(downloader.getStatus())) {
            downloader.execute();
        } else if (AsyncTask.Status.FINISHED.equals(downloader.getStatus())) {
            throw new IllegalStateException("Downloader already executed");
        }
    }

    private Downloader getDownloaderFromQueue(String id) {
        synchronized (downloadQueue) {
            for (Downloader downloader : downloadQueue) {
                if (id.equals(downloader.getId())) return downloader;
            }
        }
        throw new IllegalArgumentException(id + "wasn't found in queue.");
    }

    /**
     * Check if a url is in the queue.
     *
     * @param id The id.
     * @return weather a id is in the queue or not.
     */
    private boolean isInQueue(@NonNull String id) {
        synchronized (downloadQueue) {
            for (Downloader downloader : downloadQueue) {
                if (id.equals(downloader.getId()))
                    return true;
            }
        }
        return false;
    }

    private boolean isQueueOpen() {
        synchronized (downloadQueue) {
            return downloadQueue.size() < QUEUE_SIZE;
        }
    }

    private class Downloader extends BaseDownloader {
        public Downloader(String id) {
            super(id);
        }

        @Override
        protected void onPostExecute(final Integer status) {
            super.onPostExecute(status);
            Realm realm = Realm.getDefaultInstance();
            final Download download = Utils.getDownloadById(realm, getId());
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    download.setStatus(status);
                    realm.copyToRealmOrUpdate(download);
                }
            });
            realm.close();
            checkDownloadQueue();
        }

        @Override
        protected void onProgressUpdate(DownloadProgress... values) {
            super.onProgressUpdate(values);
            Realm realm = Realm.getDefaultInstance();
            final DownloadProgress progress = values[0];
            final Download download = Utils.getDownloadById(realm, getId());
            Log.d(TAG, "onProgressUpdate: " + progress);
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    download.updateProgress(progress);
                    realm.copyToRealmOrUpdate(download);
                }
            });
            realm.close();
        }
    }
}

这是我得到的错误:

java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.next(ArrayList.java:831)
    at com.vibhinna.sreni.DownloadIntentService.checkDownloadQueue(DownloadIntentService.java:261)
    at com.vibhinna.sreni.DownloadIntentService.access$000(DownloadIntentService.java:35)
    at com.vibhinna.sreni.DownloadIntentService$Downloader.onPostExecute(DownloadIntentService.java:420)
    at com.vibhinna.sreni.DownloadIntentService$Downloader.onPostExecute(DownloadIntentService.java:402)
    at android.os.AsyncTask.finish(AsyncTask.java:660)
    at android.os.AsyncTask.-wrap1(AsyncTask.java)
    at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:677)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:6044)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)

我做错了什么?我不确定从同步块调用包含同步块的方法是否合法。

完整的课程来源:http://pastebin.com/aijT62Yx

3 个答案:

答案 0 :(得分:2)

您正在获取异常,因为您在使用迭代器遍历它时从底层集合(列表)中删除了一个项目。

Extended是在迭代期间修改集合的唯一安全方法;如果在迭代进行过程中以任何其他方式修改基础集合,则行为未指定。

答案 1 :(得分:1)

您正在修改当前正在迭代的列表。这是不允许的。在使用it.remove()进行迭代时,必须使用Iterator函数更改列表。如果要在迭代期间修改列表,请不要使用Iterator,使用直接索引迭代或使用列表的副本,并在副本的迭代期间操作原始列表。

答案 2 :(得分:1)

方法checkDownloadQueue中的

而不是使用downloadQueue.remove(downloader)使用iterator的remove方法。