前言
記錄個(gè)人在2017年08月的學(xué)習(xí)和總結(jié),不定期更新
2017-08-02
有序的Map
HashMap是無(wú)序的,有序的Map是TreeMap和LinkedHashMap,TreeMap里的元素排序要key實(shí)現(xiàn)Comparable接口或者傳入一個(gè)Comparator比較器,LinkedHashMap里面元素的順序和插入的順序一致
效率比較:
TreeMap采用紅黑樹(shù)的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),而LinkedHashMap采用鏈表的方式實(shí)現(xiàn),查找效率上來(lái)說(shuō)TreeMap會(huì)比LinkedHashMap高
TCP創(chuàng)建連接和斷開(kāi)連接的過(guò)程
TCP特點(diǎn)
- 三次握手
- 四次揮手
- 可靠連接
- 丟包重傳
核心:tcp是可以可靠傳輸協(xié)議,它的所有特點(diǎn)都為這個(gè)可靠傳輸服務(wù)。
創(chuàng)建連接時(shí)的三次握手
來(lái)看一個(gè)java代碼連接數(shù)據(jù)庫(kù)的三次握手過(guò)程

- 第一步:client 發(fā)送 syn 到server 發(fā)起握手;
- 第二步:server 收到 syn后回復(fù)syn+ack給client;
- 第三步:client 收到syn+ack后,回復(fù)server一個(gè)ack表示收到了server的syn+ack(此時(shí)client的48287端口的連接已經(jīng)是established)
握手的核心目的是告知對(duì)方seq(綠框是client的初始seq,藍(lán)色框是server 的初始seq),對(duì)方回復(fù)ack(收到的seq+包的大?。@樣發(fā)送端就知道有沒(méi)有丟包了。
握手的次要目的是告知和協(xié)商一些信息,圖中黃框。
MSS–最大傳輸包
SACK_PERM–是否支持Selective ack(用戶優(yōu)化重傳效率)
WS–窗口計(jì)算指數(shù)(有點(diǎn)復(fù)雜的話先不用管)
這就是tcp為什么要握手建立連接,就是為了解決tcp的可靠傳輸。
斷開(kāi)連接時(shí)的四次揮手
再來(lái)看java連上mysql后,執(zhí)行了一個(gè)SQL: select sleep(2); 然后就斷開(kāi)了連接
四個(gè)紅框表示斷開(kāi)連接的四次揮手:

第一步: client主動(dòng)發(fā)送fin包給server
第二步: server回復(fù)ack(對(duì)應(yīng)第一步fin包的ack)給client,表示server知道client要斷開(kāi)了
第三步: server發(fā)送fin包給client,表示server也可以斷開(kāi)了
第四部: client回復(fù)ack給server,表示既然雙發(fā)都發(fā)送fin包表示斷開(kāi),那么就真的斷開(kāi)吧
為什么是握手是三次而揮手是四次
這是因?yàn)楫?dāng)client發(fā)送fin包給服務(wù)器的時(shí)候,server可能還需要有一些后續(xù)工作要做,比如OS通知應(yīng)用層要關(guān)閉,這里應(yīng)用層可能需要做些準(zhǔn)備工作,或者說(shuō)server還有一些數(shù)據(jù)需要發(fā)送給client,等準(zhǔn)備工作做完或者是數(shù)據(jù)發(fā)送完畢,就可以發(fā)送fin包了
2017-08-03
JVM 參數(shù)初探
堆參數(shù)
-XX:+PrintGC 使用這個(gè)參數(shù),虛擬機(jī)啟動(dòng)后,只要遇到GC就會(huì)打印日志
-XX:+UseSerialGC 配置串行回收器
-XX:+PrintGCDetails 可以查看詳細(xì)信息,包括各個(gè)區(qū)的情況
-Xms 設(shè)置最小堆
-Xmx 設(shè)置最大堆
-Xmx20m -Xms5m -XX:+PrintCommandLineFlags 可以將隱式或者顯示傳遞給虛擬機(jī)的參數(shù)輸出,打印虛擬機(jī)配置
★技巧:JVM初始分配的內(nèi)存由-Xms指定,默認(rèn)是物理內(nèi)存的1/64;JVM最 大分配的內(nèi)存由-Xmx指定,默認(rèn)是物理內(nèi)存的1/4。默認(rèn)空余堆內(nèi)存小于40%時(shí),JVM就會(huì)增大堆直到-Xmx的最大限制;空余堆內(nèi)存大于70% 時(shí),JVM會(huì)減少堆直到-Xms的最小限制。因此服務(wù)器一般設(shè)置-Xms、-Xmx相等以避免在每次GC 后調(diào)整堆的大小。
新生代配置
-Xmn 可以設(shè)置新生代的大小,設(shè)置一個(gè)比較大的新生代會(huì)減少老年代的大小,這個(gè)參數(shù)對(duì)系統(tǒng)性能以及GC行為有很大的影響,新生代GC頻繁,老年代相對(duì)少,新生代大小一般會(huì)設(shè)置整合堆空間的1/3到1/4左右。
-XX:SurvivorRatio 用來(lái)設(shè)置新生代中eden區(qū)和from/to區(qū)的比例,默認(rèn)8:1:1
含義:-XX:SurvivorRatio=eden/from=eden/to
★技巧:不同的堆分布情況,對(duì)系統(tǒng)執(zhí)行會(huì)產(chǎn)生一定影響,在實(shí)際情況下,應(yīng)該根據(jù)系統(tǒng)的特點(diǎn)做出合理的配置,基本策略:盡可能將對(duì)象預(yù)留在新生代,減少老年代的GC次數(shù)。
除了可以設(shè)置新生代的絕對(duì)大小-Xmn,還可以使用-XX:NewRatio來(lái)設(shè)置新生代和老年代的比例:-XX:NewRatio=新生代/老年代
堆溢出處理
堆溢出處理
在Java程序的運(yùn)行過(guò)程中,如果堆空間不足,則會(huì)拋出內(nèi)存溢出的錯(cuò)誤Out of Memory(OOM),一旦這類問(wèn)題發(fā)生在生產(chǎn)環(huán)境,就可能引起嚴(yán)重的業(yè)務(wù)中斷,java虛擬機(jī)提供了-XX:HeapDumpOnOutOfMemoryError,使用該參數(shù)可以在內(nèi)存溢出時(shí)導(dǎo)出整個(gè)堆信息,與之配合使用的還有參數(shù)。
-XX:HeapDumpPath,可以設(shè)置導(dǎo)出堆的存放路徑
內(nèi)存分析工具:Memory Analyzer
dump信息:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/Test03.dump
棧參數(shù)
-Xss 指定線程最大??臻g,整合參數(shù)也直接決定了函數(shù)可調(diào)用的最大深度
方法區(qū)
JDK8以前,方法區(qū)和java堆一樣,是一塊所有線程共享的內(nèi)存區(qū)域,它用于保存系統(tǒng)的類信息,方法區(qū)(永久區(qū))可以保存多少信息可以對(duì)其進(jìn)行配置,在默認(rèn)情況下,-XX:MaxPermSize為64M,如果系統(tǒng)運(yùn)行時(shí)產(chǎn)生大量的類,就需要設(shè)置一個(gè)相對(duì)合適的方法區(qū),以免出現(xiàn)永久區(qū)0內(nèi)存溢出的問(wèn)題。
-XX:MaxPermSize=64M -XX:PermSize=64M
jdk1.7以后不分client或者server模式
JDK8以后,永久區(qū)被移除,使用本地內(nèi)存來(lái)存儲(chǔ)類元數(shù)據(jù)信息并稱之為:元空間(Metaspace)
2017-08-04
Spring IOC和AOP
特點(diǎn)
- 降低了組件之間的耦合性 ,實(shí)現(xiàn)了軟件各層之間的解耦
- 可以使用容易提供的眾多服務(wù),如事務(wù)管理,消息服務(wù)等
- 容器提供單例模式支持
- 容器提供了AOP技術(shù),利用它很容易實(shí)現(xiàn)如權(quán)限攔截,運(yùn)行期監(jiān)控等功能
- 容器提供了眾多的輔助類,能加快應(yīng)用的開(kāi)發(fā)
- spring對(duì)于主流的應(yīng)用框架提供了集成支持,如hibernate,JPA,struts等
- spring屬于低侵入式設(shè)計(jì),代碼的污染極低
- 獨(dú)立于各種應(yīng)用服務(wù)器
- spring的DI機(jī)制降低了業(yè)務(wù)對(duì)象替換的復(fù)雜性
- spring的高度開(kāi)放性,并不強(qiáng)制應(yīng)用完全依賴于Spring,開(kāi)發(fā)者可以自由選擇spring的部分或全部
根本目的
根本目的:簡(jiǎn)化Java開(kāi)發(fā)。
為了降低Java開(kāi)發(fā)的復(fù)雜性,Spring采取以下4種關(guān)鍵策略:
- 基于POJO的輕量級(jí)和最小侵入性編程
- 通過(guò)依賴注入和面向接口實(shí)現(xiàn)松耦合
- 基于切面和慣例進(jìn)行聲明式編程
- 通過(guò)切面和模版減少樣板示代碼
IOC
概念
Control,即“控制反轉(zhuǎn)”,不是什么技術(shù),而是一種設(shè)計(jì)思想。在Java開(kāi)發(fā)中,Ioc意味著將你設(shè)計(jì)好的對(duì)象交給容器控制,而不是傳統(tǒng)的在你的對(duì)象內(nèi)部直接控制。如何理解好Ioc呢?理解好Ioc的關(guān)鍵是要明確“誰(shuí)控制誰(shuí),控制什么,為何是反轉(zhuǎn)(有反轉(zhuǎn)就應(yīng)該有正轉(zhuǎn)了),哪些方面反轉(zhuǎn)了”,那我們來(lái)深入分析一下:
誰(shuí)控制誰(shuí),控制什么:傳統(tǒng)Java SE程序設(shè)計(jì),我們直接在對(duì)象內(nèi)部通過(guò)new進(jìn)行創(chuàng)建對(duì)象,是程序主動(dòng)去創(chuàng)建依賴對(duì)象;而IoC是有專門一個(gè)容器來(lái)創(chuàng)建這些對(duì)象,即由Ioc容器來(lái)控制對(duì) 象的創(chuàng)建;誰(shuí)控制誰(shuí)?當(dāng)然是IoC 容器控制了對(duì)象;控制什么?那就是主要控制了外部資源獲?。ú恢皇菍?duì)象包括比如文件等)。
為何是反轉(zhuǎn),哪些方面反轉(zhuǎn)了:有反轉(zhuǎn)就有正轉(zhuǎn),傳統(tǒng)應(yīng)用程序是由我們自己在對(duì)象中主動(dòng)控制去直接獲取依賴對(duì)象,也就是正轉(zhuǎn);而反轉(zhuǎn)則是由容器來(lái)幫忙創(chuàng)建及注入依賴對(duì)象;為何是反轉(zhuǎn)?因?yàn)橛扇萜鲙臀覀儾檎壹白⑷胍蕾噷?duì)象,對(duì)象只是被動(dòng)的接受依賴對(duì)象,所以是反轉(zhuǎn);哪些方面反轉(zhuǎn)了?依賴對(duì)象的獲取被反轉(zhuǎn)了。
IoC能做什么
IoC 不是一種技術(shù),只是一種思想,一個(gè)重要的面向?qū)ο缶幊痰姆▌t,它能指導(dǎo)我們?nèi)绾卧O(shè)計(jì)出松耦合、更優(yōu)良的程序。傳統(tǒng)應(yīng)用程序都是由我們?cè)陬悆?nèi)部主動(dòng)創(chuàng)建依賴對(duì)象,從而導(dǎo)致類與類之間高耦合,難于測(cè)試;有了IoC容器后,把創(chuàng)建和查找依賴對(duì)象的控制權(quán)交給了容器,由容器進(jìn)行注入組合對(duì)象,所以對(duì)象與對(duì)象之間是 松散耦合,這樣也方便測(cè)試,利于功能復(fù)用,更重要的是使得程序的整個(gè)體系結(jié)構(gòu)變得非常靈活。
其實(shí)IoC對(duì)編程帶來(lái)的最大改變不是從代碼上,而是從思想上,發(fā)生了“主從換位”的變化。應(yīng)用程序原本是老大,要獲取什么資源都是主動(dòng)出擊,但是在IoC/DI思想中,應(yīng)用程序就變成被動(dòng)的了,被動(dòng)的等待IoC容器來(lái)創(chuàng)建并注入它所需要的資源了。
IoC很好的體現(xiàn)了面向?qū)ο笤O(shè)計(jì)法則之一—— 好萊塢法則:“別找我們,我們找你”;即由IoC容器幫對(duì)象找相應(yīng)的依賴對(duì)象并注入,而不是由對(duì)象主動(dòng)去找。
IoC和DI
DI—Dependency Injection,即“依賴注入”:組件之間依賴關(guān)系由容器在運(yùn)行期決定,形象的說(shuō),即由容器動(dòng)態(tài)的將某個(gè)依賴關(guān)系注入到組件之中。依賴注入的目的并非為軟件系統(tǒng)帶來(lái)更多功能,而是為了提升組件重用的頻率,并為系統(tǒng)搭建一個(gè)靈活、可擴(kuò)展的平臺(tái)。通過(guò)依賴注入機(jī)制,我們只需要通過(guò)簡(jiǎn)單的配置,而無(wú)需任何代碼就可指定目標(biāo)需要的資源,完成自身的業(yè)務(wù)邏輯,而不需要關(guān)心具體的資源來(lái)自何處,由誰(shuí)實(shí)現(xiàn)。
理解DI的關(guān)鍵是:“誰(shuí)依賴誰(shuí),為什么需要依賴,誰(shuí)注入誰(shuí),注入了什么”,那我們來(lái)深入分析一下:
誰(shuí)依賴于誰(shuí):當(dāng)然是應(yīng)用程序依賴于IoC容器;
為什么需要依賴:應(yīng)用程序需要IoC容器來(lái)提供對(duì)象需要的外部資源;
誰(shuí)注入誰(shuí):很明顯是IoC容器注入應(yīng)用程序某個(gè)對(duì)象,應(yīng)用程序依賴的對(duì)象;
注入了什么:就是注入某個(gè)對(duì)象所需要的外部資源(包括對(duì)象、資源、常量數(shù)據(jù))。
IoC和DI由什么關(guān)系呢?其實(shí)它們是同一個(gè)概念的不同角度描述,由于控制反轉(zhuǎn)概念比較含糊(可能只是理解為容器控制對(duì)象這一個(gè)層面,很難讓人想到誰(shuí)來(lái)維護(hù)對(duì)象關(guān)系),所以2004年大師級(jí)人物Martin Fowler又給出了一個(gè)新的名字:“依賴注入”,相對(duì)IoC 而言,“依賴注入”明確描述了“被注入對(duì)象依賴IoC容器配置依賴對(duì)象”。
AOP
什么是AOP
AOP(Aspect-OrientedProgramming,面向方面編程),可以說(shuō)是OOP(Object-Oriented Programing,面向?qū)ο缶幊蹋┑难a(bǔ)充和完善。OOP引入封裝、繼承和多態(tài)性等概念來(lái)建立一種對(duì)象層次結(jié)構(gòu),用以模擬公共行為的一個(gè)集合。當(dāng)我們需要為分散的對(duì)象引入公共行為的時(shí)候,OOP則顯得無(wú)能為力。也就是說(shuō),OOP允許你定義從上到下的關(guān)系,但并不適合定義從左到右的關(guān)系。例如日志功能。日志代碼往往水平地散布在所有對(duì)象層次中,而與它所散布到的對(duì)象的核心功能毫無(wú)關(guān)系。對(duì)于其他類型的代碼,如安全性、異常處理和透明的持續(xù)性也是如此。這種散布在各處的無(wú)關(guān)的代碼被稱為橫切(cross-cutting)代碼,在OOP設(shè)計(jì)中,它導(dǎo)致了大量代碼的重復(fù),而不利于各個(gè)模塊的重用。
而AOP技術(shù)則恰恰相反,它利用一種稱為“橫切”的技術(shù),剖解開(kāi)封裝的對(duì)象內(nèi)部,并將那些影響了多個(gè)類的公共行為封裝到一個(gè)可重用模塊,并將其名為“Aspect”,即方面。所謂“方面”,簡(jiǎn)單地說(shuō),就是將那些與業(yè)務(wù)無(wú)關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任封裝起來(lái),便于減少系統(tǒng)的重復(fù)代碼,降低模塊間的耦合度,并有利于未來(lái)的可操作性和可維護(hù)性。AOP代表的是一個(gè)橫向的關(guān)系,如果說(shuō)“對(duì)象”是一個(gè)空心的圓柱體,其中封裝的是對(duì)象的屬性和行為;那么面向方面編程的方法,就仿佛一把利刃,將這些空心圓柱體剖開(kāi),以獲得其內(nèi)部的消息。而剖開(kāi)的切面,也就是所謂的“方面”了。然后它又以巧奪天功的妙手將這些剖開(kāi)的切面復(fù)原,不留痕跡。
使用“橫切”技術(shù),AOP把軟件系統(tǒng)分為兩個(gè)部分:核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)。業(yè)務(wù)處理的主要流程是核心關(guān)注點(diǎn),與之關(guān)系不大的部分是橫切關(guān)注點(diǎn)。橫切關(guān)注點(diǎn)的一個(gè)特點(diǎn)是,他們經(jīng)常發(fā)生在核心關(guān)注點(diǎn)的多處,而各處都基本相似。比如權(quán)限認(rèn)證、日志、事務(wù)處理。Aop 的作用在于分離系統(tǒng)中的各種關(guān)注點(diǎn),將核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)分離開(kāi)來(lái)。正如Avanade公司的高級(jí)方案構(gòu)架師Adam Magee所說(shuō),AOP的核心思想就是“將應(yīng)用程序中的商業(yè)邏輯同對(duì)其提供支持的通用服務(wù)進(jìn)行分離?!?/p>
實(shí)現(xiàn)AOP的技術(shù),主要分為兩大類:一是采用動(dòng)態(tài)代理技術(shù),利用截取消息的方式,對(duì)該消息進(jìn)行裝飾,以取代原有對(duì)象行為的執(zhí)行;二是采用靜態(tài)織入的方式,引入特定的語(yǔ)法創(chuàng)建“方面”,從而使得編譯器可以在編譯期間織入有關(guān)“方面”的代碼。
使用場(chǎng)景
AOP用來(lái)封裝橫切關(guān)注點(diǎn),具體可以在下面的場(chǎng)景中使用:
- Authentication 權(quán)限
- Caching 緩存
- Context passing 內(nèi)容傳遞
- Error handling 錯(cuò)誤處理
- Lazy loading 懶加載
- Debugging 調(diào)試
- logging, tracing, profiling and monitoring 記錄跟蹤 優(yōu)化 校準(zhǔn)
- Performance optimization 性能優(yōu)化
- Persistence 持久化
- Resource pooling 資源池
- Synchronization 同步
- Transactions 事務(wù)
概念
首先讓我們從一些重要的AOP概念和術(shù)語(yǔ)開(kāi)始。這些術(shù)語(yǔ)不是Spring特有的。不過(guò)AOP術(shù)語(yǔ)并不是特別的直觀,如果Spring使用自己的術(shù)語(yǔ),將會(huì)變得更加令人困惑。
切面(Aspect):一個(gè)關(guān)注點(diǎn)的模塊化,這個(gè)關(guān)注點(diǎn)可能會(huì)橫切多個(gè)對(duì)象。事務(wù)管理是J2EE應(yīng)用中一個(gè)關(guān)于橫切關(guān)注點(diǎn)的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式來(lái)實(shí)現(xiàn)。
連接點(diǎn)(Joinpoint):在程序執(zhí)行過(guò)程中某個(gè)特定的點(diǎn),比如某方法調(diào)用的時(shí)候或者處理異常的時(shí)候。在Spring AOP中,一個(gè)連接點(diǎn)總是表示一個(gè)方法的執(zhí)行。
通知(Advice):在切面的某個(gè)特定的連接點(diǎn)上執(zhí)行的動(dòng)作。其中包括了“around”、“before”和“after”等不同類型的通知(通知的類型將在后面部分進(jìn)行討論)。許多AOP框架(包括Spring)都是以攔截器做通知模型,并維護(hù)一個(gè)以連接點(diǎn)為中心的攔截器鏈。
切入點(diǎn)(Pointcut):匹配連接點(diǎn)的斷言。通知和一個(gè)切入點(diǎn)表達(dá)式關(guān)聯(lián),并在滿足這個(gè)切入點(diǎn)的連接點(diǎn)上運(yùn)行(例如,當(dāng)執(zhí)行某個(gè)特定名稱的方法時(shí))。切入點(diǎn)表達(dá)式如何和連接點(diǎn)匹配是AOP的核心:Spring缺省使用AspectJ切入點(diǎn)語(yǔ)法。
引入(Introduction):用來(lái)給一個(gè)類型聲明額外的方法或?qū)傩裕ㄒ脖环Q為連接類型聲明(inter-type declaration))。Spring允許引入新的接口(以及一個(gè)對(duì)應(yīng)的實(shí)現(xiàn))到任何被代理的對(duì)象。例如,你可以使用引入來(lái)使一個(gè)bean實(shí)現(xiàn)IsModified接口,以便簡(jiǎn)化緩存機(jī)制。
目標(biāo)對(duì)象(Target Object): 被一個(gè)或者多個(gè)切面所通知的對(duì)象。也被稱做被通知(advised)對(duì)象。 既然Spring AOP是通過(guò)運(yùn)行時(shí)代理實(shí)現(xiàn)的,這個(gè)對(duì)象永遠(yuǎn)是一個(gè)被代理(proxied)對(duì)象。
AOP代理(AOP Proxy):AOP框架創(chuàng)建的對(duì)象,用來(lái)實(shí)現(xiàn)切面契約(例如通知方法執(zhí)行等等)。在Spring中,AOP代理可以是JDK動(dòng)態(tài)代理或者CGLIB代理。
織入(Weaving):把切面連接到其它的應(yīng)用程序類型或者對(duì)象上,并創(chuàng)建一個(gè)被通知的對(duì)象。這些可以在編譯時(shí)(例如使用AspectJ編譯器),類加載時(shí)和運(yùn)行時(shí)完成。Spring和其他純Java AOP框架一樣,在運(yùn)行時(shí)完成織入。
通知類型:
前置通知(Before advice):在某連接點(diǎn)之前執(zhí)行的通知,但這個(gè)通知不能阻止連接點(diǎn)之前的執(zhí)行流程(除非它拋出一個(gè)異常)。
后置通知(After returning advice):在某連接點(diǎn)正常完成后執(zhí)行的通知:例如,一個(gè)方法沒(méi)有拋出任何異常,正常返回。
異常通知(After throwing advice):在方法拋出異常退出時(shí)執(zhí)行的通知。
最終通知(After (finally) advice):當(dāng)某連接點(diǎn)退出的時(shí)候執(zhí)行的通知(不論是正常返回還是異常退出)。
環(huán)繞通知(Around Advice):包圍一個(gè)連接點(diǎn)的通知,如方法調(diào)用。這是最強(qiáng)大的一種通知類型。環(huán)繞通知可以在方法調(diào)用前后完成自定義的行為。它也會(huì)選擇是否繼續(xù)執(zhí)行連接點(diǎn)或直接返回它自己的返回值或拋出異常來(lái)結(jié)束執(zhí)行。
環(huán)繞通知是最常用的通知類型。和AspectJ一樣,Spring提供所有類型的通知,我們推薦你使用盡可能簡(jiǎn)單的通知類型來(lái)實(shí)現(xiàn)需要的功能。例如,如果你只是需要一個(gè)方法的返回值來(lái)更新緩存,最好使用后置通知而不是環(huán)繞通知,盡管環(huán)繞通知也能完成同樣的事情。用最合適的通知類型可以使得編程模型變得簡(jiǎn)單,并且能夠避免很多潛在的錯(cuò)誤。比如,你不需要在JoinPoint上調(diào)用用于環(huán)繞通知的proceed()方法,就不會(huì)有調(diào)用的問(wèn)題。
2017-08-06
線程和進(jìn)程
什么是線程?
線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位,它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。程序員可以通過(guò)它進(jìn)行多處理器編程,你可以使用多線程對(duì)運(yùn)算密集型任務(wù)提速。比如,如果一個(gè)線程完成一個(gè)任務(wù)要100毫秒,那么用十個(gè)線程完成改任務(wù)只需10毫秒。Java在語(yǔ)言層面對(duì)多線程提供了卓越的支持,它也是一個(gè)很好的賣點(diǎn)。
線程和進(jìn)程有什么區(qū)別?
線程是進(jìn)程的子集,一個(gè)進(jìn)程可以有很多線程,每條線程并行執(zhí)行不同的任務(wù)。不同的進(jìn)程使用不同的內(nèi)存空間,而所有的線程共享一片相同的內(nèi)存空間。別把它和棧內(nèi)存搞混,每個(gè)線程都擁有單獨(dú)的棧內(nèi)存用來(lái)存儲(chǔ)本地?cái)?shù)據(jù)。
2017-08-07
Java棧上分配對(duì)象
我們?cè)趯W(xué)習(xí)使用Java的過(guò)程中,一般認(rèn)為new出來(lái)的對(duì)象都是被分配在堆上,但是這個(gè)結(jié)論不是那么的絕對(duì),通過(guò)對(duì)Java對(duì)象分配的過(guò)程分析,可以知道有兩個(gè)地方會(huì)導(dǎo)致Java中new出來(lái)的對(duì)象并不一定分別在所認(rèn)為的堆上。這兩個(gè)點(diǎn)分別是Java中的逃逸分析和TLAB(Thread Local Allocation Buffer)
逃逸分析
定義
逃逸分析,是一種可以有效減少Java 程序中同步負(fù)載和內(nèi)存堆分配壓力的跨函數(shù)全局?jǐn)?shù)據(jù)流分析算法。通過(guò)逃逸分析,Java Hotspot編譯器能夠分析出一個(gè)新的對(duì)象的引用的使用范圍從而決定是否要將這個(gè)對(duì)象分配到堆上。
在計(jì)算機(jī)語(yǔ)言編譯器優(yōu)化原理中,逃逸分析是指分析指針動(dòng)態(tài)范圍的方法,它同編譯器優(yōu)化原理的指針?lè)治龊屯庑畏治鱿嚓P(guān)聯(lián)。當(dāng)變量(或者對(duì)象)在方法中分配后,其指針有可能被返回或者被全局引用,這樣就會(huì)被其他過(guò)程或者線程所引用,這種現(xiàn)象稱作指針(或者引用)的逃逸(Escape)。
Java在java SE 6u23以及以后的版本中支持并默認(rèn)開(kāi)啟了逃逸分析的選項(xiàng)。Java的 HotSpot JIT編譯器,能夠在方法重載或者動(dòng)態(tài)加載代碼的時(shí)候?qū)Υa進(jìn)行逃逸分析,同時(shí)Java對(duì)象在堆上分配和內(nèi)置線程的特點(diǎn)使得逃逸分析成Java的重要功能。
逃逸分析的方法
Java Hotspot編譯器使用的是
Choi J D, Gupta M, Serrano M, et al. Escape analysis for Java[J]. Acm Sigplan Notices, 1999, 34(10): 1-19.
Jong-Deok Choi, Manish Gupta, Mauricio Seffano,Vugranam C. Sreedhar, Sam Midkiff等在論文《Escape Analysis for Java》中描述的算法進(jìn)行逃逸分析的。該算法引入了連通圖,用連通圖來(lái)構(gòu)建對(duì)象和對(duì)象引用之間的可達(dá)性關(guān)系,并在次基礎(chǔ)上,提出一種組合數(shù)據(jù)流分析法。由于算法是上下文相關(guān)和流敏感的,并且模擬了對(duì)象任意層次的嵌套關(guān)系,所以分析精度較高,只是運(yùn)行時(shí)間和內(nèi)存消耗相對(duì)較大。
絕大多數(shù)逃逸分析的實(shí)現(xiàn)都基于一個(gè)所謂“封閉世界(closed world)”的前提:所有可能被執(zhí)行的,方法在做逃逸分析前都已經(jīng)得知,并且,程序的實(shí)際運(yùn)行不會(huì)改變它們之間的調(diào)用關(guān)系 。但當(dāng)真實(shí)的 Java 程序運(yùn)行時(shí),這樣的假設(shè)并不成立。Java 程序擁有的許多特性,例如動(dòng)態(tài)類加載、調(diào)用本地函數(shù)以及反射程序調(diào)用等等,都將打破所謂“封閉世界”的約定。
逃逸分析后的處理
經(jīng)過(guò)逃逸分析之后,可以得到三種對(duì)象的逃逸狀態(tài)。
- GlobalEscape(全局逃逸), 即一個(gè)對(duì)象的引用逃出了方法或者線程。例如,一個(gè)對(duì)象的引用是復(fù)制給了一個(gè)類變量,或者存儲(chǔ)在在一個(gè)已經(jīng)逃逸的對(duì)象當(dāng)中,或者這個(gè)對(duì)象的引用作為方法的返回值返回給了調(diào)用方法。
- ArgEscape(參數(shù)級(jí)逃逸),即在方法調(diào)用過(guò)程當(dāng)中傳遞對(duì)象的引用給一個(gè)方法。這種狀態(tài)可以通過(guò)分析被調(diào)方法的二進(jìn)制代碼確定。
- NoEscape(沒(méi)有逃逸),一個(gè)可以進(jìn)行標(biāo)量替換的對(duì)象??梢圆粚⑦@種對(duì)象分配在傳統(tǒng)的堆上。
編譯器可以使用逃逸分析的結(jié)果,對(duì)程序進(jìn)行一下優(yōu)化。 - 堆分配對(duì)象變成棧分配對(duì)象。一個(gè)方法當(dāng)中的對(duì)象,對(duì)象的引用沒(méi)有發(fā)生逃逸,那么這個(gè)方法可能會(huì)被分配在棧內(nèi)存上而非常見(jiàn)的堆內(nèi)存上。
- 消除同步。線程同步的代價(jià)是相當(dāng)高的,同步的后果是降低并發(fā)性和性能。逃逸分析可以判斷出某個(gè)對(duì)象是否始終只被一個(gè)線程訪問(wèn),如果只被一個(gè)線程訪問(wèn),那么對(duì)該對(duì)象的同步操作就可以轉(zhuǎn)化成沒(méi)有同步保護(hù)的操作,這樣就能大大提高并發(fā)程度和性能。
- 矢量替代。逃逸分析方法如果發(fā)現(xiàn)對(duì)象的內(nèi)存存儲(chǔ)結(jié)構(gòu)不需要連續(xù)進(jìn)行的話,就可以將對(duì)象的部分甚至全部都保存在CPU寄存器內(nèi),這樣能大大提高訪問(wèn)速度。
下面,我們看一下逃逸分析的例子。
class Main {
public static void main(String[] args) {
example();
}
public static void example() {
Foo foo = new Foo(); //alloc
Bar bar = new Bar(); //alloc
bar.setFoo(foo);
}
}
class Foo {}
class Bar {
private Foo foo;
public void setFoo(Foo foo) {
this.foo = foo;
}
}
在這個(gè)例子當(dāng)中,我們創(chuàng)建了兩個(gè)對(duì)象,F(xiàn)oo對(duì)象和Bar對(duì)象,同時(shí)我們把Foo對(duì)象的應(yīng)用賦值給了Bar對(duì)象的方法。此時(shí),如果Bar對(duì)在堆上就會(huì)引起Foo對(duì)象的逃逸,但是,在本例當(dāng)中,編譯器通過(guò)逃逸分析,可以知道Bar對(duì)象沒(méi)有逃出example()方法,因此這也意味著Foo也沒(méi)有逃出example方法。因此,編譯器可以將這兩個(gè)對(duì)象分配到棧上。
編譯器經(jīng)過(guò)逃逸分析的效果
測(cè)試代碼
class EscapeAnalysis {
private static class Foo {
private int x;
private static int counter;
public Foo() {
x = (++counter);
}
}
public static void main(String[] args) {
long start = System.nanoTime();
for (int i = 0; i < 1000 * 1000 * 10; ++i) {
Foo foo = new Foo();
}
long end = System.nanoTime();
System.out.println("Time cost is " + (end - start));
}
}
未開(kāi)啟逃逸分析設(shè)置為:
-server -verbose:gc
開(kāi)啟逃逸分析設(shè)置為:
-server -verbose:gc -XX:+DoEscapeAnalysis
在未開(kāi)啟逃逸分析的狀況下運(yùn)行情況如下:
[GC 5376K->427K(63872K), 0.0006051 secs]
[GC 5803K->427K(63872K), 0.0003928 secs]
[GC 5803K->427K(63872K), 0.0003639 secs]
[GC 5803K->427K(69248K), 0.0003770 secs]
[GC 11179K->427K(69248K), 0.0003987 secs]
[GC 11179K->427K(79552K), 0.0003817 secs]
[GC 21931K->399K(79552K), 0.0004342 secs]
[GC 21903K->399K(101120K), 0.0002175 secs]
[GC 43343K->399K(101184K), 0.0001421 secs]
Time cost is 58514571
開(kāi)啟逃逸分析的狀況下,運(yùn)行情況如下:
Time cost is 10031306
未開(kāi)啟逃逸分析時(shí),運(yùn)行上訴代碼,JVM執(zhí)行了GC操作,而在開(kāi)啟逃逸分析情況下,JVM并沒(méi)有執(zhí)行GC操作。同時(shí),操作時(shí)間上,開(kāi)啟逃逸分析的程序運(yùn)行時(shí)間是未開(kāi)啟逃逸分析時(shí)間的1/5。
TLAB
JVM在內(nèi)存新生代Eden Space中開(kāi)辟了一小塊線程私有的區(qū)域,稱作TLAB(Thread-local allocation buffer)。默認(rèn)設(shè)定為占用Eden Space的1%。在Java程序中很多對(duì)象都是小對(duì)象且用過(guò)即丟,它們不存在線程共享也適合被快速GC,所以對(duì)于小對(duì)象通常JVM會(huì)優(yōu)先分配在TLAB上,并且TLAB上的分配由于是線程私有所以沒(méi)有鎖開(kāi)銷。因此在實(shí)踐中分配多個(gè)小對(duì)象的效率通常比分配一個(gè)大對(duì)象的效率要高。
也就是說(shuō),Java中每個(gè)線程都會(huì)有自己的緩沖區(qū)稱作TLAB(Thread-local allocation buffer),每個(gè)TLAB都只有一個(gè)線程可以操作,TLAB結(jié)合bump-the-pointer技術(shù)可以實(shí)現(xiàn)快速的對(duì)象分配,而不需要任何的鎖進(jìn)行同步,也就是說(shuō),在對(duì)象分配的時(shí)候不用鎖住整個(gè)堆,而只需要在自己的緩沖區(qū)分配即可。
關(guān)于對(duì)象分配的JDK源碼可以參見(jiàn)JVM 之 Java對(duì)象創(chuàng)建[初始化]中對(duì)OpenJDK源碼的分析。
Java對(duì)象分配的過(guò)程
編譯器通過(guò)逃逸分析,確定對(duì)象是在棧上分配還是在堆上分配。如果是在堆上分配,則進(jìn)入選項(xiàng)2.
如果tlab_top + size <= tlab_end,則在在TLAB上直接分配對(duì)象并增加tlab_top 的值,如果現(xiàn)有的TLAB不足以存放當(dāng)前對(duì)象則3.
重新申請(qǐng)一個(gè)TLAB,并再次嘗試存放當(dāng)前對(duì)象。如果放不下,則4.
在Eden區(qū)加鎖(這個(gè)區(qū)是多線程共享的),如果eden_top + size <= eden_end則將對(duì)象存放在Eden區(qū),增加eden_top 的值,如果Eden區(qū)不足以存放,則5.
執(zhí)行一次Young GC(minor collection)。
經(jīng)過(guò)Young GC之后,如果Eden區(qū)任然不足以存放當(dāng)前對(duì)象,則直接分配到老年代。
對(duì)象不在堆上分配主要的原因還是堆是共享的,在堆上分配有鎖的開(kāi)銷。無(wú)論是TLAB還是棧都是線程私有的,私有即避免了競(jìng)爭(zhēng)(當(dāng)然也可能產(chǎn)生額外的問(wèn)題例如可見(jiàn)性問(wèn)題),這是典型的用空間換效率的做法。
2017-08-08
UnSafe類
JDK源碼中,在研究AQS框架時(shí),會(huì)發(fā)現(xiàn)很多地方都使用了CAS操作,在并發(fā)實(shí)現(xiàn)中CAS操作必須具備原子性,而且是硬件級(jí)別的原子性,Java被隔離在硬件之上,明顯力不從心,這時(shí)為了能直接操作操作系統(tǒng)層面,肯定要通過(guò)用C++編寫(xiě)的native本地方法來(lái)擴(kuò)展實(shí)現(xiàn)。JDK提供了一個(gè)類來(lái)滿足CAS的要求,sun.misc.Unsafe,從名字上可以大概知道它用于執(zhí)行低級(jí)別、不安全的操作,AQS就是使用此類完成硬件級(jí)別的原子操作。
Unsafe是一個(gè)很強(qiáng)大的類,它可以分配內(nèi)存、釋放內(nèi)存、可以定位對(duì)象某字段的位置、可以修改對(duì)象的字段值、可以使線程掛起、使線程恢復(fù)、可進(jìn)行硬件級(jí)別原子的CAS操作等等,但平時(shí)我們沒(méi)有這么特殊的需求去使用它,而且必須在受信任代碼(一般由JVM指定)中調(diào)用此類,例如直接Unsafe unsafe = Unsafe.getUnsafe();獲取一個(gè)Unsafe實(shí)例是不會(huì)成功的,因?yàn)檫@個(gè)類的安全性很重要,設(shè)計(jì)者對(duì)其進(jìn)行了如下判斷,它會(huì)檢測(cè)調(diào)用它的類是否由啟動(dòng)類加載器Bootstrap ClassLoader(它的類加載器為null)加載,由此保證此類只能由JVM指定的類使用。
public static Unsafe getUnsafe() {
Class cc = sun.reflect.Reflection.getCallerClass(2);
if (cc.getClassLoader() != null)
throw new SecurityException("Unsafe");
return theUnsafe;
}
當(dāng)然可以通過(guò)反射繞過(guò)上面的限制,用下面的getUnsafeInstance方法可以獲取Unsafe實(shí)例,這段代碼演示了如何獲取java對(duì)象的相對(duì)地址偏移量及使用Unsafe完成CAS操作,最終輸出的是flag字段的內(nèi)存偏移量及CAS操作后的值。分別為12和101。另外如果使用開(kāi)發(fā)工具如Eclipse,可能會(huì)編譯通不過(guò),只要把編譯錯(cuò)誤提示關(guān)掉即可。
package com.example.demo;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* @author zhangguoji
* @date 2017/8/8 11:46
*/
public class UnsafeTest {
private int flag = 100;
private static long offset;
private static Unsafe unsafe = null;
static {
try {
unsafe = getUnsafeInstance();
offset = unsafe.objectFieldOffset(UnsafeTest.class
.getDeclaredField("flag"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
int expect = 100;
int update = 101;
UnsafeTest unsafeTest = new UnsafeTest();
System.out.println("unsafeTest對(duì)象的flag字段的地址偏移量為:" + offset);
unsafeTest.doSwap(offset, expect, update);
System.out.println("CAS操作后的flag值為:" + unsafeTest.getFlag());
}
private boolean doSwap(long offset, int expect, int update) {
return unsafe.compareAndSwapInt(this, offset, expect, update);
}
public int getFlag() {
return flag;
}
private static Unsafe getUnsafeInstance() throws SecurityException,
NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeInstance.setAccessible(true);
return (Unsafe) theUnsafeInstance.get(Unsafe.class);
}
}
結(jié)果
unsafeTest對(duì)象的flag字段的地址偏移量為:12
CAS操作后的flag值為:101
Unsafe類讓我們明白了java是如何實(shí)現(xiàn)對(duì)操作系統(tǒng)操作的,一般我們使用java是不需要在內(nèi)存中處理java對(duì)象及內(nèi)存地址位置的,但有的時(shí)候我們確實(shí)需要知道java對(duì)象相關(guān)的地址,于是我們使用Unsafe類,盡管java對(duì)其提供了足夠的安全管理。
Java語(yǔ)言的設(shè)計(jì)者們極力隱藏涉及底層操作系統(tǒng)的相關(guān)操作,但此節(jié)我們本著對(duì)AQS框架實(shí)現(xiàn)的目的,不得不剖析了Unsafe類,因?yàn)锳QS里面即是使用Unsafe獲取對(duì)象字段的地址偏移量、相關(guān)原子操作來(lái)實(shí)現(xiàn)CAS操作的。
MySQL value和vlues的區(qū)別
兩者功能一樣,可以混合使用,但是value插入多條數(shù)據(jù)時(shí)性能較好,values插入單條數(shù)據(jù)時(shí)性能較好,跟我們的邏輯相反的,很奇怪
單是SQL Sever只能使用values
ConcurrentHashMap跨版本問(wèn)題
背景知識(shí)
javac
javac有兩個(gè)指令:-source和-target,比如下述指令:
/usr/lib/java/jdk1.8.0_131/bin/javac -source 1.7 -target 1.7 HelloWorld.java
-source:表示我的代碼將采用哪個(gè)java版本來(lái)編譯。該值影響的是編譯器對(duì)語(yǔ)法規(guī)則的校驗(yàn)。比如HelloWorld.java中含有jdk8的語(yǔ)法,但是你的-source為1.7,那么編譯器就會(huì)報(bào)錯(cuò)。
-target:表示生成的字節(jié)碼將會(huì)在哪個(gè)版本(及以上)的jvm上運(yùn)行。比如HelloWorld.java指定了-target為1.8,那么HelloWorld.class只能在1.8即以上的jvm中運(yùn)行,如果在1.7的jvm上運(yùn)行,就會(huì)報(bào)錯(cuò)。
rt.jar
jdk的rt.jar里面包含了jdk的核心類,比如String,集合等。JVM在加載類時(shí),對(duì)于rt.jar包里面的所有的類持有最高的信任而不做任何校驗(yàn)。
ConcurrentHashMap
ConcurrentHashMap類有一個(gè)方法叫做keySet,用來(lái)返回當(dāng)前map中的key集合。雖然返回的是key的集合,但是在1.7和1.8中用來(lái)表示該集合的類卻完全不同。在1.7中,返回的是Set:
public Set<K> More ...keySet() {
Set<K> ks = keySet;
return (ks != null) ? ks : (keySet = new KeySet());
}
然而在1.8中返回的是KeySetView:
public KeySetView<K,V> keySet() {
KeySetView<K,V> ks;
return (ks = keySet) != null ? ks : (keySet = new KeySetView<K,V>(this, null));
}
其中KeySetView其實(shí)是Set接口的一個(gè)實(shí)現(xiàn)類。我們?cè)賮?lái)看下述代碼:
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class HelloWorld {
public static void main(String[] args) {
ConcurrentHashMap<String, String> test = new ConcurrentHashMap<>();
Set<String> keySet = test.keySet();
}
}
然后我們用jdk8的javac來(lái)進(jìn)行編譯:
$ /usr/lib/java8/bin/javac -source 1.7 -target 1.7 HelloWorld.java
warning: [options] bootstrap class path not set in conjunction with -source 1.7
1 warning
或者中文版的報(bào)錯(cuò)信息如下:
警告: [options] 未與 -source 1.7 一起設(shè)置引導(dǎo)類路徑
1 個(gè)警告
但是上述代碼是可以通過(guò)編譯的,因?yàn)?code>KeySetView是Set的實(shí)現(xiàn)類,所以1.7的語(yǔ)法沒(méi)有任何問(wèn)題。但是編譯生成的class文件無(wú)法在1.7版本的jvm上運(yùn)行。我們看一下字節(jié)碼的實(shí)際內(nèi)容:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentHashMap.KeySetView;
public class HelloWorld
{
public static void main(String[] paramArrayOfString)
{
ConcurrentHashMap localConcurrentHashMap = new ConcurrentHashMap();
ConcurrentHashMap.KeySetView localKeySetView = localConcurrentHashMap.keySet();
}
}
我們可以看到,在字節(jié)碼中,實(shí)際上keySet返回的是1.8中指定的KeySetView類,但是這個(gè)類在jdk1.7中是不存在的,所以當(dāng)用1.7的jvm運(yùn)行時(shí),會(huì)拋出NoSuchMethodError的異常。
解決方法
為了解決這個(gè)問(wèn)題,還是要看編譯時(shí)的警告信息(不能忽視任何一個(gè)警告)。從warning的信息中我們可以得知,當(dāng)指定了-source時(shí),我們還需要一起指定引導(dǎo)類即bootstrap類,否則可能會(huì)出現(xiàn)某些兼容性的問(wèn)題,比如剛才我們遇到的ConcurrentHashMap的問(wèn)題。所以我們?cè)诰幾g的時(shí)候需要再加上引導(dǎo)類:
$ /usr/lib/java8/bin/javac -source 1.7 -target 1.7 HelloWorld.java -bootclasspath /usr/lib/java7/jre/lib/rt.jar
我們先來(lái)反編譯生成的class文件
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class HelloWorld
{
public static void main(String[] paramArrayOfString)
{
ConcurrentHashMap localConcurrentHashMap = new ConcurrentHashMap();
Set localSet = localConcurrentHashMap.keySet();
}
}
我們可以看到現(xiàn)在class文件中返回的類變?yōu)榱薙et,然后我們?cè)谟?.7的jvm來(lái)運(yùn)行,發(fā)現(xiàn)一切正常,問(wèn)題被解決了!
總結(jié)
以后在指定-source時(shí),還需要同時(shí)指定-bootclasspath,否則就會(huì)默認(rèn)使用當(dāng)前javac所用到的jdk版本的核心jar包(比如rt.jar)。
2017-08-11
Iterator和Enumeration
Enumeration是一個(gè)接口,它的源碼如下:
代碼
package java.util;
public interface Enumeration<E> {
boolean hasMoreElements();
E nextElement();
}
Iterator也是一個(gè)接口,它的源碼如下:
package java.util;
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
區(qū)別
函數(shù)接口不同
Enumeration只有2個(gè)函數(shù)接口。通過(guò)Enumeration,我們只能讀取集合的數(shù)據(jù),而不能對(duì)數(shù)據(jù)進(jìn)行修改。
Iterator只有3個(gè)函數(shù)接口。Iterator除了能讀取集合的數(shù)據(jù)之外,也能數(shù)據(jù)進(jìn)行刪除操作。Iterator支持fail-fast機(jī)制,而Enumeration不支持。
Enumeration 是JDK 1.0添加的接口。使用到它的函數(shù)包括Vector、Hashtable等類,這些類都是JDK 1.0中加入的,Enumeration存在的目的就是為它們提供遍歷接口。Enumeration本身并沒(méi)有支持同步,而在Vector、Hashtable實(shí)現(xiàn)Enumeration時(shí),添加了同步。
而Iterator 是JDK 1.2才添加的接口,它也是為了HashMap、ArrayList等集合提供遍歷接口。Iterator是支持fail-fast機(jī)制的:當(dāng)多個(gè)線程對(duì)同一個(gè)集合的內(nèi)容進(jìn)行操作時(shí),就可能會(huì)產(chǎn)生fail-fast事件。
遍歷速度比較
下面,我們編寫(xiě)一個(gè)Hashtable,然后分別通過(guò) Iterator 和 Enumeration 去遍歷它,比較它們的效率。代碼如下:
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Random;
public class IteratorEnumeration {
public static void main(String[] args) {
int val;
Random r = new Random();
Hashtable table = new Hashtable();
for (int i=0; i<10000000; i++) {
// 隨機(jī)獲取一個(gè)[0,100)之間的數(shù)字
val = r.nextInt(100);
table.put(String.valueOf(i), val);
}
// 通過(guò)Iterator遍歷Hashtable
iterateHashtable(table) ;
// 通過(guò)Enumeration遍歷Hashtable
enumHashtable(table);
}
/*
* 通過(guò)Iterator遍歷Hashtable
*/
private static void iterateHashtable(Hashtable table) {
long startTime = System.currentTimeMillis();
Iterator iter = table.entrySet().iterator();
while(iter.hasNext()) {
//System.out.println("iter:"+iter.next());
iter.next();
}
long endTime = System.currentTimeMillis();
countTime(startTime, endTime);
}
/*
* 通過(guò)Enumeration遍歷Hashtable
*/
private static void enumHashtable(Hashtable table) {
long startTime = System.currentTimeMillis();
Enumeration enu = table.elements();
while(enu.hasMoreElements()) {
//System.out.println("enu:"+enu.nextElement());
enu.nextElement();
}
long endTime = System.currentTimeMillis();
countTime(startTime, endTime);
}
private static void countTime(long start, long end) {
System.out.println("time: "+(end-start)+"ms");
}
}
運(yùn)行結(jié)果如下:
time: 9ms
time: 5ms
從中,我們可以看出。Enumeration 比 Iterator 的遍歷速度更快。為什么呢?
這是因?yàn)?,Hashtable中Iterator是通過(guò)Enumeration去實(shí)現(xiàn)的,而且Iterator添加了對(duì)fail-fast機(jī)制的支持;所以,執(zhí)行的操作自然要多一些。
2017-08-15
Runnable、Callable、Future、FutureTask的區(qū)別
Runnable
其中Runnable應(yīng)該是我們最熟悉的接口,它只有一個(gè)run()函數(shù),用于將耗時(shí)操作寫(xiě)在其中,該函數(shù)沒(méi)有返回值。然后使用某個(gè)線程去執(zhí)行該runnable即可實(shí)現(xiàn)多線程,Thread類在調(diào)用start()函數(shù)后就是執(zhí)行的是Runnable的run()函數(shù)。Runnable的聲明如下
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
Callable
Callable與Runnable的功能大致相似,Callable中有一個(gè)call()函數(shù),但是call()函數(shù)有返回值,而Runnable的run()函數(shù)不能將結(jié)果返回給客戶程序。Callable的聲明如下 :
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Future
Executor就是Runnable和Callable的調(diào)度容器,F(xiàn)uture就是對(duì)于具體的Runnable或者Callable任務(wù)的執(zhí)行結(jié)果進(jìn)行
取消、查詢是否完成、獲取結(jié)果、設(shè)置結(jié)果操作。get方法會(huì)阻塞,直到任務(wù)返回結(jié)果(Future簡(jiǎn)介)。Future聲明如下
/**
* @see FutureTask
* @see Executor
* @since 1.5
* @author Doug Lea
* @param <V> The result type returned by this Future's <tt>get</tt> method
*/
public interface Future<V> {
/**
* Attempts to cancel execution of this task. This attempt will
* fail if the task has already completed, has already been cancelled,
* or could not be cancelled for some other reason. If successful,
* and this task has not started when <tt>cancel</tt> is called,
* this task should never run. If the task has already started,
* then the <tt>mayInterruptIfRunning</tt> parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task. *
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
* Returns <tt>true</tt> if this task was cancelled before it completed
* normally.
*/
boolean isCancelled();
/**
* Returns <tt>true</tt> if this task completed.
*
*/
boolean isDone();
/**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return the computed result
*/
V get() throws InterruptedException, ExecutionException;
/**
* Waits if necessary for at most the given time for the computation
* to complete, and then retrieves its result, if available.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the timeout argument
* @return the computed result
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
FutureTask
FutureTask則是一個(gè)RunnableFuture<V>,而RunnableFuture實(shí)現(xiàn)了Runnbale又實(shí)現(xiàn)了Futrue<V>這兩個(gè)接口,
public class FutureTask<V> implements RunnableFuture<V>
### RunnableFuture
```java
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
另外它還可以包裝Runnable和Callable<V>, 由構(gòu)造函數(shù)注入依賴。
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
可以看到,Runnable注入會(huì)被Executors.callable()函數(shù)轉(zhuǎn)換為Callable類型,即FutureTask最終都是執(zhí)行Callable類型的任務(wù)。該適配函數(shù)的實(shí)現(xiàn)如下 :
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
RunnableAdapter適配器
/**
* A callable that runs given task and returns given result
*/
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
由于FutureTask實(shí)現(xiàn)了Runnable,因此它既可以通過(guò)Thread包裝來(lái)直接執(zhí)行,也可以提交給ExecuteService來(lái)執(zhí)行。
并且還可以直接通過(guò)get()函數(shù)獲取執(zhí)行結(jié)果,該函數(shù)會(huì)阻塞,直到結(jié)果返回。因此FutureTask既是Future、
Runnable,又是包裝了(Callable如果是Runnable最終也會(huì)被轉(zhuǎn)換為Callable ), 它是這兩者的合體。
代碼演示
```java
import java.util.concurrent.*;
public class RunnableFutureTask {
/**
* ExecutorService
*/
static ExecutorService mExecutor = Executors.newSingleThreadExecutor();
/**
*
* @param args
*/
public static void main(String[] args) {
runnableDemo();
futureDemo();
}
/**
* runnable, 無(wú)返回值
*/
static void runnableDemo() {
new Thread(() -> System.out.println(fibc(20))).start();
}
/**
* 其中Runnable實(shí)現(xiàn)的是void run()方法,無(wú)返回值;Callable實(shí)現(xiàn)的是 V
* call()方法,并且可以返回執(zhí)行結(jié)果。其中Runnable可以提交給Thread來(lái)包裝下
* ,直接啟動(dòng)一個(gè)線程來(lái)執(zhí)行,而Callable則一般都是提交給ExecuteService來(lái)執(zhí)行。
*/
static void futureDemo() {
try {
/**
* 提交runnable則沒(méi)有返回值, future沒(méi)有數(shù)據(jù)
*/
Future<?> result = mExecutor.submit(() -> fibc(20));
System.out.println("future result from runnable : " + result.get());
/**
* 提交Callable, 有返回值, future中能夠獲取返回值
*/
Future<Integer> result2 = mExecutor.submit(() -> fibc(20));
System.out
.println("future result from callable : " + result2.get());
/**
* FutureTask則是一個(gè)RunnableFuture<V>,即實(shí)現(xiàn)了Runnbale又實(shí)現(xiàn)了Futrue<V>這兩個(gè)接口,
* 另外它還可以包裝Runnable(實(shí)際上會(huì)轉(zhuǎn)換為Callable)和Callable
* <V>,所以一般來(lái)講是一個(gè)符合體了,它可以通過(guò)Thread包裝來(lái)直接執(zhí)行,也可以提交給ExecuteService來(lái)執(zhí)行
* ,并且還可以通過(guò)v get()返回執(zhí)行結(jié)果,在線程體沒(méi)有執(zhí)行完成的時(shí)候,主線程一直阻塞等待,執(zhí)行完則直接返回結(jié)果。
*/
FutureTask<Integer> futureTask = new FutureTask<>(() -> fibc(20));
// 提交futureTask
mExecutor.submit(futureTask) ;
System.out.println("future result from futureTask : "
+ futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
/**
* 效率底下的斐波那契數(shù)列, 耗時(shí)的操作
*
* @param num
* @return
*/
static int fibc(int num) {
if (num == 0) {
return 0;
}
if (num == 1) {
return 1;
}
return fibc(num - 1) + fibc(num - 2);
}
``
2017-08-16
linux根目錄下的文件詳解
linux哲學(xué)思想:
- 一切皆文件;
- 體積小,目的單一的小程序組成;組合小程序,完成復(fù)雜的任務(wù);
- 盡量避免捕獲用戶接口;
- 通過(guò)配置文件保存程序的配置信息,而配置文件通常是純文本文件;
根目錄下的文件:
/boot該目錄默認(rèn)下存放的是Linux的啟動(dòng)文件和內(nèi)核。/initrd它的英文含義是boot loader initialized RAM disk,就是由boot loader初始化的內(nèi)存盤(pán)。在linux
內(nèi)核啟動(dòng)前,boot loader會(huì)將存儲(chǔ)介質(zhì)(一般是硬盤(pán))中的initrd文件加載到內(nèi)存,內(nèi)核啟動(dòng)時(shí)會(huì)在訪問(wèn)真正的根文件系統(tǒng)前先訪問(wèn)該內(nèi)存中的initrd文件系統(tǒng)。/bin該目錄中存放Linux的常用命令。/sbin該目錄用來(lái)存放系統(tǒng)管理員使用的管理程序。/var該目錄存放那些經(jīng)常被修改的文件,包括各種日志、數(shù)據(jù)文件。/etc該目錄存放系統(tǒng)管理時(shí)要用到的各種配置文件和子目錄,例如網(wǎng)絡(luò)配置文件、文件系統(tǒng)、X系統(tǒng)配置文件、設(shè)備配置信息、設(shè)置用戶信息等。/dev該目錄包含了Linux系統(tǒng)中使用的所有外部設(shè)備,它實(shí)際上是訪問(wèn)這些外部設(shè)備的端口,訪問(wèn)這些外部設(shè)備與訪問(wèn)一個(gè)文件或一個(gè)目錄沒(méi)有區(qū)別。/mnt臨時(shí)將別的文件系統(tǒng)掛在該目錄下。/root如果你是以超級(jí)用戶的身份登錄的,這個(gè)就是超級(jí)用戶的主目錄。/home如果建立一個(gè)名為“xx”的用戶,那么在/home目錄下就有一個(gè)對(duì)應(yīng)的“/home/xx”路徑,用來(lái)存放該用戶的主目錄。/usr用戶的應(yīng)用程序和文件幾乎都存放在該目錄下。/lib該目錄用來(lái)存放系統(tǒng)動(dòng)態(tài)鏈接共享庫(kù),幾乎所有的應(yīng)用程序都會(huì)用到該目錄下的共享庫(kù)。/opt第三方軟件在安裝時(shí)默認(rèn)會(huì)找這個(gè)目錄,所以你沒(méi)有安裝此類軟件時(shí)它是空的,但如果你一旦把它刪除了,以后在安裝此類軟件時(shí)就有可能碰到麻煩。/tmp用來(lái)存放不同程序執(zhí)行時(shí)產(chǎn)生的臨時(shí)文件,該目錄會(huì)被系統(tǒng)自動(dòng)清理干凈。/proc可以在該目錄下獲取系統(tǒng)信息,這些信息是在內(nèi)存中由系統(tǒng)自己產(chǎn)生的,該目錄的內(nèi)容不在硬盤(pán)上而在內(nèi)存里。/misc可以讓多用戶堆積和臨時(shí)轉(zhuǎn)移自己的文件。/lost+found該目錄在大多數(shù)情況下都是空的。但當(dāng)突然停電、或者非正常關(guān)機(jī)后,有些文件就臨時(shí)存放在這里。
2017-08-18
線程池ThreadPoolExecutor解析
JDK1.5中引入了強(qiáng)大的concurrent包,其中最常用的莫過(guò)了線程池的實(shí)現(xiàn)ThreadPoolExecutor,它給我們帶來(lái)了極大的方便,但同時(shí),對(duì)于該線程池不恰當(dāng)?shù)脑O(shè)置也可能使其效率并不能達(dá)到預(yù)期的效果,甚至僅相當(dāng)于或低于單線程的效率。
線程池的構(gòu)造函數(shù)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
ThreadPoolExecutor類可設(shè)置的參數(shù)主要有:
- corePoolSize 核心線程數(shù)
- 核心線程會(huì)一直存活,即使沒(méi)有任務(wù)需要處理。
- 當(dāng)線程數(shù)小于核心線程數(shù)時(shí),即使現(xiàn)有的線程空閑,線程池也會(huì)優(yōu)先創(chuàng)建新線程來(lái)處理任務(wù),而不是直接交給現(xiàn)有的線程處理。
- 核心線程在allowCoreThreadTimeout被設(shè)置為true時(shí)會(huì)超時(shí)退出,默認(rèn)情況下不會(huì)退出。
- maxPoolSize 最大線程數(shù)
- 當(dāng)線程數(shù)大于或等于核心線程,且任務(wù)隊(duì)列已滿時(shí),線程池會(huì)創(chuàng)建新的線程,直到線程數(shù)量達(dá)到maxPoolSize。
- 如果線程數(shù)已等于maxPoolSize,且任務(wù)隊(duì)列已滿,則已超出線程池的處理能力,線程池會(huì)拒絕處理任務(wù)而拋出異常。
- keepAliveTime 線程空閑時(shí)間
- 當(dāng)線程空閑時(shí)間達(dá)到keepAliveTime,該線程會(huì)退出,直到線程數(shù)量等于corePoolSize。
- 如果allowCoreThreadTimeout設(shè)置為true,則所有線程均會(huì)退出直到線程數(shù)量為0。
- allowCoreThreadTimeout 允許核心線程超時(shí)
- 是否允許核心線程空閑退出,默認(rèn)值為false
- queueCapacity 任務(wù)隊(duì)列容量
- 當(dāng)核心線程數(shù)達(dá)到最大時(shí),新任務(wù)會(huì)放在隊(duì)列中排隊(duì)等待執(zhí)行
- rejectedExecutionHandler:任務(wù)拒絕處理器
-
兩種情況會(huì)拒絕處理任務(wù):
- 當(dāng)線程數(shù)已經(jīng)達(dá)到maxPoolSize,切隊(duì)列已滿,會(huì)拒絕新任務(wù)
- 當(dāng)線程池被調(diào)用shutdown()后,會(huì)等待線程池里的任務(wù)執(zhí)行完畢,再shutdown。如果在調(diào)用shutdown()和線程池真正shutdown之間提交任務(wù),會(huì)拒絕新任務(wù)
線程池會(huì)調(diào)用rejectedExecutionHandler來(lái)處理這個(gè)任務(wù)。如果沒(méi)有設(shè)置默認(rèn)是AbortPolicy,會(huì)拋出異常
-
ThreadPoolExecutor類有幾個(gè)內(nèi)部實(shí)現(xiàn)類來(lái)處理這類情況:
- AbortPolicy 丟棄任務(wù),拋運(yùn)行時(shí)異常
- CallerRunsPolicy 執(zhí)行任務(wù)
- DiscardPolicy 忽視,什么都不會(huì)發(fā)生
- DiscardOldestPolicy 從隊(duì)列中踢出最先進(jìn)入隊(duì)列(最后一個(gè)執(zhí)行)的任務(wù),如果使用的任務(wù)隊(duì)列是優(yōu)先隊(duì)列PriorityBlockingQueue,那么拋棄權(quán)重最高的任務(wù)
實(shí)現(xiàn)RejectedExecutionHandler接口,可自定義處理器
如何執(zhí)行任務(wù)
線程池按以下行為執(zhí)行任務(wù):
- 當(dāng)線程數(shù)小于核心線程數(shù)時(shí),創(chuàng)建線程。
- 當(dāng)線程數(shù)大于等于核心線程數(shù),且任務(wù)隊(duì)列未滿時(shí),將任務(wù)放入任務(wù)隊(duì)列。
- 當(dāng)線程數(shù)大于等于核心線程數(shù),且任務(wù)隊(duì)列已滿
- 若線程數(shù)小于最大線程數(shù),創(chuàng)建線程
- 若線程數(shù)等于最大線程數(shù),拋出異常,拒絕任務(wù)
如何設(shè)置參數(shù)
默認(rèn)值:
corePoolSize=1
queueCapacity=Integer.MAX_VALUE
maxPoolSize=Integer.MAX_VALUE
keepAliveTime=60s
allowCoreThreadTimeout=false
rejectedExecutionHandler=AbortPolicy()
需要根據(jù)幾個(gè)值來(lái)決定
- tasks :每秒的任務(wù)數(shù),假設(shè)為500~1000
- taskcost:每個(gè)任務(wù)花費(fèi)時(shí)間,假設(shè)為0.1s
- responsetime:系統(tǒng)允許容忍的最大響應(yīng)時(shí)間,假設(shè)為1s
做幾個(gè)計(jì)算 - corePoolSize = 每秒需要多少個(gè)線程處理?
- threadcount = tasks / (1 / taskcost) = tasks
- taskcout = (500~1000) * 0.1 = 50~100 個(gè)線程。corePoolSize設(shè)置應(yīng)該大于50
- 根據(jù)8020原則,如果80%的每秒任務(wù)數(shù)小于800,那么corePoolSize設(shè)置為80即可
- queueCapacity = (coreSizePool/taskcost) * responsetime
- 計(jì)算可得 queueCapacity = 80 / 0.1 * 1 = 80。意思是隊(duì)列里的線程可以等待1s,超過(guò)了的需要新開(kāi)線程來(lái)執(zhí)行
- 切記不能設(shè)置為Integer.MAX_VALUE,這樣隊(duì)列會(huì)很大,線程數(shù)只會(huì)保持在corePoolSize大小,當(dāng)任務(wù)陡增時(shí),不能新開(kāi)線程來(lái)執(zhí)行,響應(yīng)時(shí)間會(huì)隨之陡增。
- maxPoolSize = (max(tasks)- queueCapacity) / (1 / taskcost)
- 計(jì)算可得 maxPoolSize = (1000 - 80) / 10 = 92 * (最大任務(wù)數(shù)-隊(duì)列容量)/ 每個(gè)線程每秒處理能力 = 最大線程數(shù)
- rejectedExecutionHandler:根據(jù)具體情況來(lái)決定,任務(wù)不重要可丟棄,任務(wù)重要?jiǎng)t要利用一些緩沖機(jī)制來(lái)處理
- keepAliveTime和allowCoreThreadTimeout采用默認(rèn)通常能滿足
要想合理的配置線程池,就必須首先分析任務(wù)特性,可以從以下幾個(gè)角度來(lái)進(jìn)行分析:
任務(wù)的性質(zhì):CPU密集型任務(wù),IO密集型任務(wù)和混合型任務(wù)。
任務(wù)的優(yōu)先級(jí):高,中和低。
任務(wù)的執(zhí)行時(shí)間:長(zhǎng),中和短。
任務(wù)的依賴性:是否依賴其他系統(tǒng)資源,如數(shù)據(jù)庫(kù)連接。
任務(wù)性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開(kāi)處理。CPU密集型任務(wù)配置盡可能小的線程,如配置Ncpu+1個(gè)線程的線程池。IO密集型任務(wù)則由于線程并不是一直在執(zhí)行任務(wù),則配置盡可能多的線程,如2*Ncpu?;旌闲偷娜蝿?wù),如果可以拆分,則將其拆分成一個(gè)CPU密集型任務(wù)和一個(gè)IO密集型任務(wù),只要這兩個(gè)任務(wù)執(zhí)行的時(shí)間相差不是太大,那么分解后執(zhí)行的吞吐率要高于串行執(zhí)行的吞吐率,如果這兩個(gè)任務(wù)執(zhí)行時(shí)間相差太大,則沒(méi)必要進(jìn)行分解。我們可以通過(guò)Runtime.getRuntime().availableProcessors()方法獲得當(dāng)前設(shè)備的CPU個(gè)數(shù)。
優(yōu)先級(jí)不同的任務(wù)可以使用優(yōu)先級(jí)隊(duì)列PriorityBlockingQueue來(lái)處理。它可以讓優(yōu)先級(jí)高的任務(wù)先得到執(zhí)行,需要注意的是如果一直有優(yōu)先級(jí)高的任務(wù)提交到隊(duì)列里,那么優(yōu)先級(jí)低的任務(wù)可能永遠(yuǎn)不能執(zhí)行。
執(zhí)行時(shí)間不同的任務(wù)可以交給不同規(guī)模的線程池來(lái)處理,或者也可以使用優(yōu)先級(jí)隊(duì)列,讓執(zhí)行時(shí)間短的任務(wù)先執(zhí)行。
依賴數(shù)據(jù)庫(kù)連接池的任務(wù),因?yàn)榫€程提交SQL后需要等待數(shù)據(jù)庫(kù)返回結(jié)果,如果等待的時(shí)間越長(zhǎng)CPU空閑時(shí)間就越長(zhǎng),那么線程數(shù)應(yīng)該設(shè)置越大,這樣才能更好的利用CPU。
建議使用有界隊(duì)列,有界隊(duì)列能增加系統(tǒng)的穩(wěn)定性和預(yù)警能力,可以根據(jù)需要設(shè)大一點(diǎn),比如幾千。有一次我們組使用的后臺(tái)任務(wù)線程池的隊(duì)列和線程池全滿了,不斷的拋出拋棄任務(wù)的異常,通過(guò)排查發(fā)現(xiàn)是數(shù)據(jù)庫(kù)出現(xiàn)了問(wèn)題,導(dǎo)致執(zhí)行SQL變得非常緩慢,因?yàn)楹笈_(tái)任務(wù)線程池里的任務(wù)全是需要向數(shù)據(jù)庫(kù)查詢和插入數(shù)據(jù)的,所以導(dǎo)致線程池里的工作線程全部阻塞住,任務(wù)積壓在線程池里。如果當(dāng)時(shí)我們?cè)O(shè)置成無(wú)界隊(duì)列,線程池的隊(duì)列就會(huì)越來(lái)越多,有可能會(huì)撐滿內(nèi)存,導(dǎo)致整個(gè)系統(tǒng)不可用,而不只是后臺(tái)任務(wù)出現(xiàn)問(wèn)題。當(dāng)然我們的系統(tǒng)所有的任務(wù)是用的單獨(dú)的服務(wù)器部署的,而我們使用不同規(guī)模的線程池跑不同類型的任務(wù),但是出現(xiàn)這樣問(wèn)題時(shí)也會(huì)影響到其他任務(wù)。
線程池的監(jiān)控
通過(guò)線程池提供的參數(shù)進(jìn)行監(jiān)控。線程池里有一些屬性在監(jiān)控線程池的時(shí)候可以使用
- taskCount:線程池需要執(zhí)行的任務(wù)數(shù)量。
- completedTaskCount:線程池在運(yùn)行過(guò)程中已完成的任務(wù)數(shù)量。小于或等于taskCount。
- largestPoolSize:線程池曾經(jīng)創(chuàng)建過(guò)的最大線程數(shù)量。通過(guò)這個(gè)數(shù)據(jù)可以知道線程池是否滿過(guò)。如等于線程池的最大大小,則表示線程池曾經(jīng)滿了。
- getPoolSize:線程池的線程數(shù)量。如果線程池不銷毀的話,池里的線程不會(huì)自動(dòng)銷毀,所以這個(gè)大小只增不+ getActiveCount:獲取活動(dòng)的線程數(shù)。
通過(guò)擴(kuò)展線程池進(jìn)行監(jiān)控。通過(guò)繼承線程池并重寫(xiě)線程池的beforeExecute,afterExecute和terminated方法,我們可以在任務(wù)執(zhí)行前,執(zhí)行后和線程池關(guān)閉前干一些事情。如監(jiān)控任務(wù)的平均執(zhí)行時(shí)間,最大執(zhí)行時(shí)間和最小執(zhí)行時(shí)間等。這幾個(gè)方法在線程池里是空方法。如:
protected void beforeExecute(Thread t, Runnable r) { }
2017-08-19
Linux后臺(tái)運(yùn)行jar文件
方式一
java -jar XXX.jar
特點(diǎn):當(dāng)前ssh窗口被鎖定,可按CTRL + C打斷程序運(yùn)行,或直接關(guān)閉窗口,程序退出
那如何讓窗口不鎖定?
方式二
java -jar XXX.jar &
&代表在后臺(tái)運(yùn)行。
特定:當(dāng)前ssh窗口不被鎖定,但是當(dāng)窗口關(guān)閉時(shí),程序中止運(yùn)行。
繼續(xù)改進(jìn),如何讓窗口關(guān)閉時(shí),程序仍然運(yùn)行?
方式三
nohup java -jar XXX.jar &
nohup 意思是不掛斷運(yùn)行命令,當(dāng)賬戶退出或終端關(guān)閉時(shí),程序仍然運(yùn)行
當(dāng)用 nohup 命令執(zhí)行作業(yè)時(shí),缺省情況下該作業(yè)的所有輸出被重定向到nohup.out的文件中,除非另外指定了輸出文件。
方式四
nohup java -jar XXX.jar >result.log 2>error.log &
command >out.file
command >out.file是將command的輸出重定向到out.file文件,即輸出內(nèi)容不打印到屏幕上,而是輸出到out.file文件中
這里我們將結(jié)果輸出重定向到result.log中
2 > out.file是指將錯(cuò)誤的輸出重定向到文件,我們將錯(cuò)誤的輸出重定向到error.log
查看命令
可通過(guò)jobs命令查看后臺(tái)運(yùn)行任務(wù)
jobs
那么就會(huì)列出所有后臺(tái)執(zhí)行的作業(yè),并且每個(gè)作業(yè)前面都有個(gè)編號(hào)。
如果想將某個(gè)作業(yè)調(diào)回前臺(tái)控制,只需要 fg + 編號(hào)即可。
fg 23
2017-08-23
內(nèi)部類為什么可以訪問(wèn)外部類的屬性
內(nèi)部類定義
內(nèi)部類就是定義在一個(gè)類內(nèi)部的類。定義在類內(nèi)部的類有兩種情況:一種是被static關(guān)鍵字修飾的, 叫做靜態(tài)內(nèi)部類, 另一種是不被static關(guān)鍵字修飾的, 就是普通內(nèi)部類。 在下文中所提到的內(nèi)部類都是指這種不被static關(guān)鍵字修飾的普通內(nèi)部類。 靜態(tài)內(nèi)部類雖然也定義在外部類的里面, 但是它只是在形式上(寫(xiě)法上)和外部類有關(guān)系, 其實(shí)在邏輯上和外部類并沒(méi)有直接的關(guān)系。而一般的內(nèi)部類,不僅在形式上和外部類有關(guān)系(寫(xiě)在外部類的里面), 在邏輯上也和外部類有聯(lián)系。 這種邏輯上的關(guān)系可以總結(jié)為以下兩點(diǎn):
- 內(nèi)部類對(duì)象的創(chuàng)建依賴于外部類對(duì)象;
- 內(nèi)部類對(duì)象持有指向外部類對(duì)象的引用。
上邊的第二條可以解釋為什么在內(nèi)部類中可以訪問(wèn)外部類的成員。就是因?yàn)閮?nèi)部類對(duì)象持有外部類對(duì)象的引用。但是我們不禁要問(wèn), 為什么會(huì)持有這個(gè)引用?
在源代碼層面, 我們無(wú)法看到原因,因?yàn)镴ava為了語(yǔ)法的簡(jiǎn)介, 省略了很多該寫(xiě)的東西, 也就是說(shuō)很多東西本來(lái)應(yīng)該在源代碼中寫(xiě)出, 但是為了簡(jiǎn)介起見(jiàn), 不必在源碼中寫(xiě)出,編譯器在編譯時(shí)會(huì)加上一些代碼。 現(xiàn)在我們就看看Java的編譯器為我們加上了什么?
首先建一個(gè)工程TestInnerClass用于測(cè)試。 在該工程中為了簡(jiǎn)單起見(jiàn), 沒(méi)有創(chuàng)建包, 所以源代碼直接在默認(rèn)包中。在該工程中, 只有下面一個(gè)簡(jiǎn)單的文件。
public class Outer {
int outerField = 0;
class Inner{
void InnerMethod(){
int i = outerField;
}
}
}
編譯后產(chǎn)生兩個(gè)class文件,分別是Outer$Inner.class和Outer.class,這里我們看起來(lái)內(nèi)部類除了前面有個(gè)外部類的名字之外,和其他類并沒(méi)有區(qū)別,別的類和外部類也是兩個(gè)不同的class文件,為什么內(nèi)部類就可以訪問(wèn)呢?我們這樣想,java總歸還是java,再怎么變也不會(huì)超過(guò)這個(gè)語(yǔ)言的特性,能訪問(wèn)這個(gè)類說(shuō)明,肯定是內(nèi)部類持有一個(gè)引用,指向了外部類,編譯器應(yīng)該是幫我們做了這些事,我們不知道而已。
反編譯
這里我們的目的是探究?jī)?nèi)部類的行為, 所以只反編譯內(nèi)部類的class文件Outer$Inner.class 。 在命令行中, 切換到工程的bin目錄, 輸入以下命令反編譯這個(gè)類文件:
javap -classpath . -v Outer$Inner
-classpath . 說(shuō)明在當(dāng)前目錄下尋找要反編譯的class文件 -v 加上這個(gè)參數(shù)輸出的信息比較全面。包括常量池和方法內(nèi)的局部變量表, 行號(hào), 訪問(wèn)標(biāo)志等等。
注意, 如果有包名的話, 要寫(xiě)class文件的全限定名, 如:
javap -classpath . -v com.baidu.Outer$Inner
反編譯的輸出結(jié)果很多, 為了篇幅考慮, 在這里我們省略了常量池。 下面給出除了常量池之外的輸出信息
{
final Outer this$0;
flags: ACC_FINAL, ACC_SYNTHETIC
Outer$Inner(Outer);
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #10 // Field this$0:LOuter;
5: aload_0
6: invokespecial #12 // Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this LOuter$Inner;
void InnerMethod();
flags:
Code:
stack=1, locals=2, args_size=1
0: aload_0
1: getfield #10 // Field this$0:LOuter;
4: getfield #20 // Field Outer.outerField:I
7: istore_1
8: return
LineNumberTable:
line 7: 0
line 8: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LOuter$Inner;
8 1 1 i I
}
首先我們會(huì)看到, 第一行的信息如下:
final Outer this$0;
這句話的意思是, 在內(nèi)部類Outer$Inner中, 存在一個(gè)名字為this$0 , 類型為Outer的成員變量, 并且這個(gè)變量是final的。 其實(shí)這個(gè)就是所謂的“在內(nèi)部類對(duì)象中存在的指向外部類對(duì)象的引用”。但是我們?cè)诙x這個(gè)內(nèi)部類的時(shí)候, 并沒(méi)有聲明它, 所以這個(gè)成員變量是編譯器加上的。
雖然編譯器在創(chuàng)建內(nèi)部類時(shí)為它加上了一個(gè)指向外部類的引用, 但是這個(gè)引用是怎樣賦值的呢?畢竟必須先給他賦值, 它才能指向外部類對(duì)象。 下面我們把注意力轉(zhuǎn)移到構(gòu)造函數(shù)上。 下面這段輸出是關(guān)于構(gòu)造函數(shù)的信息。
Outer$Inner(Outer);
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #10 // Field this$0:LOuter;
5: aload_0
6: invokespecial #12 // Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this LOuter$Inner;
我們知道, 如果在一個(gè)類中, 不聲明構(gòu)造方法的話, 編譯器會(huì)默認(rèn)添加一個(gè)無(wú)參數(shù)的構(gòu)造方法。 但是這句話在這里就行不通了, 因?yàn)槲覀兠髅骺吹剑?這個(gè)構(gòu)造函數(shù)有一個(gè)構(gòu)造方法, 并且類型為Outer。 所以說(shuō),編譯器會(huì)為內(nèi)部類的構(gòu)造方法添加一個(gè)參數(shù), 參數(shù)的類型就是外部類的類型。
下面我們看看在構(gòu)造參數(shù)中如何使用這個(gè)默認(rèn)添加的參數(shù)。 我們來(lái)分析一下構(gòu)造方法的字節(jié)碼。 下面是每行字節(jié)碼的意義:
aload_0 :
將局部變量表中的第一個(gè)引用變量加載到操作數(shù)棧。 這里有幾點(diǎn)需要說(shuō)明。 局部變量表中的變量在方法執(zhí)行前就已經(jīng)初始化完成;局部變量表中的變量包括方法的參數(shù);成員方法的局部變量表中的第一個(gè)變量永遠(yuǎn)是this;操作數(shù)棧就是執(zhí)行當(dāng)前代碼的棧。所以這句話的意思是: 將this引用從局部變量表加載到操作數(shù)棧。
aload_1:
將局部變量表中的第二個(gè)引用變量加載到操作數(shù)棧。 這里加載的變量就是構(gòu)造方法中的Outer類型的參數(shù)。
putfield #10 // Field this$0:LOuter;
使用操作數(shù)棧頂端的引用變量為指定的成員變量賦值。 這里的意思是將外面?zhèn)魅氲腛uter類型的參數(shù)賦給成員變量this$0 。
這一句putfield字節(jié)碼就揭示了, 指向外部類對(duì)象的這個(gè)引用變量是如何賦值的。
下面幾句字節(jié)碼和本文討論的話題無(wú)關(guān), 只做簡(jiǎn)單的介紹。 下面幾句字節(jié)碼的含義是: 使用this引用調(diào)用父類(Object)的構(gòu)造方法然后返回。
用我們比較熟悉的形式翻譯過(guò)來(lái), 這個(gè)內(nèi)部類和它的構(gòu)造函數(shù)有點(diǎn)像這樣: (注意, 這里不符合Java的語(yǔ)法, 只是為了說(shuō)明問(wèn)題)
class Outer$Inner{
final Outer this$0;
public Outer$Inner(Outer outer){
this.this$0 = outer;
super();
}
}
關(guān)于在內(nèi)部類中如何使用指向外部類的引用訪問(wèn)外部類成員, 就不用多做解釋了, 其實(shí)和普通的通過(guò)引用訪問(wèn)成員的方式是相同的。 在內(nèi)部類的InnerMethod方法中, 訪問(wèn)了外部類的成員變量outerField, 下面的字節(jié)碼揭示了訪問(wèn)是如何進(jìn)行的:
void InnerMethod();
flags:
Code:
stack=1, locals=2, args_size=1
0: aload_0
1: getfield #10 // Field this$0:LOuter;
4: getfield #20 // Field Outer.outerField:I
7: istore_1
8: return
getfield #10 // Field this$0:LOuter;
將成員變量this$0加載到操作數(shù)棧上來(lái)
getfield #20 // Field Outer.outerField:I
使用上面加載的this$0引用, 將外部類的成員變量outerField加載到操作數(shù)棧
istore_1
將操作數(shù)棧頂端的int類型的值保存到局部變量表中的第二個(gè)變量上(注意, 第一個(gè)局部變量被this占用, 第二個(gè)局部變量是i)。操作數(shù)棧頂端的int型變量就是上一步加載的outerField變量。 所以, 這句字節(jié)碼的含義就是: 使用outerField為i賦值。
上面三步就是內(nèi)部類中是如何通過(guò)指向外部類對(duì)象的引用, 來(lái)訪問(wèn)外部類成員的。
總結(jié)
通過(guò)反編譯內(nèi)部類的字節(jié)碼, 說(shuō)明了內(nèi)部類是如何訪問(wèn)外部類對(duì)象的成員的,除此之外, 我們也對(duì)編譯器的行為有了一些了解, 編譯器在編譯時(shí)會(huì)自動(dòng)加上一些邏輯, 這正是我們感覺(jué)困惑的原因。
關(guān)于內(nèi)部類如何訪問(wèn)外部類的成員, 分析之后其實(shí)也很簡(jiǎn)單, 主要是通過(guò)以下幾步做到的:
- 編譯器自動(dòng)為內(nèi)部類添加一個(gè)成員變量, 這個(gè)成員變量的類型和外部類的類型相同, 這個(gè)成員變量就是指向外部類對(duì)象的引用;
- 編譯器自動(dòng)為內(nèi)部類的構(gòu)造方法添加一個(gè)參數(shù), 參數(shù)的類型是外部類的類型, 在構(gòu)造方法內(nèi)部使用這個(gè)參數(shù)為1中添加的成員變量賦值;
- 在調(diào)用內(nèi)部類的構(gòu)造函數(shù)初始化內(nèi)部類對(duì)象時(shí), 會(huì)默認(rèn)傳入外部類的引用。
其實(shí)內(nèi)部類可以訪問(wèn)類這個(gè)細(xì)節(jié)我們都知道,可是為什么呢?這就需要我們有思考問(wèn)題的能力,深入探究細(xì)節(jié),知其然和知其所以然,思維方式需要轉(zhuǎn)變,深入的去考慮問(wèn)題,不要只停留在表面,這也是自己需要提升的地方。
2017-08-29
字節(jié)數(shù)組轉(zhuǎn)16進(jìn)制字符串
對(duì)每一個(gè)字節(jié),先和0xFF做與運(yùn)算,然后使用Integer.toHexString()函數(shù),如果結(jié)果只有1位,需要在前面加0。
/*
* 字節(jié)數(shù)組轉(zhuǎn)16進(jìn)制字符串
*/
public static String bytes2HexString(byte[] b) {
String r = "";
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
r += hex.toUpperCase();
}
return r;
}