2011-02-02 16 views
9

Desidero chiedere quale potrebbe essere la soluzione migliore per l'applicazione multithread Java per garantire che tutti i thread accedano in modo sincrono db. Ad esempio, ogni thread rappresenta una transazione separata, e prima controlla db per il valore e quindi a seconda della risposta deve inserire o aggiornare alcuni campi nel database (nota tra check, insert e commit l'applicazione sta facendo altre elaborazioni). Ma il problema è che un altro thread potrebbe fare la stessa cosa sullo stesso tavolo. Esempio più specifico. Thread T1 avvia la transazione, quindi controlla la tabella ENTITY_TABLE per l'immissione con il codice '111' se trova la data di aggiornamento, se non trova inserisce una nuova voce, quindi esegue la transazione. Ora immagina che il thread T2 faccia esattamente la stessa cosa. Ora ci sono pochi problemi: 1. T1 e T2 controllano db e non trovano nulla ed entrambi inseriscono la stessa voce. 2. T1 controlla db, trova la voce con la vecchia data, ma sul commit T2 ha già aggiornato la voce a una data più recente. 3. Se utilizziamo la cache e sincronizziamo l'accesso alla cache abbiamo un problema: T1 acquisisce i controlli di blocco db e cache se non trovati aggiungono alla cache, rilascia il blocco, commettono. T2 fa lo stesso, trova la voce nella cache andando a commit. Ma la transazione T1 fallisce e viene ripristinata. Ora T2 è in cattive condizioni, perché dovrebbe essere inserito in ENTITY_TABLE ma non lo sa. 4. di più?Accesso al database con più thread Java

Sto lavorando alla creazione di cache personalizzata semplice con sincronizzazione e risoluzione del problema 3. Ma mi interessa forse c'è qualche soluzione più semplice? Qualcuno ha dovuto risolvere un problema simile? Come l'hai fatto?

risposta

7

Questo deve essere gestito principalmente all'interno del DB, configurando il livello transaction isolation desiderato. Quindi, in cima a questo, è necessario selezionare la strategia di blocco (optimistic o pessimista).

Senza isolamento della transazione, sarà difficile provare a garantire l'integrità delle transazioni esclusivamente nel dominio Java. Soprattutto considerando che anche se al momento il DB è accessibile solo dalla tua app Java, questo potrebbe cambiare in futuro.

Ora, per quanto riguarda il livello di isolamento da scegliere, potrebbe sembrare che sia necessario il livello di isolamento più alto, serializzabile. Tuttavia, nella pratica questo tende ad essere un vero maiale performante a causa del blocco esteso. Quindi potresti voler rivalutare le tue esigenze per trovare il miglior equilibrio tra isolamento e prestazioni per la tua situazione specifica.

+0

Grazie per una risposta. La cosa è che l'applicazione deve essere molto performante, potrebbe funzionare su 16 o forse anche 32 thread. E ognuno avvia la transazione, esegue molta elaborazione, quindi acquisisce risultati e inserisce inserti e aggiornamenti in fase di commit, tutto in lotti jdbc (per prestazioni). Quindi la transazione è impegnata. Comunque nella mia situazione il livello serializzabile sarebbe eccessivo soprattutto per 32 thread. Spero di essere in grado di risolvere questo problema con la cache nell'applicazione Java. – nesvarbu

1

Esaurito, penso che avresti dovuto bloccare il tavolo prima di interrogarlo. Ciò imporrà l'operazione sequenziale dei tuoi thread. Le discussioni dovrebbero quindi essere preparate al fatto che dovranno attendere il blocco e, naturalmente, l'acquisizione del blocco potrebbe scadere. Questo potrebbe introdurre un collo di bottiglia nell'applicazione e tutti i thread dovranno accodarsi per le risorse del database.

1

Il problema riscontrato è transaction isolation.

Sembra che sia necessario che ogni thread blocchi la riga in questione nella clausola where, che richiede l'isolamento serializzabile.

1

Perché stai cercando di reinventare la ruota? Suggerirei di utilizzare un framework OR mapper per l'accesso al database con le transazioni (ad esempio un implementatore di specifiche JPA come Hibernate o Eclipselink). Puoi anche aggiungere DAO Spring che gestisce le transazioni per te. Quindi puoi concentrarti sulla logica di business e non devi preoccuparti di cose di basso livello.

2

Se si desidera che SQL SELEZIONA una riga da un database e successivamente UPDATE la stessa riga, si dispone di 2 scelte come sviluppatore Java.

  1. SELECT con ROWLOCK, o qualsiasi altra cosa la sintassi di blocco fila è per il vostro particolare banca dati.

  2. Selezionare la riga, fare la tua elaborazione, e poco prima si è pronti a aggiornamento, selezionare di nuovo la fila per vedere se qualsiasi altro thread apportato modifiche. Se i due SELETTIVI restituiscono gli stessi valori , AGGIORNA. In caso contrario, genera un errore o esegui nuovamente l'elaborazione.

2

Sono caduto in questo problema quando si lavora con un programma Java multi-thread che utilizzava un database Sqllite. Usa il blocco dei file quindi dovevo assicurarmi che solo un thread stesse lavorando allo stesso tempo.

Fondamentalmente ho finito con l'utilizzo sincronizzato. Quando ConnectionFactory restituisce una connessione db, restituisce anche un oggetto di blocco che si dovrebbe bloccare quando si utilizza la connessione. Così si potrebbe fare di blocco della sincronizzazione manualmente, o creare una sottoclasse della classe di sotto del quale lo fa per te:

/** 
    * Subclass this class and implement the persistInTransaction method to perform 
    * an update to the database. 
    */ 
public abstract class DBOperationInTransaction { 

    protected Logger logger = Logger.getLogger(DBOperationInTransaction.class.getName()); 

    public DBOperationInTransaction(ConnectionFactory connectionFactory) { 
     DBConnection con = null; 

     try { 
      con = connectionFactory.getConnection(); 

      if(con == null) { 
       logger.log(Level.SEVERE, "Could not get db connection"); 
       throw new RuntimException("Could not get db connection"); 
      } 

      synchronized (con.activityLock) { 
       con.connection.setAutoCommit(false); 
       persistInTransaction(con.connection); 
       con.connection.commit(); 
      } 

     } catch (Exception e) { 
      logger.log(Level.SEVERE, "Failed to persist data:", e); 
      throw new RuntimeException(e); 
     } finally { 
      if(con != null) { 
       //Close con.connection silently. 
      } 
     } 
    } 

    /** 
     * Method for persisting data within a transaction. If any SQLExceptions 
     * occur they are logged and the transaction is rolled back. 
     * 
     * In the scope of the method there is a logger object available that any 
     * errors/warnings besides sqlException that you want to log. 
     * 
     * @param con 
     *   Connection ready for use, do not do any transaction handling 
     *   on this object. 
     * @throws SQLException 
     *    Any SQL exception that your code might throw. These errors 
     *    are logged. Any exception will rollback the transaction. 
     * 
     */ 
    abstract protected void persistInTransaction(Connection con) throws SQLException; 

} 

E la struct DBConnection:

final public class DBConnection { 
    public final Connection connection; 
    public final String activityLock; 

    public DBConnection(Connection connection, String activityLock) { 
     this.connection = connection; 
     this.activityLock = activityLock; 
    } 

} 
Problemi correlati