Domanda

Sto scrivendo alcuni client-server-applicazioni in cui ho a che fare con più thread. Ho alcuni server, che inviano vivi-pacchetti ogni pochi secondi. Tali server sono mantenuti in una ConcurrentHashMap, che contiene gli endpoint in coppia con il tempo l'ultimo viva-pacco è arrivato del rispettivo server.

Ora ho un filo, che deve "risolvere" tutti i server che non hanno inviato vivi-pacchetti per un determinato periodo di tempo.

mi sa che non posso farlo così, posso?

for( IPEndPoint server : this.fileservers.keySet() )
{
    Long time = this.fileservers.get( server );

    //If server's time is updated here, I got a problem

    if( time > fileserverTimeout )
        this.fileservers.remove( server );
}

C'è un modo per aggirare questo senza acquisendo un blocco per l'intero ciclo (che poi devono rispettare negli altri thread così)?

È stato utile?

Soluzione

Probabilmente non esiste un problema qui, a seconda di cosa esattamente si memorizza nella mappa. Il tuo codice sembra un po 'strano per me, dal momento che sembra di salvare "la durata per cui il server non è stato attivo".

La mia prima idea per la registrazione che i dati è stato quello di conservare "l'ultimo timestamp in cui il server è stato attivo". Poi il codice sarebbe simile a questa:

package so3950354;

import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class ServerManager {

  private final ConcurrentMap<Server, Long> lastActive = new ConcurrentHashMap<Server, Long>();

  /** May be overridden by a special method for testing. */
  protected long now() {
    return System.currentTimeMillis();
  }

  public void markActive(Server server) {
    lastActive.put(server, Long.valueOf(now()));
  }

  public void removeInactive(long timeoutMillis) {
    final long now = now();

    Iterator<Map.Entry<Server, Long>> it = lastActive.entrySet().iterator();
    while (it.hasNext()) {
      final Map.Entry<Server, Long> entry = it.next();
      final long backThen = entry.getValue().longValue();
      /*
       * Even if some other code updates the timestamp of this server now,
       * the server had timed out at some point in time, so it may be
       * removed. It's bad luck, but impossible to avoid.
       */
      if (now - backThen >= timeoutMillis) {
        it.remove();
      }
    }
  }

  static class Server {

  }
}

Se si vuole davvero evitare che nessun codice mai chiama markActive durante una chiamata a removeInactive, non v'è alcun modo per aggirare chiusura esplicito. Che probabilmente si desidera è:

  • chiamate simultanee a markActive sono ammessi.
  • durante markActive sono ammessi nessuna chiamata a removeInactive.
  • durante removeInactive sono ammessi nessuna chiamata a markActive.

Questo appare come un tipico scenario per un ReadWriteLock, dove markActive è l'operazione "lettura" e removeInactive è la "scrittura" Operation.

Altri suggerimenti

non vedo come un altro thread può aggiornare l'ora del server a quel punto nel codice. Una volta recuperato il tempo di un server dalla mappa utilizzando this.fileservers.get( server ), un altro thread non può cambiare il suo valore come oggetti lunghi sono immutabili. Sì, un altro thread può mettere un nuovo oggetto a lungo per quel server nella mappa, ma che non pregiudica questa discussione, perché è già recuperato il tempo del server.

Così così com'è non riesco a vedere niente di sbagliato con il codice. Iteratori in una ConcurrentHashMap sono debolmente coerente i quali mezzi possono tollerare modifica simultanea, quindi non c'è rischio di essere gettato ConcurrentModificationException sia.

(vedi risposta di Roland, che prende le idee qui e li dà corpo in un esempio più completo, con alcune grandi intuizioni aggiuntive.)

Dal momento che è una mappa hash concorrente, è possibile effettuare le seguenti operazioni. Si noti che iteratori di CHM tutte implementare i metodi opzionali, tra cui remove(), che volete. Vedere CHM Documentazione API , in cui si afferma:

  

Questa classe ed i suoi punti di vista e iteratori   implementare tutti i metodi opzionali   delle interfacce Map e Iterator.

Questo codice dovrebbe funzionare (non so il tipo di Key nel vostro CHM):

ConcurrentHashMap<K,Long> fileservers = ...;

for(Iterator<Map.Entry<K,Long>> fsIter = fileservers.entrySet().iterator(); fileservers.hasNext(); )
{
    Map.Entry<K,Long> thisEntry = fsIter.next();
    Long time = thisEntry.getValue();

    if( time > fileserverTimeout )
        fsIter.remove( server );
}

Ma si noti che ci possono essere condizioni di gara altrove ... È necessario fare in modo che altri pezzi di codice di accesso a mappa può far fronte a questo tipo di rimozione spontanea - vale a dire, probabilmente, dovunque si tocca fileservers.put() avrete bisogno di un 'di logica coinvolgono fileservers.putIfAbsent(). Questa soluzione è meno probabile di creare colli di bottiglia che utilizza synchronized, ma richiede anche un po 'più pensato.

Dove hai scritto "Se il tempo del server viene aggiornato qui, ho avuto un problema" è esattamente dove putIfAbsent() entra in gioco. Se la voce è assente, o si non si era visto prima, o semplicemente recentemente abbandonato dalla tabella . Se i due lati di questo bisogno di essere coordinati, allora si può invece vuole introdurre un record con serratura per la voce, ed effettuare la sincronizzazione a quel livello (ad esempio, la sincronizzazione sul record mentre si fa remove(), piuttosto che l'intera tabella ). Poi la fine put() delle cose può anche sincronizzazione sul lo stesso record, eliminando una potenziale gara.

In primo luogo far mappa sincronizzata

this.fileservers = Collections.synchronizedMap(Map)

quindi usare la strategia che viene utilizzato nelle classi Singleton

if( time > fileserverTimeout )
    {
        synchronized(this.fileservers)
        {
             if( time > fileserverTimeout )
                  this.fileservers.remove( server );
        }
    }

Ora, questo fa in modo che una volta che all'interno del blocco sincronizzato, possono verificarsi aggiornamenti. Questo perché una volta presa la serratura della carta, carta (involucro sincronizzato) non si avrà a disposizione per realizzare una serratura filo su di esso per aggiornare, rimuovere etc.

Verifica del tempo rende due volte in modo che la sincronizzazione viene utilizzato solo quando c'è un caso vero e proprio di eliminazione

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top