Вам нужно будет реализовать какую-то схему синхронизации (блокировку), чтобы предотвратить запуск второго потока для загрузки того же файла, который уже загружает другой поток.
Одно решение, которое приходит на ум, будетпередать каждому из ваших 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
для значений)