【问题标题】:synchronizing io operation in java on a string method argument? [duplicate]在字符串方法参数上同步java中的io操作? [复制]
【发布时间】:2010-09-16 18:21:35
【问题描述】:

基本上,我有一个具有 2 种方法的类:一种将对象序列化为 XML 文件,另一种用于从 XML 读取对象。 以下是恢复对象的方法中同步部分的示例:

    public T restore(String from) throws Exception {
     // variables declaration
        synchronized (from) {
            try {
                decoder = new XMLDecoder(new BufferedInputStream(
                        new FileInputStream(from)));
                restoredItem = decoder.readObject();
                decoder.close();
            } catch (Exception e) {
                logger.warning("file not found or smth: " + from);
                throw new Exception(e);
            }
        }
    // try to cast it
    }

序列化对象时采用了类似的方法。现在,当我创建一个单元测试时,它又创建了 10 个线程,每个线程尝试序列化并立即读取布尔值或字符串,它会失败,表明发生 ClassCastExceptions。这让我觉得我弄错了序列化(在单线程环境中一切正常)。如果你一直和我在一起:),以下是我需要你帮助的 2 个问题:

  1. 在传递给方法的字符串参数上进行同步是否有意义(考虑到 java 中有一个字符串池)?顺便说一句,我尝试在 XMLSerializer 类本身上进行同步,结果保持不变。
  2. 如何正确同步单个文件的 io 操作?

【问题讨论】:

  • ClassCastException 发生在哪一行?
  • restoredItem 对象是实例变量还是局部变量?这可能是多线程问题的根源。一个解码器解码一个对象,下一个线程用其他东西覆盖它。

标签: java multithreading synchronization io


【解决方案1】:

鉴于您的代码的某些部分丢失了,我敢打赌,问题在于在字符串上进行同步。您不能随意假设字符串是池化的(这会破坏您的同步方案)。

最好的方法是添加一个将键(字符串)与其实际同步对象相关联的映射。

除此之外,我建议使用多线程测试看看是什么导致它失败。例如,如果您让所有线程只存储字符串值(而不是字符串或 beoolean),测试是否仍然失败?

【讨论】:

  • 感谢您的回复。是的,仅使用不同的字符串运行时测试仍然失败。我也尝试过在它们所在的类上同步整个方法体。结果是一样的。感谢地图上的提示。
【解决方案2】:

这种方法存在许多问题。

  1. 除非您调用了 String.intern,否则您的 from 字符串可能与您正在调用的另一个字符串不同。依赖内部java字符串缓存的行为不是很健壮。

  2. 您没有在 finally 块中正确处理您的 XMLDecoder,在该调用期间引发的任何异常都会泄漏与该 FileInputStream 关​​联的文件描述。

  3. 你不需要将 e 包裹在另一个 Exception(e) 中,你可以直接抛出 e,因为你声明了封闭方法也会抛出异常

  4. 捕获/抛出异常是一种代码异味。是的,它是 IOException 的超类,可能抛出任何 XML 解码异常,但它也是一堆您可能不想捕获的其他东西的超类,例如 NullPointerException。

要回答您的问题,如何序列化对共享文件的访问以确保其不被多个线程使用,这很棘手。 FileChannel.lock() 在 JVM 中不起作用,它们只是锁定文件不被机器中的其他进程修改。

我的方法是从这个类中去除任何锁定,并将其包装在可以识别代码线程问题的东西中。

我也不会传递一个字符串作为文件名,而是一个文件,它使您能够使用 File.createTempFile(2) 在写入 xml 的事物和读取 xml 的事物之间创建不透明的文件名。

最后,您是要同步对共享文件的访问,还是在检测到对同一文件的多个访问时失败?

【讨论】:

  • 谢谢。修正了我那些愚蠢的错误。您能否详细说明“File.createTempFile(2) to create opaque filenames...”?考虑到如果 smth 已被写入并不意味着它会在同一会话期间被读取,请不要明白这一点。想要同步访问。
  • 你能更新你原来的问题吗? 300 个字符太短,无法解释您想要什么。
【解决方案3】:

一个字符串不能很好地制造一个好的互斥体,但可以用来创建一个:Java: synchronizing on an ID

【讨论】:

    【解决方案4】:

    1。 是的,可以在字符串上同步,但是您需要在字符串上同步。intern() 以便始终获得相同的对象

    StringBuffer sb = new StringBuffer(); sb.append("a").append("b");
    String a = new String(sb.toString());
    String b = new String(sb.toString());
    a == b; //false
    a.equals(b); //true
    a.intern() == b.intern(); //true
    

    由于您想在同一台监视器上进行同步,因此您需要 intern()。

    2。 您可能不想在 String 上同步,因为它可能在其他地方同步,在您的代码内部,在第 3 方或在 JRE 中。如果我想保持同步,我会做的是创建一个 ID 类(它可能只包含字符串),覆盖 equals() 和 hashcode() 以匹配,把它放在一个 WeakHashMap 中,同时作为键和值(请参阅 IdentityHashMap 了解原因)并仅使用地图中的我 .get() (sync map{ syncKey = map.get(new ID(from)); if syncKey==null create and put new key} sync{syncKey} )。

    3。 再说一次,我会一起放弃同步并使用 java.util.concurrent.locks.Lock 代替,在与上述相同的设置中,只是将锁附加到 ID。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-06
      • 2020-10-14
      • 1970-01-01
      相关资源
      最近更新 更多