当前位置: 首页 > 知识库问答 >
问题:

同步方法上的异常

司空实
2023-03-14

我正在处理来自TCP套接字的大量事件(每秒10个套接字),因此我使用多线程来处理这些事件。

public class MainActivity extends Activity {
  ...

  // In this Map I store the tab name and the associated TabHost.TabSpec instance
  private static Map<String, TabHost.TabSpec> Tabs = Collections.synchronizedMap(new LinkedHashMap<String, TabHost.TabSpec>());
  // In this Map I store pairs of tab-names and a HashSet of undelivered messages
  // It's a class that extends Map<String, HashSet<String>> with some additional functions that doesn't have anything to do with Tabs.
  private static NamesListing Names = Collections.synchronizedMap(new LinkedHashMap<String, HashSet<String>>());

  // Yes, I know the names don't follow the Java standards, but I keeped them for mantaining the question coherence. I will change it in my code, though.

  synchronized private static void ProcessEvent(final String name, final String message) {
    // Low-priority thread
    final Thread lowp = new Thread(
      new Runnable() { 
        public void run() {
          final Iterator<String> iter = Tabs.keySet().iterator();
          while (iter.hasNext()) {
            final String tabname = iter.next();
            // This just returns an int making some calculations over the tabname
            final int Status = Names.getUserStatus(tabname, message);

            // Same than getUserStatus
            if ((Names.isUserSpecial(Status)) && (name.equals(tabname))) {
              // This just removes a line from the HashSet
              Names.delLine(tabname, message);
            }
          }
        }
      });
    lowp.setPriority(3);
    lowp.start();
  }

  ...
}

大多数情况下,这是正确的,但有时会出现这些事件的雪崩,有时我会得到一个ConcurrentModificationException:

12-10 14:08:42.071:E/AndreidRuntime(28135):致命例外:线程-369 12-10 14:08:42.071:E/AndreidRuntime(28135):java.util.并发修改异常12-10 14:08:42.071:E/AndreidRuntime(28135):在java.util.LinkedHashMap$LinkedHashIterator.next条目(LinkedHashMap.java:347)12-10 14:08:42.071:E/AndreidRuntime(28135):在java.util.LinkedHashMap$KeyIterator.next(LinkedHashMap.java:367)12-10 14:08:42.071:E/AndreidRuntime(28135):在es.irchispano.chat.Main活动$6.run(MainActivity.java:244)12-10 14:08:42.071:E/AndreidRuntime(28135):在java.lang.Thread.run(Thread.java:841)

注:244行对应于

final String tabname = iter.next();

陈述

在我看来,这很奇怪,因为我使用的是一个系列。synchronizedMap和处理这些行的方法是同步的,那么为什么它仍然在发生呢?

谢谢

----------编辑----------

很抱歉最初的代码很简洁;我已经尽可能地简化了,但显然这不是一个好主意。我正在粘贴实际的代码。当然,这些结构中的每一个都是初始化的(否则我就不会有问题了:-)),现在我要带着良心阅读你们所有的评论,我会发布我将会发现的东西。谢谢大家的支持!

共有3个答案

缑高朗
2023-03-14

本例中的同步关键字在类MainActive处获取锁,然后该方法启动一个新线程并立即释放锁。

不能保证在处理新请求之前第一个事件的迭代已经结束。

新请求可能会导致两个线程同时遍历映射。

如果在方法中有对映射的修改操作,这将导致一个线程修改映射,而另一个线程仍在迭代它,从而导致并发修改异常。

赵选
2023-03-14

使用集合。synchronizedMap只意味着多个线程可以同时调用get/put/delete。当迭代器在集合上迭代时,集合发生更改时,它无法防止发生错误。即使在单个线程中也可能发生这种情况,例如,如果映射中至少有3个元素(我认为),那么下面的线程应该抛出它:

final Iterator<String> iter = Tabs.keySet().iterator();
iter.next(); 
String k = iter.next();
final Iterator<String> iter2 = Tabs.keySet().iterator();
iter2.next();
Tabs.delete(k);
iter2.next();

现在,正如其他人指出的那样,多个线程可以同时在Map上迭代,因为run方法是不同步的。但是如果选项卡没有被修改,那么这不是错误的来源。

您尚未显示选项卡修改位置。这是因为在迭代器对映射进行迭代时,映射正在被修改,从而导致了异常。

修复方法是对其进行迭代,并使用相同的锁对其进行修改。

董和泽
2023-03-14

您已经向我们展示了mapTabs是如何创建的(Java命名约定要求它的名称以小写字母开头),但没有展示它是如何填充的。实际上,map将始终为空,而当循环将运行零次。此外,本地变量tabname未使用,但在实际代码中可能并非未使用。

也就是说,ProcessEvent似乎将为每个事件运行一次。它是静态的、同步的,这意味着它将获得main活动的监视器。类,以及在同一对象上同步的其他方法不能同时运行。

但是,它会启动一个新的线程,执行实际的工作,并立即返回。该线程不同步,因此任何数量的工作线程都可以同时运行,都使用相同的映射。映射用Collections.synchronizedMap包装,这是防止并发修改的唯一保护。

这种同步映射不允许多个调用者同时调用其方法,但对不同方法的单独调用可以任意交错。例如,当一个调用者将新条目放入映射时,其他调用者无法访问映射。然而,一个调用者可以从映射中获取密钥集,从密钥集中获取迭代器,并开始对其进行迭代,然后另一个线程中的另一个调用者添加、修改或删除条目,最后,第一个线程继续对密钥集进行迭代,并获得ConcurrentModificationException

我建议使用java.util.concurrent.Con相当HashMap来代替,并从ProcessCase中删除同步关键字。

 类似资料:
  • 问题内容: 在Java中制作异步方法的同步版本的最佳方法是什么? 假设您有一个使用以下两种方法的类: 您将如何实现直到任务完成才返回的同步? 问题答案: 看看CountDownLatch。您可以使用以下类似方式模拟所需的同步行为: 您还可以通过2个参与者使用相同的行为,如下所示: 但是,如果您可以控制I 的源代码,建议您重新设计它以返回一个对象。这样,您可以在需要时轻松地在异步/同步行为之间进行切

  • 同步调用异步方法最安全的方法是什么?

  • 我正试图从同步方法运行异步方法。但是我不能等待异步方法,因为我在同步方法中。我一定不理解TPL,因为这是我第一次使用它。 每个方法都需要前一个方法来完成,因为第一个方法的数据用于第二个方法。 Await运算符只能在异步方法中使用。考虑用'async'修饰符标记此方法,并将其返回类型更改为'task' 但是,如果我使用async修饰符,这将是一个异步操作。因此,如果我对的调用没有使用await运算符

  • 本文向大家介绍java synchronized同步静态方法和同步非静态方法的异同,包括了java synchronized同步静态方法和同步非静态方法的异同的使用技巧和注意事项,需要的朋友参考一下 java synchronized 详解 synchronized关键字有两种用法,一种是只用于方法的定义中,另外一种是synchronized块,我们不仅可以使用synchronized来同步一个对

  • 本文向大家介绍不同js异步函数同步的实现方法,包括了不同js异步函数同步的实现方法的使用技巧和注意事项,需要的朋友参考一下 不同函数达到同步的函数模拟 funcList是函数执行函数的队列,其中回调函数中flag=true是同步标记量 以上这篇不同js异步函数同步的实现方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持呐喊教程。

  • 本文向大家介绍JS同步、异步、延迟加载的方法,包括了JS同步、异步、延迟加载的方法的使用技巧和注意事项,需要的朋友参考一下 本文讲述了JS同步、异步、延迟加载的方法。分享给大家供大家参考,具体如下: 一:同步加载 我们平时使用的最多的一种方式。 <script src="http://yourdomain.com/script.js"></script> 同步模式,又称阻塞模式,会阻止浏览器的后续