Threads Zwei Threads, aber doppelte Ausgabe verhindern (synchronized)

Bitte aktiviere JavaScript!
Mir wurde ein Programm zugesendet und meine Frage wär jetzt, wie ich die doppelte Ausgabe verhindere...

Java:
import java.util.Arrays;
import java.util.Scanner;

public class Unittest {
  private static Scanner s1;
  private static String p1, p2;

  public static void main(String[] args) {
    s1 = new Scanner(System.in);
    System.out.println("Adresse 1: ");
    p1 = s1.nextLine();
    System.out.println("Adresse 2: ");
    p2 = s1.nextLine();
    byte[] ip1 = ipv4ToBytes(p1);
    byte[] ip2 = ipv4ToBytes(p2);

    // Thread
    Runnable r1 = new Runnable() {
      @Override
      public void run() {
        byte[] a = ip1;
        while (true) {
          String s = bytesToIpv4(a);
          System.out.println("Die Adresse Thread 1 ist " + s);
          synchronized (s1) {
            if (isNotEquals(a, ip2)) {
              count(a);
            } else {
              break;
            }
          }
        }
      }
    };
    Runnable r2 = new Runnable() {
      @Override
      public void run() {
        byte[] a = ip2;
        while (true) {
          String s = bytesToIpv4(a);
          System.out.println("Die Adresse Thread 2 ist " + s);
          synchronized (s1) {
            if (isNotEquals(a, ip1)) {
              count2(a);
            } else {
              break;
            }
          }
        }
      }
    };

    new Thread(r1).start();
    new Thread(r2).start();
  }

  public static boolean isNotEquals(byte[] a, byte[] b) {
    return !Arrays.equals(a, b);
  }

  public static byte[] ipv4ToBytes(String ip) {
    String[] parts = ip.split("\\.");
    byte[] result = new byte[4];

    for (int i = 0; i < result.length; i++) {

      result[i] = (byte) Integer.parseInt(parts[i]);
    }
    return result;
  }

  public static void count(byte[] value) {

    int i = value.length;

    do {

      i--;
      value[i]++;
    } while (value[i] == 0);
  }

  public static void count2(byte[] value) {

    int i = value.length;

    do {

      i--;
      value[i]--;
    } while ((value[i] & 0xFF) == 255);
  }

  public static String bytesToIpv4(byte[] value) {

    return String.format("%d.%d.%d.%d", value[0] & 0xff, value[1] & 0xff, value[2] & 0xff, value[3] & 0xff);
  }

}

Code:
Adresse 1: 
255.255.254.240
Adresse 2: 
255.255.255.5
Die Adresse Thread 1 ist 255.255.254.240
Die Adresse Thread 2 ist 255.255.255.5
Die Adresse Thread 2 ist 255.255.255.4
Die Adresse Thread 1 ist 255.255.254.241
Die Adresse Thread 2 ist 255.255.255.3
Die Adresse Thread 1 ist 255.255.254.242
Die Adresse Thread 2 ist 255.255.255.2
Die Adresse Thread 1 ist 255.255.254.243
Die Adresse Thread 2 ist 255.255.255.1
Die Adresse Thread 2 ist 255.255.255.0
Die Adresse Thread 2 ist 255.255.254.255
Die Adresse Thread 2 ist 255.255.254.254
Die Adresse Thread 2 ist 255.255.254.253
Die Adresse Thread 2 ist 255.255.254.252
Die Adresse Thread 2 ist 255.255.254.251
Die Adresse Thread 2 ist 255.255.254.250
Die Adresse Thread 2 ist 255.255.254.249
Die Adresse Thread 2 ist 255.255.254.248
Die Adresse Thread 2 ist 255.255.254.247
Die Adresse Thread 1 ist 255.255.254.244
Die Adresse Thread 1 ist 255.255.254.245
Die Adresse Thread 1 ist 255.255.254.246
Die Adresse Thread 2 ist 255.255.254.246

Zusatzfrage: Wird das Programm immer anhalten? (bitte begründen)
 
Der Code ist mit Fehlern behaftet.
z.B.
Java:
public static void count2(byte[] value) {
    int i = value.length;
    do {
      i--;
      value[i]--;
    } while ((value[i] & 0xFF) == 255);
  }
Die while Schleife macht hier keinen Sinn, da die Bedingung maximal 1 mal erfüllt sein kann. Wenn value[i] i == 256 --> value[i]-- --> value[i] -- --> Bedinung nicht mehr erfüllt.
Da der Name der Funktion count2 nicht aussagekräftig ist, kann man hier nur vermuten was die Funktion eigentlich machen soll.
Meine Annahme ist: Sie soll eine IP4 Adresse um eine Stelle herunterzählen.
Eine Andere Frage an dieser Stelle:
Java:
       synchronized (s1) {
            if (isNotEquals(a, ip1)) {
              count2(a);
            } else {
              break;
            }
s1 ist der Scanner der zum Einlesen der ip4 Strings dient. Warum wird eine Variable synchronisiert, auf die gar nicht mehr zugegriffen wird?
Zusatzfrage: Wird das Programm immer anhalten? (bitte begründen)
Da die Bedingung zum Abbruch des jeweiligen Threads die Gleichheit beider IP4 Adressen ist . Die Countfunktion aber immer nur die Letzte Stelle herauf oder herunter zählt --- > Nein es wird nicht immer abgebrochen.

Hier ein andere Variante.

Java:
public class Unittest {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.println("Gib 2 IP4 Adressen ein:\nIP1 wird hinauf- und IP2 wird heruntergezählt.\n");
        System.out.println(
                "Die zählenden Threads stoppen wenn:\na) Beide die selbe IP Adresse haben.\nb) Die Limits erreicht wurden.\n");
        System.out.print("Adresse 1: ");
        String tmp1 = input.nextLine();
        System.out.print("Adresse 2: ");
        String tmp2 = input.nextLine();
        input.close();
        byte[] ip1 = ipv4ToBytes(tmp1);
        byte[] ip2 = ipv4ToBytes(tmp2);

        // Thread
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    String s = bytesToIpv4(ip1);
                    System.out.println("Die Adresse Thread 1 ist " + s);
                    synchronized (ip2) {
                        if (Arrays.equals(ip1, ip2) || !countUp(ip1)) {
                            System.out.println("Thread 1 exit");
                            break;
                        }
                    }
                }
            }
        };
        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    String s = bytesToIpv4(ip2);
                    System.out.println("Die Adresse Thread 2 ist " + s);
                    synchronized (ip1) {
                        if (Arrays.equals(ip2, ip1) || !countDown(ip2)) {
                            System.out.println("Thread 2 exit");
                            break;
                        }
                    }
                }
            }
        };
        new Thread(r1).start();
        new Thread(r2).start();
    }

    public static String bytesToIpv4(byte[] value) {
        return String.format("%d.%d.%d.%d", value[0] & 0xff, value[1] & 0xff, value[2] & 0xff, value[3] & 0xff);
    }

    public static boolean countUp(byte[] value) {
        int i = value.length - 1;
        while (true) {
            if ((value[i] & 255) == 255) {
                if (i == 0)
                    return false;
                value[i] = 0;
                i--;
            } else {
                value[i]++;
                return true;
            }
        }
    }

    public static boolean countDown(byte[] value) {
        int i = value.length - 1;
        while (true) {
            if ((value[i] & 255) == 0) {
                if (i == 0)
                    return false;
                value[i] = (byte) 255;
                i--;
            } else {
                value[i]--;
                return true;
            }
        }
    }

    public static byte[] ipv4ToBytes(String ip) {
        String[] parts = ip.split("\\.");
        byte[] result = new byte[4];
        for (int i = 0; i < result.length; i++)
            result[i] = (byte) Integer.parseInt(parts[i]);
        return result;
    }
}
 
Zuletzt bearbeitet von einem Moderator:
Ich Frage doch nur stellvertretend und der Fehler ist mir gar nicht aufgefallen, ich denke es wäre besser die mittlere IP herauszufinden und nicht zu synchronisieren...

Btw. Ja, Zugeständnis, ich habe den Code etwas ver(schlimm)bessert bevor ich ihn hier postete.
 
Hier ein andere Variante.
Ist das jetzt wirklich besser? Du synchronisierst jetzt auf ip1, bzw. ip2, aber es greift sowieso nur jeweils ein Thread auf diese Variable zu.

Meine schlichte Herangehenweise wäre, entweder wie schon @Tobias-nrw angeregt hat, jedem Thread seinen eigenen Bereich an IP-Adressen zur Bearbeitung zu geben.

Oder eine Liste von IP-Adressen zu erstellen und darauf zu synchronisieren. Dann können die Threads sich immer die nächste zu prüfende IP holen. Eine Alternative dazu wäre ein Set auf dem synchronisiert wird, in das jeder Thread die IP-Adressen schreibt, die er gerade prüft. Damit kann vor jeder neuen Prüfung geschaut werden, ob die Adresse bereits bearbeitet wurde.

Edit:

Der ganze Code ist (wie auch schon in einem anderen Thema bereits angedeutet) wenig objektorientiert. Ich würde eine Klasse "IPv4Range" vorsehen, die über eine threadsichere Methode "next()" die nächste Adresse der Range liefert. Diese kann dann von beliebig vielen Threads abgerufen und getestet werden.
 
Zuletzt bearbeitet:
Die while Schleife macht hier keinen Sinn, da die Bedingung maximal 1 mal erfüllt sein kann. Wenn value[i] i == 256 --> value[i]-- --> value[i] -- --> Bedinung nicht mehr erfüllt.
Nein, die Schleife kann durchaus mehrfach durchlaufen werden, am einfachsten zu sehen mit {0,0,0,0}.

Fängt mit Index 3 an, 0 - 1 = -1 , -1 & 0xFF = 255, Bedingung ist also wahr.
Dann das gleiche mit Index 2, genau gleiche Rechnung, Bedingung ist weiterhin wahr.
Dann das gleiche mit Index 1, usw...


Da die Bedingung zum Abbruch des jeweiligen Threads die Gleichheit beider IP4 Adressen ist . Die Countfunktion aber immer nur die Letzte Stelle herauf oder herunter zählt --- > Nein es wird nicht immer abgebrochen.
Zählt wie gesagt nicht nur die letzte Stelle ;)


s1 ist der Scanner der zum Einlesen der ip4 Strings dient. Warum wird eine Variable synchronisiert, auf die gar nicht mehr zugegriffen wird?
Ob drauf zugegriffen wird ist egal, wichtig ist in dem Fall nur, dass alle Threads ein gemeinsames Objekt zum synchronisieren nutzen.
Für die Nutzung des Scanners gibts keinen Grund, es funktioniert allerdings, anders als deine Variante:

Ist das jetzt wirklich besser? Du synchronisierst jetzt auf ip1, bzw. ip2, aber es greift sowieso nur jeweils ein Thread auf diese Variable zu.
Eher deutlich schlechter - wenn jeder Thread eine eigenes Objekt zum synchronisieren hat, ist das sinnlos.
In diesem Fall verändert Thread1 ip1, während Thread2 lesend darauf zugreift.
 
Für die Nutzung des Scanners gibts keinen Grund, es funktioniert allerdings
Naja, korrekter wäre: Die Synchronisation auf den Scanner funktioniert zwar, aber sie bewirkt nicht das, was damit bezweckt wurde. Es wird ja nicht verhindert, dass zwei Threads die gleiche IP (bzw. eine bereits getestete IP) testen.
 
Eher deutlich schlechter - wenn jeder Thread eine eigenes Objekt zum synchronisieren hat, ist das sinnlos.
In diesem Fall verändert Thread1 ip1, während Thread2 lesend darauf zugreift.
Oder Thread1 vergleicht ip1 und ip2 und verbietet währen dessen den Schreibzugriff auf ip2, um zu verhindern dass eine Veränderung während des Vergleichs stattfindet.
 
Naja, korrekter wäre: Die Synchronisation auf den Scanner funktioniert zwar, aber sie bewirkt nicht das, was damit bezweckt wurde. Es wird ja nicht verhindert, dass zwei Threads die gleiche IP (bzw. eine bereits getestete IP) testen.
Sie verhindert, das ein Thread ein Objekt verändert, während ein andere es liest. Das sie verhindert, dass eine IP mehrmals getestet wird, wurde doch von niemandem erwartet?
 
Oder Thread1 vergleicht ip1 und ip2 und verbietet währen dessen den Schreibzugriff auf ip2, um zu verhindern dass eine Veränderung während des Vergleichs stattfindet.
Nein, lies dir noch mal durch, was synchronized bewirkt ;)

Auch wenn Thread 1 über ip2 synchronisiert, kann Thread 2 munter ip2 verändern.

Hier, ein ganz reduziertes Beispiel dazu, zwei Threads, einer synchronisiert über der Variable, der andere verändert:
Java:
public static void main(String[] args) {
    int[] ints = new int[1];

    new Thread() {
        @Override
        public void run() {
            synchronized (ints) {
                while (true) {
                    System.out.println("Thread1: " + ints[0]);
                }
            }
        }
    }.start();

    while (true) {
        ints[0]++;
    }

}
 
Fängt mit Index 3 an, 0 - 1 = -1 , -1 & 0xFF = 255, Bedingung ist also wahr.
Dann das gleiche mit Index 2, genau gleiche Rechnung, Bedingung ist weiterhin wahr.
Dann das gleiche mit Index 1, usw...
Das sehe ich nicht so!
Java:
int i = value.lenght;   // i ist hier 4
i--; // i = 3
value[i]++;// Zugriff auf value[3] // hier werden die letzten 8 Bit verändert
while( (value[i] & 0xFF) == 255 ); // nur solange die letzen 8 Bit gesetzt sind
 
Ich habe das mal so dem Titel entnommen und da ich im Code nichts anderes erkenne, was das bewerkstelligen könnte, nahm ich einfach an, dass es damit geplant gewesen ist.
Hm, stimmt, in Verbindung mit dem Titel könnte man das so verstehen, wenn das so gemeint war hast du natürlich recht und das synchronized ist dafür Unsinn :) Nötig ist es allerdings trotzdem, um gleichzeitige zugriffe zu verhindern.
 
Das sehe ich nicht so!
Java:
int i = value.lenght;   // i ist hier 4
i--; // i = 3
value[i]++;// Zugriff auf value[3] // hier werden die letzten 8 Bit verändert
while( (value[i] & 0xFF) == 255 ); // nur solange die letzen 8 Bit gesetzt sind
Du hast jetzt aber nicht übersehen, dass das eine do-while-Schleife ist?

Probier es einfach aus, wenn dir mein Vorrechnen nicht reicht...
 
Zuletzt bearbeitet:
Nein, lies dir noch mal durch, was synchronized bewirkt ;)

Auch wenn Thread 1 über ip2 synchronisiert, kann Thread 2 munter ip2 verändern.
14.5.7 Synchronisieren mit »synchronized« Zur nächsten ÜberschriftZur vorigen Überschrift
Schon seit Java 1.0 können kritische Abschnitte mit synchronized geschützt werden. Im einfachsten Fall markiert der Modifizierer synchronized die gesamte Methode. Ein betretender Thread setzt bei Objektmethoden den Monitor des this-Objekts und bei statischen Methoden den Lock des dazugehörigen Class-Objekts.

Betritt ein Thread A eine synchronisierte Methode eines Objekts O und versucht anschließend Thread B eine synchronisierte Methode des gleichen Objekts O aufzurufen, muss der nachfolgende Thread B so lange warten, bis A wieder aus dem synchronisierten Teil austritt. Das geschieht, wenn der erste Thread A die Methode verlässt, denn mit dem Verlassen einer Methode – oder auch einer Ausnahme – gibt die JVM automatisch den Lock frei. Die Dauer eines Locks hängt folglich mit der Dauer des Methodenaufrufs zusammen, was zur Konsequenz hat, dass längere kritische Abschnitte die Parallelität einschränken und zu längeren Wartezeiten führen. Eine Endlosschleife in der synchronisierten Methode gäbe den Lock niemals frei.
 
Du hast jetzt aber nicht übersehen, dass das eine do-while-Schleife ist?
Java:
int i = value.lenght;   // i ist hier 4
i--; // i = 3
value[i]++;// Zugriff auf value[3] // hier werden die letzten 8 Bit verändert 
// wenn hier die letzten Bits davor gesetzt waren sind sie es jetzt nicht mehr
// einziger Fall die letzten 8 Bits waren 1111 1110 -> 1111 1111 dann wird die Schleife wieder holt
while( (value[i] & 0xFF) == 255 ); // nur solange die letzen 8 Bit gesetzt sind
Würde die Schleife hier öfters als 3 mal Durchlaufen werden --> dann ist i = -1 und es folgt eine IndexOutofBounds Exception
value[-1] geht nicht.
Das geschieht aber nicht, da ein Mehrfachdurchlauf nicht stattfindet. Habe ich laufen lassen.
 
14.5.7 Synchronisieren mit »synchronized« Zur nächsten ÜberschriftZur vorigen Überschrift
Schon seit Java 1.0 können kritische Abschnitte mit synchronized geschützt werden. Im einfachsten Fall markiert der Modifizierer synchronized die gesamte Methode. Ein betretender Thread setzt bei Objektmethoden den Monitor des this-Objekts und bei statischen Methoden den Lock des dazugehörigen Class-Objekts.

Betritt ein Thread A eine synchronisierte Methode eines Objekts O und versucht anschließend Thread B eine synchronisierte Methode des gleichen Objekts O aufzurufen, muss der nachfolgende Thread B so lange warten, bis A wieder aus dem synchronisierten Teil austritt. Das geschieht, wenn der erste Thread A die Methode verlässt, denn mit dem Verlassen einer Methode – oder auch einer Ausnahme – gibt die JVM automatisch den Lock frei. Die Dauer eines Locks hängt folglich mit der Dauer des Methodenaufrufs zusammen, was zur Konsequenz hat, dass längere kritische Abschnitte die Parallelität einschränken und zu längeren Wartezeiten führen. Eine Endlosschleife in der synchronisierten Methode gäbe den Lock niemals frei.
Richtig, genau so funktioniert synchronized (wobei es in diesem Thread allerdings um Blöcke und nicht Methoden geht).

In deinem Code gibt es Thread 1 und Thread 2.
Thread 1 hat einen Block, der ip2 zur Synchronisierung nutzt, und Thread 2 nutzt ip1.

Solange sich Thread 1 in dem Block befindet, kann kein andere Thread sich in einem Block befinde, der auch ip2 zur Synchronisierung nutzt. Thread 2 nutzt allerdings ip1, die beiden synchronized-Blöcke sind also unabhängig voneinander und können gleichzeitig ausgeführt werden.
Und insbesondere können sie nicht nur gleichzeitig ausgeführt werden, Thread 1 kann auch ip2 verändern, während Thread 2 ip2 list (und andersrum mit ip1).

Damit sie nicht gleichzeitig ein Objekte schreiben und lesen, müssen beide Blöcke das selbe Objekt zum synchronisieren nutzen.

(Feinheiten wie wait mal außer acht gelassen)
 
Würde die Schleife hier öfters als 3 mal Durchlaufen werden --> dann ist i = -1 und es folgt eine IndexOutofBounds Exception
value[-1] geht nicht.
Das geschieht aber nicht, da ein Mehrfachdurchlauf nicht stattfindet. Habe ich laufen lassen.
Keine Ahnung was du hast laufen lassen, aber ich bekomme bei passenden Werten jedes mal wie erwartete eine IndexOutOfBoundsException:

Code:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 4

Java:
public static void main(String[] args) {
    byte[] bs =new byte[]{0,0,0,0};
    count2(bs);
}

public static void count2(byte[] value) {

    int i = value.length;

    do {

      i--;
      value[i]--;
    } while ((value[i] & 0xFF) == 255);
  }
 
Zuletzt bearbeitet:
Passende Stellenanzeigen aus deiner Region:

Oben