2013-05-30 13 views
5

Ich möchte Methodenaufrufe auf Basis einer ID synchronisieren, d. H. So etwas wie eine Nebenläufigkeit Decorator einer bestimmten Objektinstanz.
Zum Beispiel:
Alle Threads, die die Methode mit dem Parameter "id1" aufrufen, sollten seriell zueinander ausgeführt werden.
Alle übrigen, die die Methode mit unterschiedlichem Argument aufrufen, sagen "id2", sollten parallel zu den Threads ausgeführt werden, die die Methode mit dem Parameter "id1" aufrufen, aber wiederum seriell zueinander.Feinkörnige Synchronisation/Sperren von Methodenaufrufen auf der Grundlage von Methodenparametern

Also in meinen Gedanken kann dies implementiert werden, indem eine Sperre (http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLock.html) Instanz pro solche Methode param. Jedes Mal, wenn die Methode mit dem Parameter aufgerufen wird, wird die dem spezifischen Parameterwert (z. B. "id1") entsprechende Sperrinstanz nachgeschlagen, und der aktuelle Thread versucht, die Sperre zu erhalten.

in Code Sprechen:

public class ConcurrentPolicyWrapperImpl implements Foo { 

private Foo delegate; 

/** 
* Holds the monitor objects used for synchronization. 
*/ 
private Map<String, Lock> concurrentPolicyMap = Collections.synchronizedMap(new HashMap<String, Lock>()); 

/** 
* Here we decorate the call to the wrapped instance with a synchronization policy. 
*/ 
@Override 
public Object callFooDelegateMethod (String id) { 
     Lock lock = getLock(id); 
     lock.lock(); 
     try { 
      return delegate.delegateMethod(id); 
     } finally { 
      lock.unlock(); 
     } 
} 


protected Lock getLock(String id) { 
     Lock lock = concurrentPolicyMap.get(id); 

     if (lock == null) { 
      lock = createLock(); 
      concurrentPolicyMap.put(id, lock); 

     } 
     return lock; 
    } 

} 

protected Lock createLock() { 
     return new ReentrantLock(); 
    } 

Es scheint, dass das funktioniert - ich mit jmeter einige Performance-Tests haben und so weiter. Dennoch, wie wir alle wissen, Parallelität in Java ist eine knifflige Sache, habe ich beschlossen, um Ihre Meinung hier zu fragen.

Ich kann nicht aufhören zu denken, dass es einen besseren Weg geben könnte, dies zu erreichen. Zum Beispiel mit einer der BlockingQueue-Implementierungen. Was denken Sie?

Ich kann auch nicht wirklich sicher entscheiden, ob es ein mögliches Synchronisationsproblem mit der Sperre gibt, d. H. Die protected Lock getLock(String id) Methode. Ich benutze eine synchronisierte Sammlung, aber ist das genug? I.e. sollte es nicht etwas sein, das wie folgt statt dessen, was ich gerade habe:

protected Lock getLock(String id) { 
    synchronized(concurrentPolicyMap) { 
    Lock lock = concurrentPolicyMap.get(id); 

    if (lock == null) { 
     lock = createLock(); 
     concurrentPolicyMap.put(id, lock); 

    } 
    return lock; 
    } 

} 

Also was denkst du?

Antwort

3

Abgesehen von Problemen bei der Erstellung von Sperren ist das Muster OK, außer dass Sie möglicherweise eine unbegrenzte Anzahl von Sperren haben. Im Allgemeinen vermeiden Menschen dies, indem sie eine Striped-Sperre erstellen/verwenden. Es gibt eine gute/einfache Implementierung in der Guava-Bibliothek.

Application area of lock-striping

How to acquire a lock by a key

http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/Striped.html

Beispielcode mit Guave Implementierung:

private Striped<Lock> STRIPPED_LOCK = Striped.lock(64); 

public static void doActualWork(int id) throws InterruptedException { 
    try { 
     STRIPPED_LOCK.get(id).lock(); 
     ... 
    } finally { 
     STRIPPED_LOCK.get(id).unlock(); 
    } 
} 
+0

Sie sagen "Sperren Erstellungsfragen beiseite" ... Finden Sie irgendwelche oder meinen Sie, dass Sie nicht kommentieren? – Svilen

+0

Wenn ich nun genauer hinschaue ... ja, dann würden Sie definitiv den synchronisierten Block in Ihrer Methode getLock() brauchen. Wie du es im zweiten Beispiel hast. Ansonsten rufen image (a) 5 Threads gleichzeitig getLock() für die gleiche ID auf; (b) sie alle finden die Sperre null; (c) sie alle erstellen und geben eine neue Sperrinstanz zurück. Scheitern. Der Synchronisierungsblock in Ihrem zweiten Bitcode vermeidet dies. – Keith

+0

Danke, Keith. Übrigens, haben Sie vielleicht eine Idee, wie Sie das Gleiche machen können, aber mit Vollstreckern/Aufgaben. I.e. einen einzelnen ThreadPoolExecutor haben, aber die Task-Ausführung durch einen Schlüssel partitionieren. Zum Beispiel werden Aufgaben für den Schlüssel "id1" in serieller Weise zueinander ausgeführt, während gleichzeitig parallel zu allen anderen Aufgaben für Schlüssel "id2", "id3", etc ... – Svilen

1

Obwohl ich persönlich würde Striped<Lock> Ansatz von Keith vorgeschlagen Guava Bevorzugen, nur für die Diskussion & Vollständigkeit, Ich würde Ich möchte darauf hinweisen, dass die Verwendung eines Dynamic Proxy oder der allgemeineren AOP (Aspect Oriented Programming) ein Ansatz ist.

So würden wir eine IStripedConcurrencyAware Schnittstelle definieren, die als „so etwas wie eine Gleichzeitigkeit Decorator“ dienen würden, die Sie wünschen, und das Dynamic Proxy/AOP Methode Hijacking auf dieser Schnittstelle basieren würde den Methodenaufruf in die entsprechenden Executor demultiplexen/Thread.

Ich persönlich mag AOP (oder die meisten von Spring, für die Angelegenheit), weil es die Was-Sie-sehen-ist-was-Sie-erhalten Einfachheit von Core Java, aber YMMV.

Verwandte Themen