2013-05-30 5 views
5

Desidero sincronizzare le chiamate di metodo sulla base di un ID, ad esempio un decoratore di concorrenza di una determinata istanza di oggetto.
Ad esempio:
Tutti i thread che chiamano il metodo con param "id1", devono essere eseguiti in serie l'un l'altro.
Tutti gli altri, che chiamano il metodo con argomenti diversi, ad esempio "id2", devono essere eseguiti in parallelo ai thread che chiamano il metodo con param "id1", ma di nuovo in serie tra loro.Sincronizzazione fine/blocco delle chiamate di metodo basate sui parametri del metodo

Quindi nella mia mente questo può essere implementato avendo un'istanza di blocco (http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLock.html) per tale parametro param. Ogni volta che il metodo viene chiamato con il parametro, l'istanza di blocco corrispondente al valore del parametro specifico (ad esempio "id1") verrà cercata e il thread corrente cercherà di ottenere il blocco.

Parlando in codice:

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(); 
    } 

Sembra che questo funziona - ho fatto qualche test delle prestazioni con JMeter e così via. Ancora, come tutti sappiamo che la concorrenza in Java è una cosa complicata, ho deciso di chiedere la tua opinione qui.

Non riesco a smettere di pensare che potrebbe esserci un modo migliore per farlo. Ad esempio, utilizzando una delle implementazioni di BlockingQueue. Cosa ne pensi?

Inoltre non posso davvero decidere con certezza se c'è un potenziale problema di sincronizzazione con il blocco, ad esempio il metodo protected Lock getLock(String id). Sto usando una collezione sincronizzata, ma è abbastanza? Cioè non dovrebbe essere qualcosa di simile al seguente invece di quello che attualmente ho:

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

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

    } 
    return lock; 
    } 

} 

Allora, cosa ne pensate voi ragazzi?

risposta

3

Problemi di creazione blocco a parte, il modello è OK tranne che si può avere un numero illimitato di blocchi. Generalmente le persone lo evitano creando/usando un blocco a strisce. C'è una buona/semplice implementazione nella libreria guava.

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

codice di esempio di implementazione utilizzando guava:

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

Stai dicendo "Blocca i problemi di creazione da parte" ... Hai notato qualcosa o intendi che non stai commentando su di esso? – Svilen

+0

Guardando il problema più da vicino ora - sì, avresti sicuramente bisogno del blocco sincronizzato nel tuo metodo getLock(). Come hai nel secondo esempio. Altrimenti, l'immagine (a) 5 thread chiama getLock() allo stesso tempo, per lo stesso ID; (b) trovano tutti il ​​blocco nullo; (c) tutti creano e restituiscono una nuova istanza di blocco. Fallire. Il blocco di sincronizzazione nel secondo bit di codice lo evita. – Keith

+0

Grazie, Keith. A proposito, hai qualche idea su come fare la cosa simile ma con esecutori/compiti. Cioè avere un singolo ThreadPoolExecutor ma eseguire l'esecuzione di una partizione con una chiave. Ad esempio, le attività per la chiave "id1" verranno eseguite in modo seriale tra loro, mentre allo stesso tempo in parallelo a tutte le altre attività per le chiavi "id2", "id3", ecc ... – Svilen

1

Anche se io personalmente preferisco l'approccio di Guava Striped<Lock> suggerito da Keith, proprio per la discussione & completezza, mi piacerebbe piace sottolineare che l'utilizzo di un Proxy dinamico, o il più generico AOP (Aspect Oriented Programming), è un approccio.

così avremmo definire un'interfaccia IStripedConcurrencyAware che sarebbe servito come il "qualcosa di simile a un decoratore concorrenza" che si desidera, e il metodo di dirottamento Dinamico Proxy/AOP sulla base di questa interfaccia sarebbe de-multiplex la chiamata al metodo nella appropriata Executor/Thread.

Personalmente non amo AOP (o la maggior parte di Spring, se è per questo) perché rompe la semplicità what-you-see-is-what-get di Core Java, ma YMMV.