Должен ли я синхронизировать ObjectOutputStream.writeObject(Object)?

У меня есть сервер с несколькими клиентами. Он использует один серверный сокет и два пула потоков для приема и обработки запросов от удаленных клиентов: один пул — для обработки клиентских подключений, а другой — для обработки удаленных задач клиентов. Каждый клиент отправляет асинхронные задачи с уникальным идентификатором задачи (в рамках каждого соединения) и набором параметров. При десериализации задачи сервер ищет соответствующую службу, вызывает для нее заданный метод, оборачивает результат вместе с идентификатором задачи в объект ответа и отправляет его обратно клиенту с помощью ObjectOutputStream.

Поскольку задачи обрабатываются одновременно, два или более потока могут закончить обработку задач для одного клиента одновременно и попытаться конкурировать за ObjectOutputStream.

Что происходит дальше? Я имею в виду, пишут ли они свои объекты в выходной поток атомарно или я должен синхронизировать их доступ к ObjectOutputStream, чтобы не было ситуации, когда один поток пишет половину своего объекта - потом вмешивается другой поток и... Объект Франкенштейна будет отправлен клиенту.

import java.io.*;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.*;

public class Server {

    private final ExecutorService connExecutor = Executors.newCachedThreadPool();  
    private final ExecutorService tasksExecutor = Executors.newCachedThreadPool(); 

    public void start() {
        try (ServerSocket socket = new ServerSocket(2323);) {
            while (true) {
                try (Socket conn = socket.accept()) {
                    connExecutor.execute(() -> {
                        try (ObjectInputStream in = new ObjectInputStream(conn.getInputStream());
                             ObjectOutputStream out = new ObjectOutputStream(conn.getOutputStream())) {
                            while (true) {
                                RemoteTask task = (RemoteTask) in.readObject();
                                tasksExecutor.execute(() -> {
                                    handleTask(task, out);
                                });
                            }
                        } catch (IOException | ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    });
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void handleTask(RemoteTask task, ObjectOutputStream out) {
        RemoteAnswer answer = new RemoteAnswer();
        // unwrap remote task 
        // lookup local service 
        // invoke task's method
        // wrap result into remote answer

        // send answer to the client 
        try {
            out.writeObject(answer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

person escudero380    schedule 21.11.2019    source источник
comment
Да. Метод не синхронизирован, поэтому вам следует синхронизировать его самостоятельно.   -  person user207421    schedule 21.11.2019
comment
И просто для протокола: вы понимаете, что встроенная сериализация Java — это технология 2000 года, а не то, что многие люди будут использовать для нового продукта в 2019 году?   -  person GhostCat    schedule 21.11.2019
comment
@GhostCatsaysReinstateMonica, не могли бы вы дать мне подсказку или направление относительно альтернативных подходов к сериализации. Спасибо.   -  person escudero380    schedule 21.11.2019


Ответы (2)


В этом здесь хорошо сказано:

Является ли запись объекта в ObjectOutputStream потокобезопасной операцией?

Абсолютно нет.

Итак, да, ваш код должен сам принимать меры предосторожности.

person GhostCat    schedule 21.11.2019
comment
Теперь я немного запутался. Говорят, что передача ссылки на объект в writeObject() не является атомарной операцией, поэтому если другим потокам разрешено одновременно обращаться к любому из объектов в графе или изменять их во время сериализации объекта, то объект состояние может потерять целостность. Но что произойдет, если два потока передают неизменяемые или ограниченные потоком объекты в общем ObjectOutputStream? - person escudero380; 21.11.2019
comment
Предположим, два потока имеют общий ObjectOutputStream и оба пытаются параллельно записать два неизменяемых объекта (поток A записывает объект A1, поток B записывает объект B1). Создает ли это возможность того, что объекты A1 и B1 могут как-то объединиться, или в любом случае они будут записаны в базовый OutputStream по отдельности: сначала идет A1, затем B1 (может быть, наоборот). Если это так, то я не понимаю, зачем мне использовать синхронизацию в моем коде, если каждая задача обрабатывается в своем собственном потоке, создавая свой собственный локальный объект ответа, который никогда не модифицируется другими потоками? - person escudero380; 21.11.2019
comment
@ escudero380 не имеет значения, являются ли ваши объекты неизменяемыми, ObjectOutputStream - нет. Запись объекта в поток изменит его внутреннее состояние и не является потокобезопасным. Следовательно, вы должны синхронизироваться. И иметь дело с возможностью того, что ответы могут быть отправлены в другом порядке, чем вопросы… - person Holger; 22.11.2019

Эмпирическое правило: если в документации не указано, что определенный класс является потокобезопасным, вероятно, это не так. Потокобезопасность явно является «преднамеренным качеством» (намек на сообщение в блоге Романа Елизарова , один из дизайнеров языка Kotlin), и поэтому его следует всегда упоминать.

Однако, если вы все еще не уверены, обеспечивает ли класс Java SE-библиотеки потокобезопасность или нет (как это может быть упомянуто где-то еще, например, в документации суперкласса), вы также можете просто бросить беглый взгляд на тип исходный код. Как видите, ObjectOutputStream не реализует никаких механизмов синхронизации.

person Quaffel    schedule 21.11.2019