您将不得不实施某种同步方案(锁定)以防止第二个线程开始下载另一个线程已经在下载的相同文件。
想到的一个解决方案是向您的每个Runnables 构造函数传递对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 作为值)