Синхронизированы ли конструкторы до полного завершения? - PullRequest
0 голосов
/ 08 ноября 2018

Я создаю программу, которая требует создания некоторых объектов, для создания которых требуются такие интенсивные вычисления, мой самый разумный путь - это создать их в своих собственных выделенных потоках, в то время как главный поток продолжает обрабатывать другие объекты, пока объекты нужны.

Поэтому я подумал о создании специального класса, специально предназначенного для создания пользовательских объектов в собственном потоке. Вот так:

public abstract class DedicatedThreadBuilder<T> {

    private T object;

    public DedicatedThreadBuilder() {
        DedicatedThread dt = new DedicatedThread(this);
        dt.start();
    }

    private void setObject(T i) {
        object = i;
    }

    protected abstract T constructObject();

    public synchronized T getObject() {
        return object;
    }

    private class DedicatedThread extends Thread {

        private DedicatedThreadBuilder dtb;

        public DedicatedThread(DedicatedThreadBuilder builder){
            dtb = builder;
        }

        public void run() {
            synchronized(dtb) {
                dtb.setObject(dtb.constructObject());
            }
        }

    }

}

Меня беспокоит только то, что этот механизм будет работать должным образом, только если главный поток (т. Е. Поток, который создает DedicatedThreadBuilder ) имеет синхронизированную блокировку на DedicatedThreadBuilder до тех пор, пока его конструкция не будет завершено, и поэтому блокирует попытку DedicatedThread построить объект продукта, пока не будет завершено построение DedicatedThreadBuilder . Зачем? Поскольку подклассы DedicatedThreadBuilder , без сомнения, должны быть сконструированы с параметрами, их необходимо будет передать в собственное хранилище, чтобы их можно было использовать в процессе constructObject () .

, например

public class JellybeanStatisticBuilder extends DedicatedThreadBuilder<JellybeanStatistics> {

    private int greens;
    private int blacks;
    private int yellows;

    public JellybeanStatisticBuilder(int g, int b, int y) {
        super();
        greens = g;
        blacks = b;
        yellows = y;
    }

    protected JellybeanStatistics constructObject() {
        return new JellybeanStatistics(greens, blacks, yellows);
    }

}

Это будет работать правильно только в том случае, если объект заблокирован другими потоками до тех пор, пока он не будет полностью построен. В противном случае DedicatedThread может попытаться построить объект до назначения необходимых переменных.

Так вот как работает Java?

Ответы [ 3 ]

0 голосов
/ 08 ноября 2018

Я думаю, что вы хотите иметь какой-то синхронизированный фабричный класс:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

public class SyncFactory<T> {    
    // alternatively, use newCachedThreadPool or newFixedThreadPool if you want to allow some degree of parallel construction
    private ExecutorService executor = Executors.newSingleThreadExecutor();

    public Future<T> create() {        
        return executor.submit(() -> {
            return new T();
        });
    }
}

Теперь вы замените использование T, которое может потребоваться до того, как T будет готов с Future<T>, и у вас будет выбор между вызовом метода Future.get() для блокировки до его готовности, установить тайм-аут, или позвонить Future.isDone(), чтобы проверить конструкцию без блокировки. Кроме того, вместо опроса Future вы можете захотеть, чтобы фабрика вызвала обратный вызов или опубликовала событие, чтобы уведомить основной поток о завершении строительства.

0 голосов
/ 08 ноября 2018

Проблема в том, что вы используете подкласс до его создания. Это не имеет ничего общего с многопоточностью. Если бы вы вызывали constructObject непосредственно из конструктора DedicatedThreadBuilder, это было бы так же плохо.

Разумная реализация, близкая к тому, что у вас есть, - просто предоставить DedicatedThreadBuidler отдельный метод start(), который следует вызывать после создания объекта.

Или вы можете расширить его Thread и использовать методы Thread.

Или вы можете заставить его реализовать Runnable, чтобы вы могли использовать его с Thread или Executor или чем-то еще.

0 голосов
/ 08 ноября 2018

Если ( большой если), то это действительно необходимо (подумайте, что сначала) ...

Общая идея, к которой вы стремитесь, может сработать, но ваш код сбивает с толку и, на мой взгляд, на первый взгляд, в любом случае кажется, что он может не сработать. Этот тип сложности, который может сломать вещи, является очень веской причиной для двоякого и даже тройного мышления, идущего по этому пути.

Основная проблема, которую я сразу заметил, заключается в том, что вы создаете только один экземпляр объекта. Если это фабрика, которая просто создает вещи в другом потоке, то DedicatedThread должен вызываться в DedicatedThreadBuilder constructObject, а не в его конструкторе.

Если, с другой стороны, вы на самом деле намереваетесь для DedicatedThreadBuilder создать только 1 экземпляр T, тогда эта абстракция кажется ненужной ... просто переместите поведение DedicatedThread в DedicatedThreadBuilder, поскольку DedicatedThreadBuilder на самом деле, кажется, не делает ничего лишнего.

Во-вторых, второстепенная вещь, которая не является неправильной настолько, насколько это просто не нужно: у вас есть внутренний класс, который вы передаете экземпляру внешнего класса его конструктору (то есть * 1023) Конструктор * принимает ссылку на своего родителя DedicatedThreadBuilder). В этом нет необходимости, поскольку нестатические внутренние классы уже связаны с их внешними классами, поэтому внутренний класс может ссылаться на внешний класс без какой-либо дополнительной ссылки на него.

В-третьих, если вы переместите поведение из конструктора в отдельный метод, вы можете синхронизировать это. Лично я хотел бы, чтобы constructObject был тем, что запустило процесс, чтобы вызов dtb.constructObject() начал создание объекта, а constructObject сам установил object = newlyCreatedThing, когда это было сделано. Тогда вы можете синхронизировать этот метод, если хотите, или делать все что угодно, и вам не нужно беспокоиться о том, что конструктор может вести себя не так, как вы хотите - на мой взгляд, вам не следует беспокоиться о том, что конструктор может иметь некоторые странные побочные эффекты.

В-четвертых, есть ли у вас какой-нибудь способ узнать, когда объект готов и доступен для получения? Возможно, вы захотите добавить какой-то механизм для этого, например, наблюдателя или другой обратный вызов.

...