2 Stimmen

Java-Synchronisierung auf Collection mit teuren Operationen

Ich habe eine Liste namens synchronizedMap, auf die ich in meiner Funktion doMapOperation synchronisiere. In dieser Funktion muss ich Elemente zu einer Map hinzufügen/entfernen und auf diesen Objekten aufwendige Operationen ausführen. Ich weiß, dass ich keine teure Operation in einem synchronisierten Block aufrufen möchte, aber ich weiß nicht, wie ich sicherstellen kann, dass die Map konsistent ist, während ich diese Operationen durchführe. Wie mache ich das am besten?

Das ist mein initiales Layout, von dem ich sicher bin, dass es falsch ist, weil man teure Operationen in einem synchronisierten Block vermeiden möchte:

public void doMapOperation(Object key1, Object key2) {
    synchronized (synchronizedMap) {

        // Entfernen von key1, wenn es vorhanden ist.
        if (synchronizedMap.containsKey(key1)) {
            Object value = synchronizedMap.get(key1);
            value.doExpensiveOperation(); // Sollte nicht im synchronisierten Block sein.

            synchronizedMap.remove(key1);
        }

        // Hinzufügen von key2, wenn nötig.
        Object value = synchronizedMap.get(key2);
        if (value == null) {
            Object value = new Object();
            synchronizedMap.put(key2, value);
        }

        value.doOtherExpensiveOperation(); // Sollte nicht im synchronisierten Block sein.
    } // Ende der Synchronisation.
}

Als Fortführung dieser Frage: Wie würden Sie dies in einer Schleife tun?

public void doMapOperation(Object... keys) {
    synchronized (synchronizedMap) {

        // Durchlaufen der Keys und Entfernen von ihnen.
        for (Object key : keys) {
            // Überprüfen, ob die Map den Key hat, entfernen, wenn der Key existiert, hinzufügen, wenn der Key nicht existiert.
            if (synchronizedMap.containsKey(key)) {
                Object value = synchronizedMap.get(key);
                value.doExpensiveOperation(); // Sollte hier nicht sein.

                synchronizedMap.remove(key);
            } else {
                Object value = new Object();
                value.doAnotherExpensiveOperation(); // Sollte nicht hier sein.

                synchronizedMap.put(key, value);
            }
        }
    } // Ende des Synchronisationsblocks.
}

Vielen Dank für die Hilfe.

2voto

Garrett Hall Punkte 28608

Sie können einen Wrapper für Ihre synchronizedMap erstellen und sicherstellen, dass die Operationen wie containsKey, remove und put synchronisierte Methoden sind. Dann ist nur der Zugriff auf die Map synchronisiert, während Ihre teuren Operationen außerhalb des synchronisierten Blocks stattfinden können.

Ein weiterer Vorteil besteht darin, dass Sie durch die Auslagerung Ihrer teuren Operationen aus dem synchronisierten Block ein mögliches Deadlock-Risiko vermeiden, wenn die Operationen eine andere synchronisierte Map-Methode aufrufen.

2voto

Garrett Hall Punkte 28608

Sie können teure Operationen außerhalb Ihres synchronisierten Blocks wie folgt durchführen:

public void doMapOperation(Object... keys) {
    ArrayList contained = new ArrayList();
    ArrayList missing = new ArrayList();

    synchronized (synchronizedMap) {
        if (synchronizedMap.containsKey(key)) {
            contained.add(synchronizedMap.get(key));
            synchronizedMap.remove(key);
        } else {
            missing.add(synchronizedMap.get(key));
            synchronizedMap.put(key, value);
        }
    }

    for (Object o : contained)
        o.doExpensiveOperation();
    for (Object o : missing)
        o.doAnotherExpensiveOperation();
}

Der einzige Nachteil ist, dass Sie möglicherweise Operationen an Werten durchführen, nachdem sie aus der synchronizedMap entfernt wurden.

1voto

Bozho Punkte 570413

Im ersten Snippet: Deklariere die beiden Werte außerhalb der if-Klausel und weise sie einfach in der if-Klausel zu. Mach die if-Klausel synchronisiert und rufe die teuren Operationen außerhalb auf.

Im 2. Fall mach dasselbe, aber innerhalb der Schleife (synchronisiert innerhalb der Schleife). Du kannst natürlich nur eine synchronized Anweisung außerhalb der Schleife haben und einfach eine List von Objekten füllen, auf denen die teure Operation aufgerufen werden soll. Dann rufe in einer 2. Schleife außerhalb des synchronisierten Blocks diese Operationen auf alle Werte in der Liste auf.

1voto

John Vint Punkte 38604

Sie können tatsächlich alles mit nur einem Synchronisationsaufruf erledigen. Das erste Entfernen ist wahrscheinlich das einfachste. Wenn Sie wissen, dass das Objekt existiert, und Sie wissen, dass die Entfernung atomar ist, warum entfernen Sie es dann nicht einfach und rufen Sie, wenn das zurückgegebene Element nicht null ist, die teuren Operationen auf?

 // Entfernen Sie key1, wenn es existiert.
        if (synchronizedMap.containsKey(key1)) {
            Object value = synchronizedMap.remove(key1);
            if(value != null){ //der Thread hat ausschließlichen Zugriff auf den Wert
              value.doExpensiveOperation();
            }
        }

Für das Einfügen, da es teuer ist und atomar sein sollte, haben Sie ziemlich viel Pech und müssen den Zugriff synchronisieren. Ich würde empfehlen, eine Art Berechnungskarte zu verwenden. Werfen Sie einen Blick auf die Google-Sammlungen und MapMaker

Sie können eine ConcurrentMap erstellen, die das teure Objekt basierend auf Ihrem Schlüssel aufbaut, zum Beispiel

 ConcurrentMap expensiveObjects = new MapMaker()
       .concurrencyLevel(32)
       .makeComputingMap(
           new Function() {
             public ExpensiveObject apply(Key key) {
               return createNewExpensiveObject(key);
             }
           });

Dies ist einfach eine Form von Memoisierung

In beiden Fällen müssen Sie synchronized nicht verwenden (zumindest nicht explizit)

1voto

Jared Punkte 1859

Wir sollten uns etwa 97% der Zeit keine Gedanken über kleine Effizienzen machen: Frühe Optimierung ist die Wurzel allen Übels. Dennoch sollten wir nicht unsere Chancen in diesen entscheidenden 3% verpassen. Ein guter Programmierer wird sich durch solches Denken nicht in Sicherheit wiegen lassen, er wird klug sein, sorgfältig den kritischen Code zu betrachten; aber erst nachdem dieser Code identifiziert wurde. — Donald Knuth

Sie haben eine einzige Methode, doMapOperation(). Wie ist Ihre Leistung, wenn diese Methode weiterhin blockiert synchronisiert wird? Wenn Sie es nicht wissen, wie werden Sie wissen, wenn Sie eine gut funktionierende Lösung haben? Sind Sie bereit, mehrere Aufrufe Ihrer teuren Operationen zu behandeln, auch nachdem sie aus der Map entfernt wurden?

Ich versuche nicht herablassend zu sein, denn vielleicht verstehen Sie das Problem besser als Sie es dargestellt haben, aber es scheint, als ob Sie sich in eine Ebene der Optimierung stürzen, für die Sie vielleicht nicht bereit sind und die vielleicht nicht notwendig ist.

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