Wątek nie kończy działania

0

Zadanie polega na wczytaniu z pliku danych dotyczących towarów (w jednym wątku) - każda linia pliku txt zawiera dwie dane: id_towaru waga_towaru - wątek powinien informować o utworzeniu odpowiedniej liczby towarów (np. co 200).
Drugi równoległy wątek powinien sumować wagę tych towarów, informować o zważeniu odpowiedniej liczby towarów (np. co 100) i na koniec podawać łączną wagę wszystkich towarów.

Moje rozwiązanie oparłem o problem producent - konsument. Wątek wczytujący towar pobiera go z pliku i "wrzuca" do schowka. Wątek ważący pobiera towar ze schowka i pobiera jego wagę. Wszystko wygląda OK, ale wątek ważący nie kończy pracy i nie podaje łącznej sumy towarów (a dokładniej - robi to od czasu do czasu). Gdzie tkwi błąd?

Kody
Klasa Main:

public class Main {

    public static void main(String[] args) {
        Schowek s = new Schowek();
        Wczytanie wczytanie = new Wczytanie(s);
        Wazenie wazenie = new Wazenie(s);
        Thread wczytaj = new Thread (wczytanie);
        Thread zwaz = new Thread (wazenie);
        wczytaj.start();
        zwaz.start();
    }
}

Klasa Towar:

public class Towar {

    int idTowaru;
    double wagaTowaru;
    static int liczbaTowarow = 0;

    public Towar (int i, double w) {
        idTowaru = i;
        wagaTowaru = w;
        ++liczbaTowarow;
    }

    public double getWagaTowaru() {
        return wagaTowaru;
    }

}

Klasa Schowek:

public class Schowek {

    Towar towar;
    boolean ready = false;

    synchronized Towar get() {
           try {
             while(!ready)
               wait();
           } catch (InterruptedException exc) { return null; }
           ready = false;
           return towar;
       }

       synchronized void put(Towar t) {
            towar = t;
            ready = true;
            notify();
       }

}

Klasa Wczytanie:

import java.io.File;
import java.util.Scanner;
import java.util.StringTokenizer;

public class Wczytanie implements Runnable {

    private Towar towar;
    private Schowek schowek;
    volatile static boolean czyJestJeszczeTowar = true;

    public Wczytanie(Schowek schowek) {
        super();
        this.schowek = schowek;
    }

    @Override
    public void run() {
        try {
            Scanner scan = new Scanner(new File("../Towary.txt"));
            while (scan.hasNextLine()) {            
                  StringTokenizer st = new StringTokenizer (scan.nextLine());
                  int a = Integer.parseInt(st.nextToken());
                  double b = Double.parseDouble(st.nextToken());
                  towar = new Towar(a, b);
                  schowek.put(towar);
                  if (Towar.liczbaTowarow%200 == 0) System.out.println("utworzono " + Towar.liczbaTowarow + " obiektów");
            }
            czyJestJeszczeTowar = false;
            scan.close();
        }
        catch (Exception exc) {
            System.out.println("Brak pliku");
        }
        System.out.println("Razem utworzono: " + Towar.liczbaTowarow + " obiektów");
    }

}

Klasa Wazenie:

public class Wazenie implements Runnable {

    private Towar towar;
    private Schowek schowek;
    double waga = 0;

    public Wazenie(Schowek schowek) {
        super();
        this.schowek = schowek;
    }

    @Override
    public void run() {
        while (Wczytanie.czyJestJeszczeTowar == true) {
            towar = schowek.get();
            waga += towar.getWagaTowaru();
            if (Towar.liczbaTowarow%100 == 0) {
                System.out.println("obliczono wage " + Towar.liczbaTowarow + " towarów");
            }
        }
        System.out.println("Łączna waga wszystkich towarów: " + waga);
    }

}

Drugi problem to prezentacja wyników. W wynikach drugi wątek podaje nierówne dane dotyczące ważenia (103 towary poniżej), a niektóre dane w ogóle pomija (brak informacji o zważeniu 200 towarów):
obliczono wage 103 towarów
utworzono 200 obiektów
obliczono wage 300 towarów
utworzono 400 obiektów
obliczono wage 400 towarów
Razem utworzono: 444 obiektów

0

Twoje rozwiązanie problemu Producent-Konsument jest niewłaściwe - gubisz towary.
Wątek wczytujący dane z pliku nie sprawdza czy w schowku znajduje się towar. Jeżeli twoje wątki nie wykonują się dokładnie naprzemiennie referencja do towaru w schowku zostanie nadpisana.
Jak coś działa "od czasu do czasu" to przyczyną jest zawsze Race Condition. W twoim przypadku wątek ważący robi wait() w momencie kiedy wątek wczytujący skończył pracę i nie ma kto zrobić notify(). Problem z prezentacją wyników to dokładnie to samo - Kiedy liczba produktów wynosi 200 wątek ważący wisi i nie zauważa tego stanu. A nawet jak zauważy to zanim zdąży coś wypisać to ta liczba już się zmienia i wtedy dostajesz 103 zamiast 100.
Zamiast rzeźbić metody synchronizowane oprzyj sobie schowek o kolejkę blokującą - Wątek wczytujący wrzuca do kolejki, wątek ważący pobiera, każdy trzyma swój licznik "przetworzonych" towarów. i w odpowiednich momentach wypisuje co trzeba.

0

Oczywiście myślałem o kolejce blokującej, ale niestety prowadzący zajęcia życzy sobie, żebym poćwiczył synchronizowanie wątków, więc muszę się jakoś z tym problemem uporać bez niej...

0

W metodzie put() dodaj warunek sprawdzający czy towar został pobrany, utrzymuj oddzielne liczniki w klasach Wczytanie i Wazenie, zmień nazwy na anglojęzyczne i przemyśl kwestię zakończenia metody run w klasie Wazenie. W takiej wersji jak jest obecnie w końcu dopadnie Cię Race Condition.

0

Zmodyfikowałem klasę Schowek:

public class Schowek {

    Towar towar;
    boolean ready = false;

    synchronized Towar get() {
        while(ready == false) {
        try {
              wait();
           } catch (InterruptedException exc) { return null; }
        }
           ready = false;
           notify();
           return towar;
       }

       synchronized void put(Towar t) {
           while (ready == true) {
               try {
                   wait();
               }
               catch (InterruptedException exc) {return;}
           }
           towar = t;
           ready = true;
           notify();
       }

}

i dodałem licznik wczytanych i licznik zważonych w klasach Wczytanie i Wazenie:

import java.io.File;
import java.util.Scanner;
import java.util.StringTokenizer;

public class Wczytanie implements Runnable {

    private Towar towar;
    private Schowek schowek;
    volatile static boolean czyJestJeszczeTowar = true;
    private int licznikWczytanych;

    public Wczytanie(Schowek schowek) {
        super();
        this.schowek = schowek;
    }

    @Override
    public void run() {
        try {
            Scanner scan = new Scanner(new File("../Towary.txt"));
            while (scan.hasNextLine()) {            
                  StringTokenizer st = new StringTokenizer (scan.nextLine());
                  int a = Integer.parseInt(st.nextToken());
                  double b = Double.parseDouble(st.nextToken());
                  towar = new Towar(a, b);
                  licznikWczytanych++;
                  schowek.put(towar);
                  if (licznikWczytanych%200 == 0) System.out.println("utworzono " + licznikWczytanych + " obiektów");
            }
            scan.close();
        }
        catch (Exception exc) {
            System.out.println("Brak pliku");
        }
        czyJestJeszczeTowar = false;
        System.out.println("Razem utworzono: " + Towar.liczbaTowarow + " obiektów");
    }

}
public class Wazenie implements Runnable {

    private Towar towar;
    private Schowek schowek;
    double waga = 0;
    int licznikZwazonych = 0;

    public Wazenie(Schowek schowek) {
        super();
        this.schowek = schowek;
    }

    @Override
    public void run() {
        while (Wczytanie.czyJestJeszczeTowar == true) {
            towar = schowek.get();
            licznikZwazonych++;
            waga += towar.getWagaTowaru();
            if (licznikZwazonych%100 == 0) {
                System.out.println("obliczono wage " + licznikZwazonych + " towarów");
            }
        }
        System.out.println("Łączna waga wszystkich towarów: " + waga);
    }

}

ale nadal nie umiem sobie poradzić z zakończeniem wątku odpowiedzialnego za ważenie...
Jeżeli już ten wątek się skończy, to waga jest podawana całkowicie prawidłowo (sprawdzałem też stany "pośrednie" ważenia i tam dane też są OK). Jak sprawić, żeby wątek odpowiedzialny za ważenie kończył się zawsze???

0

Na przykład poprzez prawidłowe obsłużenie wyjątku InterruptedException. Wątek wczytujący jak już wczyta wszystko to na wątku ważącym wywołuje metodę interrupt() i w ten sposób przerywa wait() lub ustawia status na interrupted. W obsłudze InterruptedException sprawdzasz czy w schowku coś jest, jeżeli tak to pobierasz, jak nie to kończysz. Mało to eleganckie rozwiązanie ale powinno zadziałać

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0