Threads Zwei Threads, aber doppelte Ausgabe verhindern (synchronized)

Bitte aktiviere JavaScript!
Mmm, ich komme bezüglich der doppelten Ausgabe auf keinen Grünen Zweig:
Java:
  private static volatile Boolean runs = true;
// ...
    Runnable r1 = new Runnable() {
      @Override
      public void run() {
        byte[] a = ip1;
        while (runs) {
          String s = bytesToIpv4(a);
          System.out.println("Die Adresse Thread 1 ist " + s);
          synchronized (runs) {
            if (runs) {
              if (isNotEquals(a, ip2)) {
                count(a);
              } else {
                runs = false;
              }
            }
          }
        }
      }
    };
 
a) Speicher alle bereits ausgegebenen IPs und check jeweils vor der Ausgabe
b) Statt sich von zwei Seiten dem Mittelwert anzunähern, für einen expliziten "IpCounter" ein, aus dem sich alle Threads die nächste IP holen. Threads müssen dann nicht synchronisiert sein, nur der Counter muss das intern regeln
c) Variante b mit Queue und explizitem Generator-Thread, dieser legt IPs in eine Queue, Worker-Threads holen die sich da raus und testen sie
d) Den gesamten Bereich direkt aufteilen, jeder Thread kümmert sich nur um seinen Bereich



BTW: der übliche Weg für synchronized ist ein explizites "lock"-Object, was einfach nur zum synchronisieren genutzt wird. Das macht den Code meist einfacher lesbar, als wenn man irgendein beliebiges Objekt dafür "missbraucht".
 
b) Statt sich von zwei Seiten dem Mittelwert anzunähern, für einen expliziten "IpCounter" ein, aus dem sich alle Threads die nächste IP holen. Threads müssen dann nicht synchronisiert sein, nur der Counter muss das intern regeln
Ich hab es ja weiter oben schon geschrieben; die Möglichkeit b) wäre mein persönlicher Favorit, weil die Verarbeitung bei Bedarf sehr simpel auf zwei, drei oder noch mehr Threads verteilt werden kann.
 
Ich hab es ja weiter oben schon geschrieben; die Möglichkeit b) wäre mein persönlicher Favorit, weil die Verarbeitung bei Bedarf sehr simpel auf zwei, drei oder noch mehr Threads verteilt werden kann.
Ja, sind im wesentlichen deine vier Varianten, nur anders formuliert.

Für beliebig viele Threads dürften sich alle vier Varianten nutzen lassen, wobei Variante d) da am unschönsten sein dürfte.
 
Just for fun und ohne Anspruch auf die Ideallösung:

Intern wird für die IP ein long verwendet, dann kann man einfach addieren um die nächste Adresse zu erhalten. Wer mag kann auch gerne noch ein paar zusätzliche Threads verwenden.

Java:
public final class Ipv4 {

    private static final int MAX_SHIFT = 24;
    private static final int PART_SHIFT = 8;

    public Ipv4(String value) {
        String[] parts = value.split("\\.");

        if (parts.length != 4) {
            throw new IllegalArgumentException("IP string has no valid length");
        }

        long temp = 0;

        for (int part = 0; part < parts.length; part++) {
            temp += Long.parseLong(parts[part]) << (MAX_SHIFT - PART_SHIFT * part);
        }

        this.value = temp;
    }

    public Ipv4(long value) {
        this.value = value;
    }

    public long getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "Ipv4 {" +
                (value >> 24 & 0xFF) + "." +
                (value >> 16 & 0xFF) + "." +
                (value >>  8 & 0xFF) + "." +
                (value       & 0xFF) +
                "}";
    }

    private final long value;
}

Java:
public final class Ipv4Range {

    public Ipv4Range(final Ipv4 from, final Ipv4 to) {
        this.from = from.getValue();
        this.to = to.getValue();
        first();
    }

    public synchronized Ipv4 first() {
        return new Ipv4(current = from);
    }

    public synchronized Ipv4 next() {
        if (current <= to) {
            return new Ipv4(current++);
        }

        throw new NoSuchElementException("There is no next IP");
    }

    public synchronized boolean hasNext() {
        return current <= to;
    }

    private final long from, to;
    private long current;
}

Java:
public class Main {

    public static void main(String[] args) {

        Ipv4 from = new Ipv4("192.168.0.1");
        Ipv4 to = new Ipv4("192.168.0.25");

        Ipv4Range range = new Ipv4Range(from, to);

        // Thread 1
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                while (range.hasNext()) {
                    System.out.println("Die Adresse Thread 1 ist " + range.next());
                }
            }
        };

        // Thread 2
        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                while (range.hasNext()) {
                    System.out.println("Die Adresse Thread 2 ist " + range.next());
                }
            }
        };

        new Thread(r1).start();
        new Thread(r2).start();
    }
}
Code:
Die Adresse Thread 2 ist Ipv4 {192.168.0.2}
Die Adresse Thread 1 ist Ipv4 {192.168.0.1}
Die Adresse Thread 2 ist Ipv4 {192.168.0.3}
Die Adresse Thread 1 ist Ipv4 {192.168.0.4}
Die Adresse Thread 2 ist Ipv4 {192.168.0.5}
Die Adresse Thread 1 ist Ipv4 {192.168.0.6}
Die Adresse Thread 2 ist Ipv4 {192.168.0.7}
Die Adresse Thread 1 ist Ipv4 {192.168.0.8}
Die Adresse Thread 2 ist Ipv4 {192.168.0.9}
Die Adresse Thread 1 ist Ipv4 {192.168.0.10}
Die Adresse Thread 2 ist Ipv4 {192.168.0.11}
Die Adresse Thread 1 ist Ipv4 {192.168.0.12}
Die Adresse Thread 2 ist Ipv4 {192.168.0.13}
Die Adresse Thread 1 ist Ipv4 {192.168.0.14}
Die Adresse Thread 2 ist Ipv4 {192.168.0.15}
Die Adresse Thread 1 ist Ipv4 {192.168.0.16}
Die Adresse Thread 2 ist Ipv4 {192.168.0.17}
Die Adresse Thread 1 ist Ipv4 {192.168.0.18}
Die Adresse Thread 2 ist Ipv4 {192.168.0.19}
Die Adresse Thread 1 ist Ipv4 {192.168.0.20}
Die Adresse Thread 2 ist Ipv4 {192.168.0.21}
Die Adresse Thread 1 ist Ipv4 {192.168.0.22}
Die Adresse Thread 2 ist Ipv4 {192.168.0.23}
Die Adresse Thread 1 ist Ipv4 {192.168.0.24}
Die Adresse Thread 2 ist Ipv4 {192.168.0.25}
 
Zuletzt bearbeitet:
Kleine Anmerkung zur Synchronisierung; zwischen hasNext und next können sich die beiden Threads in die Quere kommen. Wenn nur noch ein Element "vorhanden ist", bekommen beide bei hasNext true, next wirft aber dann bei einem eine Exception.

Man könnte da mit Optional statt der Exception arbeiten oder in Richtung Spliterator gehen.
 
Wenn ich es richtig sehe wird es nicht immer anhalten bzw eine NoSuchElementException geben weil hasNext( und next( zwar einzeln synchronisiert sind, allerdings nicht zusammen, wodurch hasNext true zurückgeben kann aber zwischenzeitlich ein anderer Thread auch next aufrufen kann.
 
Ist es nicht so, dass durch das "synchronized" auf der Methodenebene auf "this" gelockt wird?

Damit kann keine der synchronisierten Methoden betreten werden, solange ein Thread innerhalb einer der synchronisierten Methoden ist, oder?
 
Was soll sich denn dann ändern?
Man spart sich hasNext und es kann keine Exception fliegen.

Ist es nicht so, dass durch das "synchronized" an der Methode auf "this" gelockt wird?

Damit kann auch keine synchronisierte Methode betreten werden, solange ein Thread innerhalb einer der synchronisierten Methoden ist, oder?
Ja, aber bei zwei Methoden schlägt das fehl:

Thread 1 ruft hasNext auf und bekommt true
Thread 2 wartete bis Thread 1 zurückkehrt aus hasNext und ruft danach hasNext auf und bekommt true
Thread 1 wartete bis Thread 2 mit hasNext fertig ist und führt danach next aus, bekommt den Wert
Thread 2 wartete bis Thread 1 mit next fertig ist, ruft danach next auf und bekommt die Exception
 
- IPv4 gibt nach außen nicht den long preis, stattdessen bekommt sie 'ne Methode um die IP zu inkrementieren.
- IPRange bekommt statt hasNext und next eine getNext, die ein Optional zurück gibt
- außerdem zusätzlich tryAdvance und forEachRemaining wie zB Spliterator das hat

Alle drei Varianten sollten sich parallel nutzen lassen, falls jemand Fehler sieht gerne drauf hinweisen :)

Java:
public class IPv4 implements Comparable<IPv4> {

    private static final int MAX_SHIFT = 24;

    private static final int PART_SHIFT = 8;

    private final long value;

    public IPv4(String value) {
        String[] parts = value.split("\\.");

        if (parts.length != 4) {
            throw new IllegalArgumentException("IP string has no valid length");
        }

        long temp = 0;

        for (int part = 0; part < parts.length; part++) {
            temp += Long.parseLong(parts[part]) << (MAX_SHIFT - PART_SHIFT * part);
        }

        this.value = temp;
    }

    private IPv4(long value) {
        this.value = value;
    }

    public IPv4 increment() {
        return new IPv4(this.value+1);
    }

    @Override
    public String toString() {
        return "" +
               (value >> 24 & 0xFF) + "." +
               (value >> 16 & 0xFF) + "." +
               (value >> 8 & 0xFF) + "." +
               (value & 0xFF);
    }

    @Override
    public int compareTo(final IPv4 that) {
        return Long.compare(this.value, that.value);
    }

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

    @Override
    public int hashCode() {
        return Objects.hash(value);
    }

}

Java:
public class IPv4Range {

    private final IPv4 from;

    private final IPv4 to;

    private IPv4 current;

    public IPv4Range(final IPv4 from, final IPv4 to) {
        this.from = from;
        this.to = to;
        this.current = this.from;
    }

    void forEachRemaining(Consumer<? super IPv4> action) {
        while (this.tryAdvance(ip -> System.out.printf("%s: %s%n", Thread.currentThread().getName(), ip)));
    }

    /**
     * @see Spliterator#tryAdvance(Consumer)
     */
    boolean tryAdvance(Consumer<? super IPv4> action) {
        Optional<IPv4> ip = getNext();
        if (ip.isEmpty()) {
            return false;
        }
        action.accept(ip.get());
        return true;

    }

    public synchronized Optional<IPv4> getNext() {
        if (!hasNext()) {
            return Optional.empty();
        }
        return Optional.of(next());
    }

    private IPv4 next() {
        if (hasNext()) {
            IPv4 next = current;
            current = current.increment();
            return next;
        }
        throw new NoSuchElementException("There is no next IP");
    }

    private boolean hasNext() {
        return current.compareTo(to) <= 0;
    }

}

Java:
public class Main {

    public static void main(String[] args) {

        IPv4 from = new IPv4("192.168.0.1");
        IPv4 to = new IPv4("192.168.1.0");

        IPv4Range range = new IPv4Range(from, to);

        Runnable withOptional = new Runnable() {
            @Override
            public void run() {
                for (Optional<IPv4> ip = range.getNext(); ip.isPresent(); ip = range.getNext()) {
                    System.out.printf("%s: %s%n", Thread.currentThread().getName(), ip.get());
                }
            }
        };

        Runnable withTryAdvance = new Runnable() {
            @Override
            public void run() {
                while (range.tryAdvance(ip -> System.out.printf("%s: %s%n", Thread.currentThread().getName(), ip))) {

                }
            }
        };
        Runnable withForEachRemaining = new Runnable() {
            @Override
            public void run() {
                range.forEachRemaining(ip -> System.out.printf("%s: %s%n", Thread.currentThread().getName(), ip));
            }
        };

        new Thread(withOptional).start();
        new Thread(withTryAdvance).start();
        new Thread(withForEachRemaining).start();
    }

}
 
Passende Stellenanzeigen aus deiner Region:

Neue Themen

Oben