如何根据特定条件锁定代码块?
Posted
技术标签:
【中文标题】如何根据特定条件锁定代码块?【英文标题】:How to lock a code block based on a certain condition? 【发布时间】:2016-04-21 15:26:40 【问题描述】:编辑:我添加了一个表格示例(请参阅 google 表格链接)以及生成的苹果对象的外观。
我使用 Jsoup 编写了一个多线程网络爬虫程序,它从网站中提取信息并将其保存到地图中。我无法开始工作的主要事情是,如果程序已经抓取了某些信息,它就不会连接到网站。
项目信息
它从网站上的表格中提取信息,并为表格中的每个单词启动一个线程。
所以线程以某个单词作为类成员开始。每个线程也有相同的 ConcurrentHashMap 对象。我的计划是检查该单词是否已作为键存在于地图中。 如果不是,它应该连接到一个网站以获取有关该单词的信息,向其中添加一些数据,然后将其放入地图中。 如果 map 已经包含这个词,线程应该从 map 中获取 value,并且只添加数据。
所以主要目标不是为了同一个词两次连接到网站。
这里是相关代码sn-ps:
主类 为表中的每个单词启动一个线程。 “元素”包含单词和一个 url 以获取有关该单词的更多信息。
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:元素包含“Apple”和子网址“/apple”, 数据包含“1,20€” 线程 2:元素包含“Orange”和子网址“/orange”, 数据包含“2,40€” 线程 3:元素包含“Apple”和子网址“/apple”, 数据包含“1,50€”
问题是所有线程几乎同时运行,因此线程 1 和 3 都会检查“apple”是否已经在映射中,结果两者都会返回 false。所以他们都连接到网站fruitinformation.com/apple 并获取我只需要一次的关于苹果的基本信息。然后 BOTH 会将他们的数据添加到返回的对象并将其放入地图中,但线程 1 将首先使用“1,20€”执行此操作,然后线程 2 用他的“1,50”覆盖“1,20€”苹果€ apple 作为值。
但是目标是只有一个苹果线程连接到网站并添加他的数据(例如 1,20 欧元),然后另一个意识到地图中已经存在一个苹果对象并且只添加他的数据(1 ,50 欧元)到现有的苹果。水果对象有相应的列表。
因此生成的地图条目应如下所示:Key=Apple , Value= Fruit["Apple", basicInformationFromWebsite, List["1,20€"; "1,50€"]]
另一个线程(橙色)应该完全不受这一切的影响。 所以所有不同的水果应该同时运行,但具有相同水果的元素必须以某种方式相互尊重。 是否有一种同步类型只阻止具有相同水果名称的实例,但不阻止任何其他实例?
我已经阅读了很多关于同步、锁等的信息,但找不到解决我的问题的方法。 如果有人可以帮助我,那就太好了,在此先感谢!
【问题讨论】:
... 并为表中的每个单词启动一个线程。 这听起来是个坏主意。一个更好的主意是为表中的每个单词提交一个 task 到ExecutorService
。为 ExecutorService 配置适当数量的线程,以满足您正在运行的机器的计算和 I/O 容量。
正如您在我的“Main”类中看到的,我已经使用了 ExecutorService。也许它的措辞令人困惑,但“启动线程”的意思是“向 ExecutorService 提交任务以在线程中处理”。我使用 20 的 FixedThreadPool,因为更多的连接被网站阻止。
【参考方案1】:
XY 问题。同步不会解决这个问题。即使假设您可以实现它,第二个线程也会被第一个线程阻塞,然后继续执行不需要的爬取。
您可以添加一组已经开始处理的单词,或者在地图中添加一个虚拟元素,以显示它已经在处理但尚未完成。
【讨论】:
【参考方案2】:如果您首先获得单词的总列表,则只需使用占位符值预先填充地图。那么您只需要为地图中的每个键启动线程即可。
【讨论】:
是的,我首先解析单词表。然而,仅仅为每个映射键启动线程是行不通的,因为大多数单词在映射中多次存在,并且每次它们在另一列中有不同的数据,我需要在每个键的值中添加到对象中。 @MasterReY - 这没有意义,如果您使用水果名称作为表的键,则每个名称只能有一个水果。 是的,但是在表格中可以说我多次使用“苹果”这个词。每次它在同一行的某一列中都有不同的数据。 (1,20€, 1,50€, 2,30€, ....) 就像彼得以 1.20 欧元卖了一个苹果,丽莎以 1.50 欧元卖了一个苹果等等。所以目标是单词“apple”作为地图中的键和一个苹果对象,其中包含来自网站的一些关于苹果的基本信息以及所有不同的数据(1,20 欧元、1,50 欧元等)。水果对象有一个列表。 @MasterReY - 我没有看到问题。解析表格并使用水果名称和表格信息填充地图。然后运行一堆线程来抓取基本的水果信息。同样,不需要复杂的协调。 我看到的问题是基本水果信息被多次抓取同一种水果。我想我可以在主线程中有一个“已经刮掉”的水果列表,然后用一个布尔值启动线程,告诉它仍然需要这个特定水果的基本信息,或者它不需要再刮掉它已经在列表中。我只是认为有一种方法可以在并行线程内部处理这个问题。我想这是不可能的,我必须在外面使用一个列表。感谢您的帮助。【参考方案3】:不确定我的回答是否与您构建应用程序的方式一致,但接下来是处理您的问题类型的“正确”方法,这在并行应用程序中很常见。
获得你想要的东西并避免“双重”计算当然是可行的。我建议您阅读 java concurrency in practice,更具体地说,我认为是第 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()
【讨论】:
第一段不清楚。你指的是什么“这个”?用户的提议?你的答案是什么? 答案如下。我已经更正了,所以希望现在应该更清楚了。 '这是处理您的问题类型的“正确”方法'仍然读起来就像您在第一段中支持 OP 的建议。我建议你使用类似“如下”而不是“这个”。 嗯...我在以前版本的程序中做过这个,但因为它不起作用而放弃了这个想法。我真的不记得这个问题了。我知道我已经为线程使用了 Callables,将它们提交给 ExecutorService 并将返回的 Futures 放入列表中。然后我遍历期货列表并处理结果。这可能是使用 Futures 的错误方法,所以我会研究您的建议并再次尝试它们。如果我理解正确,您建议使用地图将单词与期货一起存储,然后将对象添加到第二张地图? 是使用 Callable 还是 Runnable 您可能最清楚,因为这取决于您希望如何使用结果。但是是的,正如您所说,这个想法是使用单词(键)和期货(值)的映射。当然,这些未来正在运行,所以每当一个线程/任务试图计算一个已经见过的单词的结果时,它只是等待现有的未来完成而不是添加一个新的!我的伪代码不正确,所以是的,看看我推荐的书。以上是关于如何根据特定条件锁定代码块?的主要内容,如果未能解决你的问题,请参考以下文章