Многопоточность Java ждать и уведомлять в одном блоке - PullRequest
3 голосов
/ 24 мая 2019

Я работаю над небольшой проблемой, когда мне нужно последовательно печатать числа двумя нитями поочередно.Как поток 1 печатает 1, поток 2 печатает 2, поток 1 печатает 3 и так далее ...

Итак, я создал фрагмент кода ниже, но в какой-то момент оба потока переходят в состояние ожидания и ничего не печатается наконсоль.

import java.util.concurrent.atomic.AtomicInteger;

public class MultiPrintSequence {

    public static void main(String[] args) {
        AtomicInteger integer=new AtomicInteger(0);
        Sequence sequence1=new Sequence(integer);
        Sequence sequence2=new Sequence(integer);
        sequence1.start();
        sequence2.start();
    }
}

class Sequence extends Thread{

    private AtomicInteger integer;
    boolean flag=false;

    public Sequence(AtomicInteger integer) {
        this.integer=integer;
    }

    @Override
    public void run() {
        while(true) {
            synchronized (integer) {
                while (flag) {
                    flag=false;
                    try {
                        System.out.println(Thread.currentThread().getName()+" waiting");
                        integer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+" "+integer.incrementAndGet());
                flag = true;
                System.out.println(Thread.currentThread().getName()+" notifying");
                integer.notify();
            }
        }
    }
}

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

Thread-1 510
Thread-1 notifying
Thread-1 waiting
Thread-0 511
Thread-0 notifying
Thread-0 waiting
Thread-1 512
Thread-1 notifying
Thread-1 waiting
**Thread-0 513
Thread-0 notifying
Thread-1 514
Thread-1 notifying
Thread-1 waiting
Thread-0 waiting**

Ответы [ 3 ]

1 голос
/ 24 мая 2019

Ваша переменная flag не разделяется между потоками, но логика вокруг этого флага в любом случае нечетна. Обратите внимание, что вам не нужно использовать AtomicInteger, когда вы используете synchronized.

При правильном использовании synchronized обычной переменной int уже достаточно для реализации всей логики:

public class MultiPrintSequence {
    public static void main(String[] args) {
        final Sequence sequence = new Sequence();
        new Thread(sequence).start();
        new Thread(sequence).start();
    }
}
class Sequence implements Runnable {
    private final Object lock = new Object();
    private int sharedNumber;

    @Override
    public void run() {
        synchronized(lock) {
            for(;;) {
                int myNum = ++sharedNumber;
                lock.notify();
                System.out.println(Thread.currentThread()+": "+myNum);
                while(sharedNumber == myNum) try {
                    lock.wait();
                } catch (InterruptedException ex) {
                    throw new AssertionError(ex);
                }
            }
        }
    }
}

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

1 голос
/ 24 мая 2019

Рассмотрим эту неудачную последовательность событий. Thread1 увеличивает значение, устанавливает флаг в true и уведомляет все потоки в ожидании, установленном для блокировки. Теперь Thread0 уже был в наборе ожидания. Затем Thread0 просыпается, и его флаг = false. Затем Thread0 выходит из цикла while, печатает увеличенное значение и уведомляет все ожидающие потоки. Затем он переходит к следующей итерации в цикле while и вызывает wait для объекта блокировки. Но Thread1 не находится в состоянии ожидания, скорее он выключается из CPU планировщиком после завершения его синхронизированного блока, чтобы дать возможность Thread0. Поток 1 находится в состоянии выполнения и ему снова предоставляется шанс по расписанию, так как не осталось никаких выполняемых потоков. Затем Tread1 входит в цикл while, поскольку flag = true, и вызывает wait для того же объекта блокировки. Теперь оба потока находятся в состоянии ожидания, и их некому разбудить. Так что это хороший пример живой блокировки в системе.

Это происходит из-за того, что флаг является полем экземпляра и поэтому не используется совместно для потоков. Таким образом, каждый поток имеет свою собственную копию флага. Если вы пометите его как статическую переменную, тогда оба потока будут использовать это значение, поэтому проблема решена. Вот как должна выглядеть декларация флага.

static boolean flag = false;

Как это решает проблему? Ну, рассмотрим ту же последовательность событий. И теперь Thread1 устанавливает значение флага в true, прежде чем он вызовет notify для объекта блокировки. Thread0 уже находится в состоянии ожидания. Schedular отключает Thread1 от процессора и дает шанс Thread0. Он запускается и, поскольку flag = true, он входит в цикл while, устанавливает флаг в false и вызывает ожидание для объекта блокировки. Затем Thread0 переходит в состояние ожидания и Schedular дает шанс Thread1. Thread1 возобновляет выполнение и flag = false, следовательно, он выходит из цикла while, печатая увеличенное значение и уведомляя ожидающий поток. Так что сейчас нет живой блокировки.

Однако я не вижу смысла в использовании synchronized и неблокирующих атомарных переменных. Вы не должны использовать их обоих вместе. Более качественная и эффективная реализация приведена ниже.

public class Sequence extends Thread {
    private static final Object lock = new Object();
    private static int integer = 0;
    static boolean flag = false;

    @Override
    public void run() {
        while (true) {
            synchronized (lock) {
                while (flag) {
                    flag = false;
                    try {
                        System.out.println(Thread.currentThread().getName() + " waiting");
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + " " + ++integer);
                flag = true;
                System.out.println(Thread.currentThread().getName() + " notifying");
                lock.notify();
            }
        }
    }

    public static void main(String[] args) {
        Sequence sequence1=new Sequence();
        Sequence sequence2=new Sequence();
        sequence1.start();
        sequence2.start();
    }
}
0 голосов
/ 24 мая 2019

В коде, даже если целое число является атомарным и совместно используется потоками, сам флаг это не так.

class Sequence extends Thread{

    private AtomicInteger integer; //shared
    boolean flag=false; //local

    public Sequence(AtomicInteger integer) {
      this.integer=integer;
    }

Что вызывает изменение в одном потоке, не отражаясь в другом.

Предлагаемое решение:

Вы также можете решить использовать Atomic для флага и поделиться, например:

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

public class StackOverflow {

    public static void main(String[] args) {
        AtomicInteger integer=new AtomicInteger(0);
        AtomicBoolean flag=new AtomicBoolean(true);
        Sequence sequence1=new Sequence(integer, flag);
        Sequence sequence2=new Sequence(integer, flag);
        sequence1.start();
        sequence2.start();
    }
}

И последовательность:

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

class Sequence extends Thread{

    private final AtomicInteger integer;
    private AtomicBoolean flag;

    public Sequence(AtomicInteger integer, AtomicBoolean flag) {
        this.integer=integer;
        this.flag=flag;
    }

    @Override
    public void run() {
        while(true) {
            synchronized (integer) {
                while (flag.get()) {
                    flag.set(false);
                    try {
                        System.out.println(Thread.currentThread().getName()+" waiting");
                        integer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+" "+integer.incrementAndGet());
                flag.set(true);
                System.out.println(Thread.currentThread().getName()+" notifying");
                integer.notify();
            }
        }
    }
}

Это часть вывода:

Thread-1 8566
Thread-1 notifying
Thread-1 waiting
Thread-0 8567
Thread-0 notifying
Thread-0 waiting
Thread-1 8568
Thread-1 notifying
Thread-1 waiting
Thread-0 8569
Thread-0 notifying
Thread-0 waiting
...