1.spring對mybatis的事務(wù)管理是怎么支持的
一引颈、事務(wù)的基本原理
Spring事務(wù)的本質(zhì)其實就是數(shù)據(jù)庫對事務(wù)的支持揩尸,沒有數(shù)據(jù)庫的事務(wù)支持偷厦,spring是無法提供事務(wù)功能的霹肝。對于純JDBC操作數(shù)據(jù)庫申钩,想要用到事務(wù)叉袍,可以按照以下步驟進行:
1.獲取連接 Connection con = DriverManager.getConnection()
2.開啟事務(wù)con.setAutoCommit(true/false);
3.執(zhí)行CRUD
4.提交事務(wù)/回滾事務(wù) con.commit() / con.rollback();
5.關(guān)閉連接 conn.close();
使用Spring的事務(wù)管理功能后始锚,我們可以不再寫步驟 2 和 4 的代碼,而是由Spirng 自動完成喳逛。那么Spring是如何在我們書寫的 CRUD 之前和之后開啟事務(wù)
和關(guān)閉事務(wù)的呢瞧捌?解決這個問題,也就可以從整體上理解Spring的事務(wù)管理實現(xiàn)原理了润文。下面簡單地介紹下姐呐,注解方式為例子
1.配置文件開啟注解驅(qū)動,在相關(guān)的類和方法上通過注解@Transactional標(biāo)識典蝌。
2.spring 在啟動的時候會去解析生成相關(guān)的bean曙砂,這時候會查看擁有相關(guān)注解的類和方法,并且為這些類和方法生成代理骏掀,并根據(jù)@Transaction的相關(guān)參數(shù)進行相關(guān)配置注入麦轰,這樣就在代理中為我們把相關(guān)的事務(wù)處理掉了(開啟正常提交事務(wù),異匙┲回滾事務(wù))款侵。
3.真正的數(shù)據(jù)庫層的事務(wù)提交和回滾是通過binlog或者redo log實現(xiàn)的。
二侧纯、Spring 事務(wù)的傳播屬性
所謂spring事務(wù)的傳播屬性新锈,就是定義在存在多個事務(wù)同時存在的時候,spring應(yīng)該如何處理這些事務(wù)的行為眶熬。這些屬性在TransactionDefinition中定義妹笆,具體常量的解釋見下表:
常量名稱 | 常量解釋 |
---|---|
PROPAGATION_REQUIRED | 支持當(dāng)前事務(wù)块请,如果當(dāng)前沒有事務(wù),就新建一個事務(wù)拳缠。這是最常見的選擇墩新,也是 Spring 默認的事務(wù)的傳播。 |
PROPAGATION_REQUIRES_NEW | 新建事務(wù)窟坐,如果當(dāng)前存在事務(wù)海渊,把當(dāng)前事務(wù)掛起。新建的事務(wù)將和被掛起的事務(wù)沒有任何關(guān)系哲鸳,是兩個獨立的事務(wù)臣疑,外層事務(wù)失敗回滾之后,不能回滾內(nèi)層事務(wù)執(zhí)行的結(jié)果徙菠,內(nèi)層事務(wù)失敗拋出異常讯沈,外層事務(wù)捕獲,也可以不處理回滾操作 |
PROPAGATION_SUPPORTS | 支持當(dāng)前事務(wù)婿奔,如果當(dāng)前沒有事務(wù)缺狠,就以非事務(wù)方式執(zhí)行。 |
PROPAGATION_MANDATORY | 支持當(dāng)前事務(wù)萍摊,如果當(dāng)前沒有事務(wù)儒老,就拋出異常。 |
PROPAGATION_NOT_SUPPORTED | 以非事務(wù)方式執(zhí)行操作记餐,如果當(dāng)前存在事務(wù)驮樊,就把當(dāng)前事務(wù)掛起。 |
PROPAGATION_NEVER | 以非事務(wù)方式執(zhí)行片酝,如果當(dāng)前存在事務(wù)囚衔,則拋出異常。 |
PROPAGATION_NESTED | 如果一個活動的事務(wù)存在雕沿,則運行在一個嵌套的事務(wù)中练湿。如果沒有活動事務(wù),則按REQUIRED屬性執(zhí)行审轮。它使用了一個單獨的事務(wù)肥哎,這個事務(wù)擁有多個可以回滾的保存點。內(nèi)部事務(wù)的回滾不會對外部事務(wù)造成影響疾渣。它只對DataSourceTransactionManager事務(wù)管理器起效篡诽。 |
三、數(shù)據(jù)庫隔離級別
隔離級別 | 隔離級別的值 | 導(dǎo)致的問題 |
---|---|---|
Read-Uncommitted | 0 | 導(dǎo)致臟讀 |
Read-Committed | 1 | 避免臟讀榴捡,允許不可重復(fù)讀和幻讀 |
Repeatable-Read | 2 | 避免臟讀杈女,不可重復(fù)讀,允許幻讀 |
Serializable | 3 | 串行化讀,事務(wù)只能一個一個執(zhí)行达椰,避免了臟讀翰蠢、不可重復(fù)讀、幻讀啰劲。執(zhí)行效率慢梁沧,使用時慎重 |
臟讀:一事務(wù)對數(shù)據(jù)進行了增刪改,但未提交蝇裤,另一事務(wù)可以讀取到未提交的數(shù)據(jù)廷支。如果第一個事務(wù)這時候回滾了,那么第二個事務(wù)就讀到了臟數(shù)據(jù)猖辫。
不可重復(fù)讀:一個事務(wù)中發(fā)生了兩次讀操作酥泞,第一次讀操作和第二次操作之間砚殿,另外一個事務(wù)對數(shù)據(jù)進行了修改啃憎,這時候兩次讀取的數(shù)據(jù)是不一致的。
幻讀:第一個事務(wù)對一定范圍的數(shù)據(jù)進行批量修改似炎,第二個事務(wù)在這個范圍增加一條數(shù)據(jù)辛萍,這時候第一個事務(wù)就會丟失對新增數(shù)據(jù)的修改。
總結(jié):
隔離級別越高羡藐,越能保證數(shù)據(jù)的完整性和一致性贩毕,但是對并發(fā)性能的影響也越大。
大多數(shù)的數(shù)據(jù)庫默認隔離級別為 Read Commited仆嗦,比如 SqlServer辉阶、Oracle
少數(shù)數(shù)據(jù)庫默認隔離級別為:Repeatable Read 比如: MySQL InnoDB
四、Spring中的隔離級別
常量 | 解釋 |
---|---|
ISOLATION_DEFAULT | 這是個 PlatfromTransactionManager 默認的隔離級別瘩扼,使用數(shù)據(jù)庫默認的事務(wù)隔離級別谆甜。另外四個與 JDBC 的隔離級別相對應(yīng)。 |
ISOLATION_READ_UNCOMMITTED | 這是事務(wù)最低的隔離級別集绰,它充許另外一個事務(wù)可以看到這個事務(wù)未提交的數(shù)據(jù)规辱。這種隔離級別會產(chǎn)生臟讀,不可重復(fù)讀和幻像讀栽燕。 |
ISOLATION_READ_COMMITTED | 保證一個事務(wù)修改的數(shù)據(jù)提交后才能被另外一個事務(wù)讀取罕袋。另外一個事務(wù)不能讀取該事務(wù)未提交的數(shù)據(jù)。 |
ISOLATION_REPEATABLE_READ | 這種事務(wù)隔離級別可以防止臟讀碍岔,不可重復(fù)讀浴讯。但是可能出現(xiàn)幻像讀。 |
ISOLATION_SERIALIZABLE | 這是花費最高代價但是最可靠的事務(wù)隔離級別蔼啦。事務(wù)被處理為順序執(zhí)行兰珍。 |
五、事務(wù)的嵌套
通過上面的理論知識的鋪墊,我們大致知道了數(shù)據(jù)庫事務(wù)和spring事務(wù)的一些屬性和特點掠河,接下來我們通過分析一些嵌套事務(wù)的場景亮元,來深入理解spring事務(wù)傳播的機制。
假設(shè)外層事務(wù) Service A 的 Method A() 調(diào)用 內(nèi)層Service B 的 Method B()
PROPAGATION_REQUIRED(spring 默認)
如果ServiceB.methodB() 的事務(wù)級別定義為 PROPAGATION_REQUIRED唠摹,那么執(zhí)行 ServiceA.methodA() 的時候spring已經(jīng)起了事務(wù)爆捞,這時調(diào)用 ServiceB.methodB(),ServiceB.methodB() 看到自己已經(jīng)運行在 ServiceA.methodA() 的事務(wù)內(nèi)部勾拉,就不再起新的事務(wù)煮甥。
假如 ServiceB.methodB() 運行的時候發(fā)現(xiàn)自己沒有在事務(wù)中,他就會為自己分配一個事務(wù)藕赞。
這樣成肘,在 ServiceA.methodA() 或者在 ServiceB.methodB() 內(nèi)的任何地方出現(xiàn)異常启泣,事務(wù)都會被回滾加派。
PROPAGATION_REQUIRES_NEW
比如我們設(shè)計 ServiceA.methodA() 的事務(wù)級別為 PROPAGATION_REQUIRED梳猪,ServiceB.methodB() 的事務(wù)級別為 PROPAGATION_REQUIRES_NEW逗旁。
那么當(dāng)執(zhí)行到 ServiceB.methodB() 的時候玖喘,ServiceA.methodA() 所在的事務(wù)就會掛起戈次,ServiceB.methodB() 會起一個新的事務(wù)蔑歌,等待 ServiceB.methodB() 的事務(wù)完成以后碌上,它才繼續(xù)執(zhí)行均芽。
他與 PROPAGATION_REQUIRED 的事務(wù)區(qū)別在于事務(wù)的回滾程度了丘逸。因為 ServiceB.methodB() 是新起一個事務(wù),那么就是存在兩個不同的事務(wù)掀宋。如果 ServiceB.methodB() 已經(jīng)提交深纲,那么 ServiceA.methodA() 失敗回滾,ServiceB.methodB() 是不會回滾的劲妙。如果 ServiceB.methodB() 失敗回滾湃鹊,如果他拋出的異常被 ServiceA.methodA() 捕獲,ServiceA.methodA() 事務(wù)仍然可能提交(主要看B拋出的異常是不是A會回滾的異常)是趴。
PROPAGATION_SUPPORTS
假設(shè)ServiceB.methodB() 的事務(wù)級別為 PROPAGATION_SUPPORTS涛舍,那么當(dāng)執(zhí)行到ServiceB.methodB()時,如果發(fā)現(xiàn)ServiceA.methodA()已經(jīng)開啟了一個事務(wù)唆途,則加入當(dāng)前的事務(wù)富雅,如果發(fā)現(xiàn)ServiceA.methodA()沒有開啟事務(wù),則自己也不開啟事務(wù)肛搬。這種時候没佑,內(nèi)部方法的事務(wù)性完全依賴于最外層的事務(wù)。
PROPAGATION_NESTED
現(xiàn)在的情況就變得比較復(fù)雜了, ServiceB.methodB() 的事務(wù)屬性被配置為 PROPAGATION_NESTED, 此時兩者之間又將如何協(xié)作呢? ServiceB#methodB 如果 rollback, 那么內(nèi)部事務(wù)(即 ServiceB#methodB) 將回滾到它執(zhí)行前的 SavePoint 而外部事務(wù)(即 ServiceA#methodA) 可以有以下兩種處理方式:
a温赔、捕獲異常蛤奢,執(zhí)行異常分支邏輯
void methodA() {
try {
ServiceB.methodB();
} catch (SomeException) {
// 執(zhí)行其他業(yè)務(wù), 如 ServiceC.methodC();
}
}
這種方式也是嵌套事務(wù)最有價值的地方, 它起到了分支執(zhí)行的效果, 如果 ServiceB.methodB 失敗, 那么執(zhí)行 ServiceC.methodC(), 而 ServiceB.methodB 已經(jīng)回滾到它執(zhí)行之前的 SavePoint, 所以不會產(chǎn)生臟數(shù)據(jù)(相當(dāng)于此方法從未執(zhí)行過), 這種特性可以用在某些特殊的業(yè)務(wù)中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都沒有辦法做到這一點。
b、 外部事務(wù)回滾/提交 代碼不做任何修改, 那么如果內(nèi)部事務(wù)(ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滾到它執(zhí)行之前的 SavePoint(在任何情況下都會如此), 外部事務(wù)(即 ServiceA#methodA) 將根據(jù)具體的配置決定自己是 commit 還是 rollback
另外三種事務(wù)傳播屬性基本用不到啤贩,在此不做分析待秃。
六、總結(jié)
對于項目中需要使用到事務(wù)的地方痹屹,我建議開發(fā)者還是使用spring的TransactionCallback接口來實現(xiàn)事務(wù)章郁,不要盲目使用spring事務(wù)注解,如果一定要使用注解志衍,那么一定要對spring事務(wù)的傳播機制和隔離級別有個詳細的了解暖庄,否則很可能發(fā)生意想不到的效果。
2.spring的bean的生命周期
Spring上下問中的Bean的生命周期與Servlet的生命周期相似楼肪,實例化培廓,初始化init、接收請求service春叫、銷毀destroy肩钠;
- 實例化一個Bean,就是我們通常說的new
- 按照Spring上下問對實例化的Bean進行配置象缀,也就是IOC注入
3.如果這個Bean實現(xiàn)了BeanNameAware接口蔬将,會調(diào)用它實現(xiàn)的setBeanName(String beanId)方法爷速,此處傳遞的是Spring配置文件中的Bean的id
4.如果這個Bean實現(xiàn)了BeanFactoryAware接口央星,會調(diào)用它實現(xiàn)的setBeanFactory(),傳遞的是Spring工廠本身(可以用這個方法獲取到其他的Bean)
5.如果這個Bean實現(xiàn)了ApplicationContextAware接口,會調(diào)用setApplicationContext(ApplicationContext)方法惫东,傳入Spring上下文莉给,該方式同樣可以實現(xiàn)步驟4,但比4更好廉沮,因為ApplicationContext是BeanFactory的子接口颓遏,有更多的實現(xiàn)方法
6.如果這個Bean關(guān)聯(lián)了BeanPostProcessor接口,將會調(diào)用postProcessBeforeInitialization(Object obj,String s)方法滞时,BeanPostProcessor經(jīng)常被用作是Bean內(nèi)容的更改叁幢,并且由于這個是在Bean初始化結(jié)束是調(diào)用的After方法,也可用與內(nèi)存或緩存技術(shù)
7.如果這個Bean在Spring配置文件中配置了init-method屬性會自動調(diào)用其配置的初始化方法
8.如果這個Bean關(guān)聯(lián)了BeanPostProcessor接口坪稽,將會調(diào)用postAfterInitialization(Object obj,String s)方法
注意:以上工作完成以后就可以用這個Bean了曼玩,那這個Bean是一個single的,所以一般情況下我們調(diào)用同一個id的Bean會是在內(nèi)容地址相同的實例
9.當(dāng)Bean不再需要時窒百,會經(jīng)過清理階段黍判,如果Bean實現(xiàn)了DisposableBean接口,會調(diào)用其實現(xiàn)的destroy方法
10.最后篙梢,如果這個Bean的Spring配置中配置了destroy-method屬性顷帖,會自動調(diào)用其配置的銷毀方法
3.java線程池模型有幾種
Java提供了四種線程池
newCachedThreadPool :
可緩存線程池,若線程池長度超過處理需要,則回收空線程贬墩,否則創(chuàng)建新線程榴嗅,線程規(guī)模可無限大陶舞。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
當(dāng)執(zhí)行第二個任務(wù)時第一個任務(wù)已經(jīng)完成录肯,會復(fù)用執(zhí)行第一個任務(wù)的線程,而不用每次新建線程吊说。
newFixedThreadPool :
定長線程池论咏,可控制線程最大并發(fā)數(shù),超出的線程會在隊列中等待颁井。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
定長線程池的大小最好根據(jù)系統(tǒng)資源進行設(shè)置厅贪。如Runtime.getRuntime().availableProcessors()。
newScheduledThreadPool :
定長線程池雅宾,支持定時及周期性任務(wù)執(zhí)行养涮,類似Timer。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
使用實例:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//表示延遲1秒后每3秒執(zhí)行一次眉抬。
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
newSingleThreadExecutor :
單線程 的線程池贯吓,支持FIFO, LIFO, 優(yōu)先級策略。
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
通過觀察源碼蜀变,其中四種線程的創(chuàng)建都是創(chuàng)建一個ThreadPoolExecutor悄谐。其中ThreadPoolExecutor是ExecutorService接口的實現(xiàn)類。
4.如何讓有狀態(tài)的服務(wù)變?yōu)闊o狀態(tài)的服務(wù)
未整理
5.java gc有幾種模型
Java有四種類型的垃圾回收器:
- 串行垃圾回收器(Serial Garbage Collector)
- 并行垃圾回收器(Parallel Garbage Collector)
- 并發(fā)標(biāo)記掃描垃圾回收器(CMS Garbage Collector)
- G1垃圾回收器(G1 Garbage Collector)
每種類型都有自己的優(yōu)勢與劣勢库北。重要的是爬舰,我們編程的時候可以通過JVM選擇垃圾回收器類型。我們通過向JVM傳遞參數(shù)進行選擇寒瓦。每種類型在很大程度上有 所不同并且可以為我們提供完全不同的應(yīng)用程序性能情屹。理解每種類型的垃圾回收器并且根據(jù)應(yīng)用程序選擇進行正確的選擇是非常重要的。
1杂腰、串行垃圾回收器
串行垃圾回收器通過持有應(yīng)用程序所有的線程進行工作垃你。它為單線程環(huán)境設(shè)計,只使用一個單獨的線程進行垃圾回收喂很,通過凍結(jié)所有應(yīng)用程序線程進行工作惜颇,所以可能不適合服務(wù)器環(huán)境。它最適合的是簡單的命令行程序恤筛。
通過JVM參數(shù)-XX:+UseSerialGC
可以使用串行垃圾回收器官还。
2、并行垃圾回收器
并行垃圾回收器也叫做 throughput collector 毒坛。它是JVM的默認垃圾回收器望伦。與串行垃圾回收器不同林说,它使用多線程進行垃圾回收。相似的是屯伞,它也會凍結(jié)所有的應(yīng)用程序線程當(dāng)執(zhí)行垃圾回收的時候
3腿箩、并發(fā)標(biāo)記掃描垃圾回收器
并發(fā)標(biāo)記垃圾回收使用多線程掃描堆內(nèi)存,標(biāo)記需要清理的實例并且清理被標(biāo)記過的實例劣摇。并發(fā)標(biāo)記垃圾回收器只會在下面兩種情況持有應(yīng)用程序所有線程珠移。
- 當(dāng)標(biāo)記的引用對象在tenured區(qū)域;
- 在進行垃圾回收的時候末融,堆內(nèi)存的數(shù)據(jù)被并發(fā)的改變钧惧。
相比并行垃圾回收器,并發(fā)標(biāo)記掃描垃圾回收器使用更多的CPU來確保程序的吞吐量勾习。如果我們可以為了更好的程序性能分配更多的CPU浓瞪,那么并發(fā)標(biāo)記上掃描垃圾回收器是更好的選擇相比并發(fā)垃圾回收器。
通過JVM參數(shù) XX:+USeParNewGC
打開并發(fā)標(biāo)記掃描垃圾回收器巧婶。
4乾颁、G1垃圾回收器
G1垃圾回收器適用于堆內(nèi)存很大的情況,他將堆內(nèi)存分割成不同的區(qū)域艺栈,并且并發(fā)的對其進行垃圾回收英岭。G1也可以在回收內(nèi)存之后對剩余的堆內(nèi)存空間進行壓縮。并發(fā)掃描標(biāo)記垃圾回收器在STW情況下壓縮內(nèi)存湿右。G1垃圾回收會優(yōu)先選擇第一塊垃圾最多的區(qū)域
通過JVM參數(shù) –XX:+UseG1GC
使用G1垃圾回收器
Java 8 的新特性
在使用G1垃圾回收器的時候诅妹,通過 JVM參數(shù) -XX:+UseStringDeduplication
。 我們可以通過刪除重復(fù)的字符串诅需,只保留一個char[]來優(yōu)化堆內(nèi)存漾唉。這個選擇在Java 8 u 20被引入荧库。
我們給出了全部的四種Java垃圾回收器堰塌,需要根據(jù)應(yīng)用場景,硬件性能和吞吐量需求來決定使用哪一種分衫。
垃圾回收的JVM配置
下面的JVM關(guān)鍵配置都與Java垃圾回收有關(guān)场刑。
運行的垃圾回收器類型
配置 | 描述 |
---|---|
-XX:+UseSerialGC | 串行垃圾回收器 |
-XX:+UseParallelGC | 并行垃圾回收器 |
-XX:+UseConcMarkSweepGC | 并發(fā)標(biāo)記掃描垃圾回收器 |
-XX:ParallelCMSThreads= | 并發(fā)標(biāo)記掃描垃圾回收器 =為使用的線程數(shù)量 |
-XX:+UseG1GC | G1垃圾回收器 |
GC的優(yōu)化配置
配置 | 描述 |
---|---|
-Xms | 初始化堆內(nèi)存大小 |
-Xmx | 堆內(nèi)存最大值 |
-Xmn | 新生代大小 |
-XX:PermSize | 初始化永久代大小 |
-XX:MaxPermSize | 永久代最大容量 |
使用JVM GC參數(shù)的例子
1
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar java-application.jar
在Java垃圾回收教程的下一部分,我們將會用一個Java程序演示如何監(jiān)視和分析垃圾回收蚪战。
6.codeReview的內(nèi)容
整潔的代碼
清單項目 | 分類 |
---|---|
使用可以表達實際意圖(Intention-Revealing)的名稱 | 有意義的名稱 |
每一個概念只用一個詞 | 有意義的名稱 |
使用方案/問題領(lǐng)域名稱 | 有意義的名稱 |
類應(yīng)該是比較小的! | 類 |
函數(shù)應(yīng)該是比較小的! | 函數(shù) |
只做一件事 | 函數(shù) |
DRY(Don’t Repeat Yourself)原則牵现,(拒絕重復(fù)) | 函數(shù) |
用代碼來解釋自己的做法(譯者注:即代碼注釋) | 注釋 |
確定應(yīng)用了代碼格式化 | 格式 |
使用異常而不是返回碼 | 異常 |
不要返回Null | 異常 |
安全
清單項目 | 分類 |
---|---|
如果不用于繼承,使類為final | 基礎(chǔ) |
避免重復(fù)代碼 | 基礎(chǔ) |
權(quán)限限制:程序應(yīng)該運行在保證功能正常的最小權(quán)限模式下邀桑。 | 基礎(chǔ) |
最小化類和成員的可訪問性 | 基礎(chǔ) |
注釋出安全相關(guān)的信息 | 基礎(chǔ) |
系統(tǒng)的輸入必須檢查是否有效和在允許范圍內(nèi) | 拒絕服務(wù)(Denial of Service) |
避免對于一些不尋常行為的過分日志 | 拒絕服務(wù)(Denial of Service) |
在任何情況下都釋放資源(流瞎疼,連接等等) | 拒絕服務(wù)(Denial of Service) |
從異常中清除敏感信息(暴露文件路徑,系統(tǒng)內(nèi)部相關(guān)壁畸,配置)P | 私密信息(Confidential Information) |
不要把高度敏感的信息寫到日志 | 私密信息(Confidential Information) |
考慮把高度敏感的信息在使用后從內(nèi)存中清除 | 私密信息(Confidential Information) |
限制包贼急,類茅茂,接口,方法和域的可訪問性 | 可訪問性的擴展(Accessibility Extensibility) |
限制類和方法的可擴展性(通過使它為final) | 可訪問性的擴展(Accessibility Extensibility) |
檢驗輸入(有效的數(shù)據(jù)太抓,大小空闲,范圍,邊界情況等等) | 輸入檢驗(Input Validation) |
把從不可信對象得到的輸出作為輸入來檢驗 | 輸入檢驗(Input Validation) |
為native方法定義包裝類(而不是定義native方法為pulibc) | 輸入檢驗(Input Validation) |
把從不可信對象得到的輸出作為輸入來對待 | 可變性 |
使public static域為final(避免調(diào)用方(caller)修改它的值) | 可變性 |
避免暴露敏感類的構(gòu)造函數(shù) | 對象構(gòu)造 |
避免安全敏感類的序列化 | 序列化反序列化(Serialization Deserialization) |
通過序列化來保護敏感數(shù)據(jù) | 序列化反序列化(Serialization Deserialization) |
小心地緩存潛在的特權(quán)操作結(jié)果 | 序列化反序列化(Serialization Deserialization) |
只有在需要的時候才使用JNI | 訪問限制 |
*參考自: http://www.oracle.com/technetwork/java/seccodeguide-139067.html
性能
清單項目 | 分類 |
---|---|
避免過分的同步 | 并發(fā) |
保持同步區(qū)域比較小 | 并發(fā) |
知道string連接的性能情況 | 綜合編程 |
避免創(chuàng)建不需要的對象 | 創(chuàng)建和銷毀對象 |
*參考自: http://techbus.safaribooksonline.com/book/programming/java/9780137150021
綜合(譯者注:原文中的作者把checklist和category對應(yīng)的列搞錯了走敌,譯文中已修正)
清單項目 | 分類 |
---|---|
對可以恢復(fù)的情況使用已受檢異常(checked exceptions)碴倾,對于程序錯誤使用運行時異常(runtime exceptions) | 異常 |
更多地使用標(biāo)準異常 | 異常 |
不要忽略異常 | 異常 |
檢查參數(shù)的有效性 | 方法 |
返回空數(shù)組或集合,而不是null | 方法 |
最小化類和成員的可訪問性 | 類和接口 |
在pulibc類中掉丽,使用訪問器方法(accessor methods)(譯者注:訪問器方法即我們平常用的get/set方法)而不是public域 | 類和接口 |
最小化本地變量的范圍 | 綜合編程 |
通過接口引用對象 | 綜合編程 |
遵循廣泛接受的命名規(guī)則 | 綜合編程 |
避免使用finalizer | 創(chuàng)建和銷毀對象 |
當(dāng)你重寫equals時總是重寫hashCode | 綜合編程 |
總是重寫toString | 綜合編程 |
使用枚舉來代替int常量 | 枚舉和注解(Annotations) |
使用標(biāo)記接口(marker interface)(譯者注:標(biāo)記接口是一種沒有任何行為的接口跌榔,實現(xiàn)它只是為了讓實現(xiàn)類屬于某種類型,如JDK中的Serializable,Cloneable等)來定義類型 | 枚舉和注解(Annotations) |
對共享可變的數(shù)據(jù)使用同步訪問 | 并發(fā) |
使用executors而不是task和thread | 并發(fā) |
注釋中描述線程安全情況 | 并發(fā) |
存在有效的JUnit/JBehave測試用例 | 測試 |
*參考自: http://techbus.safaribooksonline.com/book/programming/java/9780137150021
靜態(tài)代碼分析
清單項目 | 分類 |
---|---|
查看靜態(tài)代碼分析器的報告來進行類的添加和修改 | 靜態(tài)代碼分析 |
7.悲觀鎖與樂觀鎖的實現(xiàn)
1.樂觀鎖是假設(shè)不會發(fā)生并發(fā)問題捶障,通過數(shù)據(jù)庫添加版本號字段矫户,更新時將版本號字段+1,樂觀并發(fā)控制相信事務(wù)之間的數(shù)據(jù)競爭(data race)的概率是比較小的残邀,因此盡可能直接做下去皆辽,直到提交的時候才去鎖定,所以不會產(chǎn)生任何鎖和死鎖芥挣。但如果直接簡單這么做驱闷,還是有可能會遇到不可預(yù)期的結(jié)果,例如兩個事務(wù)都讀取了數(shù)據(jù)庫的某一行空免,經(jīng)過修改以后寫回數(shù)據(jù)庫空另,這時就遇到了問題。
2.悲觀鎖蹋砚,正如其名扼菠,它指的是對數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度(悲觀)坝咐,因此循榆,在整個數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)墨坚。 悲觀鎖的實現(xiàn)秧饮,往往依靠數(shù)據(jù)庫提供的鎖機制 (也只有數(shù)據(jù)庫層提供的鎖機制才能真正保證數(shù)據(jù)訪問的排他性,否則泽篮,即使在本系統(tǒng)中實現(xiàn)了加鎖機制盗尸,也無法保證外部系統(tǒng)不會修改數(shù)據(jù))
在數(shù)據(jù)庫中,悲觀鎖的流程如下:
在對任意記錄進行修改前帽撑,先嘗試為該記錄加上排他鎖(exclusive locking)泼各。
如果加鎖失敗,說明該記錄正在被修改亏拉,那么當(dāng)前查詢可能要等待或者拋出異常扣蜻。 具體響應(yīng)方式由開發(fā)者根據(jù)實際需要決定寸癌。
如果成功加鎖,那么就可以對記錄做修改弱贼,事務(wù)完成后就會解鎖了蒸苇。
其間如果有其他對該記錄做修改或加排他鎖的操作,都會等待我們解鎖或直接拋出異常吮旅。
8.不同用戶對同一賬號的并發(fā)操作的加鎖方式
未整理
9.==與equals的區(qū)別
==比較的是地址溪烤,除了java自帶的基本類型,java自帶的基本類型是比較值
equals是比較對象的值
這個問題引申出來的問題就是java的內(nèi)存模型
Java內(nèi)存分配主要包括以下幾個區(qū)域:
寄存器:我們在程序中無法控制
棧:存放基本類型的數(shù)據(jù)和對象的引用庇勃,但對象本身不存放在棧中檬嘀,而是存放在堆中
堆:存放用new產(chǎn)生的數(shù)據(jù)
靜態(tài)域:存放在對象中用static定義的靜態(tài)成員
常量池:存放常量
非RAM(隨機存取存儲器)存儲:硬盤等永久存儲空間
Java內(nèi)存分配中的棧
在函數(shù)中定義的一些基本類型的變量數(shù)據(jù)和對象的引用變量都在函數(shù)的棧內(nèi)存中分配。當(dāng)在一段代碼塊定義一個變量時责嚷,Java就在棧中為這個變量分配內(nèi)存空間鸳兽,當(dāng)該變量退出該作用域后,Java會自動釋放掉為該變量所分配的內(nèi)存空間罕拂,該內(nèi)存空間可以立即被另作他用揍异。
Java內(nèi)存分配中的堆
堆內(nèi)存用來存放由new創(chuàng)建的對象和數(shù)組。 在堆中分配的內(nèi)存爆班,由Java虛擬機的自動垃圾回收器來管理衷掷。
在堆中產(chǎn)生了一個數(shù)組或?qū)ο蠛螅€可以 在棧中定義一個特殊的變量柿菩,讓棧中這個變量的取值等于數(shù)組或?qū)ο笤诙褍?nèi)存中的首地址戚嗅,棧中的這個變量就成了數(shù)組或?qū)ο蟮囊米兞俊R米兞烤拖喈?dāng)于是為數(shù)組或?qū)ο笃鸬囊粋€名稱枢舶,以后就可以在程序中使用棧中的引用變量來訪問堆中的數(shù)組或?qū)ο笈嘲R米兞烤拖喈?dāng)于是為數(shù)組或者對象起的一個名稱。
引用變量是普通的變量凉泄,定義時在棧中分配躏尉,引用變量在程序運行到其作用域之外后被釋放涯保。而數(shù)組和對象本身在堆中分配颗搂,即使程序運行到使用 new 產(chǎn)生數(shù)組或者對象的語句所在的代碼塊之外,數(shù)組和對象本身占據(jù)的內(nèi)存不會被釋放,數(shù)組和對象在沒有引用變量指向它的時候吼具,才變?yōu)槔荒茉诒皇褂镁鼐啵?然占據(jù)內(nèi)存空間不放拗盒,在隨后的一個不確定的時間被垃圾回收器收走(釋放掉)。這也是 Java 比較占內(nèi)存的原因锥债。
實際上陡蝇,棧中的變量指向堆內(nèi)存中的變量痊臭,這就是Java中的指針!
常量池 (constant pool)
常量池指的是在編譯期被確定登夫,并被保存在已編譯的.class文件中的一些數(shù)據(jù)广匙。除了包含代碼中所定義的各種基本類型(如int、long等等)和對象型(如String及數(shù)組)的常量值(final)還包含一些以文本形式出現(xiàn)的符號引用恼策,比如:
類和接口的全限定名鸦致;
字段的名稱和描述符;
方法和名稱和描述符涣楷。
虛擬機必須為每個被裝載的類型維護一個常量池分唾。常量池就是該類型所用到常量的一個有序集和,包括直接常量(string,integer和 floating point常量)和對其他類型狮斗,字段和方法的符號引用绽乔。
對于String常量,它的值是在常量池中的碳褒。而JVM中的常量池在內(nèi)存當(dāng)中是以表的形式存在的折砸, 對于String類型,有一張固定長度的CONSTANT_String_info表用來存儲文字字符串值沙峻,注意:該表只存儲文字字符串值鞍爱,不存儲符號引 用。說到這里专酗,對常量池中的字符串值的存儲位置應(yīng)該有一個比較明了的理解了睹逃。
在程序執(zhí)行的時候,常量池會儲存在Method Area,而不是堆中。
堆與棧
Java的堆是一個運行時數(shù)據(jù)區(qū),類的(對象從中分配空間祷肯。這些對象通過new沉填、newarray、 anewarray和multianewarray等指令建立佑笋,它們不需要程序代碼來顯式的釋放翼闹。堆是由垃圾回收來負責(zé)的,堆的優(yōu)勢是可以動態(tài)地分配內(nèi)存大小蒋纬,生存期也不必事先告訴編譯器猎荠,因為它是在運行時動態(tài)分配內(nèi)存的,Java的垃圾收集器會自動收走這些不再使用的數(shù)據(jù)蜀备。但缺點是关摇,由于要在運行時動態(tài) 分配內(nèi)存,存取速度較慢碾阁。
棧的優(yōu)勢是输虱,存取速度比堆要快,僅次于寄存器脂凶,棧數(shù)據(jù)可以共享宪睹。但缺點是愁茁,存在棧中的數(shù)據(jù)大小與生存期必須是確定的,缺乏靈活性亭病。棧中主要存放一些基本類型的變量數(shù)據(jù)(int, short, long, byte, float, double, boolean, char)和對象句柄(引用)鹅很。
這里我們主要關(guān)心棧,堆和常量池罪帖,對于棧和常量池中的對象可以共享促煮,對于堆中的對象不可以共享。棧中的數(shù)據(jù)大小和生命周期是可以確定的胸蛛,當(dāng)沒有引用指向數(shù)據(jù)時污茵,這個數(shù)據(jù)就會消失。堆中的對象的由垃圾回收器負責(zé)回收葬项,因此大小和生命周期不需要確定泞当,具有很大的靈活性。
字符串內(nèi)存分配:
對于字符串民珍,其對象的引用都是存儲在棧中的襟士,如果是編譯期已經(jīng)創(chuàng)建好(直接用雙引號定義的)的就存儲在常量池中,如果是運行期(new出來的)才能確定的就存儲在堆中嚷量。對于equals相等的字符串陋桂,在常量池中永遠只有一份,在堆中有多份蝶溶。
原文:https://www.cnblogs.com/SaraMoring/p/5687466.html
10.runtimeException與普通Exception的區(qū)別
1.java將所有的錯誤封裝為一個對象嗜历,其根本父類為Throwable, Throwable有兩個子類:Error和Exception。
*2.Error
是Throwable
的子類抖所,用于指示合理的應(yīng)用程序不應(yīng)該試圖捕獲的嚴重問題梨州。大多數(shù)這樣的錯誤都是異常條件。雖然 ThreadDeath
錯誤是一個“正規(guī)”的條件田轧,但它也是 Error
的子類暴匠,因為大多數(shù)應(yīng)用程序都不應(yīng)該試圖捕獲它。在執(zhí)行該方法期間傻粘,無需在其 throws
子句中聲明可能拋出但是未能捕獲的 Error
的任何子類每窖,因為這些錯誤可能是再也不會發(fā)生的異常條件。
3.Exception
類及其子類是 Throwable
的一種形式弦悉,它指出了合理的應(yīng)用程序想要捕獲的條件窒典。
4.RuntimeException
是那些可能在 Java 虛擬機正常運行期間拋出的異常的超類【ǎ可能在執(zhí)行方法期間拋出但未被捕獲的RuntimeException
的任何子類都無需在 throws
子句中進行聲明崇败。它是Exception的子類。*
5.異常的分類
Error:一般為底層的不可恢復(fù)的類肩祥;
Exception:分為未檢查異常(RuntimeException)和已檢查異常(非RuntimeException)后室。
未檢查異常是因為程序員沒有進行必需要的檢查,因為疏忽和錯誤而引起的錯誤混狠。幾個經(jīng)典的RunTimeException如下:*
1. java.lang.NullPointerException;
2. java.lang.ArithmaticException;
3. java.lang.ArrayIndexoutofBoundsException</pre>
Runtime Exception:
在定義方法時不需要聲明會拋出runtime exception岸霹; 在調(diào)用這個方法時不需要捕獲這個runtime exception; runtime exception是從java.lang.RuntimeException或java.lang.Error類衍生出來的将饺。 例如:nullpointexception贡避,IndexOutOfBoundsException就屬于runtime exception
Exception:
定義方法時必須聲明所有可能會拋出的exception; 在調(diào)用這個方法時予弧,必須捕獲它的checked exception刮吧,不然就得把它的exception傳遞下去;exception是從java.lang.Exception類衍生出來的掖蛤。例如:IOException杀捻,SQLException就屬于Exception
Exception 屬于應(yīng)用程序級別的異常,這類異常必須捕捉,Exception體系包括RuntimeException體系和其他非RuntimeException的體系
RuntimeException 表示系統(tǒng)異常蚓庭,比較嚴重致讥,如果出現(xiàn)RuntimeException,那么一定是程序員的錯誤
11.java try catch 的原理
1.類會跟隨一張 異常表(exception table)器赞,每一個try catch都會在這個表里添加行記錄垢袱,每一個記錄都有4個信息(try catch的開始地址,結(jié)束地址港柜,異常的處理起始位请契,異常類名稱)。
2.當(dāng)代碼在運行時拋出了異常時夏醉,首先拿著拋出位置到異常表中查找是否可以被catch(例如看位置是不是處于任何一欄中的開始和結(jié)束位置之間)爽锥,如果可以則跑到異常處理的起始位置開始處理,如果沒有找到則原地return授舟,并且copy異常的引用給父調(diào)用方救恨,接著看父調(diào)用的異常表。释树。肠槽。以此類推。
12.表單防止重復(fù)提交
1.在前端用js設(shè)置標(biāo)志位奢啥,無法控制刷新頁面提交的秸仙,也無法控制后退頁面重新提交的
2.具體的做法:在服務(wù)器端生成一個唯一的隨機標(biāo)識號,專業(yè)術(shù)語稱為Token(令牌)桩盲,同時在當(dāng)前用戶的Session域中保存這個Token寂纪。然后將Token發(fā)送到客戶端的Form表單中,在Form表單中使用隱藏域來存儲這個Token,表單提交的時候連同這個Token一起提交到服務(wù)器端捞蛋,然后在服務(wù)器端判斷客戶端提交上來的Token與服務(wù)器端生成的Token是否一致孝冒,如果不一致,那就是重復(fù)提交了拟杉,此時服務(wù)器端就可以不處理重復(fù)提交的表單庄涡。如果相同則處理表單提交,處理完后清除當(dāng)前用戶的Session域中存儲的標(biāo)識號搬设,多節(jié)點服務(wù)的此方法會失效穴店。
在下列情況下,服務(wù)器程序?qū)⒕芙^處理用戶提交的表單請求:
存儲Session域中的Token(令牌)與表單提交的Token(令牌)不同拿穴。
當(dāng)前用戶的Session中不存在Token(令牌)泣洞。
用戶提交的表單數(shù)據(jù)中沒有Token(令牌)。
3.多服務(wù)的時候?qū)oken存在redis中默色,在springmvc配置文件中加入攔截器的配置球凰,當(dāng)轉(zhuǎn)到頁面的請求到來時,生成token的名字和token值该窗,一份放到redis緩存中弟蚀,一份放傳給頁面表單的隱藏域。
當(dāng)表單請求提交時酗失,攔截器得到參數(shù)中的tokenName和token义钉,然后到緩存中去取token值,如果能匹配上规肴,請求就通過捶闸,不能匹配上就不通過。這里的tokenName生成時也是隨機的拖刃,每次請求都不一樣删壮。而從緩存中取token值時,會立即將其刪除(刪與讀是原子的兑牡,無線程安全問題)央碟。
對于重復(fù)提交、重復(fù)刷新均函、防止后退等等都是屬于系統(tǒng)為避免重復(fù)記錄而需要解決的問題亿虽,在客戶端去處理需要針對每一種的可能提出相應(yīng)的解決方案,然而在服務(wù)器端看來只不過是對于數(shù)據(jù)真實性的檢驗問題苞也,基于令牌的處理就是一勞永逸的方法洛勉。
4.使用timestamp和nonce來做的重放機制。
timestamp用來表示請求的當(dāng)前時間戳如迟,這個時間戳當(dāng)然要和服務(wù)器時間戳進行校正過的收毫。我們預(yù)期正常請求帶的timestamp參數(shù)會是不同的(預(yù)期是正常的人每秒至多只會做一個操作)攻走。每個請求帶的時間戳不能和當(dāng)前時間超過一定規(guī)定的時間。比如60s此再。這樣昔搂,這個請求即使被截取了,你也只能在60s內(nèi)進行重放攻擊引润。過期失效巩趁。
但是這樣也是不夠的痒玩,還有給攻擊者60s的時間淳附。所以我們就需要使用一個nonce,隨機數(shù)蠢古。
nonce是由客戶端根據(jù)足夠隨機的情況生成的奴曙,比如 md5(timestamp+rand(0, 1000)); 它就有一個要求,正常情況下草讶,在短時間內(nèi)(比如60s)連續(xù)生成兩個相同nonce的情況幾乎為0洽糟。
服務(wù)端
服務(wù)端第一次在接收到這個nonce的時候做下面行為:
1 去redis中查找是否有key為nonce:{nonce}的string
2 如果沒有,則創(chuàng)建這個key堕战,把這個key失效的時間和驗證timestamp失效的時間一致坤溃,比如是60s。
3 如果有嘱丢,說明這個key在60s內(nèi)已經(jīng)被使用了薪介,那么這個請求就可以判斷為重放請求。
示例
那么比如越驻,下面這個請求:
http://a.com?uid=123×tamp=1480556543&nonce=43f34f33&sign=80b886d71449cb33355d017893720666
這個請求中的uid是我們真正需要傳遞的有意義的參數(shù)
timestamp汁政,nonce,sign都是為了簽名和防重放使用缀旁。
timestamp是發(fā)送接口的時間记劈,nonce是隨機串,sign是對uid并巍,timestamp,nonce(對于一些rest風(fēng)格的api目木,我建議也把url放入sign簽名)。簽名的方法可以是md5({密鑰}key1=val1&key2=val2&key3=val3...)
服務(wù)端接到這個請求:
1 先驗證sign簽名是否合理懊渡,證明請求參數(shù)沒有被中途篡改
2 再驗證timestamp是否過期刽射,證明請求是在最近60s被發(fā)出的
3 最后驗證nonce是否已經(jīng)有了,證明這個請求不是60s內(nèi)的重放請求
13.翻轉(zhuǎn)單向鏈表
/**
* 遞歸距贷,在反轉(zhuǎn)當(dāng)前節(jié)點之前先反轉(zhuǎn)后續(xù)節(jié)點
*/
public static Node Reverse1(Node head) {
// head看作是前一結(jié)點柄冲,head.getNext()是當(dāng)前結(jié)點,reHead是反轉(zhuǎn)后新鏈表的頭結(jié)點
if (head == null || head.getNext() == null) {
return head;// 若為空鏈或者當(dāng)前結(jié)點在尾結(jié)點忠蝗,則直接還回
}
Node reHead = Reverse1(head.getNext());// 先反轉(zhuǎn)后續(xù)節(jié)點head.getNext()
head.getNext().setNext(head);// 將當(dāng)前結(jié)點的指針域指向前一結(jié)點
head.setNext(null);// 前一結(jié)點的指針域令為null;
return reHead;// 反轉(zhuǎn)后新鏈表的頭結(jié)點
}
/**
* 遍歷现横,將當(dāng)前節(jié)點的下一個節(jié)點緩存后更改當(dāng)前節(jié)點指針
*/
public static Node reverse2(Node head) {
if (head == null)
return head;
Node pre = head;// 上一結(jié)點
Node cur = head.getNext();// 當(dāng)前結(jié)點
Node tmp;// 臨時結(jié)點,用于保存當(dāng)前結(jié)點的指針域(即下一結(jié)點)
while (cur != null) {// 當(dāng)前結(jié)點為null,說明位于尾結(jié)點
tmp = cur.getNext();
cur.setNext(pre);// 反轉(zhuǎn)指針域的指向
// 指針往下移動
pre = cur;
cur = tmp;
}
// 最后將原鏈表的頭節(jié)點的指針域置為null戒祠,還回新鏈表的頭結(jié)點骇两,即原鏈表的尾結(jié)點
head.setNext(null);
return pre;
}
14.java鎖機制的底層實現(xiàn)
一、synchronized的字節(jié)碼表示:
在java語言中存在兩種內(nèi)建的synchronized語法:1姜盈、synchronized語句低千;2、synchronized方法馏颂。對于synchronized語句當(dāng)Java源代碼被javac編譯成bytecode的時候示血,會在同步塊的入口位置和退出位置分別插入monitorenter和monitorexit字節(jié)碼指令。而synchronized方法則會被翻譯成普通的方法調(diào)用和返回指令如:invokevirtual救拉、areturn指令难审,在VM字節(jié)碼層面并沒有任何特別的指令來實現(xiàn)被synchronized修飾的方法,而是在Class文件的方法表中將該方法的access_flags字段中的synchronized標(biāo)志位置1亿絮,表示該方法是同步方法并使用調(diào)用該方法的對象或該方法所屬的Class在JVM的內(nèi)部對象表示Klass做為鎖對象告喊。
二、JVM中鎖的優(yōu)化:
簡單來說在JVM中monitorenter和monitorexit字節(jié)碼依賴于底層的操作系統(tǒng)的Mutex Lock來實現(xiàn)的派昧,但是由于使用Mutex Lock需要將當(dāng)前線程掛起并從用戶態(tài)切換到內(nèi)核態(tài)來執(zhí)行黔姜,這種切換的代價是非常昂貴的;然而在現(xiàn)實中的大部分情況下蒂萎,同步方法是運行在單線程環(huán)境(無鎖競爭環(huán)境)如果每次都調(diào)用Mutex Lock那么將嚴重的影響程序的性能秆吵。不過在jdk1.6中對鎖的實現(xiàn)引入了大量的優(yōu)化,如鎖粗化(Lock Coarsening)岖是、鎖消除(Lock Elimination)帮毁、輕量級鎖(Lightweight Locking)、偏向鎖(Biased Locking)豺撑、適應(yīng)性自旋(Adaptive Spinning)等技術(shù)來減少鎖操作的開銷烈疚。
鎖粗化(Lock Coarsening):也就是減少不必要的緊連在一起的unlock,lock操作聪轿,將多個連續(xù)的鎖擴展成一個范圍更大的鎖爷肝。
鎖消除(Lock Elimination):通過運行時JIT編譯器的逃逸分析來消除一些沒有在當(dāng)前同步塊以外被其他線程共享的數(shù)據(jù)的鎖保護,通過逃逸分析也可以在線程本地Stack上進行對象空間的分配(同時還可以減少Heap上的垃圾收集開銷)陆错。
輕量級鎖(Lightweight Locking):這種鎖實現(xiàn)的背后基于這樣一種假設(shè)灯抛,即在真實的情況下我們程序中的大部分同步代碼一般都處于無鎖競爭狀態(tài)(即單線程執(zhí)行環(huán)境),在無鎖競爭的情況下完全可以避免調(diào)用操作系統(tǒng)層面的重量級互斥鎖音瓷,取而代之的是在monitorenter和monitorexit中只需要依靠一條CAS原子指令就可以完成鎖的獲取及釋放对嚼。當(dāng)存在鎖競爭的情況下,執(zhí)行CAS指令失敗的線程將調(diào)用操作系統(tǒng)互斥鎖進入到阻塞狀態(tài)绳慎,當(dāng)鎖被釋放的時候被喚醒(具體處理步驟下面詳細討論)纵竖。
偏向鎖(Biased Locking):是為了在無鎖競爭的情況下避免在鎖獲取過程中執(zhí)行不必要的CAS原子指令极舔,因為CAS原子指令雖然相對于重量級鎖來說開銷比較小但還是存在非沉闪模可觀的本地延遲(可參考這篇文章)锚烦。
適應(yīng)性自旋(Adaptive Spinning):當(dāng)線程在獲取輕量級鎖的過程中執(zhí)行CAS操作失敗時掸犬,在進入與monitor相關(guān)聯(lián)的操作系統(tǒng)重量級鎖(mutex semaphore)前會進入忙等待(Spinning)然后再次嘗試,當(dāng)嘗試一定的次數(shù)后如果仍然沒有成功則調(diào)用與該monitor關(guān)聯(lián)的semaphore(即互斥鎖)進入到阻塞狀態(tài)通殃。
三度液、對象頭(Object Header):
在JVM中創(chuàng)建對象時會在對象前面加上兩個字大小的對象頭,在32位機器上一個字為32bit画舌,根據(jù)不同的狀態(tài)位Mark World中存放不同的內(nèi)容堕担,如上圖所示在輕量級鎖中,Mark Word被分成兩部分骗炉,剛開始時LockWord為被設(shè)置為HashCode照宝、最低三位表示LockWord所處的狀態(tài),初始狀態(tài)為001表示無鎖狀態(tài)句葵。Klass ptr指向Class字節(jié)碼在虛擬機內(nèi)部的對象表示的地址。Fields表示連續(xù)的對象實例字段兢仰。
四乍丈、Monitor Record:
Monitor Record是線程私有的數(shù)據(jù)結(jié)構(gòu),每一個線程都有一個可用monitor record列表把将,同時還有一個全局的可用列表轻专;那么這些monitor record有什么用呢?每一個被鎖住的對象都會和一個monitor record關(guān)聯(lián)(對象頭中的LockWord指向monitor record的起始地址察蹲,由于這個地址是8byte對齊的所以LockWord的最低三位可以用來作為狀態(tài)位)请垛,同時monitor record中有一個Owner字段存放擁有該鎖的線程的唯一標(biāo)識,表示該鎖被這個線程占用洽议。如下圖所示為Monitor Record的內(nèi)部結(jié)構(gòu):
Owner:初始時為NULL表示當(dāng)前沒有任何線程擁有該monitor record宗收,當(dāng)線程成功擁有該鎖后保存線程唯一標(biāo)識,當(dāng)鎖被釋放時又設(shè)置為NULL亚兄;
EntryQ:關(guān)聯(lián)一個系統(tǒng)互斥鎖(semaphore)混稽,阻塞所有試圖鎖住monitor record失敗的線程。
RcThis:表示blocked或waiting在該monitor record上的所有線程的個數(shù)审胚。
Nest:用來實現(xiàn)重入鎖的計數(shù)匈勋。
HashCode:保存從對象頭拷貝過來的HashCode值(可能還包含GC age)。
Candidate:用來避免不必要的阻塞或等待線程喚醒膳叨,因為每一次只有一個線程能夠成功擁有鎖洽洁,如果每次前一個釋放鎖的線程喚醒所有正在阻塞或等待的線程,會引起不必要的上下文切換(從阻塞到就緒然后因為競爭鎖失敗又被阻塞)從而導(dǎo)致性能嚴重下降菲嘴。Candidate只有兩種可能的值0表示沒有需要喚醒的線程1表示要喚醒一個繼任線程來競爭鎖饿自。
五碎浇、輕量級鎖具體實現(xiàn):
一個線程能夠通過兩種方式鎖住一個對象:1、通過膨脹一個處于無鎖狀態(tài)(狀態(tài)位001)的對象獲得該對象的鎖璃俗;2奴璃、對象已經(jīng)處于膨脹狀態(tài)(狀態(tài)位00)但LockWord指向的monitor record的Owner字段為NULL,則可以直接通過CAS原子指令嘗試將Owner設(shè)置為自己的標(biāo)識來獲得鎖城豁。
獲取鎖(monitorenter)的大概過程如下:
(1)當(dāng)對象處于無鎖狀態(tài)時(RecordWord值為HashCode苟穆,狀態(tài)位為001),線程首先從自己的可用moniter record列表中取得一個空閑的moniter record唱星,初始Nest和Owner值分別被預(yù)先設(shè)置為1和該線程自己的標(biāo)識雳旅,一旦monitor record準備好然后我們通過CAS原子指令安裝該monitor record的起始地址到對象頭的LockWord字段來膨脹(原文為inflate,我覺得之所以叫inflate主要是由于當(dāng)對象被膨脹后擴展了對象的大屑淞摹攒盈;為了空間效率,將monitor record結(jié)構(gòu)從對象頭中抽出去哎榴,當(dāng)需要的時候才將該結(jié)構(gòu)attach到對象上型豁,但是和這篇Paper有點互相矛盾,兩種實現(xiàn)方式稍微有點不同)該對象尚蝌,如果存在其他線程競爭鎖的情況而調(diào)用CAS失敗迎变,則只需要簡單的回到monitorenter重新開始獲取鎖的過程即可。
(2)對象已經(jīng)被膨脹同時Owner中保存的線程標(biāo)識為獲取鎖的線程自己飘言,這就是重入(reentrant)鎖的情況衣形,只需要簡單的將Nest加1即可。不需要任何原子操作姿鸿,效率非常高谆吴。
(3)對象已膨脹但Owner的值為NULL,當(dāng)一個鎖上存在阻塞或等待的線程同時鎖的前一個擁有者剛釋放鎖時會出現(xiàn)這種狀態(tài)苛预,此時多個線程通過CAS原子指令在多線程競爭狀態(tài)下試圖將Owner設(shè)置為自己的標(biāo)識來獲得鎖句狼,競爭失敗的線程在則會進入到第四種情況(4)的執(zhí)行路徑。
(4)對象處于膨脹狀態(tài)同時Owner不為NULL(被鎖住)碟渺,在調(diào)用操作系統(tǒng)的重量級的互斥鎖之前先自旋一定的次數(shù)鲜锚,當(dāng)達到一定的次數(shù)時如果仍然沒有成功獲得鎖,則開始準備進入阻塞狀態(tài)苫拍,首先將rfThis的值原子性的加1芜繁,由于在加1的過程中可能會被其他線程破壞Object和monitor record之間的關(guān)聯(lián),所以在原子性加1后需要再進行一次比較以確保LockWord的值沒有被改變绒极,當(dāng)發(fā)現(xiàn)被改變后則要重新進行monitorenter過程骏令。同時再一次觀察Owner是否為NULL,如果是則調(diào)用CAS參與競爭鎖垄提,鎖競爭失敗則進入到阻塞狀態(tài)榔袋。
釋放鎖(monitorexit)的大概過程如下:
(1)首先檢查該對象是否處于膨脹狀態(tài)并且該線程是這個鎖的擁有者周拐,如果發(fā)現(xiàn)不對則拋出異常;
(2)檢查Nest字段是否大于1凰兑,如果大于1則簡單的將Nest減1并繼續(xù)擁有鎖妥粟,如果等于1,則進入到第(3)步吏够;
(3)檢查rfThis是否大于0勾给,設(shè)置Owner為NULL然后喚醒一個正在阻塞或等待的線程再一次試圖獲取鎖,如果等于0則進入到第(4)步
(4)縮泄(deflate)一個對象播急,通過將對象的LockWord置換回原來的HashCode值來解除和monitor record之間的關(guān)聯(lián)來釋放鎖,同時將monitor record放回到線程是有的可用monitor record列表售睹。
轉(zhuǎn)自http://www.cnblogs.com/javaminer/p/3889023.html
15.java并發(fā)包內(nèi)容
java.util.concurrent下主要的接口和類:
Executor:具體Runnable任務(wù)的執(zhí)行者桩警。
ExecutorService:一個線程池管理者,其實現(xiàn)類有多種昌妹,比如普通線程池捶枢,定時調(diào)度線程池ScheduledExecutorService等,我們能把一個Runnable,Callable提交到池中讓其調(diào)度捺宗。
Future:是與Runnable,Callable進行交互的接口柱蟀,比如一個線程執(zhí)行結(jié)束后取返回的結(jié)果等等,還提供了cancel終止線程蚜厉。
BlockingQueue:阻塞隊列。
16.jvm的組成
默認的畜眨,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過參數(shù) –XX:NewRatio 來指定 )昼牛,即:新生代 ( Young ) = 1/3 的堆空間大小。老年代 ( Old ) = 2/3 的堆空間大小康聂。其中贰健,新生代 ( Young ) 被細分為 Eden 和 兩個 Survivor 區(qū)域,這兩個 Survivor 區(qū)域分別被命名為 from 和 to恬汁,以示區(qū)分伶椿。
默認的,Edem : from : to = 8 : 1 : 1 ( 可以通過參數(shù) –XX:SurvivorRatio 來設(shè)定 )氓侧,即: Eden = 8/10 的新生代空間大小脊另,from = to = 1/10 的新生代空間大小。
JVM 每次只會使用 Eden 和其中的一塊 Survivor 區(qū)域來為對象服務(wù)约巷,所以無論什么時候偎痛,總是有一塊 Survivor 區(qū)域是空閑著的。
因此独郎,新生代實際可用的內(nèi)存空間為 9/10 ( 即90% )的新生代空間踩麦。
17.gc新生代什么時候會晉升為老年代
Java 中的堆也是 GC 收集垃圾的主要區(qū)域枚赡。GC 分為兩種:Minor GC、Full GC ( 或稱為 Major GC )谓谦。
Minor GC 是發(fā)生在新生代中的垃圾收集動作贫橙,所采用的是復(fù)制算法。
新生代幾乎是所有 Java 對象出生的地方反粥,即 Java 對象申請的內(nèi)存以及存放都是在這個地方卢肃。Java 中的大部分對象通常不需長久存活,具有朝生夕滅的性質(zhì)星压。
當(dāng)一個對象被判定為 "死亡" 的時候践剂,GC 就有責(zé)任來回收掉這部分對象的內(nèi)存空間。新生代是 GC 收集垃圾的頻繁區(qū)域娜膘。
當(dāng)對象在 Eden ( 包括一個 Survivor 區(qū)域逊脯,這里假設(shè)是 from 區(qū)域 ) 出生后,在經(jīng)過一次 Minor GC 后竣贪,如果對象還存活军洼,并且能夠被另外一塊 Survivor 區(qū)域所容納( 上面已經(jīng)假設(shè)為 from 區(qū)域,這里應(yīng)為 to 區(qū)域演怎,即 to 區(qū)域有足夠的內(nèi)存空間來存儲 Eden 和 from 區(qū)域中存活的對象 )匕争,則使用復(fù)制算法將這些仍然還存活的對象復(fù)制到另外一塊 Survivor 區(qū)域 ( 即 to 區(qū)域 ) 中,然后清理所使用過的 Eden 以及 Survivor 區(qū)域 ( 即 from 區(qū)域 )爷耀,并且將這些對象的年齡設(shè)置為1甘桑,以后對象在 Survivor 區(qū)每熬過一次 Minor GC,就將對象的年齡 + 1歹叮,當(dāng)對象的年齡達到某個值時 ( 默認是 15 歲跑杭,可以通過參數(shù) -XX:MaxTenuringThreshold 來設(shè)定 ),這些對象就會成為老年代咆耿。
但這也不是一定的德谅,對于一些較大的對象 ( 即需要分配一塊較大的連續(xù)內(nèi)存空間 ) 則是直接進入到老年代。
Full GC 是發(fā)生在老年代的垃圾收集動作萨螺,所采用的是標(biāo)記-清除算法窄做。
現(xiàn)實的生活中,老年代的人通常會比新生代的人 "早死"慰技。堆內(nèi)存中的老年代(Old)不同于這個椭盏,老年代里面的對象幾乎個個都是在 Survivor 區(qū)域中熬過來的,它們是不會那么容易就 "死掉" 了的惹盼。因此庸汗,F(xiàn)ull GC 發(fā)生的次數(shù)不會有 Minor GC 那么頻繁,并且做一次 Full GC 要比進行一次 Minor GC 的時間更長手报。
另外蚯舱,標(biāo)記-清除算法收集垃圾的時候會產(chǎn)生許多的內(nèi)存碎片 ( 即不連續(xù)的內(nèi)存空間 )改化,此后需要為較大的對象分配內(nèi)存空間時,若無法找到足夠的連續(xù)的內(nèi)存空間枉昏,就會提前觸發(fā)一次 GC 的收集動作陈肛。
18.類加載問題
package com.learingspring.demo.markov;
public class ClassB extends ClassA{
ClassB(){
System.out.println("load B");
}
{
System.out.println("it is B");
}
static {
System.out.println("static B");
}
public static void main(String[] args) {
new ClassB();
}
}
創(chuàng)建ClassB時會先尋找依賴的ClassA,執(zhí)行順序是
1.執(zhí)行A里的靜態(tài)塊語句
2.執(zhí)行B里的靜態(tài)塊語句
3.執(zhí)行A里的初始化塊語句兄裂,執(zhí)行A的構(gòu)造方法
4.執(zhí)行B里的初始化塊語句句旱,執(zhí)行B的構(gòu)造方法