Threads paralleler Zugriff auf ein Objekt

firefall777

Mitglied
Hallo,

ich habe ein Problem mit meinem Multithreading Programm beim parallelen Zugriff auf ein Objekt.
Ich habe ein Objekt namens Bus welcher eine ArrayList von Personen enthält.
Nun sollen Personen mit Hilfe von Threads parallel in den Bus eingefügt werden bis dieser komplett voll ist. Ist das erreicht sollen diese Threads terminieren und zwei oder mehr andere Threads wiederum den Bus komplett leeren und dann terminieren.

Allerdings habe ich bei ca 50% der Durchläufe das Problem, dass der Bus ständig kreuz und quer gefüllt und geleert wird weil ich scheinbar die synchronized Blöcke falsch verwende oder die Monitore.


Hier ist mein Ansatz:

Java:
public class Person {

    private String name;

    public Person(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    @Override
    public String toString()
    {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

Java:
public class Bus {

    private ArrayList<Person> places;
    private int maxCapacity;
    private int currentCapacity;

    public Bus(int maxCapacity)
    {
        this.places = new ArrayList<>();
        this.maxCapacity = maxCapacity;
        this.currentCapacity = 0;
    }

    public boolean addPerson(Person person){
        if(person != null && currentCapacity+1 <= maxCapacity){
            places.add(person);
            this.currentCapacity++;
            return true;
        }
        return false;
    }

    public boolean removePerson(int position){
        if(position < places.size() && position >= 0){
            places.remove(position);
            currentCapacity--;
            return true;
        }
        return false;
    }

    public int getCurrentCapacity(){
        return this.currentCapacity;
    }

    public int getMaxCapacity(){
        return this.maxCapacity;
    }

    @Override
    public String toString()
    {
        return "Bus{" +
                "places=" + places +
                ", maxCapacity=" + maxCapacity +
                ", currentCapacity=" + currentCapacity +
                '}';
    }
}

Java:
public class AddPersonThread implements Runnable{

    public static Object monitor = new Object();

    private Bus bus;

    public AddPersonThread(Bus bus)
    {
        this.bus = bus;
    }

    @Override
    public void run()
    {
        while(true){
                synchronized (AddPersonThread.monitor)
                {
                    if (bus.addPerson(new Person("Hans")))
                    {
                        System.out.println(Thread.currentThread().getName() + " added a person! Bus: " + bus.getCurrentCapacity());

                    } else
                    {

                        System.out.println(Thread.currentThread().getName() + ": Bus is full");
                        synchronized (RemovePersonThread.monitor)
                        {
                            RemovePersonThread.monitor.notifyAll();
                        }
                        return;
                    }
                }

        }

    }
}

Java:
public class RemovePersonThread implements Runnable {

    public static final Object monitor = new Object();

    private Bus bus;

    public RemovePersonThread(Bus bus)
    {
        this.bus = bus;
    }

    @Override
    public void run()
    {

            synchronized (RemovePersonThread.monitor){

                try
                {
                    RemovePersonThread.monitor.wait();
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                while(bus.getCurrentCapacity() != 0){
                    bus.removePerson(0);
                    System.out.println(Thread.currentThread().getName() + " removed a person");
                }
            }
            return;

    }
}

Java:
public class Main {
    public static void main(String[] args)
    {
        Bus bus = new Bus(5);
        AddPersonThread addPersonThread = new AddPersonThread(bus);
        Thread thread1 = new Thread(addPersonThread);
        thread1.setName("Thread A: ");
        AddPersonThread addPersonThread1 = new AddPersonThread(bus);
        Thread thread2 = new Thread(addPersonThread1);
        thread2.setName("Thread B: ");


        RemovePersonThread removePersonThread = new RemovePersonThread(bus);
        RemovePersonThread removePersonThread2 = new RemovePersonThread(bus);
        Thread thread3 = new Thread(removePersonThread);
        Thread thread4 = new Thread(removePersonThread2);
        thread3.setName("Thread C: ");
        thread4.setName("Thread D: ");
        thread3.start();
        thread4.start();
        thread1.start();
        thread2.start();
    }
}

Am liebsten wäre es mir natürlich wenn nach dem leeren wieder die Einfüge Threads benachrichtigt werden würden und der Bus sozusagen endlos komplett gefüllt und wieder komplett geleert wird. Aber erstmal will ich das Konzept dahinter verstehen.

PS: mir ist natürlich klar dass immer nur ein Thread zur selben Zeit auf das Object zugreifen darf.
 
Zuletzt bearbeitet:

httpdigest

Top Contributor
Das Problem ist, dass ein AddPersonThread beim Erkennen einer vollen Liste die RemovePersonThread Threads informiert. Diese starten dann sofort, und versuchen, die Liste leer zu räumen.
Währenddessen laufen aber noch andere AddPersonThread Threads, die - wenn sie den Lock wieder bekommen - jetzt sehen, dass die Liste ja nicht mehr voll ist, und somit immer weiter machen mit Hinzufügen. Das heißt, deine Threads, die die Liste befüllen und die Threads, die diese leeren, laufen jetzt konkurrierend/gleichzeitig und unsynchronisiert!
Um das zu beheben, brauchst du einen Mechanismus, der es dir erlaubt, alle AddPersonThread Threads miteinander kommunizieren zu lassen, so dass erst die RemovePersonThread Threads gestartet werden, wenn alle AddPersonThread Threads beendet sind (bzw. erkannt haben, dass die Liste voll ist).
 

firefall777

Mitglied
Jetzt verstehe ich das Problem aber mir fällt nichts ein wie man das umgehen könnte. Wäre der ganze Prozess in eine Richtung also das Auffüllen wäre es ja ganz einfach aber so laufen die Threads ständig gegeneinander. Ich bekomme das nicht hin. Das Einzige was mir einfallen würde wäre kurz sleep zu nutzen aber das ist ja nicht Sinn der Sache.

Gibt es eine Möglichkeit auf die restlichen Threads zu warten bevor notify aufgerufen wird?
 

httpdigest

Top Contributor
Gibt es eine Möglichkeit auf die restlichen Threads zu warten bevor notify aufgerufen wird?
Na klar gibt es die. Die Threads müssen miteinander kommunizieren. Und das tut man meist mit "shared memory". Also z.B. eine Variable, die alle Threads lesen/schreiben können. Du könntest z.B. einen einfachen `int` Counter verwenden, den alle Threads herunterzählen müssen, wenn sie fertig sind. Der letzte Thread, der eine 1 sieht, weiß dann, dass er der letzte Thread ist.
Das Package java.util.concurrent hat hierfür geradezu eine Fülle an Klassen, um so was zu tun - z.B. den CountDownLatch. Ich vermute aber, die dürft ihr nicht verwenden.
 

firefall777

Mitglied
Jedes mal wenn ich etwas neues in Java lerne vergesse ich das, dass was ich davor gelernte ja auch noch funktioniert ^^

Ich habe jetzt eine statische counter Variable gesetzt welche der Anzahl der Threads entspricht und sobald die ArrayListe voll wird wird dieser counter heruntergezählt. Bei 0 wird erst notify aufgerufen somit sind alle Threads die Einlagern sollen sicher beendet. Da sich ja alle Objekte der Klasse diese eine statische Variable teilen wissen auch alle über den aktuellen Stand bescheid so zumindest wie ich das verstehe. Jetzt läuft alles problemlos durch ohne irgendwelche Anomalien. Ich werde mir mal das concurrent package anschauen, verwenden kann ich ja was ich will ich mach das ja für mich selbst. Vielen Dank für die Hilfestellung!
 

Neue Themen


Oben