Пользовательский порядок задач в ThreadPool - PullRequest
3 голосов
/ 05 февраля 2012

В настоящее время я использую FixedThreadPool для загрузки изображений из Интернета, например:

ExecutorService mThreadPool = Executors.newFixedThreadPool(MAX_THREADS);

А затем я просто отправляю новые Runnable s с изображением URL, которое либо загружает изображение из URL или, если он существует в кеше, загружает его оттуда.

Я хочу быть в состоянии гарантировать, что только один поток за раз может обработать определенный URL (чтобы предотвратить ситуации, когда изображение загружается MAX_THREADS раз), и если этот поток завершает работу и загружает это изображение, которое я хочу разрешить запускать следующее (или все, с тем же URL), чтобы загрузить ранее загруженное изображение из моего кэша.

Вот что я имею в виду, показанныйпо простой (я надеюсь, что так) схеме: http://i43.tinypic.com/xnz3f9.jpg

Я видел несколько примеров с пользовательской реализацией Runnable с Queue задачами, но все они нуждались в том, чтобы я знал все URL до выполнения этихзадач, я хочу использовать его в ListView с динамически загружаемым содержимым, чтобы эта опция была невозможна.

Спасибо за любую помощь.:)

1 Ответ

3 голосов
/ 05 февраля 2012

Вам нужно будет реализовать какую-то схему синхронизации (блокировку), чтобы предотвратить запуск второго потока для загрузки того же файла, который уже загружает другой поток.

Одно решение, которое приходит на ум, будетпередать каждому из ваших Runnable конструкторов ссылку на Map<String,Lock> и ReentrantLock для синхронизации доступа к нему.Поместите имя файла в качестве ключа на карту, пока вы загружаете его, вместе с другим ReentrantLock, поскольку другие потоки будут ждать в качестве значения.

Вы блокируете и проверяете Map перед загрузкой файла.

Если там нет записи, вы создаете новый ReentrantLock и вставляете в Map.Затем вы разблокируете замок для самого Map.После завершения загрузки файла вы снова блокируете карту, снимаете блокировку с Map и разблокируете ее, а затем разблокируете карту.

Если там есть запись, вы знаете, что другой потокзагрузка файла и у вас есть блокировка для работы.Разблокируйте карту, заблокируйте блокировку файла и подождите, пока не получите блокировку.Когда вы получаете блокировку, вы знаете, что другой поток завершен.

Пример:

...
mapLock.lock();
Lock fileLock = fileMap.get(fileName);
if (fileLock == null)
{
    fileLock = new ReentrantLock();
    fileLock.lock();
    fileMap.put(fileName, fileLock);
    mapLock.unlock();    

    // download and deal with file

    mapLock.lock();
    fileMap.remove(fileName);
    fileLock.unlock();
    map.unlock();
}
else // someone is downloading this file!
{
    mapLock.unlock();
    fileLock.lock();
    fileLock.unlock(); 

    // When you get here, you know the other thread has downloaded the file
    // do whatever it is you need to do in that case
}

Несколько более элегантным решением было бы использовать Condition вместе сReentrantLock (я не включаю геттеры для краткости)

public class LockSet {
    public Lock lock;
    public Condition condition;

    public LockSet() {
        lock = new ReentrantLock();
        condition = lock.newCondition();
    }
}

Теперь с этим удобным классом вы можете делать следующее:

mapLock.lock();
LockSet lockSet = fileMap.get(fileName);
if (lockSet == null)
{
    lockSet = new LockSet();
    fileMap.put(fileName, lockSet);
    mapLock.unlock();

    // download and deal with file

    mapLock.lock();
    fileMap.remove(fileName);
    lockSet.lock.lock();
    lockSet.condition.signalAll();
    mapLock.unlock();
}
else // someone is downloading
{
    lockSet.lock.lock();
    mapLock.unlock();
    lockSet.condition.await();

    // once we get here, the file has finished downloading in the other thread
}

Редактировать: Ответ, который был удален его автором, заставил меня задуматься над этим.

Одним из недостатков этой схемы является то, что потоки в вашем пуле время от времени будут ожидать.Поскольку вы используете фиксированный размер пула, это может стать причиной узкого места, если у вас было несколько запросов на один и тот же файл одновременно.Если бы вы внедрили механизм организации очередей для задач, как вы упомянули в своем посте, вы могли бы просто обойтись без самого Map (Map<String,String>) без механизма двойной блокировки.

Ваши рабочие потоки извлекают информацию о файле из очереди, проверяют карту, чтобы увидеть, существует ли она (все еще блокирует / разблокирует блокировку карты), но помещают файл обратно в очередь в случае, если кто-то ещезагружает его (на это указывает только тот факт, что для этого файла существует запись в Map - вы можете просто использовать null для значений)

...