Как разбудить тему из другой темы? - PullRequest
0 голосов
/ 15 апреля 2020

Чтобы проиллюстрировать мою проблему, и поскольку мы находимся в период пасхальных яиц, вот раскадровка: первый персонаж - часы, которые периодически показывают время. Но эти часы очень капризны: они не отвечают пользователю, который спрашивает время, но периодически информируют всех своих наблюдателей, и этот период определяется случайным образом. Мой второй персонаж - очень напряженный кролик. Этот кролик ничего не может сделать, если не знает времени. И когда он закончил свое действие, он снова спрашивает время и ждет, чтобы получить его, прежде чем делать что-то еще. Я мог бы добавить других персонажей (кот, безумный шляпник ...), но для этого примера это не нужно.

Итак, в Java у меня будут часы, которые можно наблюдать наблюдатели, независимо от типа наблюдателей; и кролик, который является типом наблюдателя. Я хочу сохранить шаблон «наблюдаемый / наблюдатель», поскольку часы не знают и не заботятся о том, кто наблюдатели.

Вот класс, который я использую:

Часы

public class Clock implements Runnable, ClockObservable {
    private Long time;

    public Clock () {

    }

    @Override
    public void run() {
        while (true) {
            time=System.currentTimeMillis();
            update();
            try {
                int randomTimeUpdate=(int)( (Math.random() + 1) *500); //This clock will update randomly
                Thread.sleep(randomTimeUpdate);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }   
    }

    @Override
    public void update() {
        for (ClockObserver observer : observers) {
            observer.update(time);
        }       
    }
}

ClockObservable

import java.util.ArrayList;

public interface ClockObservable {
public static ArrayList<ClockObserver> observers = new ArrayList<ClockObserver>();

    public default void addObserver (ClockObserver observer) {
        observers.add(observer);
    }

    public default void resetObservers () {
        observers.clear();
    }

    public void update ();

}

ClockObserver

public interface ClockObserver {

    public void update(Long time);

}

Кролик

public class Rabbit implements ClockObserver {

    Long time;

    public Rabbit (Clock clock) {
        clock.addObserver(this);
        whatTimeIsIt();
    }

    public synchronized void whatTimeIsIt () {
        while (true) {
            System.out.println("What time is it ?");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("It's "+time+" -> Yepeeeee !!!! I can do something before asking the time again...");
            System.out.println("-----------------------------------");
        }
    }

    @Override
    public void update(Long time) {
        this.time=time;
        System.out.println("Time = "+time);
        //How can I notify the rabbit ?
        Rabbit.this.notifyAll(); //This cause a «java.lang.IllegalMonitorStateException»
    }
}

Main

public class Main {

    public static void main(String[] args) {
        Clock clock = new Clock();
        Thread thread = new Thread (clock);
        thread.start();
        new Rabbit(clock);
    }
}

The Проблема заключается в следующей инструкции в классе Rabbit в переопределенном методе обновления, который генерирует «java .lang.IllegalMonitorStateException».

Rabbit.this.notifyAll();

И, действительно, Кролик находится в главном потоке, а уведомление находится в потоке-0.

Но что я могу сделать? Как решить мою проблему?

Спасибо за все ваши ответы.

Dr_Click

1 Ответ

1 голос
/ 15 апреля 2020
public void update(Long time) {
    this.time=time;
    System.out.println("Time = "+time);
    //How can I notify the rabbit ?
    Rabbit.this.notifyAll(); //This cause a «java.lang.IllegalMonitorStateException»
}

Функция notifyAll используется для уведомления других потоков об изменении какого-либо общего состояния. Общее состояние здесь не изменилось, поэтому нет ничего, о чем можно было бы уведомить другие темы. Если вы думаете, this.time является общим состоянием, то объясните, почему несинхронизированный метод изменяет его без удержания какой-либо блокировки. Вы не можете сделать это с общим состоянием.

public synchronized void whatTimeIsIt () {
    while (true) {
        System.out.println("What time is it ?");
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

Та же проблема здесь. Вы звоните wait, не проверяя, что то, чего вы ждете, еще не произошло. Чего ты ждешь? Каково общее состояние, которое находится не в том состоянии, в котором оно должно быть?

Представьте себе, если один поток собирается вызвать wait, но планировщик задерживает его. Затем другой поток вызывает notifyAll. Ваш поток все равно будет вызывать wait, потому что он не проверяет общее состояние, чтобы увидеть, должно ли оно ждать.

Вы не можете использовать wait, кроме как ждать, пока какой-то фрагмент общего состояния будет иметь некоторое ценность. Вы не можете использовать notify, кроме как для уведомления другого потока об изменении общего состояния.

Ваш текущий код просто не имеет никакого смысла, потому что он не ждет для ничего и не является он уведомляет о о чем угодно. Функции notify / wait не имеют семантики семпахоров. У них нет своего общего государства. Вы должны реализовать общее состояние.

Если кролик находится в состоянии ожидания, где-то вам нужна переменная, которая хранит состояние кролика, и его нужно установить в «ожидание». Затем, когда вы хотите изменить состояние кролика, вам нужно изменить это состояние на «бегущий». Затем кролик может подождать, пока его состояние станет «запущенным», а другой поток может уведомить его об изменении его состояния. Но вы не реализовали ни одного общего состояния, поэтому ждать нечего и не о чем уведомлять. И, конечно, эта переменная состояния должна быть защищена блокировкой.

Кролик должен иметь код, подобный while (state == waiting) wait();, а другой поток может иметь код, подобный state = running; notifyAll();. Затем кролик ожидает что-то , а другой поток изменил некое общее состояние, которое может потребоваться уведомить другой поток о . Конечно, состояние следует изменять или проверять только удерживая блокировку.

Кроме того, почему не метод update a synchronized? Он меняет time, который является общим.

Вот еще один вариант:

public synchronized void whatTimeIsIt () {
    Long last_time = time; // make local copy
    while (true) {
        System.out.println("What time is it ?");
        try {
            while (time == last_time)
                wait();
            last_time = time;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("It's "+time+" -> Yepeeeee !!!! I can do something before asking the time again...");
        System.out.println("-----------------------------------");
    }
}

Обратите внимание, как поток теперь ждет для чего-то? И обратите внимание, как это нечто разделяет состояние между двумя потоками?

...