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