Синхронизация методов Java не работает должным образом

Я пытаюсь реализовать шаблон проектирования пула объектов. Я создал структуру данных, как показано ниже

public class ObjectPool {
    private final Queue<PooledObject> objects;
    private final List<PooledObject> inUseObjects;

    public ObjectPool(final int poolSize) throws IllegalArgumentException {
        if (poolSize <= 0) {
            throw new IllegalArgumentException("Invalid pool size!");
        }

        objects = new ArrayDeque<PooledObject>(poolSize);
        inUseObjects = new ArrayList<PooledObject>(poolSize);

        for (int i = 0; i < poolSize; i++) {
            objects.add(new PooledObject());
        }
    }

    public synchronized PooledObject getObject() {
        PooledObject object;
        while ((object = objects.poll()) == null) {
            try {
                wait();
            } catch (final InterruptedException exception) {
                System.out.println(exception.getMessage());
            }
        }

        inUseObjects.add(object);

        return object;
    }

    public synchronized void returnObject(final PooledObject object) {
        if (object == null) {
            return;
        }

        inUseObjects.remove(object);
        objects.add(object);

        notifyAll();
    }
}

а также работающий клиент

public class Client implements Runnable {
    private final ObjectPool pool;

    private final int id;

    private static int number = 0;

    public Client(final ObjectPool pool) {
        this.id = ++number;
        this.pool = pool;
    }

    private PooledObject getObjectFromPool() {
        PooledObject object = pool.getObject();
        System.out.println(this + " gets " + object);

        return object;

    }

    private void returnObjectToPool(final PooledObject object) {
        pool.returnObject(object);
        System.out.println(this + " returns " + object);
    }

    @Override
    public void run() {
        PooledObject object = getObjectFromPool();

        try {
            Thread.sleep(0);
        } catch (final InterruptedException exception) {
            System.out.println(exception.getMessage());
        }

        returnObjectToPool(object);
    }

    @Override
    public String toString() {
        return "User #" + id;
    }
}

Я пытаюсь запустить этот код следующим образом:

ObjectPool pool = new ObjectPool(2);

ExecutorService es = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
    es.execute(new Thread(new Client(pool)));
}
es.shutdown();
try {
    if (es.awaitTermination(1, TimeUnit.MINUTES)) {
        System.out.println("Object pool pattern implemented!");
    }
} catch (InterruptedException exception) {
    System.out.println(exception.getMessage());
}

Проблема в том, что синхронизация не работает должным образом. Насколько я понимаю, назначение ключевого слова synchronized: Это позволяет синхронизировать другие блоки одного и того же объекта, так что если у вас есть два блока кода, которые могут изменить состояние одного и того же объекта, они не будут мешать друг другу. Но когда я запускаю программу, из вывода ясно видно, что два потока получают один и тот же объект из пула.

выход:

User #1 gets Object #1
User #2 gets Object #2
User #3 gets Object #2
User #5 gets Object #2
User #4 gets Object #1
User #3 returns Object #2
User #4 returns Object #1
User #2 returns Object #2
User #5 returns Object #2
User #1 returns Object #1

Возможно, операторы печати выполняются не по порядку. Они не синхронизированы. Но я все еще смотрю, вижу ли я какую-либо другую проблему.

markspace 20.02.2023 21:23

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

markspace 20.02.2023 21:31

@markspace Я не думал, что проблема может быть в операторе печати. Я также проверю количество звонивших прямо сейчас. Большое спасибо!

davidnatro 20.02.2023 21:39

@markspace Вы правы, проблема была в операторе печати. Только один поток имеет доступ к объекту из пула. Спасибо!

davidnatro 20.02.2023 21:44

Рад, что вы разобрались (и узнали кое-что о том, как работает печать)!

markspace 21.02.2023 00:13
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
5
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Мне кажется, это классическое состояние гонки. Ваши темы Client вызывают getObjectFromPool(), Thread.sleep(0) и returnObjectToPool(...). По сути, sleep(0) является неоперативным, поэтому вполне возможно, что первый поток получит и вернет объект в пул до того, как другой поток вызовет getObjectFromPool(). Кроме того, ваш метод println(...) происходит постфактум, что сбивает с толку упражнение. Обе эти проблемы создают впечатление, что несколько потоков используют один и тот же объект, когда это не так. Кроме того, важно понимать, что System.out — это PrintStream синхронизированный класс, поэтому его использование в многопоточной программе может повлиять на чередование потоков и не рекомендуется.

Если вы хотите проверить, что ваши потоки безопасно получают, обрабатывают и возвращают объекты, вам нужно будет увеличить время ожидания. Кроме того, хотя вы никогда не сделаете этого в пуле производственных объектов, перемещение операторов отладки println(...) внутри методов пула synchronized покажет вам, что происходит с вашим кодом пула потоков.

Наконец, для других есть несколько уже написанных хороших пулов объектов с открытым исходным кодом, включая Apache Commons Pool.

Другие вопросы по теме