2013-03-13 61 views
8

如果我有低於定義的Java類是在我的web應用程序通過依賴注入注入:春辛格爾頓線程安全

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

我關心線程安全。 Spring框架是否不處理一個請求正在從列表中讀取並且當前正在被另一個請求更新的情況?我以前在其他應用程序中使用過讀/寫鎖,但我從未想過像上述那樣的情況。

我打算將bean作爲單例使用,這樣可以減少數據庫負載。

順便說一句,這是下面問題的追問:

Java Memory Storage to Reduce Database Load - Safe?

編輯:

所以會像這樣的代碼解決這個問題:

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

回答

1

由於一個單例和非同步的Spring將允許任意數量的線程同時調用isActiveAccountrefreshAccounts。所以,這個類不會是線程安全的,並且不會減少數據庫負載。

+0

好了,跟進了接受:這是可以解決的容易通過包含在這個Java類(或應用程序上下文修復)代碼,還是我最好去一個緩存/數據庫解決方案? – thatidiotguy 2013-03-13 21:22:26

+0

你可以做的是使用一個臨時列表來調用'refreshAccounts()'中的數據庫。當它返回時,在'accounts'同步並將其重新分配給該列表。 – 2013-03-13 21:28:56

+0

我會說肯定緩存/數據庫。自己管理併發很困難。通過緩存,您至少可以記住併發控制。如果你真的想要任何數量的請求,我會聲明範圍=原型。然後你遇到你所關心的負載問題。 – 2013-03-13 21:32:10

2

你可以要求澄清我initial answer。 Spring不會同步對bean的訪問。如果你在默認範圍(singleton)中有一個bean,那麼這個bean只有一個對象,並且所有的併發請求都會訪問這個對象,要求這個對象對線程安全。

大多數春季豆類都沒有可變狀態,因此平凡線程安全。您的bean具有可變狀態,所以您需要確保沒有線程看到其他線程當前正在組裝的帳戶列表。

最簡單的方法是使帳戶字段volatile。假設您在填充新列表之後將新列表分配給該字段(如您所看到的那樣)。

private volatile List<Accounts> accounts; 
+0

對不起,我覺得在評論中對另一個問題的標題是不真誠的。這確實是一個單獨的問題。易變的解決方案與我上面編輯的代碼相比如何? – thatidiotguy 2013-03-13 21:32:54

+0

它比簡單的,無等待的並且可能比顯式鎖定效率更高(儘管與I/O與數據庫進行比較時該差異可以忽略不計)。 – meriton 2013-03-14 00:24:09

0

我們有很多這樣的元數據,並且有11個節點在運行。在每個應用程序節點上,我們都有這樣的數據的靜態映射,所以它只有一個實例,每天在非高峯時段啓動一次,或者在支持人員觸發它時啓動。有一個簡單的基於http post的API,可以將來自1節點的更新發送給其他人,以便我們實時更新一些數據。

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