3 Stimmen

Brian Goetzs falsche Veröffentlichung

Die Frage wurde bereits hier gestellt, aber es wurde kein richtiges Beispiel bereitgestellt, das funktioniert. Also erwähnt Brian, dass unter bestimmten Bedingungen der AssertionError im folgenden Code auftreten kann:

public class Holder {
  private int n;
  public Holder(int n) { this.n = n; }

  public void assertSanity() {
    if (n!=n)
      throw new AssertionError("Diese Aussage ist falsch");
  }
}

Wenn Holder wie folgt falsch veröffentlicht wird:

Klasse SomeClass {
    public Holder holder;

    public void initialize() {
        holder = new Holder(42);
    }
}

Ich verstehe, dass dies geschehen würde, wenn die Referenz auf Holder sichtbar gemacht wird, bevor die Instanzvariable des Objekts Holder für einen anderen Thread sichtbar gemacht wird. Daher habe ich das folgende Beispiel erstellt, um dieses Verhalten zu provozieren und somit den AssertionError mit folgender Klasse:

public class Publish {

  public Holder holder;

  public void initialize() {
    holder = new Holder(42);
  }

  public static void main(String[] args) {
    Publish publish = new Publish();
    Thread t1 = new Thread(new Runnable() {
      public void run() {
        for(int i = 0; i < Integer.MAX_VALUE; i++) {
          publish.initialize();
        }
        System.out.println("Initialisierungsthread beendet");
      }
    });

    Thread t2 = new Thread(new Runnable() {
      public void run() {
        int nullPointerHits = 0;
        int assertionErrors = 0;
        while(t1.isAlive()) {
          try {
            publish.holder.assertSanity();
          } catch(NullPointerException exc) {
            nullPointerHits++;
          } catch(AssertionError err) {
            assertionErrors ++;
          }
        }
        System.out.println("Nullpointer-Treffer: " + nullPointerHits);
        System.out.println("Assertion-Fehler: " + assertionErrors);
      }
    });

    t1.start();
    t2.start();
  }

}

Unabhängig davon, wie oft ich den Code ausführe, tritt der AssertionError nie auf. Für mich gibt es also mehrere Optionen:

  • Die JVM-Implementierung (in meinem Fall Oracle's 1.8.0.20) setzt durch, dass die Invarianten, die während der Konstruktion eines Objekts festgelegt wurden, für alle Threads sichtbar sind.
  • Das Buch ist falsch, was ich bezweifeln würde, da der Autor Brian Goetz ist ... nuf sagte
  • Ich mache etwas falsch in meinem obigen Code

Also die Fragen die ich habe: - Hat jemand jemals erfolgreich diesen Art von AssertionError provoziert? Mit welchem Code dann? - Warum provoziert mein Code den AssertionError nicht?

3voto

John Bollinger Punkte 136896

Ihr Programm ist nicht ordnungsgemäß synchronisiert, wie dieser Begriff vom Java Memory Model definiert ist.

Dies bedeutet jedoch nicht, dass ein bestimmter Durchlauf den von Ihnen gesuchten Fehler aufweist oder dass Sie überhaupt damit rechnen können, diesen Fehler jemals zu sehen. Es kann sein, dass Ihr bestimmter VM einfach dieses spezielle Programm auf eine Weise behandelt, die diesen Synchronisationsfehler nie offenbart. Oder es könnte sich herausstellen, dass, obwohl anfällig für Fehler, die Wahrscheinlichkeit gering ist.

Und nein, Ihr Test rechtfertigt nicht das Schreiben von unzureichend synchronisiertem Code auf diese bestimmte Weise. Sie können nicht von diesen Beobachtungen verallgemeinern.

1voto

Holger Punkte 264693

Du suchst nach einer sehr seltenen Bedingung. Auch wenn der Code ein nicht initialisiertes n liest, kann er beim nächsten Mal den gleichen Standardwert lesen, sodass der Wettlauf, den du suchst, ein Update genau zwischen diesen beiden aufeinanderfolgenden Lesevorgängen erfordert.

Das Problem ist, dass jeder Optimierer die beiden Lesevorgänge in deinem Code zu einem zusammenfassen wird, sobald er deinen Code verarbeitet. Daher wirst du danach nie einen AssertionError erhalten, auch wenn dieser einzelne Lesevorgang zum Standardwert ausgewertet wird.

Weiterhin, da der Zugriff auf Publish.holder nicht synchronisiert ist, darf der Optimierer seinen Wert genau einmal lesen und während aller folgenden Iterationen als unverändert annehmen. Daher würde ein optimierter zweiter Thread immer dasselbe Objekt verarbeiten, das niemals in den nicht initialisierten Zustand zurückkehren würde. Noch schlimmer ist, dass ein optimistischer Optimierer möglicherweise so weit geht, anzunehmen, dass n immer 42 ist, da du es in diesem Laufzeitumfeld nie auf etwas anderes initialisierst und er den Fall, dass du einen Wettlaufzustand wünschst, nicht berücksichtigt. Daher könnten beide Schleifen zu No-Ops optimiert werden.

Anders ausgedrückt: Wenn dein Code beim ersten Zugriff nicht fehlschlägt, sinkt die Wahrscheinlichkeit, den Fehler in den nachfolgenden Iterationen zu entdecken, dramatisch, möglicherweise auf Null. Dies steht im Gegensatz zu deiner Idee, den Code in einer langen Schleife laufen zu lassen in der Hoffnung, dass du schließlich auf den Fehler stoßen wirst.

Die besten Chancen, einen Datenwettlauf zu erhalten, bestehen bei der ersten, nicht optimierten, interpretierten Ausführung deines Codes. Aber bedenke, die Wahrscheinlichkeit für diesen spezifischen Datenwettlauf ist immer noch extrem niedrig, auch wenn der gesamte Testcode im reinen Interpretationsmodus ausgeführt wird.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X