2013-03-13 21 views
8

Jeśli mam klasy Java zdefiniowane poniżej, które wstrzykuje się w mojej aplikacji internetowych poprzez wstrzyknięcie zależności:Wiosna Singleton Bezpieczeństwo wątków

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

Jestem zaniepokojony bezpieczeństwa wątków. Czy struktura Spring nie obsługuje przypadków, w których jedno żądanie czyta z listy i jest obecnie aktualizowane przez inne? Użyłem blokad odczytu/zapisu w innych aplikacjach, ale nigdy wcześniej nie myślałem o takim przypadku jak powyżej.

Planowałem użyć komponentu bean jako singletonu, aby zmniejszyć obciążenie bazy danych.

Nawiasem mówiąc, jest to kontynuacja się z poniższym pytaniu:

Java Memory Storage to Reduce Database Load - Safe?

EDIT:

Więc kod jak to rozwiązać ten problem:

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

Odpowiedz

13

Wiosenna struktura nie robi nic pod maską, jeśli chodzi o wielowątkowe zachowanie pojedynczej fasoli. Obowiązkiem programisty jest rozwiązywanie problemów związanych z współbieżnością i bezpieczeństwem pojedynczego komponentu bean pojedynczej.

Sugerowałbym przeczytaniu poniższego artykułu: Spring Singleton, Request, Session Beans and Thread Safety

1

jako pojedyncza i niezsynchronizowana, Spring pozwoli dowolnej liczbie wątków na równoczesne wywoływanie isActiveAccount i refreshAccounts. Tak więc żadna klasa nie będzie bezpieczna dla wątków i nie zmniejszy obciążenia bazy danych.

+0

Ok, obserwacji dla zaakceptowania: Czy to naprawić łatwo poprzez kod zawarte w tej klasie Java (lub kontekst aplikacji poprawek), albo ja lepiej iść na rozwiązanie buforowanie/bazy danych? – thatidiotguy

+0

Co można zrobić, to użyć tymczasowej listy, aby wywołać bazę danych w 'refreshAccounts()'. Kiedy to się zwróci, zsynchronizuj na 'kontach' i ponownie przypisz do tej listy. –

+0

Powiedziałbym zdecydowanie buforowanie/bazy danych. Samo zarządzanie współbieżnością jest trudne. Dzięki buforowaniu możesz przynajmniej zapamiętywać kontrolę współbieżności.Jeśli naprawdę chciałeś dowolnej liczby żądań, zadeklarowałbym zasięg = prototyp. Następnie napotkasz problem z obciążeniem, którego dotyczyłeś. –

2

Można prosić o wyjaśnienie mojego initial answer. Spring nie synchronizuje dostępu do komponentu bean. Jeśli masz komponent bean w domyślnym zasięgu (singleton), będzie tylko jeden obiekt dla tego komponentu, a wszystkie współbieżne żądania będą uzyskiwać dostęp do tego obiektu, wymagając tego obiektu do bezpiecznego wątku.

Większość fasoli szparagów nie ma stanu zmiennego i jako takie są trywialnie bezpieczne dla wątków. Twój komponent bean ma zmienny stan, więc musisz upewnić się, że żaden wątek nie wyświetla listy kont, które aktualnie tworzy inny wątek.

Najprostszym sposobem, aby to zrobić, jest utworzenie konta w polu volatile. Oznacza to, że przypisujesz nową listę do pola po jej wypełnieniu (tak jak się wydaje).

private volatile List<Accounts> accounts; 
+0

Przepraszam, poczułem, że byłoby to nieszczere w tytule drugiego pytania, aby dostać się do tego w komentarzach. To naprawdę odrębny problem. W jaki sposób lotny roztwór porównuje się do edytowanego kodu, który umieściłem powyżej? – thatidiotguy

+0

Jest to prostsze, wolniejsze od oczekiwań i prawdopodobnie nieco bardziej wydajne niż jawna blokada (choć ta różnica jest pomijalna przy porównywaniu jej z I/O do bazy danych). – meriton

0

Mamy wiele takich metadanych i mamy uruchomionych 11 węzłów. na każdym węźle aplikacji mamy mapy statyczne dla takich danych, więc tylko dla jednej instancji, init z db przy uruchamianiu raz poza godzinami szczytu każdego dnia lub gdy osoba wspierająca uruchamia je. mieć prosty interfejs API oparty na http postach, aby przesyłać aktualizacje z jednego węzła do innych dla niektórych danych, które wymagają aktualizacji w czasie rzeczywistym.

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(); 
    } 
} 
Powiązane problemy