一、Spring單例模式與線程安全

          http://www.cnblogs.com/doit8791/p/4093808.html
          Spring框架里的bean,或者說組件,獲取實例的時候都是默認的單例模式,這是在多線程開發(fā)的時候要尤其注意的地方。

           http://www.cnblogs.com/hoojo/archive/2011/05/05/2038101.html

          單例模式的意思就是只有一個實例。單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例。這個類稱為單例類。
          當多用戶同時請求一個服務時,容器會給每一個請求分配一個線程,這是多個線程會并發(fā)執(zhí)行該請求多對應的業(yè)務邏輯(成員方法),此時就要注意了,如果該處理邏輯中有對該單列狀態(tài)的修改(體現(xiàn)為該單列的成員屬性),則必須考慮線程同步問題
          同步機制的比較  ThreadLocal和線程同步機制相比有什么優(yōu)勢呢?ThreadLocal和線程同步機制都是為了解決多線程中相同變量的訪問沖突問題。 
            在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析什么時候?qū)ψ兞窟M行讀寫,什么時候需要鎖定某個對象,什么時候釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。 
            而ThreadLocal則從另一個角度來解決多線程的并發(fā)訪問。ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數(shù)據(jù)的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal。 
            由于ThreadLocal中可以持有任何類型的對象,低版本JDK所提供的get()返回的是Object對象,需要強制類型轉(zhuǎn)換。但JDK 5.0通過泛型很好的解決了這個問題,在一定程度地簡化ThreadLocal的使用
           概括起來說,對于多線程資源共享的問題,同步機制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。 
            Spring使用ThreadLocal解決線程安全問題 
            我們知道在一般情況下,只有無狀態(tài)的Bean才可以在多線程環(huán)境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態(tài)采用ThreadLocal進行處理,讓它們也成為線程安全的狀態(tài),因為有狀態(tài)的Bean就可以在多線程中共享了。 
            一般的Web應用劃分為展現(xiàn)層、服務層和持久層三個層次,在不同的層中編寫對應的邏輯,下層通過接口向上層開放功能調(diào)用。在一般情況下,從接收請求到返回響應所經(jīng)過的所有程序調(diào)用都同屬于一個線程
          ThreadLocal是解決線程安全問題一個很好的思路,它通過為每個線程提供一個獨立的變量副本解決了變量并發(fā)訪問的沖突問題。在很多情況下,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單,更方便,且結(jié)果程序擁有更高的并發(fā)性。 
          如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結(jié)果和單線程運行的結(jié)果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。 或者說:一個類或者程序所提供的接口對于線程來說是原子操作或者多個線程之間的切換不會導致該接口的執(zhí)行結(jié)果存在二義性,也就是說我們不用考慮同步的問題。  線程安全問題都是由全局變量及靜態(tài)變量引起的。  
          若每個線程中對全局變量、靜態(tài)變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執(zhí)行寫操作,一般都需要考慮線程同步,否則就可能影響線程安全。
          1) 常量始終是線程安全的,因為只存在讀操作。 
          2)每次調(diào)用方法前都新建一個實例是線程安全的,因為不會訪問共享的資源。
          3)局部變量是線程安全的。因為每執(zhí)行一個方法,都會在獨立的空間創(chuàng)建局部變量,它不是共享的資源。局部變量包括方法的參數(shù)變量和方法內(nèi)變量。
          有狀態(tài)就是有數(shù)據(jù)存儲功能。有狀態(tài)對象(Stateful Bean),就是有實例變量的對象  ,可以保存數(shù)據(jù),是非線程安全的。在不同方法調(diào)用間不保留任何狀態(tài)。
          無狀態(tài)就是一次操作,不能保存數(shù)據(jù)。無狀態(tài)對象(Stateless Bean),就是沒有實例變量的對象  .不能保存數(shù)據(jù),是不變類,是線程安全的。
          有狀態(tài)對象:
          無狀態(tài)的Bean適合用不變模式,技術(shù)就是單例模式,這樣可以共享實例,提高性能有狀態(tài)的Bean,多線程環(huán)境下不安全,那么適合用Prototype原型模式。Prototype: 每次對bean的請求都會創(chuàng)建一個新的bean實例。
          Struts2默認的實現(xiàn)是Prototype模式。也就是每個請求都新生成一個Action實例,所以不存在線程安全問題。需要注意的是,如果由Spring管理action的生命周期, scope要配成prototype作用域。

           

          二、線程安全案例:

          SimpleDateFormat(下面簡稱sdf)類內(nèi)部有一個Calendar對象引用,它用來儲存和這個sdf相關(guān)的日期信息,例如sdf.parse(dateStr), sdf.format(date) 諸如此類的方法參數(shù)傳入的日期相關(guān)String, Date等等, 都是交友Calendar引用來儲存的.這樣就會導致一個問題,如果你的sdf是個static的, 那么多個thread 之間就會共享這個sdf, 同時也是共享這個Calendar引用, 并且, 觀察 sdf.parse() 方法,你會發(fā)現(xiàn)有如下的調(diào)用:
          Date parse() {
            calendar.clear(); // 清理calendar
            ... // 執(zhí)行一些操作, 設置 calendar 的日期什么的
            calendar.getTime(); // 獲取calendar的時間
          }
          這里會導致的問題就是, 如果 線程A 調(diào)用了 sdf.parse(), 并且進行了 calendar.clear()后還未執(zhí)行calendar.getTime()的時候,線程B又調(diào)用了sdf.parse(), 這時候線程B也執(zhí)行了sdf.clear()方法, 這樣就導致線程A的的calendar數(shù)據(jù)被清空了(實際上A,B的同時被清空了). 又或者當 A 執(zhí)行了calendar.clear() 后被掛起, 這時候B 開始調(diào)用sdf.parse()并順利i結(jié)束, 這樣 A 的 calendar內(nèi)存儲的的date 變成了后來B設置的calendar的date
          這個問題背后隱藏著一個更為重要的問題--無狀態(tài):無狀態(tài)方法的好處之一,就是它在各種環(huán)境下,都可以安全的調(diào)用。衡量一個方法是否是有狀態(tài)的,就看它是否改動了其它的東西,比如全局變量,比如實例的字段。format方法在運行過程中改動了SimpleDateFormat的calendar字段,所以,它是有狀態(tài)的。
            這也同時提醒我們在開發(fā)和設計系統(tǒng)的時候注意下一下三點:
            1.自己寫公用類的時候,要對多線程調(diào)用情況下的后果在注釋里進行明確說明
            2.對線程環(huán)境下,對每一個共享的可變變量都要注意其線程安全性
            3.我們的類和方法在做設計的時候,要盡量設計成無狀態(tài)的
           三.解決辦法
            1.需要的時候創(chuàng)建新實例:
            說明:在需要用到SimpleDateFormat 的地方新建一個實例,不管什么時候,將有線程安全問題的對象由共享變?yōu)榫植克接卸寄鼙苊舛嗑€程問題,不過也加重了創(chuàng)建對象的負擔。在一般情況下,這樣其實對性能影響比不是很明顯的。
            2.使用同步:同步SimpleDateFormat對象
          public class DateSyncUtil {
              private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                
              public static String formatDate(Date date)throws ParseException{
                  synchronized(sdf){
                      return sdf.format(date);
                  }  
              }
              
              public static Date parse(String strDate) throws ParseException{
                  synchronized(sdf){
                      return sdf.parse(strDate);
                  }
              } 
          }
            說明:當線程較多時,當一個線程調(diào)用該方法時,其他想要調(diào)用此方法的線程就要block,多線程并發(fā)量大的時候會對性能有一定的影響。
            3.使用ThreadLocal: 
          public class ConcurrentDateUtil {
              private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
                  @Override
                  protected DateFormat initialValue() {
                      return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                  }
              };
              public static Date parse(String dateStr) throws ParseException {
                  return threadLocal.get().parse(dateStr);
              }
              public static String format(Date date) {
                  return threadLocal.get().format(date);
              }
          }
          public class ThreadLocalDateUtil {
              private static final String date_format = "yyyy-MM-dd HH:mm:ss";
              private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(); 
           
              public static DateFormat getDateFormat()   
              {  
                  DateFormat df = threadLocal.get();  
                  if(df==null){  
                      df = new SimpleDateFormat(date_format);  
                      threadLocal.set(df);  
                  }  
                  return df;  
              }  
              public static String formatDate(Date date) throws ParseException {
                  return getDateFormat().format(date);
              }
              public static Date parse(String strDate) throws ParseException {
                  return getDateFormat().parse(strDate);
              }   
          }
            說明:使用ThreadLocal, 也是將共享變量變?yōu)楠毾?/span>,線程獨享肯定能比方法獨享在并發(fā)環(huán)境中能減少不少創(chuàng)建對象的開銷。如果對性能要求比較高的情況下,一般推薦使用這種方法。
            4.拋棄JDK,使用其他類庫中的時間格式化類:
            1.使用Apache commons 里的FastDateFormat,宣稱是既快又線程安全的SimpleDateFormat, 可惜它只能對日期進行format, 不能對日期串進行解析。
            2.使用Joda-Time類庫來處理時間相關(guān)問題
            做一個簡單的壓力測試,方法一最慢,方法三最快,但是就算是最慢的方法一性能也不差,一般系統(tǒng)方法一和方法二就可以滿足,所以說在這個點很難成為你系統(tǒng)的瓶頸所在。從簡單的角度來說,建議使用方法一或者方法二,如果在必要的時候,追求那么一點性能提升的話,可以考慮用方法三,用ThreadLocal做緩存。
            Joda-Time類庫對時間處理方式比較完美,建議使用。

          posted on 2016-06-16 14:48 youngturk 閱讀(236) 評論(0)  編輯  收藏 所屬分類: 筆試題

          <2016年6月>
          2930311234
          567891011
          12131415161718
          19202122232425
          262728293012
          3456789

          導航

          統(tǒng)計

          公告

          this year :
          1 jQuery
          2 freemarker
          3 框架結(jié)構(gòu)
          4 口語英語

          常用鏈接

          留言簿(6)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          相冊

          EJB學習

          Flex學習

          learn English

          oracle

          spring MVC web service

          SQL

          Struts

          生活保健

          解析文件

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 拉孜县| 平南县| 涪陵区| 榆树市| 明光市| 东港市| 石河子市| 乡城县| 恩施市| 涞源县| 类乌齐县| 青神县| 体育| 正安县| 崇左市| 喀喇沁旗| 清远市| 黎川县| 桂东县| 宁明县| 泉州市| 宜城市| 开平市| 桃江县| 新绛县| 五寨县| 营口市| 洱源县| 徐汇区| 繁昌县| 巴彦县| 吉木乃县| 乐亭县| 申扎县| 民县| 台山市| 晋宁县| 萨嘎县| 水富县| 崇文区| 天台县|