2013-03-13 8 views
8

Wenn ich eine Java-Klasse unterhalb definiert haben über Dependency Injection in meiner Web-Anwendung injiziert:Frühling Singleton Threadsicherheit

public AccountDao 
{ 
    private NamedParameterJdbcTemplate njt; 
    private List<Account> accounts; 

    public AccountDao(Datasource ds) 
    { 
     this.njt = new NamedParameterJdbcTemplate(ds); 
     refreshAccounts(); 
    } 

    /*called at creation, and then via API calls to inform service new users have 
    been added to the database by a separate program*/ 
    public void refreshAccounts() 
    { 
     this.accounts = /*call to database to get list of accounts*/ 
    } 

    //called by every request to web service 
    public boolean isActiveAccount(String accountId) 
    { 
     Account a = map.get(accountId); 
     return a == null ? false : a.isActive(); 
    } 
} 

Ich bin besorgt über die Thread-Sicherheit. Kann das Spring-Framework keine Fälle behandeln, in denen eine Anfrage von der Liste gelesen wird und sie gerade von einer anderen aktualisiert wird? Ich habe zuvor in anderen Anwendungen Lese-/Schreibsperren verwendet, aber ich habe nie zuvor über einen Fall wie oben nachgedacht.

Ich habe geplant, die Bohne als Singleton zur Verwendung, so konnte ich die Datenbanklast reduzieren.

By the way, ist dies ein Follow-up der folgenden Frage:

Java Memory Storage to Reduce Database Load - Safe?

EDIT:

So wie dieser Code würde dieses Problem lösen:

/*called at creation, and then via API calls to inform service new users have 
     been added to the database by a separate program*/ 
     public void refreshAccounts() 
     { 
      //java.util.concurrent.locks.Lock 
      final Lock w = lock.writeLock(); 
      w.lock(); 
      try{ 
       this.accounts = /*call to database to get list of accounts*/ 
      } 
      finally{ 
      w.unlock(); 
      } 
     } 

     //called by every request to web service 
     public boolean isActiveAccount(String accountId) 
     { 
      final Lock r = lock.readLock(); 
      r.lock(); 

      try{ 
       Account a = map.get(accountId); 
      } 
      finally{ 
       r.unlock(); 
      } 
      return a == null ? false : a.isActive(); 
     } 

Antwort

13

Spring Framework tut nichts unter dem Deckmantel in Bezug auf das Multithread-Verhalten einer Singleton-Bean. Es liegt in der Verantwortung des Entwicklers, das Problem der Parallelität und Thread-Sicherheit der Singleton-Bean zu behandeln.

Ich würde vorschlagen, die unter Artikel zu lesen: Spring Singleton, Request, Session Beans and Thread Safety

1

Wie Ein Singleton und nicht synchronisiert, ermöglicht Spring eine beliebige Anzahl von Threads gleichzeitig isActiveAccount und refreshAccounts aufrufen. Also, nein, diese Klasse wird nicht Thread-sicher und wird die Datenbanklast nicht reduzieren.

+0

Ok, Follow-up für die übernehmen: Ist das fixierbare leicht über den Code in dieser Java Klasse enthalten sind (oder Anwendungskontext Fixes), oder bin ich besser dran, für eine Zwischenspeicherung/Datenbanklösung geht? – thatidiotguy

+0

Sie können eine temporäre Liste verwenden, um den Aufruf der Datenbank in 'refreshAccounts()' durchzuführen. Wenn dies der Fall ist, synchronisieren Sie sie auf "accounts" und weisen Sie sie dieser Liste zu. –

+0

würde ich definitiv Caching/Datenbank sagen. Die Nebenläufigkeit selbst zu verwalten ist schwierig. Mit dem Caching können Sie sich zumindest die Gleichzeitigkeitskontrolle merken.Wenn Sie wirklich eine beliebige Anzahl von Anfragen haben wollten, würde ich den scope = prototype angeben. Dann stoßen Sie auf das Ladungsproblem, um das Sie besorgt waren. –

2

Sie bat um Klärung meiner initial answer haben könnte. Spring synchronisiert den Zugriff auf eine Bean nicht. Wenn Sie eine Bean im Standardbereich (Singleton) haben, gibt es nur ein einzelnes Objekt für diese Bean, und alle gleichzeitig ablaufenden Anfragen greifen auf dieses Objekt zu und benötigen dieses Objekt für den Thread-Safe.

Die meisten Frühjahr Bohnen haben keine wandelbaren Zustand, und als solche sind trivialerweise Thread-sicher. Ihre Bean hat einen änderbaren Status. Sie müssen also sicherstellen, dass kein Thread eine Liste der Konten anzeigt, die der andere Thread gerade erstellt.

Der einfachste Weg, dies zu tun, ist das Konto Feld volatile. Das setzt voraus, dass Sie die neue Liste dem Feld zuweisen, nachdem Sie es ausgefüllt haben (wie Sie scheinen).

private volatile List<Accounts> accounts; 
+0

Es tut mir leid, ich fühlte, dass es unaufrichtig wäre, um den Titel der anderen Frage in die Kommentare zu kommen. Es ist wirklich ein separates Problem. Wie vergleicht sich die flüchtige Lösung mit dem oben angegebenen editierten Code? – thatidiotguy

+0

Es ist einfacher, wartefrei und wahrscheinlich ein bisschen effizienter als eine explizite Sperre (obwohl dieser Unterschied vernachlässigbar ist, wenn er mit I/O zu einer Datenbank verglichen wird). – meriton

0

Wir haben viele solche Metadaten und haben 11 Knoten laufen. auf jedem App-Knoten haben wir statische Karten für solche Daten, also nur eine Instanz, init von db beim Start einmal außerhalb der Hauptverkehrszeit jeden Tag oder wenn die Support-Person sie auslöst. haben eine interal einfache http Post-basierte API, um Updates von 1 Knoten an andere für einige der Daten, die wir Updates in Echtzeit benötigen, zu senden.

public AccountDao 
{ 
    private static List<Account> accounts; 
    private static List<String> activeAccounts; 
    private NamedParameterJdbcTemplate njt; 

    static { 
     try{ 
     refreshAccounts(); 
     }catch(Exception e){ 
     //log but do not throw. any uncaught exceptions in static means your class is un-usable 
     } 
    } 


    public AccountDao(Datasource ds) 
    { 
     this.njt = new NamedParameterJdbcTemplate(ds); 
     //refreshAccounts(); 
    } 

    /*called at creation, and then via API calls to inform service new users have 
    been added to the database by a separate program*/ 
    public void refreshAccounts() 
    { 
     this.accounts = /*call to database to get list of accounts*/ 
    } 

    public void addAccount(Account acEditedOrAdded) 
    { 
     //add or reove from map onr row 
     //can be called from this node or other node 
     //meaning if you have 2 nodes, keep IP port of each or use a internal web service or the like to tell 
     //node B when a account id added or changed in node A ... 
    } 

    //called by every request to web service 
    public static boolean isActiveAccount(String accountId) 
    { 
     Account a = map.get(accountId); 
     return a == null ? false : a.isActive(); 
    } 
}