#java #sockets #networking #udp #datagram
#java #сокеты #сеть #udp #дейтаграмма
Вопрос:
Вопрос:
Предполагая, что вы передаете до 10 МБ / с, хорошая ли идея перерабатывать DatagramPacket
объекты вместо создания нового каждый раз, когда должен быть отправлен пакет?
История:
Я создаю приложение для синхронизации файлов локальной сети, которое иногда обрабатывает файлы объемом более 30 ГБ. Приложение синхронизации файлов будет передавать файлы по проводной локальной сети 100 Мбит. У меня уже есть система защиты от потери пакетов (которая работает безупречно).
Программа работает нормально, но использует около 10% процессора, а поскольку это фоновое приложение, для меня это слишком много. В идеале это будет около 3% максимум.
При профилировании я обнаружил, что сборщик мусора сошел с ума, активируясь каждые несколько секунд. Я знаю, что создание объектов (когда выполняется в больших количествах) становится тяжелым для Java, поэтому сейчас я пытаюсь переработать как можно больше объектов и массивов. Каждый пакет, содержащий файловые данные, имеет размер 1450 байт, что означает, что скорость передачи 10 МБ / с составит около 7200 пакетов в секунду. Я решил начать переработку этих пакетов (т. Е. Когда пакет отправляется, DatagramPacket
объект добавляется в список, и через 5 секунд DatagramPacket
его можно использовать повторно). Когда a DatagramPacket
используется повторно, метод DatagramPacket.setData()
используется для назначения данных, которые он собирается отправить.
Помимо отправки пакетов, содержащих данные файла, я также отправляю небольшие пакеты примерно каждую секунду, чтобы попытаться определить пинг соединения. Эти пакеты 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, просто никогда не должна возникать, поэтому я думаю, что здесь работает что-то еще.
Комментарии:
1. Похоже на многопоточную ошибку. Если вы каждый раз создаете новый объект, вам нечего делиться, кроме как перерабатывать его, и вам нужно убедиться, что доступ является локальным или потокобезопасным.
2. Перемещение поля в локальную переменную, улучшающее ситуацию, — это большой намек. Попробуйте проверить, что вы обращаетесь к объекту только из одного потока. Т.Е. сохраните Thread.currentThread() и проверьте его.
3. Действительно ли классу
PacketToSend
нужно поддерживать свою собственную копиюlength
, отдельную от той, которую поддерживает itspacket
? Не то чтобы удаление этого обязательно решило бы основную проблему потокобезопасности, но сохранение нескольких копий одних и тех же данных, безусловно, увеличивает вероятность ошибок.4. @JohnBollinger Я думаю, вы правы. Приведенный выше код немного отличается от версии исходного класса, но да, я мог бы удалить эту переменную. Я сделал это сейчас. 🙂 @PeterLawrey Я просмотрел код, который добавляет и удаляет
DatagramPacket
s в список, и я нашел один случай, когда поток добавил объект DatagramPacket в список несинхронизированным способом. Теперь я сделал это потокобезопасным. Однако список используетсяpeekFirst()
для проверки, подходит ли объект head для повторного использования. Если разрешено,pollFirst()
вызывается. Я надеюсь, что это исправит это, но я остаюсь осторожным. Спасибо, ребята. 🙂
Ответ №1:
Безопасно ли перерабатывать объекты DatagramPacket?
Насколько я знаю или могу определить, в повторном использовании экземпляров нет ничего небезопасного DatagramPacket
.
С другой стороны, ошибки, которые вы описываете, имеют смысл только в том случае, если экземпляры являются общими для двух или более потоков, а доступ к общим объектам из нескольких потоков без надлежащей синхронизации определенно небезопасен. Никакое количество ожиданий не заменяет синхронизацию, поэтому стратегия введения 5-секундной задержки перед повторным использованием, вероятно, контрпродуктивна — это не гарантирует корректную работу, но может привести к тому, что ваша программа будет поддерживать больше живых объектов, чем ей действительно нужно.
Без подробностей об архитектуре вашей программы мы можем говорить только в общих чертах о том, что вы можете сделать для разрешения ситуации. На самом общем уровне альтернативами являются предотвращение совместного использования объектов между потоками и доступ к общим объектам потокобезопасным способом. Однако любой механизм потокобезопасного совместного использования объектов сопряжен с относительно значительными накладными расходами, и я склонен думать, что выполнение 20 миллионов потокобезопасных операций в ходе одной передачи файла будет слишком дорогостоящим, чтобы быть приемлемым. Поэтому лучше всего избегать совместного использования объектов.
Создание новых DatagramPacket
объектов при каждой необходимости и недопущение их выхода из потоков, в которых они были созданы, является одним из способов достижения этой цели. Поскольку это вызывает слишком много GC для вас, следующим логическим шагом может быть поддержание очередей повторно используемых пакетов для каждого потока. Для этого вы можете использовать a ThreadLocal
, но если каждая передача файлов управляется одним потоком, вы также можете рассмотреть возможность использования очередей для каждого файла. В любом случае, будьте осторожны и с другим общим доступом, таким как массивы буферов данных, переносимые DatagramPacket
s (которые вполне могут быть чем-то другим, что вы могли бы использовать повторно).
Кроме того, если вы будете осторожны, чтобы не обмениваться данными между потоками, вы сможете сделать это без задержки повторного использования. Действительно, вам может потребоваться не более одного DatagramPacket
и одного буферного массива на поток. Это может сделать ваш код не только более эффективным, но и более простым.