Threads

hi,

Ich bin grade dabei ein paar Aufgaben zulösen. Hab schon ein großen Teil erlädigt. Bekommt bitte keine Angst von der Aufgabenstellung unten.Ist zwar ein bischen viel Text aber naja.

Ich will an ersterstelle einfach nur wissen ob ich meine Aufgabe bis zur b) richtig implementiert hab.
Wie kann ich die c) realisieren.

:```public class TeilnehmerListe implements TNListe {

private ArrayList<Teilnehmer> inhalt;

TeilnehmerListe() {
    inhalt = new ArrayList<Teilnehmer>();
}

// Methodenvertrag
/*
 * @pre darf undefiniertes verhalten zeigen
 */

@Override
public synchronized boolean einfügen(Teilnehmer tn) {

    // Auto-generated method stub
    
    boolean keinDoppelgänger = false;

    for (int i = 0; i <= inhalt.size(); i++) {
        if (inhalt.get(i).getName().equals(tn)) {
            System.out.println("Vorhandener Name");
            keinDoppelgänger = false;

        } else {
            keinDoppelgänger = true;
        }
    }
    if (keinDoppelgänger = true) {
        inhalt.add(tn);
        return true;
    } else {
        return false;
    }
}

@Override
public synchronized boolean entfernen(Teilnehmer tn) {
    // Auto-generated method stub
    for (int i = 0; i <= inhalt.size(); i++) {
        if (inhalt.get(i).equals(tn)) {

            inhalt.remove(i);

            return true;

        } else {
            return false;
        }
    }

    return false;
}

public static void main(String[] agrs) {

}

}

public interface TNListe {

    boolean einfügen(Teilnehmer tn);
        
        
    boolean entfernen(Teilnehmer tn);

}

public class Teilnehmer {

private String name;
private int tele;

public Teilnehmer(String nameP, int teleP) {

    this.name = nameP;
    this.tele = teleP;

}

public String getName() {

    return this.name;

}

public int getTele() {

    return this.tele;

}

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

}

public void setTele(int teleP) {

    this.tele = teleP;

}

}```

a) Schreiben Sie eine Klasse Teilnehmer, deren Objekte die privaten Attribute String
name, int Telefonnummer sowie die entsprechenden set/get-Methoden und
geeignete Konstruktoren besitzen.
(b) Schreiben Sie eine Klasse TeilnehmerListe , die folgende Schnittstelle implementiert:
interface TNListe {
boolean einfügen(Teilnehmer tn);
boolean entfernen(Teilnehmer tn);
}
Ein Objekt dieser Klasse soll eine Liste von Objekten der Klasse Teilnehmer
verwalten. Die Klasseninvariante fordert, dass die Liste niemals zwei Teilnehmer mit
gleichen Namen enthalten darf. Ergänzen Sie um entsprechende Konstruktoren.*

(c) Testen Sie diese Klasse, in dem Sie (fast) gleichzeitig zwei Threads, mit Namen
anmelder1 und anmelder2,starten und das folgende „Anmeldespiel“ spielen lassen:
Beide Threads versuchen, Teilnehmer mit den Namen „A“, „B“, …“Z“ in dieselbe Liste tL
der Klasse TeilnehmerListe mit der Methode einfügen(…) einzutragen. Die
Reihenfolge ist nicht vorgeschrieben: So könnte ein Thread bei „A“ beginnen, der andere
bei „Z“ oder aber einfach zufällig mittels Math.random() irgend einen Namen wählen.
Jeder Thread hat genau 52 Versuche, von denen natürlich mindestens 26 Versuche scheitern
müssen. Am Ende hat der Thread gewonnen, der die meisten Teilnehmer anmelden konnte.

(d) Zum Ende des Spiels, also wenn beide Threads alle ihnen zustehenden 52 Anmeldeversuche
ausgeführt haben, geben Sie den Gewinner-Thread und den Endstand (also z.B. 14:12
für anmelder1). Prüfen Sie auch, ob die Klasseninvariante eingehalten wurde, also kein
Name doppelt in tL vorkommt.

Habs jetzt nicht ausprobiert, aber grob scheint es zu stimmen.
zu c:

naja, das andere ist ja eigentlich ganz einfach, du erzeugst halt 2 threads die das selbe zu schaffen versuchen, ungefähr so:

Thread anmelder1 = new Thread(new Runnable(){
    public void run(){
         //alle nutzer A - Z einfügen...
         for(int i = 65; i < 90; i++) {
		     char c = (char) i;
	     	 String buchstabe = new String(new char[]{c});
		     einfuegen(new Teilnehmer(buchstabe));
	     }
    }
});
Thread anmelder2 = new Thread(new Runnable(){
    public void run(){
         //alle nutzer A - Z einfügen...
         for(int i = 65; i < 90; i++) {
		     char c = (char) i;
	         String buchstabe = new String(new char[]{c});
		     einfuegen(new Teilnehmer(buchstabe));
	     }
    }
});
anmelder1.start();
anmelder2.start();

Du brauchst Deinen Code nur ausprobieren, um zu sehen, dass beim ersten Einfügen eine IndexOutOfBoundsException fliegt. Den Fehler beheben und anschließend der Reihe nach „A“, „B“, „C“, „B“ einfügen und feststellen, dass B doppelt in der Liste ist.

Mir fallen 3 Fehler beim Einfügen auf:

  • Schranken der Schleife
  • Vergleich von Äpfeln mit Birnen
  • keinDoppelgänger hat Ende der Schleife den Wert des letzten Vergleichs, was nach der Schleife nicht hilft
    @mymaksimus :
    Die Codedublette ist weit weg von „beautiful“. :wink:

haha ich meinte einrücken und so ^^
aber wie wärs denn schöner? :stuck_out_tongue:

Beispielsweise statt der beiden anonymen Runnables, die komplett gleich aufgebaut sind, eine Klasse, die Runnable implementiert.

achso das meinst du ^^ naja das war nur schnell zum zeigen

Ich bekomme das Programm irrgendwie nicht zum laufen.
Ich hab das eins zu eins übernommen.

Also die oben gegebenen Methoden in meine main eingefügt. Ist da wirklich richtig was ich implementiert habe?

Deine Teilnehmerliste ist recht “ungünstig” implementiert. Du kannst da auf die geballte Power des Collection-Frameworks zurückgreifen, welches dir add und remove-Methoden bietet. Die Implementierung von einfuegen() und entfernen() wird somit zum Einzeiler:

import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class TeilnehmerListe implements TNListe {
    private final Set<Teilnehmer> storage;
    private final String lineSeparator = System.getProperty("line.separator");

    public TeilnehmerListe() {
        storage = Collections.newSetFromMap(new ConcurrentHashMap<Teilnehmer, Boolean>());
    }

    @Override
    public boolean einfuegen(Teilnehmer teilnehmer) {
        return storage.add(teilnehmer);
    }

    @Override
    public boolean entfernen(Teilnehmer teilnehmer) {
        return storage.remove(teilnehmer);
    }

    @Override
    public String toString() {
        String result = "";
        for (Teilnehmer teilnehmer : storage) {
            result += teilnehmer.getName() + ": " + teilnehmer.getTelefonnummer() + lineSeparator;
        }
        return result;
    }
}```
Beachte hierbei Zeile 10, dort wird ein **threadsicheres** [japi]Set[/japi] angelegt, deshalb brauchen die einfuegen() und entfernen() Methoden nicht synchronized zu sein.

Um die Klasseninvariante einhalten zu können, fehlen dir noch die equals() und hashCode() Methoden:
```public class Teilnehmer {
    private final String name;
    private final int telefonnummer;

    public Teilnehmer(String name, int telefonnummer) {
        if (name == null)
            throw new NullPointerException("Name darf nicht null sein.");

        this.name = name;
        this.telefonnummer = telefonnummer;
    }

    public String getName() {
        return name;
    }

    public int getTelefonnummer() {
        return telefonnummer;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Teilnehmer that = (Teilnehmer) o;

        return name.equals(that.name);

    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }
}```

Das "Spiel" habe ich spaßeshalber auch implementiert. Um dir etwas Hilfestellung zu geben: ich habe dazu einen [japi]CountDownLatch[/japi] verwendet, um die zwei Threads gleichzeitig zu starten. Außerdem habe ich einen [japi]ExecutorService[/japi] verwendet (einen passenden bekommt man mit `Executors.newFixedThreadPool(2);`), um per [japi]ExecutorService#submit(java.util.concurrent.Callable)[/japi] ein [japi]Future[/japi] zu bekommen, in welchem nach der Ausführung des Threads die Anzahl erfolgreich eingetragener Teilnehmer steht.

Also die oben gegebenen Methoden in meine main eingefügt. Ist da wirklich richtig was ich implementiert habe?

*** Edit ***

Tut mir leid für die doppelt Post aber bei der Funktion einfügen zeigt er mir einfehler. Dass ich die Funktion in eine statice umwandeln soll.

[ol]
[li]Thread anmelder1 = new Thread(new Runnable(){ [/li][li] public void run(){ [/li][li] //alle nutzer A - Z einfügen… [/li][li] for(int i = 65; i < 90; i++) { [/li][li] char c = (char) i; [/li][li] String buchstabe = new String(new char[]{c}); [/li][li] einfuegen(new Teilnehmer(buchstabe)); [/li][li] } [/li][li] } [/li][li]});[/li]

[/ol]

Achso:

[quote=simone]Prüfen Sie auch, ob die Klasseninvariante eingehalten wurde, also kein
Name doppelt in tL vorkommt.[/quote]

ist ziemlich sinnbefreit. Denn wenn man ein Set zum speichern der Teilnehmer verwendet, dann kann man keine zwei gleiche Teilnehmer einfügen. Wenn mit diesem Aufgabenteil geprüft werden soll, ob man an die equals()-Methode gedacht hat, dann bemerkt man den Fehler nur, wenn man nicht programmatisch prüft, ob ein Teilnehmer doppelt vorkommt (à la Konsolenausgabe).

*** Edit ***

Ist ja auch klar. In welchem Kontext wird einfuegen() denn aufgerufen?
Antwort

nicht im Kontext der Teilnehmerliste, sondern im Kontext des Objektes, in welchem sich deine main()-Methode befindet

Du sollst die Aufgabe selbst machen, daher habe ich dir ja meine Lösung nicht gepostet, sondern nur die notwendigen Bausteine genannt.

Für mich ist das echt kompliziert was du da gemacht hast. Aber ich wollte wenn es möglich ist es auf meinem Weg lösen.

Der da wäre? Ich habe für das „Spiel“ nur Code von @mymaksimus gesehen.

*** Edit ***

Ich sehe bei dem Spiel drei Probleme, die du lösen musst:
[ul][li]Threads gleichzeitig starten[/li][li]auf Beendigung der Threads warten[/li][*]Rückgabewerte auswerten[/ul]

ich hab es jetzt so verändert ist das richtig?


    private  ArrayList<Teilnehmer> tL;
 

    TeilnehmerListe() {
        tL = new ArrayList<Teilnehmer>();

    }

    // Methodenvertrag
    /*
     * @pre darf undefiniertes verhalten zeigen
     */

    
    public synchronized  boolean einfügen(Teilnehmer tn) {

        if (this.tL.size() == 0) {
            tL.add(tn);
            return true;
        } else {

            boolean keinDoppelgänger = false;

            for (int i = 0; i <= this.tL.size(); i++) {
                if (this.tL.get(i).getName().equals(tn.getName())) {
                    System.out.println("Vorhandener Name");
                    keinDoppelgänger = false;
                 
                } else {
                    keinDoppelgänger = true;
                }

                if (keinDoppelgänger == true) {
                    this.tL.add(tn);
                    return true;
                }

            }
        }

        return false;
    }

    @Override
    public synchronized boolean entfernen(Teilnehmer tn) {

        for (int i = 0; i <= this.tL.size(); i++) {
            
            if (this.tL.get(i).getName().equals(tn.getName())) {

                this.tL.remove(i);

                return true;

            } else {
                return false;
            }
        }

        return false;
    }


    @Override
    public void run() {
        for(int i = 0; i<2;i++){//52 Versuche
            for(char c = 'A'; 'A'<='Z';c++ ){
                String buchstabe = String.valueOf(c);
                
                einfügen(new Teilnehmer(buchstabe,123));
                
                
            }

        }
    }```

versuchs doch

Nein, ist es nicht. Die run()-Methode gehört nicht in die TeilnehmerListe-Klasse. Du hast meine Aussage mit dem Kontext missverstanden. Ja, jetzt wird die einfuegen-Methode im Kontext einer TeilnehmerListe ausgeführt. Aber es bringt dich nicht weiter, wenn du jedem Spieler eine eigene Liste gibst. Du musst von einer Instanz der TeilnehmerListe die einfuegen-Methode aufrufen. Also in etwa so:

teilnehmerListe.einfuegen(teilnehmer);```

Bevor du losprogrammierst, solltest du dir überlegen, wie du die oben genannten Probleme löst. (Threads starten, auf Beendigung warten, Rückgabewert auswerten.)
Schreib doch einfach mal deinen Ansatz in Worten, bevor du in den Quellcode gehst.

*** Edit ***

Hinweise zu deinem Code:
Die Unterscheidung, ob die Liste leer ist, oder ob bereits Teilnehmer eingetragen sind, brauchst du nicht.

Eine Liste iteriest du statt mit einer Zählschleife viel eleganter mit
```for (Teilnehmer teilnehmer : tL) {
    teilnehmer.getName(); // oder was auch immer du machen musst
}```

Der Vergleich, ob zwei Objekte gleich sind, geschieht per [japi]Object#equals()[/japi], wie du ja auch schon (halb) richtig angewendet hast. Allerdings verletzt du das ["Tell, don't ask"-Prinzip](http://www.clean-code-developer.de/Tell-don-t-ask.ashx), indem du `if (this.tL.get(i).getName().equals(tn.getName()))` machst. Wenn du die equals-Methode in Teilnehmer überschreibst (den Code habe ich oben ja schon gepostet) und mit der in diesem Beitrag gezeigten foreach-Schleife arbeitest, dann sieht die Abfrage viel schöner aus:
```if (teilnehmer.equals(tn))```

Wie oben ja auch schon angedeutet, ist die Verwendung einer ArrayList hier fehl am Platze. Du brauchst ein [japi]Set[/japi].

So, der Vollständigkeit halber (für Leute, die auf diesen Thread stoßen - den TO scheint es nicht mehr zu interessieren) hier mein oben in Worten beschriebener Ansatz in Javacode:


import java.util.concurrent.*;

public class TeilnehmerSpiel {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TNListe teilnehmerListe = new TeilnehmerListe();

        CountDownLatch latch = new CountDownLatch(1);

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Future<Integer> spieler1 = executorService.submit(new Spieler(latch, teilnehmerListe));
        Future<Integer> spieler2 = executorService.submit(new Spieler(latch, teilnehmerListe));

        latch.countDown();

        Integer ergebnis1 = spieler1.get();
        Integer ergebnis2 = spieler2.get();

        executorService.shutdown();

        System.out.println("Spieler 1: " + ergebnis1 + " Punkte");
        System.out.println("Spieler 2: " + ergebnis2 + " Punkte");

        //System.out.println(teilnehmerListe);
    }

    private static class Spieler implements Callable<Integer> {
        private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        private final CountDownLatch latch;
        private final TNListe teilnehmerListe;

        public Spieler(CountDownLatch latch, TNListe teilnehmerListe) {
            this.latch = latch;
            this.teilnehmerListe = teilnehmerListe;
        }

        @Override
        public Integer call() throws Exception {
            latch.await();
            int punkte = 0;

            for (int i = 0; i < 52; i++) {
                Teilnehmer teilnehmer = new Teilnehmer(ALPHABET.substring(i % ALPHABET.length(), i % ALPHABET.length() + 1), i);

                if (teilnehmerListe.einfuegen(teilnehmer))
                    punkte++;
            }

            return punkte;
        }
    }
}```