少年阿賓那些青春的歲月 |
關(guān)于springmvc的幾個(gè)注解,基本都在org.springframework:spring-webmvc:3.1.4.REALEASE這個(gè)包里面處理的,具體是在org.springframework.web.servlet.mvc.annotation這個(gè)包里面的AnnotationMethodHandlerAdapter這個(gè)類里面處理的。
1、使用<context:annotation-config />簡化配置 Spring2.1添加了一個(gè)新的context的Schema命名空間,該命名空間對注釋驅(qū)動(dòng)、屬性文件引入、加載期織入等功能提供了便捷的配置。我們知道注釋本身是不會(huì)做任何事情的,它僅提供元數(shù)據(jù)信息。要使元數(shù)據(jù)信息真正起作用,必須讓負(fù)責(zé)處理這些元數(shù)據(jù)的處理器工作起來。 AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor就是處理這些注釋元數(shù)據(jù)的處理器。但是直接在Spring配置文件中定義這些Bean顯得比較笨拙。Spring為我們提供了一種方便的注冊這些BeanPostProcessor的方式,這就是<context:annotation-config />: <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <context:annotation-config /> </beans>
<context:annotationconfig />將隱式地向Spring容器注冊AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、 PersistenceAnnotationBeanPostProcessor以及RequiredAnnotationBeanPostProcessor這4個(gè)BeanPostProcessor。 2、使用<context:component-scan />讓Bean定義注解工作起來 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <context:component-scan base-package="com.kedacom.ksoa" /> </beans> 這里,所有通過<bean>元素定義Bean的配置內(nèi)容已經(jīng)被移除,僅需要添加一行<context:component-scan />配置就解決所有問題了——Spring XML配置文件得到了極致的簡化(當(dāng)然配置元數(shù)據(jù)還是需要的,只不過以注釋形式存在罷了)。<context:component-scan />的base-package屬性指定了需要掃描的類包,類包及其遞歸子包中所有的類都會(huì)被處理。 < context:component-scan />還允許定義過濾器將基包下的某些類納入或排除。Spring支持以下4種類型的過濾方式: 過濾器類型 表達(dá)式范例 說明 注解 org.example.SomeAnnotation將所有使用SomeAnnotation注解的類過濾出來 類名指定 org.example.SomeClass過濾指定的類 正則表達(dá)式 com\.kedacom\.spring\.annotation\.web\..*通過正則表達(dá)式過濾一些類 AspectJ表達(dá)式 org.example..*Service+通過AspectJ表達(dá)式過濾一些類 以正則表達(dá)式為例,我列舉一個(gè)應(yīng)用實(shí)例: <context:component-scan base-package="com.casheen.spring.annotation"> <context:exclude-filter type="regex" expression="com\.casheen\.spring\.annotation\.web\..*" /> </context:component-scan> 值得注意的是<context:component-scan />配置項(xiàng)不但啟用了對類包進(jìn)行掃描以實(shí)施注釋驅(qū)動(dòng)Bean定義的功能,同時(shí)還啟用了注釋驅(qū)動(dòng)自動(dòng)注入的功能(即還隱式地在內(nèi)部注冊了AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor),因此當(dāng)使用<context:component-scan />后,就可以將<context:annotation-config />移除了.
Java 虛擬機(jī)(JVM)是可運(yùn)行Java 代碼的假想計(jì)算機(jī)。只要根據(jù)JVM規(guī)格描述將解釋器移植到特定的計(jì)算機(jī)上,就能保證經(jīng)過編譯的任何Java代碼能夠在該系統(tǒng)上運(yùn)行。本文首先簡要介紹從Java文件的編譯到最終執(zhí)行的過程,隨后對JVM規(guī)格描述作一說明。
一.Java源文件的編譯、下載 、解釋和執(zhí)行 Java應(yīng)用程序的開發(fā)周期包括編譯、下載 、解釋和執(zhí)行幾個(gè)部分。Java編譯程序?qū)ava源程序翻譯為JVM可執(zhí)行代碼?字節(jié)碼。這一編譯過程同C/C++ 的 編譯有些不同。當(dāng)C編譯器編譯生成一個(gè)對象的代碼時(shí),該代碼是為在某一特定硬件平臺(tái)運(yùn)行而產(chǎn)生的。因此,在編譯過程中,編譯程序通過查表將所有對符號(hào)的引 用轉(zhuǎn)換為特定的內(nèi)存偏移量,以保證程序運(yùn)行。Java編譯器卻不將對變量和方法的引用編譯為數(shù)值引用,也不確定程序執(zhí)行過程中的內(nèi)存布局,而是將這些符號(hào) 引用信息保留在字節(jié)碼中,由解釋器在運(yùn)行過程中創(chuàng)立內(nèi)存布局,然后再通過查表來確定一個(gè)方法所在的地址。這樣就有效的保證了Java的可移植性和安全 性。 運(yùn)行JVM字節(jié)碼的工作是由解釋器來完成的。解釋執(zhí)行過程分三部進(jìn)行:代碼的裝入、代碼的校驗(yàn)和代碼的執(zhí)行。裝入代碼的工作由"類裝載器"(class loader)完成。類裝載器負(fù)責(zé)裝入運(yùn)行一個(gè)程序需要的所有代碼,這也包括程序代碼中的類所繼承的類和被其調(diào)用的類。當(dāng)類裝載器裝入一個(gè)類時(shí),該類被放 在自己的名字空間中。除了通過符號(hào)引用自己名字空間以外的類,類之間沒有其他辦法可以影響其他類。在本臺(tái)計(jì)算機(jī)上的所有類都在同一地址空間內(nèi),而所有從外 部引進(jìn)的類,都有一個(gè)自己獨(dú)立的名字空間。這使得本地類通過共享相同的名字空間獲得較高的運(yùn)行效率,同時(shí)又保證它們與從外部引進(jìn)的類不會(huì)相互影響。當(dāng)裝入 了運(yùn)行程序需要的所有類后,解釋器便可確定整個(gè)可執(zhí)行程序的內(nèi)存布局。解釋器為符號(hào)引用同特定的地址空間建立對應(yīng)關(guān)系及查詢表。通過在這一階段確定代碼的 內(nèi)存布局,Java很好地解決了由超類改變而使子類崩潰的問題,同時(shí)也防止了代碼對地址的非法訪問。 隨后,被裝入的代碼由字節(jié)碼校驗(yàn)器進(jìn)行檢查。校驗(yàn)器可發(fā)現(xiàn)操作數(shù)棧溢出,非法數(shù)據(jù)類型轉(zhuǎn)化等多種錯(cuò)誤。通過校驗(yàn)后,代碼便開始執(zhí)行了。 Java字節(jié)碼的執(zhí)行有兩種方式: 1.即時(shí)編譯方式:解釋器先將字節(jié)碼編譯成機(jī)器碼,然后再執(zhí)行該機(jī)器碼。 2.解釋執(zhí)行方式:解釋器通過每次解釋并執(zhí)行一小段代碼來完成Java字節(jié)碼程 序的所有操作。 通常采用的是第二種方法。由于JVM規(guī)格描述具有足夠的靈活性,這使得將字節(jié)碼翻譯為機(jī)器代碼的工作 具有較高的效率。對于那些對運(yùn)行速度要求較高的應(yīng)用程序,解釋器可將Java字節(jié)碼即時(shí)編譯為機(jī)器碼,從而很好地保證了Java代碼的可移植性和高性能。 二.JVM規(guī)格描述 JVM的設(shè)計(jì)目標(biāo)是提供一個(gè)基于抽象規(guī)格描述的計(jì)算機(jī)模型,為解釋程序開發(fā)人員提很好的靈活性,同時(shí)也確保Java代碼可在符合該規(guī)范的任何系統(tǒng)上運(yùn) 行。JVM對其實(shí)現(xiàn)的某些方面給出了具體的定義,特別是對Java可執(zhí)行代碼,即字節(jié)碼(Bytecode)的格式給出了明確的規(guī)格。這一規(guī)格包括操作碼 和操作數(shù)的語法和數(shù)值、標(biāo)識(shí)符的數(shù)值表示方式、以及Java類文件中的Java對象、常量緩沖池在JVM的存儲(chǔ) 映象。這些定義為JVM解釋器開發(fā)人員提供了所需的信息和開發(fā)環(huán)境。Java的設(shè)計(jì)者希望給開發(fā)人員以隨心所欲使用Java的自由。 JVM定義了控制Java代碼解釋執(zhí)行和具體實(shí)現(xiàn)的五種規(guī)格,它們是: JVM指令系統(tǒng) JVM寄存器 JVM棧結(jié)構(gòu) JVM碎片回收堆 JVM存儲(chǔ) 區(qū) 2.1JVM指令系統(tǒng) JVM指令系統(tǒng)同其他計(jì)算機(jī)的指令系統(tǒng)極其相似。Java指令也是由 操作碼和操作數(shù)兩部分組成。操作碼為8位二進(jìn)制數(shù),操作數(shù)進(jìn)緊隨在操作碼的后面,其長度根據(jù)需要而不同。操作碼用于指定一條指令操作的性質(zhì)(在這里我們采 用匯編符號(hào)的形式進(jìn)行說明),如iload表示從存儲(chǔ)器中裝入一個(gè)整數(shù),anewarray表示為一個(gè)新數(shù)組分配空間,iand表示兩個(gè)整數(shù)的" 與",ret用于流程控制,表示從對某一方法的調(diào)用中返回。當(dāng)長度大于8位時(shí),操作數(shù)被分為兩個(gè)以上字節(jié)存放。JVM采用了"big endian"的編碼方式來處理這種情況,即高位bits存放在低字節(jié)中。這同 Motorola及其他的RISC CPU采用的編碼方式是一致的,而與Intel采用的"little endian "的編碼方式即低位bits存放在低位字節(jié)的方法不同。 Java指令系統(tǒng)是以Java語言的實(shí)現(xiàn)為目的設(shè)計(jì)的,其中包含了用于調(diào)用方法和監(jiān)視多先程系統(tǒng)的指令。Java的8位操作碼的長度使得JVM最多有256種指令,目前已使用了160多種操作碼。 2.2JVM指令系統(tǒng) 所有的CPU均包含用于保存系統(tǒng)狀態(tài)和處理器所需信息的寄存器組。如果虛擬機(jī)定義較多的寄存器,便可以從中得到更多的信息而不必對棧或內(nèi)存進(jìn)行訪問,這 有利于提高運(yùn)行速度。然而,如果虛擬機(jī)中的寄存器比實(shí)際CPU的寄存器多,在實(shí)現(xiàn)虛擬機(jī)時(shí)就會(huì)占用處理器大量的時(shí)間來用常規(guī)存儲(chǔ)器模擬寄存器,這反而會(huì)降 低虛擬機(jī)的效率。針對這種情況,JVM只設(shè)置了4個(gè)最為常用的寄存器。它們是: pc程序計(jì)數(shù)器 optop操作數(shù)棧頂指針 frame當(dāng)前執(zhí)行環(huán)境指針 vars指向當(dāng)前執(zhí)行環(huán)境中第一個(gè)局部變量的指針 所有寄存器均為32位。pc用于記錄程序的執(zhí)行。optop,frame和vars用于記錄指向Java棧區(qū)的指針。 2.3JVM棧結(jié)構(gòu) 作為基于棧結(jié)構(gòu)的計(jì)算機(jī),Java棧是JVM存儲(chǔ)信息的主要方法。當(dāng)JVM得到一個(gè)Java字節(jié)碼應(yīng)用程序后,便為該代碼中一個(gè)類的每一個(gè)方法創(chuàng)建一個(gè)棧框架,以保存該方法的狀態(tài)信息。每個(gè)棧框架包括以下三類信息: 局部變量 執(zhí)行環(huán)境 操作數(shù)棧 局部變量用于存儲(chǔ)一個(gè)類的方法中所用到的局部變量。vars寄存器指向該變量表中的第一個(gè)局部變量。 執(zhí)行環(huán)境用于保存解釋器對Java字節(jié)碼進(jìn)行解釋過程中所需的信息。它們是:上次調(diào)用的方法、局部變量指針和操作數(shù)棧的棧頂和棧底指針。執(zhí)行環(huán)境是一個(gè) 執(zhí)行一個(gè)方法的控制中心。例如:如果解釋器要執(zhí)行iadd(整數(shù)加法),首先要從frame寄存器中找到當(dāng)前執(zhí)行環(huán)境,而后便從執(zhí)行環(huán)境中找到操作數(shù)棧, 從棧頂彈出兩個(gè)整數(shù)進(jìn)行加法運(yùn)算,最后將結(jié)果壓入棧頂。 操作數(shù)棧用于存儲(chǔ)運(yùn)算所需操作數(shù)及運(yùn)算的結(jié)果。 2.4JVM碎片回收堆 Java類的實(shí)例所需的存儲(chǔ)空間是在堆上分配的。解釋器具體承擔(dān)為類實(shí)例分配空間的工作。解釋器在為一個(gè)實(shí)例分配完存儲(chǔ)空間后,便開始記錄對該實(shí)例所占用的內(nèi)存區(qū)域的使用。一旦對象使用完畢,便將其回收到堆中。 在Java語言中,除了new語句外沒有其他方法為一對象申請和釋放內(nèi)存。對內(nèi)存進(jìn)行釋放和回收的工作是由Java運(yùn)行系統(tǒng)承擔(dān)的。這允許Java運(yùn)行 系統(tǒng)的設(shè)計(jì)者自己決定碎片回收的方法。在SUN公司開發(fā)的Java解釋器和Hot Java環(huán)境中,碎片回收用后臺(tái)線程的方式來執(zhí)行。這不但為運(yùn)行系統(tǒng)提供了良好的性能,而且使程序設(shè)計(jì)人員擺脫了自己控制內(nèi)存使用的風(fēng)險(xiǎn)。 2.5JVM存儲(chǔ)區(qū) JVM有兩類存儲(chǔ)區(qū):常量緩沖池和方法區(qū)。常量緩沖池用于存儲(chǔ)類名稱、方法和字段名稱以及串常量。方法區(qū)則用于存儲(chǔ)Java方法的字節(jié)碼。對于這兩種存 儲(chǔ)區(qū)域具體實(shí)現(xiàn)方式在JVM規(guī)格中沒有明確規(guī)定。這使得Java應(yīng)用程序的存儲(chǔ)布局必須在運(yùn)行過程中確定,依賴于具體平臺(tái)的實(shí)現(xiàn)方式。 JVM是為Java字節(jié)碼定義的一種獨(dú)立于具體平臺(tái)的規(guī)格描述,是Java平臺(tái)獨(dú)立性的基礎(chǔ)。目前的JVM還存在一些限制和不足,有待于進(jìn)一步的完善,但無論如何,JVM的思想是成功的。 對比分析:如果把Java原程序想象成我們的C++ 原 程序,Java原程序編譯后生成的字節(jié)碼就相當(dāng)于C++原程序編譯后的80x86的機(jī)器碼(二進(jìn)制程序文件),JVM虛擬機(jī)相當(dāng)于80x86計(jì)算機(jī)系 統(tǒng),Java解釋器相當(dāng)于80x86CPU。在80x86CPU上運(yùn)行的是機(jī)器碼,在Java解釋器上運(yùn)行的是Java字節(jié)碼。 Java解釋器相當(dāng)于運(yùn)行Java字節(jié)碼的“CPU”,但該“CPU”不是通過硬件實(shí)現(xiàn)的,而是用軟件實(shí)現(xiàn)的。Java解釋器實(shí)際上就是特定的平臺(tái)下的一 個(gè)應(yīng)用程序。只要實(shí)現(xiàn)了特定平臺(tái)下的解釋器程序,Java字節(jié)碼就能通過解釋器程序在該平臺(tái)下運(yùn)行,這是Java跨平臺(tái)的根本。當(dāng)前,并不是在所有的平臺(tái) 下都有相應(yīng)Java解釋器程序,這也是Java并不能在所有的平臺(tái)下都能運(yùn)行的原因,它只能在已實(shí)現(xiàn)了Java解釋器程序的平臺(tái)下運(yùn)行。 Java主要靠Java虛擬機(jī)(JVM)在目標(biāo)碼級(jí)實(shí)現(xiàn)平臺(tái)無關(guān)性。JVM是一種抽象機(jī)器,它附著在具體操作系統(tǒng)之上,本身具有一套虛機(jī)器指令,并有自己的棧、寄存器組等。但JVM通常是在軟件上而不是在硬件上實(shí)現(xiàn)。(目前,SUN系統(tǒng)公司已經(jīng)設(shè)計(jì)實(shí)現(xiàn)了Java芯片,主要使用在網(wǎng)絡(luò)計(jì)算機(jī)NC上。另外,Java芯片的出現(xiàn)也會(huì)使Java更容易嵌入到家用電器中。)JVM是Java平臺(tái)無關(guān)的基礎(chǔ),在JVM上,有一個(gè)Java解釋器用來解釋Java編譯器編譯后的程序。Java編程人員在編寫完軟件后,通過Java編譯器將Java源程序編譯為JVM的字節(jié)代碼。任何一臺(tái)機(jī)器只要配備了Java解釋器,就可以運(yùn)行這個(gè)程序,而不管這種字節(jié)碼是在何種平臺(tái)上生成的(過程如圖1所示)。另外,Java采用的是基于IEEE標(biāo)準(zhǔn)的數(shù)據(jù)類型。通過JVM保證數(shù)據(jù)類型的一致性,也確保了Java的平臺(tái)無關(guān)性。 簡單說,java的解釋器只是一個(gè)基于虛擬機(jī)jvm平臺(tái)的程序
由于JDK的安全檢查耗時(shí)較多.所以通過setAccessible(true)的方式關(guān)閉安全檢查就可以達(dá)到提升反射速度的目的
在面向?qū)ο缶幊讨? 最通常的方法是一個(gè)new操作符產(chǎn)生一個(gè)對象實(shí)例,new操作符就是用來構(gòu)造對象實(shí)例的。但是在一些情況下, new操作符直接生成對象會(huì)帶來一些問題。舉例來說, 許多類型對象的創(chuàng)造需要一系列的步驟: 你可能需要計(jì)算或取得對象的初始設(shè)置; 選擇生成哪個(gè)子對象實(shí)例; 或在生成你需要的對象之前必須先生成一些輔助功能的對象。 在這些情況,新對象的建立就是一個(gè) “過程”,不僅是一個(gè)操作,像一部大機(jī)器中的一個(gè)齒輪傳動(dòng)。 模式的問題:你如何能輕松方便地構(gòu)造對象實(shí)例,而不必關(guān)心構(gòu)造對象實(shí)例的細(xì)節(jié)和復(fù)雜過程呢? 解決方案:建立一個(gè)工廠來創(chuàng)建對象 實(shí)現(xiàn): 一、引言 4)抽象工廠模式時(shí)代:隨著客戶的要求越來越高,寶馬車必須配置空調(diào)。于是這個(gè)工廠開始生產(chǎn)寶馬車和需要的空調(diào)。 最終是客戶只要對寶馬的銷售員說:我要523i空調(diào)車,銷售員就直接給他523i空調(diào)車了。而不用自己去創(chuàng)建523i空調(diào)車寶馬車. 這就是工廠模式。 二、分類 1)簡單工廠模式(Simple Factory) 這三種模式從上到下逐步抽象,并且更具一般性。 將簡單工廠模式(Simple Factory)看為工廠方法模式的一種特例,兩者歸為一類。 三、區(qū)別 http://blog.csdn.net/jason0539/article/details/23020989
重排序通常是編譯器或運(yùn)行時(shí)環(huán)境為了優(yōu)化程序性能而采取的對指令進(jìn)行重新排序執(zhí)行的一種手段。重排序分為兩類:編譯期重排序和運(yùn)行期重排序,分別對應(yīng)編譯時(shí)和運(yùn)行時(shí)環(huán)境
在并發(fā)程序中,程序員會(huì)特別關(guān)注不同進(jìn)程或線程之間的數(shù)據(jù)同步,特別是多個(gè)線程同時(shí)修改同一變量時(shí),必須采取可靠的同步或其它措施保障數(shù)據(jù)被正確地修改,這里的一條重要原則是:不要假設(shè)指令執(zhí)行的順序,你無法預(yù)知不同線程之間的指令會(huì)以何種順序執(zhí)行。 但是在單線程程序中,通常我們?nèi)菀准僭O(shè)指令是順序執(zhí)行的,否則可以想象程序會(huì)發(fā)生什么可怕的變化。理想的模型是:各種指令執(zhí)行的順序是唯一且有序的,這個(gè)順序就是它們被編寫在代碼中的順序,與處理器或其它因素?zé)o關(guān),這種模型被稱作順序一致性模型,也是基于馮·諾依曼體系的模型。當(dāng)然,這種假設(shè)本身是合理的,在實(shí)踐中也鮮有異常發(fā)生,但事實(shí)上,沒有哪個(gè)現(xiàn)代多處理器架構(gòu)會(huì)采用這種模型,因?yàn)樗窃谑翘托Я恕6诰幾g優(yōu)化和CPU流水線中,幾乎都涉及到指令重排序。 編譯期重排序 編譯期重排序的典型就是通過調(diào)整指令順序,在不改變程序語義的前提下,盡可能減少寄存器的讀取、存儲(chǔ)次數(shù),充分復(fù)用寄存器的存儲(chǔ)值。 假設(shè)第一條指令計(jì)算一個(gè)值賦給變量A并存放在寄存器中,第二條指令與A無關(guān)但需要占用寄存器(假設(shè)它將占用A所在的那個(gè)寄存器),第三條指令使用A的值且與第二條指令無關(guān)。那么如果按照順序一致性模型,A在第一條指令執(zhí)行過后被放入寄存器,在第二條指令執(zhí)行時(shí)A不再存在,第三條指令執(zhí)行時(shí)A重新被讀入寄存器,而這個(gè)過程中,A的值沒有發(fā)生變化。通常編譯器都會(huì)交換第二和第三條指令的位置,這樣第一條指令結(jié)束時(shí)A存在于寄存器中,接下來可以直接從寄存器中讀取A的值,降低了重復(fù)讀取的開銷。 現(xiàn)代CPU幾乎都采用流水線機(jī)制加快指令的處理速度,一般來說,一條指令需要若干個(gè)CPU時(shí)鐘周期處理,而通過流水線并行執(zhí)行,可以在同等的時(shí)鐘周期內(nèi)執(zhí)行若干條指令,具體做法簡單地說就是把指令分為不同的執(zhí)行周期,例如讀取、尋址、解析、執(zhí)行等步驟,并放在不同的元件中處理,同時(shí)在執(zhí)行單元EU中,功能單元被分為不同的元件,例如加法元件、乘法元件、加載元件、存儲(chǔ)元件等,可以進(jìn)一步實(shí)現(xiàn)不同的計(jì)算并行執(zhí)行。 流水線架構(gòu)決定了指令應(yīng)該被并行執(zhí)行,而不是在順序化模型中所認(rèn)為的那樣。重排序有利于充分使用流水線,進(jìn)而達(dá)到超標(biāo)量的效果。 盡管指令在執(zhí)行時(shí)并不一定按照我們所編寫的順序執(zhí)行,但毋庸置疑的是,在單線程環(huán)境下,指令執(zhí)行的最終效果應(yīng)當(dāng)與其在順序執(zhí)行下的效果一致,否則這種優(yōu)化便會(huì)失去意義。 通常無論是在編譯期還是運(yùn)行期進(jìn)行的指令重排序,都會(huì)滿足上面的原則。 Java存儲(chǔ)模型中的重排序 在Java存儲(chǔ)模型(Java Memory Model, JMM)中,重排序是十分重要的一節(jié),特別是在并發(fā)編程中。JMM通過happens-before法則保證順序執(zhí)行語義,如果想要讓執(zhí)行操作B的線程觀察到執(zhí)行操作A的線程的結(jié)果,那么A和B就必須滿足happens-before原則,否則,JVM可以對它們進(jìn)行任意排序以提高程序性能。 volatile關(guān)鍵字可以保證變量的可見性,因?yàn)閷olatile的操作都在Main Memory中,而Main Memory是被所有線程所共享的,這里的代價(jià)就是犧牲了性能,無法利用寄存器或Cache,因?yàn)樗鼈兌疾皇侨值模瑹o法保證可見性,可能產(chǎn)生臟讀。 volatile還有一個(gè)作用就是局部阻止重排序的發(fā)生,對volatile變量的操作指令都不會(huì)被重排序,因?yàn)槿绻嘏判颍挚赡墚a(chǎn)生可見性問題。 在保證可見性方面,鎖(包括顯式鎖、對象鎖)以及對原子變量的讀寫都可以確保變量的可見性。但是實(shí)現(xiàn)方式略有不同,例如同步鎖保證得到鎖時(shí)從內(nèi)存里重新讀入數(shù)據(jù)刷新緩存,釋放鎖時(shí)將數(shù)據(jù)寫回內(nèi)存以保數(shù)據(jù)可見,而volatile變量干脆都是讀寫內(nèi)存。 Java 內(nèi)存模型由于 ConcurrentHashMap 是建立在 Java 內(nèi)存模型基礎(chǔ)上的,為了更好的理解 ConcurrentHashMap,讓我們首先來了解一下 Java 的內(nèi)存模型。 Java 語言的內(nèi)存模型由一些規(guī)則組成,這些規(guī)則確定線程對內(nèi)存的訪問如何排序以及何時(shí)可以確保它們對線程是可見的。下面我們將分別介紹 Java 內(nèi)存模型的重排序,內(nèi)存可見性和 happens-before 關(guān)系。 重排序內(nèi)存模型描述了程序的可能行為。具體的編譯器實(shí)現(xiàn)可以產(chǎn)生任意它喜歡的代碼 -- 只要所有執(zhí)行這些代碼產(chǎn)生的結(jié)果,能夠和內(nèi)存模型預(yù)測的結(jié)果保持一致。這為編譯器實(shí)現(xiàn)者提供了很大的自由,包括操作的重排序。 編譯器生成指令的次序,可以不同于源代碼所暗示的“顯然”版本。重排序后的指令,對于優(yōu)化執(zhí)行以及成熟的全局寄存器分配算法的使用,都是大有脾益的,它使得程序在計(jì)算性能上有了很大的提升。 重排序類型包括:
內(nèi)存可見性由于現(xiàn)代可共享內(nèi)存的多處理器架構(gòu)可能導(dǎo)致一個(gè)線程無法馬上(甚至永遠(yuǎn))看到另一個(gè)線程操作產(chǎn)生的結(jié)果。所以 Java 內(nèi)存模型規(guī)定了 JVM 的一種最小保證:什么時(shí)候?qū)懭胍粋€(gè)變量對其他線程可見。 在現(xiàn)代可共享內(nèi)存的多處理器體系結(jié)構(gòu)中每個(gè)處理器都有自己的緩存,并周期性的與主內(nèi)存協(xié)調(diào)一致。假設(shè)線程 A 寫入一個(gè)變量值 V,隨后另一個(gè)線程 B 讀取變量 V 的值,在下列情況下,線程 B 讀取的值可能不是線程 A 寫入的最新值:
Happens-before 關(guān)系happens-before 關(guān)系保證:如果線程 A 與線程 B 滿足 happens-before 關(guān)系,則線程 A 執(zhí)行動(dòng)作的結(jié)果對于線程 B 是可見的。如果兩個(gè)操作未按 happens-before 排序,JVM 將可以對他們?nèi)我庵嘏判颉?/p> 下面介紹幾個(gè)與理解 ConcurrentHashMap 有關(guān)的 happens-before 關(guān)系法則:
1.希望復(fù)用一些現(xiàn)存的類,但是接口又與復(fù)用環(huán)境要求不一致。
2.其實(shí)適配器模式有點(diǎn)無奈之舉,在前期設(shè)計(jì)的時(shí)候,我們就不應(yīng)該考慮適配器模式,而應(yīng)該考慮通過重構(gòu)統(tǒng)一接口。 想使用一個(gè)已存在的類,但是該類不符合接口需求;或者需要?jiǎng)?chuàng)建一個(gè)可重用的類,適配沒有提供合適接口的其它類。 適配器模式主要解決的問題就是我們要調(diào)用的接口類型,無法滿足我們新系統(tǒng)的使用需求,這時(shí)候,我們需要將舊系統(tǒng)的接口,通過適配器進(jìn)行轉(zhuǎn)配,達(dá)到支持新接口調(diào)用的目的。 對于這樣的要求,我們通過適配器就可以完成,當(dāng)然如果有多個(gè)接口需要轉(zhuǎn)配,那么我們就需要為每一個(gè)接口提供一個(gè)適配器去完成轉(zhuǎn)換的工作。當(dāng)然具體的調(diào)用過程,我們可以進(jìn)行相應(yīng)的封裝。達(dá)到比較通用的方式去調(diào)用適配器,完成適配服務(wù)。 我們來看看適配的過程。 我們根據(jù)上面的適配器的特點(diǎn)的介紹中,我們來分析下適配器模式的幾類比較適用的使用場景: 1、我們在使用第三方的類庫,或者說第三方的API的時(shí)候,我們通過適配器轉(zhuǎn)換來滿足現(xiàn)有系統(tǒng)的使用需求。 2、我們的舊系統(tǒng)與新系統(tǒng)進(jìn)行集成的時(shí)候,我們發(fā)現(xiàn)舊系統(tǒng)的數(shù)據(jù)無法滿足新系統(tǒng)的需求,那么這個(gè)時(shí)候,我們可能需要適配器,完成調(diào)用需求。 3、我們在使用不同數(shù)據(jù)庫之間進(jìn)行數(shù)據(jù)同步。(我這里只是分析的是通過程序來說實(shí)現(xiàn)的時(shí)候的情況。還有其他的很多種方式[數(shù)據(jù)庫同步])。 我們本節(jié)給出適配器模式的經(jīng)典實(shí)現(xiàn)代碼,我們這里結(jié)合項(xiàng)目中的查詢服務(wù)來進(jìn)行說明,舊系統(tǒng)中提供一個(gè)查詢服務(wù)方法Query();但是我新系統(tǒng)定義底層的數(shù)據(jù)訪問服務(wù)層 的時(shí)候,卻是使用的GetList()方法,并且將之前的返回結(jié)果集合進(jìn)行包裝成泛型的形式來進(jìn)行。
枚舉單例模式:
關(guān)于單例模式的實(shí)現(xiàn)有很多種,網(wǎng)上也分析了如今實(shí)現(xiàn)單利模式最好用枚舉,好處不外乎三點(diǎn):1.線程安全 2.不會(huì)因?yàn)樾蛄谢a(chǎn)生新實(shí)例 3.防止反射攻擊 1.線程安全 下面這段代碼就是聲明枚舉實(shí)例的通常做法,它可能還包含實(shí)例變量和實(shí)例方法,但是為了簡單起見,我并沒有使用這些東西,僅僅需要小心的是如果你正在使用實(shí)例方法,那么你需要確保線程安全(如果它影響到其他對象的狀態(tài)的話)。默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,但是在枚舉中的其他任何方法由程序員自己負(fù)責(zé)。 關(guān)于線程安全的保證,其實(shí)是通過類加載機(jī)制來保證的,我們看看INSTANCE的實(shí)例化時(shí)機(jī),是在static塊中,JVM加載類的過程顯然是線程安全的。 static {}; Code: 0: new #12; //class com/abin/lee/spring/util/Singleton$1 3: dup 4: ldc #14; //String INSTANCE 6: iconst_0 7: invokespecial #15; //Method com/abin/lee/spring/util/Singleton$1."<init>":(Ljava/lang/String;I)V 10: putstatic #19; //Field INSTANCE:Lcom/abin/lee/spring/util/Singleton; 13: iconst_1 14: anewarray #1; //class com/abin/lee/spring/util/Singleton 17: dup 18: iconst_0 19: getstatic #19; //Field INSTANCE:Lcom/abin/lee/spring/util/Singleton; 22: aastore 23: putstatic #21; //Field ENUM$VALUES:[Lcom/abin/lee/spring/util/Singleton; 26: return 線程安全,從反編譯后的類源碼中可以看出也是通過類加載機(jī)制保證的,應(yīng)該是這樣吧 2.不會(huì)因?yàn)樾蛄谢a(chǎn)生新實(shí)例枚舉自己處理序列化 傳統(tǒng)單例存在的另外一個(gè)問題是一旦你實(shí)現(xiàn)了序列化接口,那么它們不再保持單例了,因?yàn)閞eadObject()方法一直返回一個(gè)新的對象就像java的構(gòu)造方法一樣,你可以通過使用readResolve()方法來避免此事發(fā)生,看下面的例子://readResolve to prevent another instance of Singleton private Object readResolve(){ return INSTANCE; } 這樣甚至還可以更復(fù)雜,如果你的單例類維持了其他對象的狀態(tài)的話,因此你需要使他們成為transient的對象。但是枚舉單例,JVM對序列化有保證。優(yōu)點(diǎn):不僅能避免多線程同步問題,而且還能防止反序列化重新創(chuàng)建新的對象 反射攻擊,我有自己試著反射攻擊了以下,不過報(bào)錯(cuò)了...看了下方的反編譯類源碼,明白了,因?yàn)閱卫惖男揎検莂bstract的,所以沒法實(shí)例化。(解決) 靜態(tài)內(nèi)部類: // Correct lazy initialization in Java @ThreadSafe class Foo { private static class HelperHolder { public static Helper helper = new Helper(); } public static Helper getHelper() { return HelperHolder.helper; } } 它利用了內(nèi)部靜態(tài)類只有在被引用的時(shí)候才會(huì)被加載的規(guī)律。 這樣一來,一旦內(nèi)部的HelperHolder被引用了,它就會(huì)首先被JVM加載,進(jìn)行該類的靜態(tài)域的初始化,從而使得Helper這一單例類被初始化。它之所以是線程安全的,也是托了JVM的福,因?yàn)?/span>JVM對于類的加載這一過程是線程安全的。
在看 Java并發(fā)包(java.util.concurrent)中大量使用了CAS操作,CAS即 Compare And Swap(CAS),比較并交換,其中由sun.misc.Unsafe實(shí)現(xiàn),Unsafe類提供了硬件級(jí)別的原子操作,Java無法直接訪問到操作系統(tǒng)底層(如系統(tǒng)硬件等),為此Java使用native方法來擴(kuò)展Java程序的功能。具體實(shí)現(xiàn)使用c++。 牛B,**Unsafe類提供了硬件級(jí)別的原子操作**,但到底是什么意思呢?CAS從字面意思上就有兩部分:1、讀取并比較;2、判斷并決定是否交換。然后把這兩步驟作為原子操作,這樣就能保證線程安全
幾句關(guān)于Unsafe的并發(fā)性。compareAndSwap方法是原子的,并且可用來實(shí)現(xiàn)高性能的、無鎖的數(shù)據(jù)結(jié)構(gòu)。比如,考慮問題:在使用大量線程的共享對象上增長值。... Java是一門安全的編程語言,防止程序員犯很多愚蠢的錯(cuò)誤,它們大部分是基于內(nèi)存管理的。但是,有一種方式可以有意的執(zhí)行一些不安全、容易犯錯(cuò)的操作,那就是使用 Unsafe 類。CAS操作有3個(gè)操作數(shù),內(nèi)存值M,預(yù)期值E,新值U,如果M==E,則將內(nèi)存值修改為B,否則啥都不做。 sun.misc.Unsafe這個(gè)類是如此地不安全,以至于JDK開發(fā)者增加了很多特殊限制來訪問它。它的構(gòu)造器是私有的,工廠方法getUnsafe()的調(diào)用器只能被Bootloader加載。如你在下面代碼片段的第8行所見,這個(gè)家伙甚至沒有被任何類加載器加載,所以它的類加載器是null。它會(huì)拋出SecurityException 異常來阻止侵入者。 public final class Unsafe { ... private Unsafe() {} private static final Unsafe theUnsafe = new Unsafe(); ... public static Unsafe getUnsafe() { Class cc = sun.reflect.Reflection.getCallerClass(2); if (cc.getClassLoader() != null) throw new SecurityException("Unsafe"); return theUnsafe; } ... } 幸運(yùn)的是這里有一個(gè)Unsafe的變量可以被用來取得Unsafe的實(shí)例。我們可以輕松地編寫一個(gè)復(fù)制方法通過反射來實(shí)現(xiàn),如下所示: (http://highlyscalable.wordpress.com/2012/02/02/direct-memory-access-in-java/) public static Unsafe getUnsafe() { try { Field f = Unsafe. class .getDeclaredField( "theUnsafe" ); f.setAccessible( true ); return (Unsafe)f.get( null ); } catch (Exception e) { /* ... */ } } Unsafe一些有用的特性
Unsafe.java是jdk并發(fā)類庫java.util.concurrent包中使用的底層類,該類中的方法都是通過native方法調(diào)用操作系統(tǒng)命令。 |