2

基本上,我有一个具有 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。这让我觉得我弄错了序列化(在单线程环境中一切正常)。如果你一直和我在一起:),这里有两个问题我需要你的帮助:

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

4 回答 4

5

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。

于 2008-10-12T18:10:49.153 回答
3

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

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

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

于 2008-10-12T13:16:12.230 回答
2

这种方法存在许多问题。

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

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

  3. 您不需要将 e 包装在另一个 Exception(e) 中,您只需抛出 e,因为您已声明封闭方法也会抛出 Exception

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

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

我的方法是从此类中剥离任何锁定并将其包装在了解代码线程问题的东西中。

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

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

于 2008-10-12T14:13:12.927 回答
2

一个 String 不能很好地制作一个好的互斥体,但可以用来创建一个:Java:在 ID 上同步

于 2008-10-12T17:05:51.720 回答