Pull to refresh

Граф сериализации в Java. Лучше 40 раз по разу, чем 1 раз 40 раз

На днях пришлось столкнуться с одним из нюансов стандартной сериализации в Java. Два экземплара приложения передавали через сокет сериализуемые объекты, которые отображались в панельке как движущиеся шарики, звездочки и т.п. При кликаньи мышью по объекту в одном приложении его копия появлялась в том же месте в панельке другого. Движение каждого объекта управлялось отдельным потоком – при десериализации ссылка на новый объект записывалась в вектор и передавалась вновь создаваемому потоку.
Эффект обнаружился при повторном клике по одному и тому же объекту – вместо создания новой копии увеличивалась скорость движения старой, а новая вообще не появлялась. Сначала грешили на синхронизацию, а потом возникла стойкая догадка: увеличение скорости может быть связано только с тем, что несколько потоков разделяют один и тот же объект.

Далее, всплыла матчасть. При сериализации связанных объектов имеет место граф сериализации. Сериализуется объект, если он содержит ссылки или массивы ссылок на сериализуемые объекты, то они сериализуются рекурсивно. При этом сериализуемые объекты помечаются, что исключает зацикливание при обходе графа.
Кроме того, для сериализуемых объектов создаются уникальные идентификаторы, которые необходимы для восстановления ссылок при десериализации. Т.е. при первой сериализации объект передается «по полной программе» и для него создается внутренний идентификатор, если алгоритм повторно «натыкается» на помеченный объект, то передается только его идентификатор.

Такая схема вполне логична, если в поток сериализуется связанная структура данных по частям, например, по ссылке p1 сериализуются объекты 1,2,3, а по ссылке p2 – 4,5, при переходе по ссылкам 4-2 и 5-3 в поток пишутся только идентификаторы объектов. Таким образом, пометка объектов в графе сериализации связана не с отдельным вызовом метода writeObject, а с потоком в целом.


image

Попытка сериализовать последовательность состояний объекта приведет к тому, что поток «заклинит» на первом состоянии, которое на приемном конце (если речь идет о сети) не будет обновляться.
Для воспроизведения ситуации написал простенький тест. Объект 10 раз серилизуется в файл, каждый раз увеличивая внутреннее значение от 0 до 10. При десериализации получаем в массиве 10 ссылок на единственный объект.

package javaserializationeffect;
import java.io.*;

/*   Один и тот же объект - содержимое меняется, сериализуется ID */

public class JavaSerializationEffect {

    public void effect() throws Throwable{
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("test.dat"));
        Test xx=new Test();
        for(int i=0;i<10;i++){ 
            xx.setA(i);
            out.writeObject(xx);
            out.reset();           	// Сброс графа сериализации в потоке !!!!!!!!!!!!!!
            }                       		// Без этого объект пишется 1 раз, а после него - ссылки
        out.close();
        Test zz[]=new Test[10];
        ObjectInputStream in=new ObjectInputStream(new FileInputStream("test.dat"));
        for(int i=0;i<10;i++) 
            zz[i]=(Test)in.readObject();
        in.close();
        for(int i=0;i<10;i++) 
            System.out.println("a="+zz[i].getA() + " "+zz[i]);
        }
    public static void main(String[] args) {
        try {
            JavaSerializationEffect xx=new JavaSerializationEffect();
            xx.effect();
            } catch(Throwable ee){ System.out.println(ee); }   
    }
}

Решение проблемы. Метод ObjectInputStream.reset сбрасывает граф сериализации потока. В тесте это приводит к корректной записи последовательности состояний объекта и ее восстановлении в виде массива ссылок на объекты с уникальными значениями. Последующие тест с двумя потоками показал, что графы сериализации потоков независимы, т.е. отметка объектов в одном потоке никак не сказывается на сериализации в другом (вероятно, поток поддерживает вектор ссылок сериализованных объектов).

Замечание. Если сериализовать структуру данных, изображенную на рисунке последовательностью вызовов writeObject(p1), reset(),writeObject(p2), то при десериализации получим 2 несвязанные структуры данных p1-1,2,3, p2-4,5,2*,3*, т.е. объекты 2 и 3 будут десериализованы в 2 экземплярах, а остальные – в одном.
Замечание. Чтобы тест соответствовал заголовку, замените константу 10 на 40 (шутка).
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.