Frage

Ich habe ein Webapp, die ich in der Mitte bin auf einige Lasten / Performance-Tests zu tun, particularily auf einem Feature, wo wir ein paar hundert Benutzer erwarten, dass die gleiche Seite zugreifen und Schlagen Refresh etwa alle 10 Sekunden auf dieser Seite. Ein Bereich, der Verbesserung, die wir fanden wir mit dieser Funktion machen konnte, war die Antworten aus dem Web-Dienst für eine gewisse Zeit zwischenzuspeichern, da die Daten nicht ändern.

Nachdem diese grundlegenden Caching-Implementierung in einigen weiteren Tests fand ich heraus, dass ich nicht überlegen, wie gleichzeitige Threads den Cache zur gleichen Zeit zugreifen können. Ich fand, dass in der Frage der ~ 100ms, etwa 50 Fäden das Objekt aus dem Cache zu holen versuchen, zu finden, dass es abgelaufen war, treffen den Web-Service, um die Daten zu holen, und dann im Cache das Objekt setzen zurück.

Der ursprüngliche Code sah etwa so aus:

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {

  final String key = "Data-" + email;
  SomeData[] data = (SomeData[]) StaticCache.get(key);

  if (data == null) {
      data = service.getSomeDataForEmail(email);

      StaticCache.set(key, data, CACHE_TIME);
  }
  else {
      logger.debug("getSomeDataForEmail: using cached object");
  }

  return data;
}

Also, um sicherzustellen, dass nur ein Thread den Web-Service anrief, wenn das Objekt bei key abgelaufen, ich dachte, ich brauchte den Cache-get / set-Betrieb zu synchronisieren, und es schien, den Cache-Schlüssel wie mit ein guter Kandidat wäre für ein Objekt zu synchronisieren, auf (diese Art und Weise, für E-Mail an diese Methode ruft b@b.com würde durch Methodenaufrufe a@a.com nicht blockiert werden).

ich die Methode zu sehen wie folgt aktualisiert:

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {


  SomeData[] data = null;
  final String key = "Data-" + email;

  synchronized(key) {      
    data =(SomeData[]) StaticCache.get(key);

    if (data == null) {
        data = service.getSomeDataForEmail(email);
        StaticCache.set(key, data, CACHE_TIME);
    }
    else {
      logger.debug("getSomeDataForEmail: using cached object");
    }
  }

  return data;
}

Ich habe auch Linien für Dinge wie „vor der Synchronisation Block“, „innen Synchronisationsblock“, „etwa verlassen Synchronisationsblock“ und „nach der Synchronisierung Block“ logging, so konnte ich feststellen, ob ich effektiv die get-Synchronisation wurde / Set-Operation.

Allerdings scheint es nicht, wie dies funktioniert hat. Meine Testprotokolle haben eine Ausgabe wie:

  

(log Ausgang ist 'threadname' 'Logger Name' 'message')
  http-80-Processor253 jsp.view-Seite - getSomeDataForEmail: über Synchronisationsblock
eingeben   http-80-Processor253 jsp.view-Seite - getSomeDataForEmail: im Synchronisationsblock
  http-80-Processor253 cache.StaticCache - erhalten: Objekt auf die Taste [SomeData-test@test.com] ist abgelaufen
  http-80-Processor253 cache.StaticCache - erhalten: key [SomeData-test@test.com] Rückkehr Wert [null]
  http-80-Processor263 jsp.view-Seite - getSomeDataForEmail: über Synchronisationsblock
eingeben   http-80-Processor263 jsp.view-Seite - getSomeDataForEmail: im Synchronisationsblock
  http-80-Processor263 cache.StaticCache - erhalten: Objekt auf die Taste [SomeData-test@test.com] ist abgelaufen
  http-80-Processor263 cache.StaticCache - erhalten: key [SomeData-test@test.com] Rückkehr Wert [null]
  http-80-Processor131 jsp.view-Seite - getSomeDataForEmail: über Synchronisationsblock
eingeben   http-80-Processor131 jsp.view-Seite - getSomeDataForEmail: im Synchronisationsblock
  http-80-Processor131 cache.StaticCache - erhalten: Objekt auf die Taste [SomeData-test@test.com] ist abgelaufen
  http-80-Processor131 cache.StaticCache - erhalten: key [SomeData-test@test.com] Rückkehr Wert [null]
  http-80-Processor104 jsp.view-Seite - getSomeDataForEmail: im Synchronisationsblock
  http-80-Processor104 cache.StaticCache - erhalten: Objekt auf die Taste [SomeData-test@test.com] ist abgelaufen
  http-80-Processor104 cache.StaticCache - erhalten: key [SomeData-test@test.com] Rückkehr Wert [null]
  http-80-Processor252 jsp.view-Seite - getSomeDataForEmail: über Synchronisationsblock
eingeben   http-80-Processor283 jsp.view-Seite - getSomeDataForEmail: über Synchronisationsblock
eingeben   http-80-Processor2 jsp.view-Seite - getSomeDataForEmail: über Synchronisationsblock
eingeben   http-80-Processor2 jsp.view-Seite - getSomeDataForEmail: im Synchronisationsblock

wollte ich nur einen Thread zu einem Zeitpunkt, um zu sehen, Eingabe / Verlassen des Synchronisationsblockes um die get / set-Operationen.

Gibt es ein Problem auf String-Objekten in Synchronisation? Ich dachte, der Cache-Schlüssel eine Schmiere würded Wahl, wie es ist einzigartig für den Betrieb, und auch wenn die final String key innerhalb der Methode deklariert ist, dachte ich, dass jeder Thread würde einen Verweis auf wird immer das gleiche Objekt und daher würde die Synchronisierung auf diesem Einzel Objekt.

Was bin ich falsch hier?

Aktualisieren : nach in den Protokollen suchen weiter, wie es scheint, Methoden mit der gleichen Synchronisationslogik, wo der Schlüssel ist immer die gleiche, wie

final String key = "blah";
...
synchronized(key) { ...

nicht das gleiche Concurrency Problem nicht aufweist -. Nur einen Thread zu einem Zeitpunkt in den Block einfährt

Update 2 : Vielen Dank an alle für die Hilfe! Ich nahm die erste Antwort über intern()ing Strings, die mein erstes Problem gelöst - wo mehrere Threads synchronisiert Blöcke eintraten, wo ich dachte, sie sollte nicht, weil der key des den gleichen Wert hatte

.

Wie andere haben darauf hingewiesen, mit intern() für einen solchen Zweck und die Synchronisierung auf diesen Strings ist in der Tat eine schlechte Idee erweisen - wenn JMeter Tests gegen die Webapp läuft die erwartete Last zu simulieren, sah ich die verwendete Speichergröße wachsen zu 1 GB fast in knapp 20 Minuten.

Zur Zeit verwende ich die einfache Lösung nur das gesamte Verfahren zu synchronisieren - aber I wirklich , wie die Code-Beispiele zur Verfügung gestellt von martinprobst und MBCook, aber da ich etwa 7 ähnlichen getData() Methoden in dieser Klasse zur Zeit (da es etwa 7 verschiedene Daten von einem Web-Service muss), habe ich nicht fast doppelte Logik über das erhalten und Freigeben von Sperren zu jeder Methode hinzufügen möchte. Aber das ist auf jeden Fall sehr, sehr nützliche und wertvolle Informationen für die künftige Nutzung. Ich denke, das sind letztlich die richtigen Antworten darauf, wie am besten eine Operation wie dieser Thread-sicher zu machen, und ich würde mehr Stimmen auf diese Antworten geben, wenn ich kann!

War es hilfreich?

Lösung

Ohne mein Gehirn voll in Gang zu setzen, von einem schnellen Scan von dem, was Sie sagen, es sieht aus, als ob Sie intern benötigen () Ihre Strings:

final String firstkey = "Data-" + email;
final String key = firstkey.intern();

Zwei Strings mit dem gleichen Wert ist sonst nicht unbedingt das gleiche Objekt.

Beachten Sie, dass dies einen neuen Streitpunkt vorstellen kann, da tief in der VM, intern () kann eine Sperre erwerben müssen. Ich habe keine Ahnung, was die moderne VMs wie in diesem Bereich aussehen, aber man hofft, dass sie teuflisch optimiert ist.

Ich nehme an, Sie wissen, dass StaticCache noch Thread-sicher sein muss. Aber die Behauptung, es sollte klein sein im Vergleich mit dem, was Sie haben würden, wenn Sie auf den Cache-Sperren wurden und nicht nur die Schlüssel während getSomeDataForEmail aufrufen.

Antwort auf Frage update :

Ich denke, das ist, weil ein Stringliteral immer das gleiche Objekt ergibt. Dave Costa weist in einem Kommentar darauf hin, dass es noch besser als das: eine wörtliche immer die kanonische Darstellung ergibt. Also alle Stringliterale mit dem gleichen Wert überall im Programm würden das gleiche Objekt ergeben.

Bearbeiten

Andere haben darauf hingewiesen, dass auf intern Strings zu synchronisieren, ist eigentlich eine wirklich schlechte Idee - zum Teil, weil die Schaffung intern Strings erlaubt ist, sie zu veranlassen, auf Dauer zu existieren, und zum Teil, weil, wenn mehr als ein Bit Code an beliebiger Stelle in Ihrem Programm auf intern Strings synchronisiert, müssen Sie Abhängigkeiten zwischen diesen Bit-Code und Deadlocks oder andere Fehler verhindern kann unmöglich sein.

Strategien, dies zu vermeiden, indem ein Sperrobjekt pro Schlüsselfolge zu speichern sind, in anderen Antworten entwickelt, wie ich geben.

Hier ist eine Alternative - es immer noch eine einzigartige Schloss verwendet, aber wir wissen, werden wir eine von denen für den Cache müssen sowieso, und Sie sprechen über 50 Threads, nicht 5000, so dass nicht tödlich sein kann. Ich gehe davon aus, dass die Performance-Engpass hier langsam blockierende I / O in DoSlowThing () die daher enorm profitieren von nicht serialisiert werden. Wenn das nicht der Engpass, dann:

  • Wenn die CPU ausgelastet ist dann dieser Ansatz möglicherweise nicht ausreichend, und Sie müssen einen anderen Ansatz.
  • Wenn die CPU nicht beschäftigt ist, und der Zugang zum Server ist kein Engpass, dann ist dieser Ansatz ist übertrieben, und man könnte genauso gut vergessen sowohl dieses als auch pro-Tastensperre, legte eine große synchronisiert (StaticCache) um den gesamten Betrieb und es die einfache Weise tun.

Natürlich muss dieser Ansatz vor der Verwendung für die Skalierbarkeit getestet werden einweichen - ich nichts garantieren

.

Dieser Code erfordert nicht, dass StaticCache synchronisiert oder anderweitig Thread-sicher. Das muss überarbeitet werden, wenn ein anderer Code (zB Sanierung von alten Daten geplant) immer den Cache berührt.

IN_PROGRESS ist ein Dummy-Wert - nicht gerade sauber, aber der Code ist einfach und spart zwei Hash-Tabellen haben. Es ist nicht InterruptedException handhaben, weil ich nicht weiß, was Ihre app will in diesem Fall zu tun ist. Auch wenn DoSlowThing () konsequent für einen bestimmten Schlüssel nicht dieser Code, wie es steht nicht gerade elegant, da jeder Faden durch sie wiederholen wird. Da ich weiß nicht, was die Ausfallkriterien sind, und ob sie geeignet sind, vorübergehend oder dauerhaft zu sein, gehe ich nicht dies entweder, ich nur sicher Threads machen immer nicht blockieren. In der Praxis sollten Sie einen Datenwert im Cache setzen, die ‚nicht verfügbar‘, vielleicht mit einem Grunde, und ein Timeout für, wenn erneut zu versuchen.

zeigen
// do not attempt double-check locking here. I mean it.
synchronized(StaticObject) {
    data = StaticCache.get(key);
    while (data == IN_PROGRESS) {
        // another thread is getting the data
        StaticObject.wait();
        data = StaticCache.get(key);
    }
    if (data == null) {
        // we must get the data
        StaticCache.put(key, IN_PROGRESS, TIME_MAX_VALUE);
    }
}
if (data == null) {
    // we must get the data
    try {
        data = server.DoSlowThing(key);
    } finally {
        synchronized(StaticObject) {
            // WARNING: failure here is fatal, and must be allowed to terminate
            // the app or else waiters will be left forever. Choose a suitable
            // collection type in which replacing the value for a key is guaranteed.
            StaticCache.put(key, data, CURRENT_TIME);
            StaticObject.notifyAll();
        }
    }
}

Jedes Mal, alles ist mit dem Cache hinzugefügt, alle Threads aufwachen und prüfen Sie den Cache (egal welche Taste sie sind nach), so dass es möglich ist, mit weniger strittigen Algorithmen bessere Leistung zu erzielen. Doch ein Großteil dieser Arbeit wird während Ihrer reichlich CPU-Leerlaufzeit nimmt Blockierung auf I / O, so kann es kein Problem sein.

Dieser Code könnte mit mehreren Caches commoned-up für den Einsatz, wenn Sie geeignete Abstraktionen für den Cache und der damit verbundenen Sperre definieren, ist es die Daten erneutdie IN_PROGRESS dummy, und der langsame Betrieb dreht, auszuführen. das Ganze in einem Verfahren Abrollen auf dem Cache vielleicht keine schlechte Idee sein.

Andere Tipps

auf einem intern'd String Synchronisieren keine gute Idee überhaupt sein könnte - durch Internierung, schaltet sich der String in ein globales Objekt, und wenn Sie auf den gleichen internierten Strings in verschiedenen Teilen Ihrer Anwendung synchronisieren, könnten Sie bekommen wirklich seltsam und im Grunde undebuggable Synchronisierungsprobleme wie Deadlocks. Es mag unwahrscheinlich, aber wenn es passiert, Sie sind wirklich geschraubt. Als allgemeine Regel gilt, synchronisieren immer nur auf einem lokalen Objekt, in dem Sie absolut sicher sind, dass kein Code außerhalb des Moduls kann es sperren.

In Ihrem Fall können Sie ein synchronisiertes Hashtable verwenden zu speichern Objekte für Ihre Schlüssel verriegelt wird.

Z. B:.

Object data = StaticCache.get(key, ...);
if (data == null) {
  Object lock = lockTable.get(key);
  if (lock == null) {
    // we're the only one looking for this
    lock = new Object();
    synchronized(lock) {
      lockTable.put(key, lock);
      // get stuff
      lockTable.remove(key);
    }
  } else {
    synchronized(lock) {
      // just to wait for the updater
    }
    data = StaticCache.get(key);
  }
} else {
  // use from cache
}

Dieser Code hat eine Race-Bedingung, wo zwei Threads hintereinander ein Objekt in die Sperrtabelle setzen könnten. Dies sollte jedoch kein Problem sein, weil dann nur Sie einen weiteren Thread haben die Webservice aufrufe und den Cache zu aktualisieren, was aber kein Problem sein sollte.

Wenn Sie den Cache nach einiger Zeit sind ungültig zu machen, sollen Sie überprüfen, ob die Daten null wieder, nachdem sie aus dem Cache, in dem Schloss Abrufen! = Null Fall.

Alternativ und viel einfacher, können Sie die gesamte Cache-Lookup-Methode ( „getSomeDataByEmail“) synchronisiert werden. Dies bedeutet, dass alle Threads zu synchronisieren, wenn sie auf den Cache zugreifen, die ein Leistungsproblem sein könnte. Aber wie immer, versuchen Sie diese einfache Lösung zuerst und sehen, ob es wirklich ein Problem! In vielen Fällen sollte es nicht sein, wie Sie wahrscheinlich viel mehr Zeit Verarbeitung des Ergebnisses ausgeben, als synchronisiert wird.

Strings sind nicht gute Kandidaten für die Synchronisation. Wenn Sie auf einer String-ID synchronisiert werden, kann es unter Verwendung der Zeichenfolge durchgeführt werden, um eine Mutex zu erstellen (siehe „ auf einer ID Synchronisieren"). Ob die Kosten dieser Algorithmus wert ist, hängt es ab, ob Berufung auf Ihren Dienst beinhaltet eine signifikante I / O.

Auch:

  • Ich hoffe, die StaticCache.get () und gesetzt () Methoden sind THREAD.
  • String.intern () ist mit Kosten verbunden (eine, die zwischen VM-Implementierungen variiert) mit Vorsicht angewendet werden und sollte.

Andere haben vorgeschlagen, die Saiten Internierung, und das wird funktionieren.

Das Problem ist, dass Java um internierten Strings zu halten hat. Mir wurde gesagt, es tut dies auch, wenn Sie nicht eine Referenz halten, weil der Wert der gleiche das nächste Mal, wenn jemand sein muss, verwendet diese Zeichenfolge. Das bedeutet, alle Fäden interning Speicher auffressen kann beginnen, die mit dem Lesen Sie beschreiben könnte ein großes Problem sein.

Ich habe zwei Lösungen zu sehen folgendermaßen aus:

Sie befinden sich auf einem anderen Objekt synchronisieren könnte

Statt die E-Mail von einem Objekt machen, dass die E-Mail (sagt das User-Objekt) hält, die den Wert der E-Mail als Variable enthält. Wenn Sie bereits ein anderes Objekt, das die Person darstellt (sagen Sie schon etwas von der DB auf der Grundlage ihrer E-Mail gezogen) Sie verwenden können. Durch die Methode equals Implementierung und die Hash-Code-Methode können Sie sicherstellen, Java die Objekte hält das gleiche, wenn Sie eine statische cache.contains tun (), um herauszufinden, ob die Daten bereits im Cache ist (Sie werden auf den Cache zu synchronisieren haben ).

Eigentlich könnten Sie eine zweite Karte halten für Objekte zu sperren. So etwas wie folgt aus:

Map<String, Object> emailLocks = new HashMap<String, Object>();

Object lock = null;

synchronized (emailLocks) {
    lock = emailLocks.get(emailAddress);

    if (lock == null) {
        lock = new Object();
        emailLocks.put(emailAddress, lock);
    }
}

synchronized (lock) {
    // See if this email is in the cache
    // If so, serve that
    // If not, generate the data

    // Since each of this person's threads synchronizes on this, they won't run
    // over eachother. Since this lock is only for this person, it won't effect
    // other people. The other synchronized block (on emailLocks) is small enough
    // it shouldn't cause a performance problem.
}

Dies wird an einem 15 Abrufe auf der gleichen E-Mail-Adresse verhindern. Sie brauchen etwas zu viele Einträge zu verhindern, dass in der emailLocks Karte endet. Mit LRUMap von Apache Commons s würde tun es.

Dies wird einige Optimierungen müssen, aber es kann Ihr Problem lösen.

Verwenden Sie eine andere Taste

Wenn Sie bereit sind, mit möglichen Fehlern zu setzen (ich weiß nicht, wie wichtig das ist) können Sie den Hash-Code des String als Schlüssel verwenden könnten. Ints braucht nicht interniert zu werden.

Zusammenfassung

Ich hoffe, das hilft. Threading ist Spaß, ist es nicht? Sie könnten auch die Sitzung verwenden, um einen Wert zu setzen bedeutet „Ich bin schon arbeiten diese auf der Suche nach“ und überprüfen, um zu sehen, ob der zweite (dritte, Nth) Thread zu erstellen, um versuchen muss warten die oder einfach nur für das Ergebnis zu zeigen im Cache. Ich glaube, ich drei Vorschläge hatte.

Sie kann das 1,5 concurrency Dienstprogramme verwenden einen Cache bereitzustellen gestaltet, um mehrere gleichzeitigen Zugriff, und einen einzelnen Punkt der Zugabe (d.h. nur ein Thread je Durchführen die teuere Objekt „creation“) zu ermöglichen:

 private ConcurrentMap<String, Future<SomeData[]> cache;
 private SomeData[] getSomeDataByEmail(final WebServiceInterface service, final String email) throws Exception {

  final String key = "Data-" + email;
  Callable<SomeData[]> call = new Callable<SomeData[]>() {
      public SomeData[] call() {
          return service.getSomeDataForEmail(email);
      }
  }
  FutureTask<SomeData[]> ft; ;
  Future<SomeData[]> f = cache.putIfAbsent(key, ft= new FutureTask<SomeData[]>(call)); //atomic
  if (f == null) { //this means that the cache had no mapping for the key
      f = ft;
      ft.run();
  }
  return f.get(); //wait on the result being available if it is being calculated in another thread
}

Offensichtlich ist dies nicht Ausnahmen behandeln, wie Sie wollen würde, und der Cache nicht Räumung eingebaut hat. Vielleicht haben Sie es als Grundlage verwenden, könnte Ihre StaticCache Klasse zu ändern, though.

Verwenden Sie ein anständiges Caching Framework wie ehcache .

einen guten Cache-Implementierung ist nicht so einfach, wie manche Leute glauben.

Im Hinblick auf die Bemerkung, dass String.intern () ist eine Quelle von Speicherlecks, das ist eigentlich nicht wahr. Internierten Strings sind Müll gesammelt, es könnte länger dauern, weil auf bestimmte JVM (SUN) sie in Perm Raum gespeichert werden, die nur durch die vollständige GC berührt wird.

Hier ist eine sichere kurze Java 8-Lösung, die eine Karte von dedizierten Sperrobjekten für die Synchronisation verwendet:

private static final Map<String, Object> keyLocks = new ConcurrentHashMap<>();

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
    final String key = "Data-" + email;
    synchronized (keyLocks.computeIfAbsent(key, k -> new Object())) {
        SomeData[] data = StaticCache.get(key);
        if (data == null) {
            data = service.getSomeDataForEmail(email);
            StaticCache.set(key, data);
        }
    }
    return data;
}

Es hat den Nachteil, dass Schlüssel und Sperrobjekte für immer in der Karte behalten würden.

Das ist wie diese umgangen werden können:

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
    final String key = "Data-" + email;
    synchronized (keyLocks.computeIfAbsent(key, k -> new Object())) {
        try {
            SomeData[] data = StaticCache.get(key);
            if (data == null) {
                data = service.getSomeDataForEmail(email);
                StaticCache.set(key, data);
            }
        } finally {
            keyLocks.remove(key); // vulnerable to race-conditions
        }
    }
    return data;
}

Aber dann populär Schlüssel würde ständig in der Karte wieder eingesetzt wird mit Sperrobjekten neu zugeordnet werden.

Aktualisieren . Und das läßt Race-Bedingung Möglichkeit, wenn zwei Threads gleichzeitig würden Abschnitt für den gleichen Schlüssel synchronisiert eingeben, sondern mit verschiedenen Schlössern

So kann es sicher und effizient sein Guava Cache auslaufenden:

private static final LoadingCache<String, Object> keyLocks = CacheBuilder.newBuilder()
        .expireAfterAccess(10, TimeUnit.MINUTES) // max lock time ever expected
        .build(CacheLoader.from(Object::new));

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
    final String key = "Data-" + email;
    synchronized (keyLocks.getUnchecked(key)) {
        SomeData[] data = StaticCache.get(key);
        if (data == null) {
            data = service.getSomeDataForEmail(email);
            StaticCache.set(key, data);
        }
    }
    return data;
}

Beachten Sie, dass hier davon ausgegangen ist, dass StaticCache Thread-sicher ist und nicht von gleichzeitigem leiden liest und schreibt für verschiedene Schlüssel.

Ihr Hauptproblem ist nicht nur, dass es möglicherweise mehr Instanzen von String mit dem gleichen Wert. Das Hauptproblem ist, dass Sie nur einen Monitor, auf dem für den Zugriff auf das StaticCache Objekt zu synchronisieren haben müssen. Ansonsten mehrere Threads können gleichzeitig modifizieren StaticCache am Ende (wenn auch unter anderen Tasten), die höchstwahrscheinlich nicht die gleichzeitige Änderung nicht unterstützt.

Der Aufruf:

   final String key = "Data-" + email;

erstellt ein neues Objekt jedes Mal, wenn die Methode aufgerufen wird. Denn das Ziel ist es, was Sie sperren verwenden, und jeder Aufruf dieser Methode erzeugt ein neues Objekt, dann sind Sie wirklich keinen Zugriff auf die auf dem Schlüssel basierend Karte zu synchronisieren.

Diese weitere erläutern Sie Ihre bearbeiten. Wenn Sie eine statische Zeichenfolge haben, dann wird es funktionieren.

intern verwenden () löst das Problem, weil es die Zeichenfolge aus einem internen Pool zurück durch die String-Klasse gehalten, die dafür sorgt, dass, wenn zwei Strings gleich sind, wird derjenige im Pool verwendet werden. Siehe

http: / /java.sun.com/j2se/1.4.2/docs/api/java/lang/String.html#intern ()

Diese Frage scheint mir ein bisschen zu breit und damit angestiftet es ebenso breites Spektrum an Antworten. Also werde ich versuchen, die Frage ich habe aus, umgeleitet worden leider, dass man als Duplikat geschlossen wurde.

public class ValueLock<T> {

    private Lock lock = new ReentrantLock();
    private Map<T, Condition> conditions  = new HashMap<T, Condition>();

    public void lock(T t){
        lock.lock();
        try {
            while (conditions.containsKey(t)){
                conditions.get(t).awaitUninterruptibly();
            }
            conditions.put(t, lock.newCondition());
        } finally {
            lock.unlock();
        }
    }

    public void unlock(T t){
        lock.lock();
        try {
            Condition condition = conditions.get(t);
            if (condition == null)
                throw new IllegalStateException();// possibly an attempt to release what wasn't acquired
            conditions.remove(t);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

Nach dem (äußeren) lock Betrieb der (innere) Sperre erworben einen exklusiven Zugriff auf die Karte für eine kurze Zeit zu bekommen, und wenn der Korrespondenten Objekt bereits in der Karte ist, wird der aktuelle Thread warten, sonst wird es neue Condition auf die Karte setzen, lassen Sie die (innere) Schloss und gehen, und die (äußeren) Verriegelung wird als erhalten. Der (äußere) unlock Betrieb zunächst eine (innere) Sperre zu erwerben, wird auf Condition signalisieren und dann das Objekt aus der Karte entfernen.

Die Klasse verwendet keine gleichzeitige Version von Map, da jeder Zugriff auf sie durch einzelne (innen) Schloss bewacht wird.

Bitte beachten Sie, ist die Semantik der lock() Methode dieser Klasse unterschiedlich, dass der ReentrantLock.lock(), werden die wiederholten lock() Anrufungen ohne gepaart unlock() unbegrenzt aktuellen Thread hängen.

Ein Beispiel für die Nutzung, die auf die Situation anwendbar sein könnte, die OP beschrieben

    ValueLock<String> lock = new ValueLock<String>();
    // ... share the lock   
    String email = "...";
    try {
        lock.lock(email);
        //... 
    } finally {
        lock.unlock(email);
    }

Das ist ziemlich spät, aber es gibt eine ganze Reihe von falschem Code präsentierte hier.

In diesem Beispiel:

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {


  SomeData[] data = null;
  final String key = "Data-" + email;

  synchronized(key) {      
    data =(SomeData[]) StaticCache.get(key);

    if (data == null) {
        data = service.getSomeDataForEmail(email);
        StaticCache.set(key, data, CACHE_TIME);
    }
    else {
      logger.debug("getSomeDataForEmail: using cached object");
    }
  }

  return data;
}

Die Synchronisation ist nicht richtig scoped. Für einen statischen Cache, der eine get / put API unterstützt, sollte es zumindest Synchronisation um die get und getIfAbsentPut Typ Operationen, für den sicheren Zugriff auf den Cache sein. Der Umfang der Synchronisation wird der Cache selbst.

Wenn Updates müssen auf die Datenelemente vorgenommen werden selbst, die eine zusätzliche Schicht der Synchronisation ergänzt, die auf den einzelnen Datenelemente sein sollte.

SynchronizedMap kann anstelle der expliziten Synchronisation verwendet werden, aber Vorsicht ist noch zu beachten. Wenn die falschen APIs verwendet werden (erhalten und statt putIfAbsent setzen), dann werden die Operationen nicht die notwendige Synchronisation haben, trotz der Verwendung der synchronisierten Karte. Beachten Sie die eingeführten Komplikationen durch die Verwendung von putIfAbsent: Entweder muss der Put-Wert auch in den Fällen berechnet werden, wenn es nicht benötigt wird (weil der Put kann nicht wissen, ob der Put-Wert benötigt wird, bis die Cache-Inhalte geprüft werden), oder erfordert eine sorgfältige Verwendung von Delegation. (sagen wir, mit Zukunft, die funktioniert, aber ist so etwas wie ein Mismatch, siehe unten), in dem der Put-Wert auf Nachfrage erhalten wird, wenn nötig

Die Verwendung von Futures ist möglich, scheint aber eher umständlich, und vielleicht ein bisschen Overengineering. Die Zukunft API ist es Kern für asynchrone Operationen ist, insbesondere für Operationen, die nicht sofort abschließen können. Die Einbeziehung Zukunft sehr wahrscheinlich eine Schicht aus Thread-Erzeugung fügt -. Extra wahrscheinlich unnötige Komplikationen

Das Hauptproblem der Zukunft unter Verwendung von für diese Art der Operation ist, dass zukünftige bindet an sich in Multi-Threading. Die Verwendung von Zukunft, wenn ein neuer Thread ist nicht notwendig, Mittel viel von der Maschinerie der Zukunft zu ignorieren, ist es eine allzu schwere API für diese Verwendung zu machen.

Warum macht nicht nur eine statische HTML-Seite, die alle x Minuten für den Benutzer und regenerierte?

serviert wird

Ich würde auch der String-Verkettung empfiehlt loszuwerden vollständig, wenn Sie es nicht brauchen.

final String key = "Data-" + email;

Gibt es andere Dinge / Arten von Objekten im Cache, die die E-Mail-Adresse verwenden, die Sie benötigen, die extra „Data-“ am Anfang des Schlüssels?

Wenn nicht, ich würde nur das machen

final String key = email;

und Sie vermeiden alles, was zusätzliche Zeichenfolge Schöpfung zu.

andere Art und Weise Synchron auf String-Objekt:

String cacheKey = ...;

    Object obj = cache.get(cacheKey)

    if(obj==null){
    synchronized (Integer.valueOf(Math.abs(cacheKey.hashCode()) % 127)){
          obj = cache.get(cacheKey)
         if(obj==null){
             //some cal obtain obj value,and put into cache
        }
    }
}

Bei anderen ein ähnliches Problem haben, der folgende Code funktioniert, soweit ich das sagen kann:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

public class KeySynchronizer<T> {

    private Map<T, CounterLock> locks = new ConcurrentHashMap<>();

    public <U> U synchronize(T key, Supplier<U> supplier) {
        CounterLock lock = locks.compute(key, (k, v) -> 
                v == null ? new CounterLock() : v.increment());
        synchronized (lock) {
            try {
                return supplier.get();
            } finally {
                if (lock.decrement() == 0) {
                    // Only removes if key still points to the same value,
                    // to avoid issue described below.
                    locks.remove(key, lock);
                }
            }
        }
    }

    private static final class CounterLock {

        private AtomicInteger remaining = new AtomicInteger(1);

        private CounterLock increment() {
            // Returning a new CounterLock object if remaining = 0 to ensure that
            // the lock is not removed in step 5 of the following execution sequence:
            // 1) Thread 1 obtains a new CounterLock object from locks.compute (after evaluating "v == null" to true)
            // 2) Thread 2 evaluates "v == null" to false in locks.compute
            // 3) Thread 1 calls lock.decrement() which sets remaining = 0
            // 4) Thread 2 calls v.increment() in locks.compute
            // 5) Thread 1 calls locks.remove(key, lock)
            return remaining.getAndIncrement() == 0 ? new CounterLock() : this;
        }

        private int decrement() {
            return remaining.decrementAndGet();
        }
    }
}

Im Fall des OP wäre es wie folgt verwendet werden:

private KeySynchronizer<String> keySynchronizer = new KeySynchronizer<>();

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
    String key = "Data-" + email;
    return keySynchronizer.synchronize(key, () -> {
        SomeData[] existing = (SomeData[]) StaticCache.get(key);
        if (existing == null) {
            SomeData[] data = service.getSomeDataForEmail(email);
            StaticCache.set(key, data, CACHE_TIME);
            return data;
        }
        logger.debug("getSomeDataForEmail: using cached object");
        return existing;
    });
}

Wenn nichts sollte von dem synchronisierten Code zurückgegeben wird, kann die Methode Synchronize wie folgt geschrieben werden:

public void synchronize(T key, Runnable runnable) {
    CounterLock lock = locks.compute(key, (k, v) -> 
            v == null ? new CounterLock() : v.increment());
    synchronized (lock) {
        try {
            runnable.run();
        } finally {
            if (lock.decrement() == 0) {
                // Only removes if key still points to the same value,
                // to avoid issue described below.
                locks.remove(key, lock);
            }
        }
    }
}

Ich habe eine kleine Schloss-Klasse hinzugefügt, die auf jedem Schlüssel, einschließlich Strings können sperren / synchronisieren.

Siehe Implementierung für Java 8, Java 6 und einen kleinen Test.

Java 8:

public class DynamicKeyLock<T> implements Lock
{
    private final static ConcurrentHashMap<Object, LockAndCounter> locksMap = new ConcurrentHashMap<>();

    private final T key;

    public DynamicKeyLock(T lockKey)
    {
        this.key = lockKey;
    }

    private static class LockAndCounter
    {
        private final Lock lock = new ReentrantLock();
        private final AtomicInteger counter = new AtomicInteger(0);
    }

    private LockAndCounter getLock()
    {
        return locksMap.compute(key, (key, lockAndCounterInner) ->
        {
            if (lockAndCounterInner == null) {
                lockAndCounterInner = new LockAndCounter();
            }
            lockAndCounterInner.counter.incrementAndGet();
            return lockAndCounterInner;
        });
    }

    private void cleanupLock(LockAndCounter lockAndCounterOuter)
    {
        if (lockAndCounterOuter.counter.decrementAndGet() == 0)
        {
            locksMap.compute(key, (key, lockAndCounterInner) ->
            {
                if (lockAndCounterInner == null || lockAndCounterInner.counter.get() == 0) {
                    return null;
                }
                return lockAndCounterInner;
            });
        }
    }

    @Override
    public void lock()
    {
        LockAndCounter lockAndCounter = getLock();

        lockAndCounter.lock.lock();
    }

    @Override
    public void unlock()
    {
        LockAndCounter lockAndCounter = locksMap.get(key);
        lockAndCounter.lock.unlock();

        cleanupLock(lockAndCounter);
    }


    @Override
    public void lockInterruptibly() throws InterruptedException
    {
        LockAndCounter lockAndCounter = getLock();

        try
        {
            lockAndCounter.lock.lockInterruptibly();
        }
        catch (InterruptedException e)
        {
            cleanupLock(lockAndCounter);
            throw e;
        }
    }

    @Override
    public boolean tryLock()
    {
        LockAndCounter lockAndCounter = getLock();

        boolean acquired = lockAndCounter.lock.tryLock();

        if (!acquired)
        {
            cleanupLock(lockAndCounter);
        }

        return acquired;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException
    {
        LockAndCounter lockAndCounter = getLock();

        boolean acquired;
        try
        {
            acquired = lockAndCounter.lock.tryLock(time, unit);
        }
        catch (InterruptedException e)
        {
            cleanupLock(lockAndCounter);
            throw e;
        }

        if (!acquired)
        {
            cleanupLock(lockAndCounter);
        }

        return acquired;
    }

    @Override
    public Condition newCondition()
    {
        LockAndCounter lockAndCounter = locksMap.get(key);

        return lockAndCounter.lock.newCondition();
    }
}

Java 6:

public class DynamicKeyLock implementiert Sperre     {         Private final static ConcurrentHashMap locksMap = new ConcurrentHashMap ();         Privat letzte T-Taste;

    public DynamicKeyLock(T lockKey) {
        this.key = lockKey;
    }

    private static class LockAndCounter {
        private final Lock lock = new ReentrantLock();
        private final AtomicInteger counter = new AtomicInteger(0);
    }

    private LockAndCounter getLock()
    {
        while (true) // Try to init lock
        {
            LockAndCounter lockAndCounter = locksMap.get(key);

            if (lockAndCounter == null)
            {
                LockAndCounter newLock = new LockAndCounter();
                lockAndCounter = locksMap.putIfAbsent(key, newLock);

                if (lockAndCounter == null)
                {
                    lockAndCounter = newLock;
                }
            }

            lockAndCounter.counter.incrementAndGet();

            synchronized (lockAndCounter)
            {
                LockAndCounter lastLockAndCounter = locksMap.get(key);
                if (lockAndCounter == lastLockAndCounter)
                {
                    return lockAndCounter;
                }
                // else some other thread beat us to it, thus try again.
            }
        }
    }

    private void cleanupLock(LockAndCounter lockAndCounter)
    {
        if (lockAndCounter.counter.decrementAndGet() == 0)
        {
            synchronized (lockAndCounter)
            {
                if (lockAndCounter.counter.get() == 0)
                {
                    locksMap.remove(key);
                }
            }
        }
    }

    @Override
    public void lock()
    {
        LockAndCounter lockAndCounter = getLock();

        lockAndCounter.lock.lock();
    }

    @Override
    public void unlock()
    {
        LockAndCounter lockAndCounter = locksMap.get(key);
        lockAndCounter.lock.unlock();

        cleanupLock(lockAndCounter);
    }


    @Override
    public void lockInterruptibly() throws InterruptedException
    {
        LockAndCounter lockAndCounter = getLock();

        try
        {
            lockAndCounter.lock.lockInterruptibly();
        }
        catch (InterruptedException e)
        {
            cleanupLock(lockAndCounter);
            throw e;
        }
    }

    @Override
    public boolean tryLock()
    {
        LockAndCounter lockAndCounter = getLock();

        boolean acquired = lockAndCounter.lock.tryLock();

        if (!acquired)
        {
            cleanupLock(lockAndCounter);
        }

        return acquired;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException
    {
        LockAndCounter lockAndCounter = getLock();

        boolean acquired;
        try
        {
            acquired = lockAndCounter.lock.tryLock(time, unit);
        }
        catch (InterruptedException e)
        {
            cleanupLock(lockAndCounter);
            throw e;
        }

        if (!acquired)
        {
            cleanupLock(lockAndCounter);
        }

        return acquired;
    }

    @Override
    public Condition newCondition()
    {
        LockAndCounter lockAndCounter = locksMap.get(key);

        return lockAndCounter.lock.newCondition();
    }
}

Test:

public class DynamicKeyLockTest
{
    @Test
    public void testDifferentKeysDontLock() throws InterruptedException
    {
        DynamicKeyLock<Object> lock = new DynamicKeyLock<>(new Object());
        lock.lock();
        AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false);
        try
        {
            new Thread(() ->
            {
                DynamicKeyLock<Object> anotherLock = new DynamicKeyLock<>(new Object());
                anotherLock.lock();
                try
                {
                    anotherThreadWasExecuted.set(true);
                }
                finally
                {
                    anotherLock.unlock();
                }
            }).start();
            Thread.sleep(100);
        }
        finally
        {
            Assert.assertTrue(anotherThreadWasExecuted.get());
            lock.unlock();
        }
    }

    @Test
    public void testSameKeysLock() throws InterruptedException
    {
        Object key = new Object();
        DynamicKeyLock<Object> lock = new DynamicKeyLock<>(key);
        lock.lock();
        AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false);
        try
        {
            new Thread(() ->
            {
                DynamicKeyLock<Object> anotherLock = new DynamicKeyLock<>(key);
                anotherLock.lock();
                try
                {
                    anotherThreadWasExecuted.set(true);
                }
                finally
                {
                    anotherLock.unlock();
                }
            }).start();
            Thread.sleep(100);
        }
        finally
        {
            Assert.assertFalse(anotherThreadWasExecuted.get());
            lock.unlock();
        }
    }
}

In Ihrem Fall, dass Sie so etwas wie dies nutzen könnten (dies Leck keinen Speicher):

private Synchronizer<String> synchronizer = new Synchronizer();

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
    String key = "Data-" + email;

    return synchronizer.synchronizeOn(key, () -> {

        SomeData[] data = (SomeData[]) StaticCache.get(key);
        if (data == null) {
            data = service.getSomeDataForEmail(email);
            StaticCache.set(key, data, CACHE_TIME);
        } else {
          logger.debug("getSomeDataForEmail: using cached object");
        }
        return data;

    });
}

, es zu benutzen Sie fügen Sie einfach eine Abhängigkeit:

compile 'com.github.matejtymes:javafixes:1.3.0'

Sie können sicher String.intern für synchronisieren, wenn Sie einigermaßen garantieren kann, dass der String-Wert in Ihrem System einzigartig ist. UUIDs ist ein guter Weg, dies zu nähern. Sie können eine UUID mit Ihrem tatsächlichen String-Schlüssel zuordnen, entweder über einen Cache, eine Karte, oder vielleicht sogar speichern die UUID als ein Feld auf Ihrem Entitätsobjekt.

    @Service   
    public class MySyncService{

      public Map<String, String> lockMap=new HashMap<String, String>();

      public void syncMethod(String email) {

        String lock = lockMap.get(email);
        if(lock==null) {
            lock = UUID.randomUUID().toString();
            lockMap.put(email, lock);
        }   

        synchronized(lock.intern()) {
                //do your sync code here
        }
    }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top