如何根据特定条件锁定代码块?

时间:2016-01-15 22:48:03

标签: java multithreading web-scraping locking conditional

编辑:我已添加了一个表格示例(请参阅google sheet链接)以及生成的苹果对象的外观。

我使用Jsoup编写了一个多线程Web抓取器,它从网站中提取信息并将其保存到地图中。我无法工作的主要事情是,如果该程序已经删除了某些信息,则该程序不会连接到该网站。

有关该计划的信息

它从网站上的表中提取信息,并为表中的每个单词启动一个帖子。

因此线程以某个单词作为类成员开始。每个线程也有相同的ConcurrentHashMap对象。我的计划是检查地图中是否已存在该词作为关键字 如果不是,它应该连接到一个网站以获取有关该单词的信息,向其中添加一些数据并在之后将其放入地图中。
如果地图已包含该单词,则该主题应从地图中获取值,并仅将数据添加到该地图中。

所以主要目标不是为同一个词连接两次网站。

以下是相关的代码段:

主要班级
为表中的每个单词启动一个线程。 "元件"包含单词和网址,以获取有关该单词的更多信息。

for (Element element : allRelevantTableElements) {
    executorService.execute(new Worker(element, data, concurrentMap));
}

工人阶级
1.检查单词是否已在地图中 2A。如果它在地图中,只需向其添加数据 2B。如果它不在地图中,则从网站抓取信息,然后向其添加数据。

public class Worker implements Runnable {

MyWebScraper scraper;
Element element;    
String data;
ConcurrentMap<String, Fruit> concurrentMap;

public Worker(Element element, String data, ConcurrentMap<String, Fruit> concurrentMap) {
    this.element = element;
    this.data = data;
    this.concurrentMap = concurrentMap;
}

@Override
public void run() {

    Fruit fruit;

    if (concurrentMap.containsKey(element.text())) { 
        fruit = concurrentMap.get(element.text());
        fruit.addData(data)
    } else {            
        scraper = new WebScraper("http://fruitinformation.com" + element.attr("href"));
        scraper.connect();
        fruit = scraper.getInformation();
        fruit.addData(data)
    }

    concurrentMap.put(element.text(), fruit);
}
}

示例
让我们说这个表看起来像这样:

https://docs.google.com/spreadsheets/d/1JF8sh8Sp9y0SV3Xb5mlISgcJp5s_DhaSp3KbnQLa248/edit?usp=sharing

主类将启动3个主题:
线程1:元素包含&#34; Apple&#34;和郊区&#34; / apple&#34;,
数据包含&#34; 1,20€&#34;
线程2:元素包含&#34;橙色&#34;和郊区&#34; / orange&#34;,
数据包含&#34; 2,40€&#34;
主题3:元素包含&#34; Apple&#34;和郊区&#34; / apple&#34;,
数据包含&#34; 1,50€&#34;

问题是所有线程几乎同时运行,因此线程1和3都将检查&#34; apple&#34;已经在地图中,结果将导致错误。因此,他们同时连接到网站fruitinformation.com/apple并获取有关苹果的基本信息,我只需要一次。然后BOTH会将他们的数据添加到返回的对象并将其放入地图中,但是线程1将首先使用&#34; 1,20€&#34;然后第2个线程覆盖&#34; 1,20€&#34;苹果与他的&#34; 1,50€苹果作为价值。

然而,目标是只有一个苹果线程连接到网站并添加他的数据(例如1,20欧元)然后另一个意识到地图中已经存在苹果对象并且只添加他的数据(1 ,50€)到现有的苹果。水果对象有列表。
因此,生成的地图条目应如下所示:
Key=Apple , Value= Fruit["Apple", basicInformationFromWebsite, List["1,20€"; "1,50€"]]

另一个线程(橙色)应该完全不受所有这些影响。 因此,所有不同的水果应该同时运行,但具有相同水果的元素必须以某种方式相互尊重。 是否存在一种同步类型,它只阻止具有相同水果名称的实例,但不会阻止任何其他实例?

我已经阅读了很多关于同步,锁等的信息,但无法找到解决问题的方法。
如果有人可以帮助我,那将是很好的,谢谢你!

3 个答案:

答案 0 :(得分:0)

XY问题。同步不会解决这个问题。即使假设您可以实现它,第二个线程也会被第一个线程阻塞,然后继续执行不需要的爬网。

您可以添加一组已经开始处理的单词,或者在地图中添加一个虚拟元素,表明它已经被处理,但不完整。

答案 1 :(得分:0)

如果您获得首先的单词总列表,则只需使用占位符值预先填充地图。那么你只需要为地图中的每个键启动线程。

答案 2 :(得分:0)

不确定我的答案是否符合您的应用程序结构,但接下来是处理您的问题类型的“正确”方法,这在并行应用程序中很常见。

当然可以获得你想要的东西并避免“双重”计算。我建议你在实践中阅读 java并发,更具体地说,我认为它是第5章,它们必须进行计算的记忆(大量计算),并且还必须避免两个线程计算相同的数字。

您可以应用的一些技巧是使用 putIfAbsent (仅用于将项目放入地图的方法,如果它尚不存在)。更重要的是,我建议您将 Futures 存储在地图中。 https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html 它们代表计算的结果,然后你们都要进行计算,并确保它不会被计算两次但是你仍然会得到两个线程的结果,因为你只需要调用future.get()来阻塞直到结果收到了。我不会详细介绍它,因为它实际上在java并发书中很好地展示了。

类似于(伪代码)

if !map.containsKey(word) {
    Future f = new Future(word)
    map.putIfAbsent(word, future<curWord>)
    f.get()
} else {
    Future f = map.get(word)
    f.get()
}