少年阿賓

          那些青春的歲月

            BlogJava :: 首頁 :: 聯系 :: 聚合  :: 管理
            500 Posts :: 0 Stories :: 135 Comments :: 0 Trackbacks

          #

               摘要: 前言:本文指在介紹Spring框架中的JdbcTemplate類的使用方法,涉及基本的Spring反轉控制的使用方法和JDBC的基本概念。目標是使讀者能夠對JdbcTemplate快速地掌握和使用。        準備:1. Spring的基本概念      ...  閱讀全文
          posted @ 2012-06-25 10:28 abin 閱讀(641) | 評論 (0)編輯 收藏

          MyBatis中,可以使用Generator自動生成代碼,包括DAO層、 MODEL層 、MAPPING SQL映射文件。 

          第一步:下載MyBatis的Generator工具 
          下載地址:http://code.google.com/p/mybatis/downloads/detail?name=mybatis-generator-core-1.3.1-bundle.zip&can=3&q=Product%3DGenerator 

          第二步:配置自動生成代碼所需的XML配置文件,例如(generator.xml)  
          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
          <generatorConfiguration>
          <!-- classPathEntry:數據庫的JDBC驅動,換成你自己的驅動位置 -->
          <classPathEntry location="D:\libs\ojdbc14.jar" />
          <context id="DB2Tables" targetRuntime="MyBatis3">
          <!-- 去除自動生成的注釋 -->
          <commentGenerator>
          <property name="suppressAllComments" value="true" />
          </commentGenerator>
          <jdbcConnection driverClass="oracle.jdbc.driver.OracleDriver" connectionURL="jdbc:oracle:thin:@172.16.88.10:1521:mydb" userId="abc" password="abc">
          </jdbcConnection>
          <javaTypeResolver >
          <property name="forceBigDecimals" value="false" />
          </javaTypeResolver>
          <!-- targetProject:自動生成代碼的位置 -->
          <javaModelGenerator targetPackage="com.test.model" targetProject="E:\eclipse 3.5.2\workspace\gao\src">
          <property name="enableSubPackages" value="true" />
          <property name="trimStrings" value="true" />
          </javaModelGenerator>
          <sqlMapGenerator targetPackage="com.test.mapping"  targetProject="E:\eclipse 3.5.2\workspace\gao\src">
          <property name="enableSubPackages" value="true" />
          </sqlMapGenerator>
          <javaClientGenerator type="XMLMAPPER" targetPackage="com.test.dao"  targetProject="E:\eclipse 3.5.2\workspace\gao\src">
          <property name="enableSubPackages" value="true" />
          </javaClientGenerator>
          <!-- tableName:用于自動生成代碼的數據庫表;domainObjectName:對應于數據庫表的javaBean類名 -->
          <table tableName="pds_system_item" domainObjectName="PdsSystemItem" />
          <table tableName="pds_system_level" domainObjectName="PdsSystemLevel" />
          </context>
          </generatorConfiguration>
          將這個文件保存至你下載的mybatis-generator-core-1.3.1文件夾下 

          第三步:用命令行運行(記得選擇自己的文件地址)  
          java -jar E:\soft\mybatis-generator-core-1.3.1\lib\mybatis-generator-core-1.3.1.jar -configfile E:\soft\mybatis-generator-core-1.3.1\genrator.xml -overwrite





          我自己寫的如下:
          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
          <generatorConfiguration>
          <!-- classPathEntry:數據庫的JDBC驅動,換成你自己的驅動位置 -->
          <classPathEntry location="D:\abin\Java\MybatisTool\mybatis-generator-core-1.3.1\lib\mysql-connector-java-5.1.20-bin.jar" />
          <context id="DB2Tables" targetRuntime="MyBatis3">
          <!-- 去除自動生成的注釋 -->
          <commentGenerator>
          <property name="suppressAllComments" value="true" />
          </commentGenerator>
          <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mycrm" userId="root" password="root">
          </jdbcConnection>
          <javaTypeResolver >
          <property name="forceBigDecimals" value="false" />
          </javaTypeResolver>
          <!-- targetProject:自動生成代碼的位置 -->
          <javaModelGenerator targetPackage="com.test.model" targetProject="F:\nb\mp\src\main\java">
          <property name="enableSubPackages" value="true" />
          <property name="trimStrings" value="true" />
          </javaModelGenerator>
          <sqlMapGenerator targetPackage="com.test.mapping"  targetProject="F:\nb\mp\src\main\java">
          <property name="enableSubPackages" value="true" />
          </sqlMapGenerator>
          <javaClientGenerator type="XMLMAPPER" targetPackage="com.test.dao"  targetProject="F:\nb\mp\src\main\java">
          <property name="enableSubPackages" value="true" />
          </javaClientGenerator>
          <!-- tableName:用于自動生成代碼的數據庫表;domainObjectName:對應于數據庫表的javaBean類名 -->
          <table tableName="testcrm" domainObjectName="testcrmone" />
          <table tableName="testmy" domainObjectName="testmyone" />
          </context>
          </generatorConfiguration>

          posted @ 2012-06-21 18:10 abin 閱讀(13824) | 評論 (1)編輯 收藏

               摘要: 1. 全局變量num=0   兩個線程同時執行以下代碼   {      for(int i=0;i<50;i++){         num+=1;      } ...  閱讀全文
          posted @ 2012-06-21 13:23 abin 閱讀(517) | 評論 (0)編輯 收藏

          多線程
          線程:是指進程中的一個執行流程。
          線程與進程的區別:每個進程都需要操作系統為其分配獨立的內存地址空間,而同一進程中的所有線程在同一塊地址空間中工作,這些線程可以共享同一塊內存和系統資源。


          如何創建一個線程?

          創建線程有兩種方式,如下:
          1、 擴展java.lang.Thread類
          2、 實現Runnable接口
          Thread類代表線程類,它的兩個最主要的方法是:
          run()——包含線程運行時所執行的代碼
          Start()——用于啟動線程

          一個線程只能被啟動一次。第二次啟動時將會拋出java.lang.IllegalThreadExcetpion異常

          線程間狀態的轉換(如圖示)

          新建狀態:用new語句創建的線程對象處于新建狀態,此時它和其它的java對象一樣,僅僅在堆中被分配了內存
          就緒狀態:當一個線程創建了以后,其他的線程調用了它的start()方法,該線程就進入了就緒狀態。處于這個狀態的線程位于可運行池中,等待獲得CPU的使用權
          運行狀態:處于這個狀態的線程占用CPU,執行程序的代碼
          阻塞狀態:當線程處于阻塞狀態時,java虛擬機不會給線程分配CPU,直到線程重新進入就緒狀態,它才有機會轉到運行狀態。
          阻塞狀態分為三種情況:
          1、 位于對象等待池中的阻塞狀態:當線程運行時,如果執行了某個對象的wait()方法,java虛擬機就回把線程放到這個對象的等待池中
          2、 位于對象鎖中的阻塞狀態,當線程處于運行狀態時,試圖獲得某個對象的同步鎖時,如果該對象的同步鎖已經被其他的線程占用,JVM就會把這個線程放到這個對象的瑣池中。
          3、 其它的阻塞狀態:當前線程執行了sleep()方法,或者調用了其它線程的join()方法,或者發出了I/O請求時,就會進入這個狀態中。

          死亡狀態:當線程退出了run()方法,就進入了死亡狀態,該線程結束了生命周期。
                     或者正常退出
                     或者遇到異常退出
                     Thread類的isAlive()方法判斷一個線程是否活著,當線程處于死亡狀態或者新建狀態時,該方法返回false,在其余的狀態下,該方法返回true.

          線程調度
          線程調度模型:分時調度模型和搶占式調度模型
          JVM采用搶占式調度模型。
          所謂的多線程的并發運行,其實是指宏觀上看,各個線程輪流獲得CPU的使用權,分別執行各自的任務。
          (線程的調度不是跨平臺,它不僅取決于java虛擬機,它還依賴于操作系統)

          如果希望明確地讓一個線程給另外一個線程運行的機會,可以采取以下的辦法之一
          1、 調整各個線程的優先級
          2、 讓處于運行狀態的線程調用Thread.sleep()方法
          3、 讓處于運行狀態的線程調用Thread.yield()方法
          4、 讓處于運行狀態的線程調用另一個線程的join()方法

          調整各個線程的優先級
          Thread類的setPriority(int)和getPriority()方法分別用來設置優先級和讀取優先級。
          如果希望程序能夠移值到各個操作系統中,應該確保在設置線程的優先級時,只使用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY這3個優先級。

          線程睡眠:當線程在運行中執行了sleep()方法時,它就會放棄CPU,轉到阻塞狀態。
          線程讓步:當線程在運行中執行了Thread類的yield()靜態方法時,如果此時具有相同優先級的其它線程處于就緒狀態,那么yield()方法將把當前運行的線程放到運行池中并使另一個線程運行。如果沒有相同優先級的可運行線程,則yield()方法什么也不做。
          Sleep()方法和yield()方法都是Thread類的靜態方法,都會使當前處于運行狀態的線程放棄CPU,把運行機會讓給別的線程,兩者的區別在于:
                   1、sleep()方法會給其他線程運行的機會,而不考慮其他線程的優先級,因此會給較低線程一個運行的機會;yield()方法只會給相同優先級或者更高優先級的線程一個運行的機會。
          2、當線程執行了sleep(long millis)方法后,將轉到阻塞狀態,參數millis指定睡眠時間;當線程執行了yield()方法后,將轉到就緒狀態。
                    3、sleep()方法聲明拋出InterruptedException異常,而yield()方法沒有聲明拋出任何異常
                    4、sleep()方法比yield()方法具有更好的移植性

          等待其它線程的結束:join()
                    當前運行的線程可以調用另一個線程的 join()方法,當前運行的線程將轉到阻塞狀態,直到另一個線程運行結束,它才恢復運行。

          定時器Timer:在JDK的java.util包中提供了一個實用類Timer, 它能夠定時執行特定的任務。

          線程的同步
          原子操作:根據Java規范,對于基本類型的賦值或者返回值操作,是原子操作。但這里的基本數據類型不包括long和double, 因為JVM看到的基本存儲單位是32位,而long 和double都要用64位來表示。所以無法在一個時鐘周期內完成。

          自增操作(++)不是原子操作,因為它涉及到一次讀和一次寫。

          原子操作:由一組相關的操作完成,這些操作可能會操縱與其它的線程共享的資源,為了保證得到正確的運算結果,一個線程在執行原子操作其間,應該采取其他的措施使得其他的線程不能操縱共享資源。

          同步代碼塊:為了保證每個線程能夠正常執行原子操作,Java引入了同步機制,具體的做法是在代表原子操作的程序代碼前加上synchronized標記,這樣的代碼被稱為同步代碼塊。

          同步鎖:每個JAVA對象都有且只有一個同步鎖,在任何時刻,最多只允許一個線程擁有這把鎖。

          當一個線程試圖訪問帶有synchronized(this)標記的代碼塊時,必須獲得 this關鍵字引用的對象的鎖,在以下的兩種情況下,本線程有著不同的命運。
          1、 假如這個鎖已經被其它的線程占用,JVM就會把這個線程放到本對象的鎖池中。本線程進入阻塞狀態。鎖池中可能有很多的線程,等到其他的線程釋放了鎖,JVM就會從鎖池中隨機取出一個線程,使這個線程擁有鎖,并且轉到就緒狀態。
          2、 假如這個鎖沒有被其他線程占用,本線程會獲得這把鎖,開始執行同步代碼塊。
          (一般情況下在執行同步代碼塊時不會釋放同步鎖,但也有特殊情況會釋放對象鎖
          如在執行同步代碼塊時,遇到異常而導致線程終止,鎖會被釋放;在執行代碼塊時,執行了鎖所屬對象的wait()方法,這個線程會釋放對象鎖,進入對象的等待池中)

          線程同步的特征:
          1、 如果一個同步代碼塊和非同步代碼塊同時操作共享資源,仍然會造成對共享資源的競爭。因為當一個線程執行一個對象的同步代碼塊時,其他的線程仍然可以執行對象的非同步代碼塊。(所謂的線程之間保持同步,是指不同的線程在執行同一個對象的同步代碼塊時,因為要獲得對象的同步鎖而互相牽制)
          2、 每個對象都有唯一的同步鎖
          3、 在靜態方法前面可以使用synchronized修飾符。
          4、 當一個線程開始執行同步代碼塊時,并不意味著必須以不間斷的方式運行,進入同步代碼塊的線程可以執行Thread.sleep()或者執行Thread.yield()方法,此時它并不釋放對象鎖,只是把運行的機會讓給其他的線程。
          5、 Synchronized聲明不會被繼承,如果一個用synchronized修飾的方法被子類覆蓋,那么子類中這個方法不在保持同步,除非用synchronized修飾。

          線程安全的類:
          1、 這個類的對象可以同時被多個線程安全的訪問。
          2、 每個線程都能正常的執行原子操作,得到正確的結果。
          3、 在每個線程的原子操作都完成后,對象處于邏輯上合理的狀態。

          釋放對象的鎖:
          1、 執行完同步代碼塊就會釋放對象的鎖
          2、 在執行同步代碼塊的過程中,遇到異常而導致線程終止,鎖也會被釋放
          3、 在執行同步代碼塊的過程中,執行了鎖所屬對象的wait()方法,這個線程會釋放對象鎖,進入對象的等待池。

          死鎖
          當一個線程等待由另一個線程持有的鎖,而后者正在等待已被第一個線程持有的鎖時,就會發生死鎖。JVM不監測也不試圖避免這種情況,因此保證不發生死鎖就成了程序員的責任。

          如何避免死鎖
          一個通用的經驗法則是:當幾個線程都要訪問共享資源A、B、C 時,保證每個線程都按照同樣的順序去訪問他們。

          線程通信
          Java.lang.Object類中提供了兩個用于線程通信的方法
          1、 wait():執行了該方法的線程釋放對象的鎖,JVM會把該線程放到對象的等待池中。該線程等待其它線程喚醒
          2、 notify():執行該方法的線程喚醒在對象的等待池中等待的一個線程,JVM從對象的等待池中隨機選擇一個線程,把它轉到對象的鎖池中。
          posted @ 2012-06-17 01:42 abin 閱讀(1586) | 評論 (0)編輯 收藏

          來自:開發者在線 
          Java多線程程序設計詳細解析  
           
          一、理解多線程
          
          多線程是這樣一種機制,它允許在程序中并發執行多個指令流,每個指令流都稱為一個線程,彼此間互相獨立。
          
          線程又稱為輕量級進程,它和進程一樣擁有獨立的執行控制,由操作系統負責調度,區別在于線程沒有獨立的存儲空間,而是和所屬進程中的其它線程共享一個存儲空間,這使得線程間的通信遠較進程簡單。
          
          多個線程的執行是并發的,也就是在邏輯上“同時”,而不管是否是物理上的“同時”。如果系統只有一個CPU,那么真正的“同時”是不可能的,但是由于CPU的速度非常快,用戶感覺不到其中的區別,因此我們也不用關心它,只需要設想各個線程是同時執行即可。
          
          多線程和傳統的單線程在程序設計上最大的區別在于,由于各個線程的控制流彼此獨立,使得各個線程之間的代碼是亂序執行的,由此帶來的線程調度,同步等問題,將在以后探討。
          
          二:在Java中實現多線程
          
          我們不妨設想,為了創建一個新的線程,我們需要做些什么?很顯然,我們必須指明這個線程所要執行的代碼,而這就是在Java中實現多線程我們所需要做的一切!
          
          真是神奇!Java是如何做到這一點的?通過類!作為一個完全面向對象的語言,Java提供了類java.lang.Thread來方便多線程編程,這個類提供了大量的方法來方便我們控制自己的各個線程,我們以后的討論都將圍繞這個類進行。
          
          那么如何提供給 Java 我們要線程執行的代碼呢?讓我們來看一看 Thread 類。Thread 類最重要的方法是run(),它為Thread類的方法start()所調用,提供我們的線程所要執行的代碼。為了指定我們自己的代碼,只需要覆蓋它!
          
          方法一:繼承 Thread 類,覆蓋方法 run(),我們在創建的 Thread 類的子類中重寫 run() ,加入線程所要執行的代碼即可。下面是一個例子:
          
          
          
          public class MyThread extends Thread 
          { 
           int count= 1, number; 
           public MyThread(int num)
          { 
            number = num; 
            System.out.println
          ("創建線程 " + number); 
           } 
           public void run() { 
            while(true) { 
             System.out.println
          ("線程 " + number + ":計數 " + count); 
             if(++count== 6) return; 
            } 
           } 
           public static void main(String args[])
          { 
            for(int i = 0; 
          i 〈 5; i++) new MyThread(i+1).start(); 
           } 
          }
           
          
          這種方法簡單明了,符合大家的習慣,但是,它也有一個很大的缺點,那就是如果我們的類已經從一個類繼承(如小程序必須繼承自 Applet 類),則無法再繼承 Thread 類,這時如果我們又不想建立一個新的類,應該怎么辦呢? 
          
          我們不妨來探索一種新的方法:我們不創建Thread類的子類,而是直接使用它,那么我們只能將我們的方法作為參數傳遞給 Thread 類的實例,有點類似回調函數。但是 Java 沒有指針,我們只能傳遞一個包含這個方法的類的實例。 
          
          那么如何限制這個類必須包含這一方法呢?當然是使用接口!(雖然抽象類也可滿足,但是需要繼承,而我們之所以要采用這種新方法,不就是為了避免繼承帶來的限制嗎?) 
          
          Java 提供了接口 java.lang.Runnable 來支持這種方法。 
          
          方法二:實現 Runnable 接口 
          
          Runnable接口只有一個方法run(),我們聲明自己的類實現Runnable接口并提供這一方法,將我們的線程代碼寫入其中,就完成了這一部分的任務。但是Runnable接口并沒有任何對線程的支持,我們還必須創建Thread類的實例,這一點通過Thread類的構造函數public Thread(Runnable target);來實現。下面是一個例子: 
          
          
          public class MyThread implements Runnable
          { 
           int count= 1, number; 
           public MyThread(int num)
          { 
            number = num; 
            System.out.println("創建線程 " + number); 
           } 
           public void run()
          { 
            while(true)
          { 
             System.out.println
          ("線程 " + number + ":計數 " + count); 
             if(++count== 6) return; 
            } 
           } 
           public static void main(String args[])
          { 
            for(int i = 0; i 〈 5;
          i++) new Thread(new MyThread(i+1)).start(); 
           } 
          }
           
          
          嚴格地說,創建Thread子類的實例也是可行的,但是必須注意的是,該子類必須沒有覆蓋 Thread 類的 run 方法,否則該線程執行的將是子類的 run 方法,而不是我們用以實現Runnable 接口的類的 run 方法,對此大家不妨試驗一下。 
          
          使用 Runnable 接口來實現多線程使得我們能夠在一個類中包容所有的代碼,有利于封裝,它的缺點在于,我們只能使用一套代碼,若想創建多個線程并使各個線程執行不同的代碼,則仍必須額外創建類,如果這樣的話,在大多數情況下也許還不如直接用多個類分別繼承 Thread 來得緊湊。 
          
          綜上所述,兩種方法各有千秋,大家可以靈活運用。 
          
          下面讓我們一起來研究一下多線程使用中的一些問題。 
          
          三、線程的四種狀態 
          
          1. 新狀態:線程已被創建但尚未執行(start() 尚未被調用)。 
          
          2. 可執行狀態:線程可以執行,雖然不一定正在執行。CPU 時間隨時可能被分配給該線程,從而使得它執行。 
          
          3. 死亡狀態:正常情況下 run() 返回使得線程死亡。調用 stop()或 destroy() 亦有同樣效果,但是不被推薦,前者會產生異常,后者是強制終止,不會釋放鎖。 
          
          4. 阻塞狀態:線程不會被分配 CPU 時間,無法執行。 
          
          四、線程的優先級 
          
          線程的優先級代表該線程的重要程度,當有多個線程同時處于可執行狀態并等待獲得 CPU 時間時,線程調度系統根據各個線程的優先級來決定給誰分配 CPU 時間,優先級高的線程有更大的機會獲得 CPU 時間,優先級低的線程也不是沒有機會,只是機會要小一些罷了。 
          
          你可以調用 Thread 類的方法 getPriority() 和 setPriority()來存取線程的優先級,線程的優先級界于1(MIN_PRIORITY)和10(MAX_PRIORITY)之間,缺省是5(NORM_PRIORITY)。 
          
          五、線程的同步 
          
          由于同一進程的多個線程共享同一片存儲空間,在帶來方便的同時,也帶來了訪問沖突這個嚴重的問題。Java語言提供了專門機制以解決這種沖突,有效避免了同一個數據對象被多個線程同時訪問。 
          
          由于我們可以通過 private 關鍵字來保證數據對象只能被方法訪問,所以我們只需針對方法提出一套機制,這套機制就是 synchronized 關鍵字,它包括兩種用法:synchronized 方法和 synchronized 塊。 
          
          1. synchronized 方法:通過在方法聲明中加入 synchronized關鍵字來聲明 synchronized 方法。如: 
          
          
          public synchronized void accessVal(int newVal);
           
          
          synchronized 方法控制對類成員變量的訪問:每個類實例對應一把鎖,每個 synchronized 方法都必須獲得調用該方法的類實例的鎖方能執行,否則所屬線程阻塞,方法一旦執行,就獨占該鎖,直到從該方法返回時才將鎖釋放,此后被阻塞的線程方能獲得該鎖,重新進入可執行狀態。 
          
          這種機制確保了同一時刻對于每一個類實例,其所有聲明為 synchronized 的成員函數中至多只有一個處于可執行狀態(因為至多只有一個能夠獲得該類實例對應的鎖),從而有效避免了類成員變量的訪問沖突(只要所有可能訪問類成員變量的方法均被聲明為 synchronized)。 
          
          在 Java 中,不光是類實例,每一個類也對應一把鎖,這樣我們也可將類的靜態成員函數聲明為 synchronized ,以控制其對類的靜態成員變量的訪問。 
          
          synchronized 方法的缺陷:若將一個大的方法聲明為synchronized 將會大大影響效率,典型地,若將線程類的方法 run() 聲明為 synchronized ,由于在線程的整個生命期內它一直在運行,因此將導致它對本類任何 synchronized 方法的調用都永遠不會成功。當然我們可以通過將訪問類成員變量的代碼放到專門的方法中,將其聲明為 synchronized ,并在主方法中調用來解決這一問題,但是 Java 為我們提供了更好的解決辦法,那就是 synchronized 塊。 
          
          2. synchronized 塊:通過 synchronized關鍵字來聲明synchronized 塊。語法如下: 
          
          
          synchronized(syncObject)
          { 
          //允許訪問控制的代碼 
          }
           
          
          synchronized 塊是這樣一個代碼塊,其中的代碼必須獲得對象 syncObject (如前所述,可以是類實例或類)的鎖方能執行,具體機制同前所述。由于可以針對任意代碼塊,且可任意指定上鎖的對象,故靈活性較高。 
          
          六、線程的阻塞 
          
          為了解決對共享存儲區的訪問沖突,Java 引入了同步機制,現在讓我們來考察多個線程對共享資源的訪問,顯然同步機制已經不夠了,因為在任意時刻所要求的資源不一定已經準備好了被訪問,反過來,同一時刻準備好了的資源也可能不止一個。為了解決這種情況下的訪問控制問題,Java 引入了對阻塞機制的支持。 
          
          阻塞指的是暫停一個線程的執行以等待某個條件發生(如某資源就緒),學過操作系統的同學對它一定已經很熟悉了。Java 提供了大量方法來支持阻塞,下面讓我們逐一分析。 
          
          1. sleep() 方法:sleep() 允許 指定以毫秒為單位的一段時間作為參數,它使得線程在指定的時間內進入阻塞狀態,不能得到CPU 時間,指定的時間一過,線程重新進入可執行狀態。典型地,sleep() 被用在等待某個資源就緒的情形:測試發現條件不滿足后,讓線程阻塞一段時間后重新測試,直到條件滿足為止。 
          
          2. suspend() 和 resume() 方法:兩個方法配套使用,suspend()使得線程進入阻塞狀態,并且不會自動恢復,必須其對應的resume() 被調用,才能使得線程重新進入可執行狀態。典型地,suspend() 和 resume() 被用在等待另一個線程產生的結果的情形:測試發現結果還沒有產生后,讓線程阻塞,另一個線程產生了結果后,調用 resume() 使其恢復。 
          
          3. yield() 方法:yield() 使得線程放棄當前分得的 CPU 時間,但是不使線程阻塞,即線程仍處于可執行狀態,隨時可能再次分得 CPU 時間。調用 yield() 的效果等價于調度程序認為該線程已執行了足夠的時間從而轉到另一個線程。 
          
          4. wait() 和 notify() 方法:兩個方法配套使用,wait() 使得線程進入阻塞狀態,它有兩種形式,一種允許 指定以毫秒為單位的一段時間作為參數,另一種沒有參數,前者當對應的 notify() 被調用或者超出指定時間時線程重新進入可執行狀態,后者則必須對應的 notify() 被調用。 
          
          初看起來它們與 suspend() 和 resume() 方法對沒有什么分別,但是事實上它們是截然不同的。區別的核心在于,前面敘述的所有方法,阻塞時都不會釋放占用的鎖(如果占用了的話),而這一對方法則相反。 
          
          上述的核心區別導致了一系列的細節上的區別。 
          
          首先,前面敘述的所有方法都隸屬于 Thread 類,但是這一對卻直接隸屬于 Object 類,也就是說,所有對象都擁有這一對方法。初看起來這十分不可思議,但是實際上卻是很自然的,因為這一對方法阻塞時要釋放占用的鎖,而鎖是任何對象都具有的,調用任意對象的 wait() 方法導致線程阻塞,并且該對象上的鎖被釋放。 
          
          而調用 任意對象的notify()方法則導致因調用該對象的 wait() 方法而阻塞的線程中隨機選擇的一個解除阻塞(但要等到獲得鎖后才真正可執行)。 
          
          其次,前面敘述的所有方法都可在任何位置調用,但是這一對方法卻必須在 synchronized 方法或塊中調用,理由也很簡單,只有在synchronized 方法或塊中當前線程才占有鎖,才有鎖可以釋放。 
          
          同樣的道理,調用這一對方法的對象上的鎖必須為當前線程所擁有,這樣才有鎖可以釋放。因此,這一對方法調用必須放置在這樣的 synchronized 方法或塊中,該方法或塊的上鎖對象就是調用這一對方法的對象。若不滿足這一條件,則程序雖然仍能編譯,但在運行時會出現IllegalMonitorStateException 異常。 
          
          wait() 和 notify() 方法的上述特性決定了它們經常和synchronized 方法或塊一起使用,將它們和操作系統的進程間通信機制作一個比較就會發現它們的相似性:synchronized方法或塊提供了類似于操作系統原語的功能,它們的執行不會受到多線程機制的干擾,而這一對方法則相當于 block 和wakeup 原語(這一對方法均聲明為 synchronized)。 
          
          它們的結合使得我們可以實現操作系統上一系列精妙的進程間通信的算法(如信號量算法),并用于解決各種復雜的線程間通信問題。關于 wait() 和 notify() 方法最后再說明兩點: 
          
          第一:調用 notify() 方法導致解除阻塞的線程是從因調用該對象的 wait() 方法而阻塞的線程中隨機選取的,我們無法預料哪一個線程將會被選擇,所以編程時要特別小心,避免因這種不確定性而產生問題。 
          
          第二:除了 notify(),還有一個方法 notifyAll() 也可起到類似作用,唯一的區別在于,調用 notifyAll() 方法將把因調用該對象的 wait() 方法而阻塞的所有線程一次性全部解除阻塞。當然,只有獲得鎖的那一個線程才能進入可執行狀態。 
          
          談到阻塞,就不能不談一談死鎖,略一分析就能發現,suspend() 方法和不指定超時期限的 wait() 方法的調用都可能產生死鎖。遺憾的是,Java 并不在語言級別上支持死鎖的避免,我們在編程中必須小心地避免死鎖。 
          
          以上我們對 Java 中實現線程阻塞的各種方法作了一番分析,我們重點分析了 wait() 和 notify()方法,因為它們的功能最強大,使用也最靈活,但是這也導致了它們的效率較低,較容易出錯。實際使用中我們應該靈活使用各種方法,以便更好地達到我們的目的。 
          
          七、守護線程 
          
          守護線程是一類特殊的線程,它和普通線程的區別在于它并不是應用程序的核心部分,當一個應用程序的所有非守護線程終止運行時,即使仍然有守護線程在運行,應用程序也將終止,反之,只要有一個非守護線程在運行,應用程序就不會終止。守護線程一般被用于在后臺為其它線程提供服務。 
          
          可以通過調用方法 isDaemon() 來判斷一個線程是否是守護線程,也可以調用方法 setDaemon() 來將一個線程設為守護線程。 
          
          八、線程組 
          
          線程組是一個 Java 特有的概念,在 Java 中,線程組是類ThreadGroup 的對象,每個線程都隸屬于唯一一個線程組,這個線程組在線程創建時指定并在線程的整個生命期內都不能更改。 
          
          你可以通過調用包含 ThreadGroup 類型參數的 Thread 類構造函數來指定線程屬的線程組,若沒有指定,則線程缺省地隸屬于名為 system 的系統線程組。 
          
          在 Java 中,除了預建的系統線程組外,所有線程組都必須顯式創建。在 Java 中,除系統線程組外的每個線程組又隸屬于另一個線程組,你可以在創建線程組時指定其所隸屬的線程組,若沒有指定,則缺省地隸屬于系統線程組。這樣,所有線程組組成了一棵以系統線程組為根的樹。 
          
          Java 允許我們對一個線程組中的所有線程同時進行操作,比如我們可以通過調用線程組的相應方法來設置其中所有線程的優先級,也可以啟動或阻塞其中的所有線程。 
          
          Java 的線程組機制的另一個重要作用是線程安全。線程組機制允許我們通過分組來區分有不同安全特性的線程,對不同組的線程進行不同的處理,還可以通過線程組的分層結構來支持不對等安全措施的采用。 
          
          Java 的 ThreadGroup 類提供了大量的方法來方便我們對線程組樹中的每一個線程組以及線程組中的每一個線程進行操作。 
          
          九、總結 
          
          在本文中,我們講述了 Java 多線程編程的方方面面,包括創建線程,以及對多個線程進行調度、管理。我們深刻認識到了多線程編程的復雜性,以及線程切換開銷帶來的多線程程序的低效性,這也促使我們認真地思考一個問題:我們是否需要多線程?何時需要多線程? 
          
          多線程的核心在于多個代碼塊并發執行,本質特點在于各代碼塊之間的代碼是亂序執行的。我們的程序是否需要多線程,就是要看這是否也是它的內在特點。 
          
          假如我們的程序根本不要求多個代碼塊并發執行,那自然不需要使用多線程;假如我們的程序雖然要求多個代碼塊并發執行,但是卻不要求亂序,則我們完全可以用一個循環來簡單高效地實現,也不需要使用多線程;只有當它完全符合多線程的特點時,多線程機制對線程間通信和線程管理的強大支持才能有用武之地,這時使用多線程才是值得的。 
          
           來自:開發者在線
          另外如果你喜歡電腦的話,我推薦你去初學者之路看看~
          posted @ 2012-06-17 01:41 abin 閱讀(498) | 評論 (0)編輯 收藏

          多線程
          線程:是指進程中的一個執行流程。
          線程與進程的區別:每個進程都需要操作系統為其分配獨立的內存地址空間,而同一進程中的所有線程在同一塊地址空間中工作,這些線程可以共享同一塊內存和系統資源。


          如何創建一個線程?

          創建線程有兩種方式,如下:
          1、 擴展java.lang.Thread類
          2、 實現Runnable接口
          Thread類代表線程類,它的兩個最主要的方法是:
          run()——包含線程運行時所執行的代碼
          Start()——用于啟動線程

          一個線程只能被啟動一次。第二次啟動時將會拋出java.lang.IllegalThreadExcetpion異常

          線程間狀態的轉換(如圖示)

          新建狀態:用new語句創建的線程對象處于新建狀態,此時它和其它的java對象一樣,僅僅在堆中被分配了內存
          就緒狀態:當一個線程創建了以后,其他的線程調用了它的start()方法,該線程就進入了就緒狀態。處于這個狀態的線程位于可運行池中,等待獲得CPU的使用權
          運行狀態:處于這個狀態的線程占用CPU,執行程序的代碼
          阻塞狀態:當線程處于阻塞狀態時,java虛擬機不會給線程分配CPU,直到線程重新進入就緒狀態,它才有機會轉到運行狀態。
          阻塞狀態分為三種情況:
          1、 位于對象等待池中的阻塞狀態:當線程運行時,如果執行了某個對象的wait()方法,java虛擬機就回把線程放到這個對象的等待池中
          2、 位于對象鎖中的阻塞狀態,當線程處于運行狀態時,試圖獲得某個對象的同步鎖時,如果該對象的同步鎖已經被其他的線程占用,JVM就會把這個線程放到這個對象的瑣池中。
          3、 其它的阻塞狀態:當前線程執行了sleep()方法,或者調用了其它線程的join()方法,或者發出了I/O請求時,就會進入這個狀態中。

          死亡狀態:當線程退出了run()方法,就進入了死亡狀態,該線程結束了生命周期。
                     或者正常退出
                     或者遇到異常退出
                     Thread類的isAlive()方法判斷一個線程是否活著,當線程處于死亡狀態或者新建狀態時,該方法返回false,在其余的狀態下,該方法返回true.

          線程調度
          線程調度模型:分時調度模型和搶占式調度模型
          JVM采用搶占式調度模型。
          所謂的多線程的并發運行,其實是指宏觀上看,各個線程輪流獲得CPU的使用權,分別執行各自的任務。
          (線程的調度不是跨平臺,它不僅取決于java虛擬機,它還依賴于操作系統)

          如果希望明確地讓一個線程給另外一個線程運行的機會,可以采取以下的辦法之一
          1、 調整各個線程的優先級
          2、 讓處于運行狀態的線程調用Thread.sleep()方法
          3、 讓處于運行狀態的線程調用Thread.yield()方法
          4、 讓處于運行狀態的線程調用另一個線程的join()方法

          調整各個線程的優先級
          Thread類的setPriority(int)和getPriority()方法分別用來設置優先級和讀取優先級。
          如果希望程序能夠移值到各個操作系統中,應該確保在設置線程的優先級時,只使用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY這3個優先級。

          線程睡眠:當線程在運行中執行了sleep()方法時,它就會放棄CPU,轉到阻塞狀態。
          線程讓步:當線程在運行中執行了Thread類的yield()靜態方法時,如果此時具有相同優先級的其它線程處于就緒狀態,那么yield()方法將把當前運行的線程放到運行池中并使另一個線程運行。如果沒有相同優先級的可運行線程,則yield()方法什么也不做。
          Sleep()方法和yield()方法都是Thread類的靜態方法,都會使當前處于運行狀態的線程放棄CPU,把運行機會讓給別的線程,兩者的區別在于:
                   1、sleep()方法會給其他線程運行的機會,而不考慮其他線程的優先級,因此會給較低線程一個運行的機會;yield()方法只會給相同優先級或者更高優先級的線程一個運行的機會。
          2、當線程執行了sleep(long millis)方法后,將轉到阻塞狀態,參數millis指定睡眠時間;當線程執行了yield()方法后,將轉到就緒狀態。
                    3、sleep()方法聲明拋出InterruptedException異常,而yield()方法沒有聲明拋出任何異常
                    4、sleep()方法比yield()方法具有更好的移植性

          等待其它線程的結束:join()
                    當前運行的線程可以調用另一個線程的 join()方法,當前運行的線程將轉到阻塞狀態,直到另一個線程運行結束,它才恢復運行。

          定時器Timer:在JDK的java.util包中提供了一個實用類Timer, 它能夠定時執行特定的任務。

          線程的同步
          原子操作:根據Java規范,對于基本類型的賦值或者返回值操作,是原子操作。但這里的基本數據類型不包括long和double, 因為JVM看到的基本存儲單位是32位,而long 和double都要用64位來表示。所以無法在一個時鐘周期內完成。

          自增操作(++)不是原子操作,因為它涉及到一次讀和一次寫。

          原子操作:由一組相關的操作完成,這些操作可能會操縱與其它的線程共享的資源,為了保證得到正確的運算結果,一個線程在執行原子操作其間,應該采取其他的措施使得其他的線程不能操縱共享資源。

          同步代碼塊:為了保證每個線程能夠正常執行原子操作,Java引入了同步機制,具體的做法是在代表原子操作的程序代碼前加上synchronized標記,這樣的代碼被稱為同步代碼塊。

          同步鎖:每個JAVA對象都有且只有一個同步鎖,在任何時刻,最多只允許一個線程擁有這把鎖。

          當一個線程試圖訪問帶有synchronized(this)標記的代碼塊時,必須獲得 this關鍵字引用的對象的鎖,在以下的兩種情況下,本線程有著不同的命運。
          1、 假如這個鎖已經被其它的線程占用,JVM就會把這個線程放到本對象的鎖池中。本線程進入阻塞狀態。鎖池中可能有很多的線程,等到其他的線程釋放了鎖,JVM就會從鎖池中隨機取出一個線程,使這個線程擁有鎖,并且轉到就緒狀態。
          2、 假如這個鎖沒有被其他線程占用,本線程會獲得這把鎖,開始執行同步代碼塊。
          (一般情況下在執行同步代碼塊時不會釋放同步鎖,但也有特殊情況會釋放對象鎖
          如在執行同步代碼塊時,遇到異常而導致線程終止,鎖會被釋放;在執行代碼塊時,執行了鎖所屬對象的wait()方法,這個線程會釋放對象鎖,進入對象的等待池中)

          線程同步的特征:
          1、 如果一個同步代碼塊和非同步代碼塊同時操作共享資源,仍然會造成對共享資源的競爭。因為當一個線程執行一個對象的同步代碼塊時,其他的線程仍然可以執行對象的非同步代碼塊。(所謂的線程之間保持同步,是指不同的線程在執行同一個對象的同步代碼塊時,因為要獲得對象的同步鎖而互相牽制)
          2、 每個對象都有唯一的同步鎖
          3、 在靜態方法前面可以使用synchronized修飾符。
          4、 當一個線程開始執行同步代碼塊時,并不意味著必須以不間斷的方式運行,進入同步代碼塊的線程可以執行Thread.sleep()或者執行Thread.yield()方法,此時它并不釋放對象鎖,只是把運行的機會讓給其他的線程。
          5、 Synchronized聲明不會被繼承,如果一個用synchronized修飾的方法被子類覆蓋,那么子類中這個方法不在保持同步,除非用synchronized修飾。

          線程安全的類:
          1、 這個類的對象可以同時被多個線程安全的訪問。
          2、 每個線程都能正常的執行原子操作,得到正確的結果。
          3、 在每個線程的原子操作都完成后,對象處于邏輯上合理的狀態。

          釋放對象的鎖:
          1、 執行完同步代碼塊就會釋放對象的鎖
          2、 在執行同步代碼塊的過程中,遇到異常而導致線程終止,鎖也會被釋放
          3、 在執行同步代碼塊的過程中,執行了鎖所屬對象的wait()方法,這個線程會釋放對象鎖,進入對象的等待池。

          死鎖
          當一個線程等待由另一個線程持有的鎖,而后者正在等待已被第一個線程持有的鎖時,就會發生死鎖。JVM不監測也不試圖避免這種情況,因此保證不發生死鎖就成了程序員的責任。

          如何避免死鎖
          一個通用的經驗法則是:當幾個線程都要訪問共享資源A、B、C 時,保證每個線程都按照同樣的順序去訪問他們。

          線程通信
          Java.lang.Object類中提供了兩個用于線程通信的方法
          1、 wait():執行了該方法的線程釋放對象的鎖,JVM會把該線程放到對象的等待池中。該線程等待其它線程喚醒
          2、 notify():執行該方法的線程喚醒在對象的等待池中等待的一個線程,JVM從對象的等待池中隨機選擇一個線程,把它轉到對象的鎖池中。
          posted @ 2012-06-17 01:24 abin 閱讀(323) | 評論 (0)編輯 收藏

          這兩個方法主要來源是,sleep用于線程控制,而wait用于線程間的通信,與wait配套的方法還有notify和notifyAll.

          區別一:

          sleep是Thread類的方法,是線程用來 控制自身流程的,比如有一個要報時的線程,每一秒中打印出一個時間,那么我就需要在print方法前面加上一個sleep讓自己每隔一秒執行一次。就像個鬧鐘一樣。

          wait是Object類的方法,用來線程間的通信,這個方法會使當前擁有該對象鎖的進程等待知道其他線程調用notify方法時再醒來,不過你也可以給他指定一個時間,自動醒來。這個方法主要是用走不同線程之間的調度的。

          區別二 :

          關于鎖的釋放 ,在這里假設大家已經知道了鎖的概念及其意義。調用sleep方法不會釋放鎖(自己的感覺是sleep方法本來就是和鎖沒有關系的,因為他是一個線程用于管理自己的方法,不涉及線程通信)

          JDK 7 中的解釋:

          “public static void sleep(long millis)

          throws InterruptedException
          Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.The thread does not lose ownership of any monitors.

          public final void wait() throws InterruptedException
          Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. In other words, this method behaves exactly as if it simply performs the call wait(0).The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.“
          調用wait方法會釋放當前線程的鎖(其實線程間的通信是靠對象來管理的,所有操作一個對象的線程是這個對象通過自己的wait方法來管理的,就好像這個對象是電視機,三個人是三個線程,那么電視機的遙控器就是這個鎖,假如現在A拿著遙控器,電視機調用wait方法,那么A就交出自己的遙控器,由jVM虛擬機調度,遙控器該交給誰。)【我想到一個好玩的例子:如果A拿遙控器的期間,他可以用自己的sleep每隔十分鐘調一次電視臺,而在他調臺休息的十分鐘期間,遙控器還在他的手上~】

          區別三:

          使用區域

          由于wait函數的特殊意義,所以他是應該放在同步語句塊中的,這樣才有意義 。

          注意:兩個方法都需要拋出異常

          個人見解:有sleep和wait的第二個區別,引起了我對Java線程機制的一個疑問,目前還沒有看過JDk這方面的源碼(其實看了,是木有看懂),線程的同步管理,是不是由對象在調度,如果是對象在調度,那么JDK 1.5新引入的ReentrantLock機制就比synchronized關鍵字更值得提倡。因為他更能反映出這么一個機制來。好多人不能理解wait和sleep的區別,我認為就是因為synchronized關鍵字的影響。當然自己還不懂JAVA的線程具體實現,留作疑問以后有時間繼續研究吧





          Java中的多線程是一種搶占式的機制 而不是分時機制。搶占式機制指的是有多個線程處于可運行狀態,但是只有一個線程在運行。

          共同點:
          1. 他們都是在多線程的環境下,都可以在程序的調用處阻塞指定的毫秒數,并返回。

          2. wait()和sleep()都可以通過interrupt()方法打斷線程的暫停狀態,從而使線程立刻拋出InterruptedException。
             如果線程A希望立即結束線程B,則可以對線程B對應的Thread實例調用interrupt方法。如果此刻線程B正在wait/sleep /join,則線程B會立刻拋出InterruptedException,在catch() {} 中直接return即可安全地結束線程。
             需要注意的是,InterruptedException是線程自己從內部拋出的,并不是interrupt()方法拋出的。對某一線程調用 interrupt()時,如果該線程正在執行普通的代碼,那么該線程根本就不會拋出InterruptedException。但是,一旦該線程進入到 wait()/sleep()/join()后,就會立刻拋出InterruptedException。

          不同點:
          1. Thread類的方法:sleep(),yield()等
             Object的方法:wait()和notify()等
            
          2. 每個對象都有一個鎖來控制同步訪問。Synchronized關鍵字可以和對象的鎖交互,來實現線程的同步。
             sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。

          3. wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在任何地方使用
          4. sleep必須捕獲異常,而wait,notify和notifyAll不需要捕獲異常   

           

           

           線程的調度
                  線程調度器按線程的優先級高低選擇高優先級線程(進入運行中狀態)執行,同時線程調度是搶先式調度,即如果在當前線程執行過程中,一個更高優先級的線程進入可運行狀態,則這個線程立即被調度執行。


          搶先式調度又分為:時間片方式和獨占方式。在時間片方式下,當前活動線程執行完當前時間片后,如果有其他處于就緒狀態的相同優先級的線程,系統會將執行權交給其他就緒態的同優先級線程;當前活動線程轉入等待執行隊列,等待下一個時間片的調度。
          在獨占方式下,當前活動線程一旦獲得執行權,將一直執行下去,直到執行完畢或由于某種原因主動放棄CPU,或者是有一高優先級的線程處于就緒狀態。

          posted @ 2012-06-17 01:15 abin 閱讀(372) | 評論 (0)編輯 收藏

          摘 要:介紹了Servlet多線程機制,通過一個實例并結合Java 的內存模型說明引起Servlet線程不安全的原因,給出了保證Servlet線程安全的三種解決方案,并說明三種方案在實際開發中的取舍。

            關鍵字:Servlet 線程安全 同步 Java內存模型 實例變量 

            Servlet/JSP技術和ASP、PHP等相比,由于其多線程運行而具有很高的執行效率。由于Servlet/JSP默認是以多線程模式執行的,所以,在編寫代碼時需要非常細致地考慮多線程的安全性問題。然而,很多人編寫Servlet/JSP程序時并沒有注意到多線程安全性的問題,這往往造成編寫的程序在少量用戶訪問時沒有任何問題,而在并發用戶上升到一定值時,就會經常出現一些莫明其妙的問題。

            Servlet的多線程機制

            
            Servlet體系結構是建立在Java多線程機制之上的,它的生命周期是由Web容器負責的。當客戶端第一次請求某個Servlet時,Servlet容器將會根據web.xml配置文件實例化這個Servlet類。當有新的客戶端請求該Servlet時,一般不會再實例化該Servlet類,也就是有多個線程在使用這個實例。Servlet容器會自動使用線程池等技術來支持系統的運行,如圖1所示。


          圖1 Servlet線程池

            這樣,當兩個或多個線程同時訪問同一個Servlet時,可能會發生多個線程同時訪問同一資源的情況,數據可能會變得不一致。所以在用Servlet構建的Web應用時如果不注意線程安全的問題,會使所寫的Servlet程序有難以發現的錯誤。

            Servlet的線程安全問題

            Servlet的線程安全問題主要是由于實例變量使用不當而引起的,這里以一個現實的例子來說明。

          Import javax.servlet. *; 
          Import javax.servlet.http. *; 
          Import java.io. *; 
          Public class Concurrent Test extends HttpServlet {PrintWriter output; 
          Public void service (HttpServletRequest request,
          HttpServletResponse response) throws ServletException, IOException {String username;
          Response.setContentType ("text/html; charset=gb2312");
          Username = request.getParameter ("username"); 
          Output = response.getWriter (); 
          Try {Thread. sleep (5000); //為了突出并發問題,在這設置一個延時
          } Catch (Interrupted Exception e){}
          output.println("用戶名:"+Username+"<BR>"); 
          }
          }

            該Servlet中定義了一個實例變量output,在service方法將其賦值為用戶的輸出。當一個用戶訪問該Servlet時,程序會正常的運行,但當多個用戶并發訪問時,就可能會出現其它用戶的信息顯示在另外一些用戶的瀏覽器上的問題。這是一個嚴重的問題。為了突出并發問題,便于測試、觀察,我們在回顯用戶信息時執行了一個延時的操作。假設已在web.xml配置文件中注冊了該Servlet,現有兩個用戶a和b同時訪問該Servlet(可以啟動兩個IE瀏覽器,或者在兩臺機器上同時訪問),即同時在瀏覽器中輸入:

            a: http://localhost: 8080/servlet/ConcurrentTest? Username=a

            b: http://localhost: 8080/servlet/ConcurrentTest? Username=b

            如果用戶b比用戶a回車的時間稍慢一點,將得到如圖2所示的輸出:


          圖2 a用戶和b用戶的瀏覽器輸出

            從圖2中可以看到,Web服務器啟動了兩個線程分別處理來自用戶a和用戶b的請求,但是在用戶a的瀏覽器上卻得到一個空白的屏幕,用戶a的信息顯示在用戶b的瀏覽器上。該Servlet存在線程不安全問題。下面我們就從分析該實例的內存模型入手,觀察不同時刻實例變量output的值來分析使該Servlet線程不安全的原因。

            Java的內存模型JMM(Java Memory Model)JMM主要是為了規定了線程和內存之間的一些關系。根據JMM的設計,系統存在一個主內存(Main Memory),Java中所有實例變量都儲存在主存中,對于所有線程都是共享的。每條線程都有自己的工作內存(Working Memory),工作內存由緩存和堆棧兩部分組成,緩存中保存的是主存中變量的拷貝,緩存可能并不總和主存同步,也就是緩存中變量的修改可能沒有立刻寫到主存中;堆棧中保存的是線程的局部變量,線程之間無法相互直接訪問堆棧中的變量。根據JMM,我們可以將論文中所討論的Servlet實例的內存模型抽象為圖3所示的模型。


          圖3 Servlet實例的JMM模型

            下面根據圖3所示的內存模型,來分析當用戶a和b的線程(簡稱為a線程、b線程)并發執行時,Servlet實例中所涉及變量的變化情況及線程的執行情況,如圖4所示。

          調度時刻 a線程 b線程
          T1 訪問Servlet頁面  
          T2   訪問Servlet頁面
          T3 output=a的輸出username=a休眠5000毫秒,讓出CPU  
          T4   output=b的輸出(寫回主存)username=b休眠5000毫秒,讓出CPU
          T5 在用戶b的瀏覽器上輸出a線程的username的值,a線程終止。  
          T6   在用戶b的瀏覽器上輸出b線程的username的值,b線程終止。
                            圖4 Servlet實例的線程調度情況

            從圖4中可以清楚的看到,由于b線程對實例變量output的修改覆蓋了a線程對實例變量output的修改,從而導致了用戶a的信息顯示在了用戶b的瀏覽器上。如果在a線程執行輸出語句時,b線程對output的修改還沒有刷新到主存,那么將不會出現圖2所示的輸出結果,因此這只是一種偶然現象,但這更增加了程序潛在的危險性。 

           

           

          設計線程安全的Servlet

            通過上面的分析,我們知道了實例變量不正確的使用是造成Servlet線程不安全的主要原因。下面針對該問題給出了三種解決方案并對方案的選取給出了一些參考性的建議。

            1、實現 SingleThreadModel 接口

            該接口指定了系統如何處理對同一個Servlet的調用。如果一個Servlet被這個接口指定,那么在這個Servlet中的service方法將不會有兩個線程被同時執行,當然也就不存在線程安全的問題。這種方法只要將前面的Concurrent Test類的類頭定義更改為:

          Public class Concurrent Test extends HttpServlet implements SingleThreadModel {
          …………
          }


            2、同步對共享數據的操作

            使用synchronized 關鍵字能保證一次只有一個線程可以訪問被保護的區段,在本論文中的Servlet可以通過同步塊操作來保證線程的安全。同步后的代碼如下: 

          …………
          Public class Concurrent Test extends HttpServlet { …………
          Username = request.getParameter ("username"); 
          Synchronized (this){
          Output = response.getWriter (); 
          Try {
          Thread. Sleep (5000);
          } Catch (Interrupted Exception e){}
          output.println("用戶名:"+Username+"<BR>"); 
          } 
          }
          }


            3、避免使用實例變量

            本實例中的線程安全問題是由實例變量造成的,只要在Servlet里面的任何方法里面都不使用實例變量,那么該Servlet就是線程安全的。

            修正上面的Servlet代碼,將實例變量改為局部變量實現同樣的功能,代碼如下:

          …… 
          Public class Concurrent Test extends HttpServlet {public void service (HttpServletRequest request, HttpServletResponse 
          Response) throws ServletException, IOException {
          Print Writer output; 
          String username;
          Response.setContentType ("text/html; charset=gb2312");
          …… 
          } 
          }


            對上面的三種方法進行測試,可以表明用它們都能設計出線程安全的Servlet程序。但是,如果一個Servlet實現了SingleThreadModel接口,Servlet引擎將為每個新的請求創建一個單獨的Servlet實例,這將引起大量的系統開銷。SingleThreadModel在Servlet2.4中已不再提倡使用;同樣如果在程序中使用同步來保護要使用的共享的數據,也會使系統的性能大大下降。這是因為被同步的代碼塊在同一時刻只能有一個線程執行它,使得其同時處理客戶請求的吞吐量降低,而且很多客戶處于阻塞狀態。另外為保證主存內容和線程的工作內存中的數據的一致性,要頻繁地刷新緩存,這也會大大地影響系統的性能。所以在實際的開發中也應避免或最小化 Servlet 中的同步代碼;在Serlet中避免使用實例變量是保證Servlet線程安全的最佳選擇。從Java 內存模型也可以知道,方法中的臨時變量是在棧上分配空間,而且每個線程都有自己私有的棧空間,所以它們不會影響線程的安全。

            小結

            Servlet的線程安全問題只有在大量的并發訪問時才會顯現出來,并且很難發現,因此在編寫Servlet程序時要特別注意。線程安全問題主要是由實例變量造成的,因此在Servlet中應避免使用實例變量。如果應用程序設計無法避免使用實例變量,那么使用同步來保護要使用的實例變量,但為保證系統的最佳性能,應該同步可用性最小的代碼路徑。

           

          posted @ 2012-06-14 09:12 abin 閱讀(545) | 評論 (0)編輯 收藏

          java動態代理和cglib動態代理在工作中用代理的地方非常多,但一直還沒仔細來看代理的原理,今天被同事提到,所以自己開始仔細研究了一下這兩者代理都做了些什么工作,并通過編寫測試用例的方式來對兩種代理原理作理解。
          在自行看代碼之前,初步問了一下朋友,大概解釋這兩者區別是,java動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。而cglib動態代理是利用asm開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。這是朋友說的,我并沒自己實驗過,所以也沒映象,所以開始自己動手實踐之:
          java動態代理

          使用方法:

          接口:

          public interface Call {
          void doCall(String doCall);
          }

          public interface Processor {
          void doProcess(String doProcess);
          }

          實現類:

          public class ServiceImpl implements Call, Processor {

          public void doCall(String doCall) {
          System.out.println("doCall");
          }

          public void doProcess(String doProcess) {
          System.out.println("doProcess");
          }
          }

          具體代理Handler:

          public class ServiceHandler implements InvocationHandler {

          private Call callService;

          public ServiceHandler(Call callService) {
          this.callService = callService;
          }

          public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable {
          System.out.println("proxyMethod=" + method.getName());
          Object obj = method.invoke(this.callService, args);
          System.out.println("after invoke!");
          return obj;
          }

          }

          使用java動態代理:

          public class JdkProxyTest {   
          @Test
          public void testJdkProxy() {
          Call call = new ServiceImpl();
          ServiceHandler handler = new ServiceHandler(call);
          Call callProxy = (Call) Proxy.newProxyInstance(call.getClass().getClassLoader(),
          new Class[]{Call.class}, handler);
          callProxy.doCall("test");
          }
          }

          最終效果就是執行代理接口的doCall方法之前,該方法被ServiceHandler給處理了。

          通過查看java.lang.reflect.Proxy代碼,大致擬了一下它的實現原理:
          1. 取到new Class[]{Call.class}這里所有接口,通過Class.forName把接口類加載到JVM,放到內部Set里保存,把接口的完善名字保存,帶包名的接口名字,并以把這組接口名稱數組轉換成List作為key,用于下面生成代理類后保存到內部Map的key.也就是相當于這一組的接口名稱對應的一個生成的代理類
          2. 主要是從內存里找是否之前已經生成好了這同一組接口的代理類,如果有就直接拿出。這里第一次是需要新建立的,所以開始創建代理,首先檢查代理目標接口的訪問控制符是否是默認包級別的,如果是就需要給生成的代理類設置目標接口同樣的包名,才能默認訪問這種級別下的接口。如果這種有默認訪問控制標識符的目標接口,又有不同包名的目標接口,則會報出錯誤。否則其它情況,是給的無包名的代理類,生成的代理類的默認名稱是$Proxy開頭加Proxy里標識唯一類名的數字,是靜態long型變量,每次生成一次代理類會累加
          3. 調用ProxyGenerator.generateProxyClass(proxyName, interfaces)動態生成class字節碼類,該類相當于是Proxy的子類,實現了需要代理的接口方法,并在每個方法里調用了InvocationHandler的invoke方法,而我們自己實現的InvocationHandler接口類里完成了以反射方式最終對目標業務類的接口方法進行調用。所以此種方式實現的動態代理只能代理接口方法,對具體類的代理不能實現。

           

          http://hi.baidu.com/dobug/blog/item/493f817e802479340cd7dab9.html

          posted @ 2012-06-07 21:51 abin 閱讀(710) | 評論 (0)編輯 收藏

          sleep,wait,suspend,resume,join,interrupt,stop,destory
          posted @ 2012-06-06 14:19 abin 閱讀(346) | 評論 (0)編輯 收藏

          僅列出標題
          共50頁: First 上一頁 37 38 39 40 41 42 43 44 45 下一頁 Last 
          主站蜘蛛池模板: 洛阳市| 兖州市| 密山市| 汝阳县| 延吉市| 双桥区| 桐城市| 徐闻县| 西乡县| 宁德市| 黑水县| 黄大仙区| 宁阳县| 长葛市| 漳浦县| 南昌市| 左云县| 连南| 旬阳县| 巨野县| 南京市| 呼伦贝尔市| 左贡县| 马山县| 新巴尔虎右旗| 洞头县| 志丹县| 万宁市| 怀安县| 衡东县| 霍林郭勒市| 盈江县| 抚顺县| 晋江市| 花莲市| 昆山市| 安达市| 大田县| 广昌县| 漳浦县| 武乡县|