【问题标题】:Is it safe to recycle DatagramPacket objects?回收 DatagramPacket 对象是否安全?
【发布时间】:2017-02-17 11:48:22
【问题描述】:

问题:

假设您的传输速度高达 10 MB/s,回收 DatagramPacket 对象而不是每次发送数据包时都创建一个新对象是个好主意吗?


故事:

我正在创建一个 LAN 文件同步应用程序,该应用程序有时会处理超过 30 GB 的文件。文件同步应用程序将通过 100Mbit 有线 LAN 传输文件。我已经有一个防丢包系统(完美无缺)。

该程序运行良好,但占用了大约 10% 的 CPU 使用率,而且由于这是一个后台应用程序,这对我来说太多了。理想情况下,它会在 3% 左右。

在进行分析时,我发现垃圾收集器正在发疯,每隔几秒就会激活一次。我知道对象创建(大量完成时)对于 Java 来说很繁重,所以现在我正在尝试尽可能多地回收对象和数组。每个包含文件数据的数据包大小为 1450 字节,这意味着以 10 MB/s 的速度传输大约每秒 7,200 个数据包。我决定开始回收这些数据包(即发送数据包时,DatagramPacket 对象将添加到列表中,5 秒后DatagramPacket 可以重新使用)。当 DatagramPacket 被重用时,DatagramPacket.setData() 方法用于分配它要发送的数据。

除了发送包含文件数据的数据包外,我还大约每秒发送一次小数据包来尝试确定连接的 ping。这些 ping 数据包的大小为 10 个字节。


错误:

在使用DatagramPacket 回收功能测试我的应用程序大约 30 分钟后,开始出现奇怪的错误。有一次传输的文件损坏了,而其他时候我得到了一些我无法理解的东西......下面是我班级的一些代码。 整数length只能通过applyData()方法设置。

public class PacketToSend {

    private int length;
    private DatagramPacket packet;

    ...

    public void applyData(byte[] newData) {
        try {
            length = newData.length;
            packet.setData(newData, 0, length);
        } catch(java.lang.IllegalArgumentException e) {
            System.out.println("Arraylength = "+newData.length);
            System.out.println("length value = "+length);
        }
    }

    ...

}

每次大约测试20-40分钟后,我得到一个IllegalArgumentException,告诉我newData的大小是10,length的值是1450,因此说长度是非法的。这怎么可能?变量length 没有在其他任何地方修改,而是在此方法中,并且在调用setData() 之前设置!好像DatagramPacket 随机切换到发送ping 数据...

这些错误仅在我启用 DatagramPacket 回收功能时发生。

请注意,发送数据包后,它会被放置在一个列表中,并等待 5 整秒,然后再重新使用它。我想知道操作系统是否以某种方式在这些数据包中占有一席之地,或者某些本机代码正在操纵数据。

我的程序中只有一个线程发送数据包,所以这不是线程或同步问题。

因此我的问题是:回收DatagramPacket 对象是否是个好主意,而不是每次发送数据包时都创建一个新对象?还是我在玩火和我真的应该独自离开的东西?


尝试的修复:

  • 我在拨打电话后放置了length = newData.length; setData(newData, 0, newData.length);,这阻止了 IllegalArgumentException 但我还是遇到了其他错误,比如 作为连接丢失。无论如何,上面提到的错误,根据 以我对 Java 的了解,根本不应该发生,所以我认为这里还有其他事情在起作用。

【问题讨论】:

  • 看起来像一个多线程错误。如果你每次都创建一个新对象,没有什么可分享的,只是回收它,你需要确保访问是线程本地的或线程安全的。
  • 将字段移动到局部变量以改善事物是一个很大的提示。尝试检查您是否只从同一线程访问该对象。即保存 Thr​​ead.currentThread() 并检查它。
  • PacketToSend 是否真的需要维护自己的length 副本,与packet 维护的副本分开?并不是说删除它就一定会解决潜在的线程安全问题,但保留相同数据的多个副本确实会暴露更大的错误范围。
  • @JohnBollinger 我猜你是对的。上面的代码与原始类的版本略有不同,但是是的,我可以删除该变量。我现在已经这样做了。 :) @PeterLawrey 我查看了将DatagramPackets 添加和删除到列表中的代码,我确实发现了一个线程确实以非同步方式将DatagramPacket 对象添加到列表中的情况。我现在已经使这个线程安全了。但是,该列表使用peekFirst() 来检查头部对象是否有资格重复使用。如果符合条件,则调用pollFirst()。我希望这能解决这个问题,但我仍然保持谨慎。多谢你们。 :)

标签: java sockets networking udp datagram


【解决方案1】:

回收 DatagramPacket 对象是否安全?

据我所知或可以确定,重复使用 DatagramPacket 实例本身并没有什么不安全的地方。

另一方面,您描述的错误仅在实例在两个或多个线程之间共享时才有意义,并且在没有适当同步的情况下从多个线程访问共享对象肯定是不安全的。没有多少等待可以代替同步,因此在重用之前施加 5 秒延迟的策略可能会适得其反——它不能保证正确操作,但它可能会导致您的程序维护的活动对象比它真正需要的要多。

如果没有您的程序架构的详细信息,我们只能笼统地谈论您可以做些什么来解决这种情况。在最一般的级别上,替代方法是避免在线程之间共享对象并以线程安全的方式访问共享对象。但是,任何线程安全对象共享机制都会带来相对较大的开销,而且我倾向于认为在一个文件传输过程中执行 2000 万个线程安全操作的成本太高而无法接受。因此,最好的办法是避免共享对象。

根据需要创建新的DatagramPackets 并且不允许它们逃离创建它们的线程是实现这一目标的一种方法。由于这会为您造成过多的 GC,因此下一个合乎逻辑的步骤可能是维护可重用数据包的 per-thread 队列。您可以为此使用ThreadLocal,但如果每个文件传输都由单个线程管理,那么您也可以考虑使用每个文件队列。无论哪种方式,也要小心其他共享,例如 DatagramPackets 携带的数据缓冲区数组(这很可能是您可以重用的其他东西)。

此外,如果您注意不要在线程之间共享数据,那么您应该能够在没有重用延迟的情况下这样做。实际上,每个线程可能不需要超过一个DatagramPacket 和一个缓冲区数组。这可以使您的代码不仅更高效而且更简单。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-28
    • 2013-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多