Аналог пинг-понга в многопоточности

93
22 декабря 2021, 11:40

Не могу понять, почему в данном примере не получается работать с 2 потоками

public class Start {

    public static void main(String[] args) {
        Start start = new Start();
        start.init();
    }
    private void init() {
        Game game = new Game();
        MyThread myThread1 = new MyThread(game, "Ping");
        MyThread myThread2 = new MyThread(game, "Pong");
        myThread1.start();
        myThread2.start();
    }
    private class MyThread extends Thread {
        Game game;
        public MyThread(Game game, String name) {
            setName(name);
            this.game = game;
        }
        @Override
        public void run() {
            while (game.isGameNotOver()) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                game.kick(getName());
            }
        }
    }
    private class Game {
         int counter = 0;
        public synchronized void kick(String name) {
            System.out.println(name + "  " + counter);
            counter++;
        }
        public boolean isGameNotOver() {
            return counter < 10;
        }
    }
}

А именно, если Sleep стоит <=100, то возможны ситуации что 1 поток сделает всю работы. Если поставить 500, то проблема перестанет наблюдаться.

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

Answer 1

DL: первый раз работаю с синхронизацией в java, так что критика приветствуется.

Я немного прокачал класс Game

private class Game {
    volatile int counter = 0;
    volatile boolean isPing = true;
    public synchronized void waitFor(boolean isPing) {
        while (isPing() != isPing && isGameNotOver()) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public synchronized void kick(boolean isPing) {
        System.out.println((isPing ? "Ping": "Pong") + "  " + counter);
        counter++;
        this.isPing = !isPing;
        this.notifyAll();
    }
    public boolean isPing() {
        return isPing;
    }
    public boolean isGameNotOver() {
        return counter < 100;
    }
}

С такой прокачкой, мы видим, что поток будет ждать нужного состояния игры, чтобы сделать ход.

private class MyThread extends Thread {
    Game game;
    private boolean isPing() {
        return "Ping".equals(getName());
    }
    public MyThread(Game game, String name) {
        setName(name);
        this.game = game;
    }
    @Override
    public void run() {
        while (game.isGameNotOver()) {
            game.waitFor(isPing());
            if (game.isGameNotOver())
                game.kick(isPing());
        }
    }
}

Как вы видите, тут мы не завсим от таймингов, а условие изменения состояния игры детерминировано. При таких условиях мы даже можем добавить игроков и все равно увидим, что Ping и Pong будут корректно чередоваться

private void init() {
    Game game = new Game();
    MyThread myThread1 = new MyThread(game, "Ping");
    MyThread myThread2 = new MyThread(game, "Pong");
    MyThread myThread3 = new MyThread(game, "Ping");
    MyThread myThread4 = new MyThread(game, "Pong");
    myThread1.start();
    myThread2.start();
    myThread3.start();
    myThread4.start();
}

Вывод

Ping  0
Pong  1
...
Ping  96
Pong  97
Ping  98
Pong  99
Answer 2

Минимальный пинг понг

public class PingPong {
    public static void main(String[] args) {
        final var numberOfPlays = 20;
        new Thread(() -> play("ping", numberOfPlays)).start();
        new Thread(() -> play("pong", numberOfPlays)).start();
    }
    private static synchronized void play(String name, int numberOfPlays) {
        while (true) {
            System.out.println(name);
            try {
                PingPong.class.notify();
                if (--numberOfPlays == 0) {
                    break;
                } else {
                    PingPong.class.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
READ ALSO
Nexus загружает артифакт, но я получаю ошибку, что артифакт нельзя найти в maven-public

Nexus загружает артифакт, но я получаю ошибку, что артифакт нельзя найти в maven-public

У меня есть своя библиотека servicejar которую я подключаю через nexus

208
Spring boot with firebird database null data

Spring boot with firebird database null data

Пробую делать REST сервис на Spring Boot + Firebird 30

199
Неправильный Consumer

Неправильный Consumer

Дано на текущий момент 3 класса и интерфейс

83
Как создать несколько окон в приложении?

Как создать несколько окон в приложении?

По нажатии на кнопку хочу создать одно диалоговое окноВот код класса

183