學(xué)習(xí)總結(jié) 2017-08

前言

記錄個(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ò)程


tcp握手.png
  • 第一步: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)連接的四次揮手:


tcp揮手.png

第一步: 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)

  1. 降低了組件之間的耦合性 ,實(shí)現(xiàn)了軟件各層之間的解耦
  2. 可以使用容易提供的眾多服務(wù),如事務(wù)管理,消息服務(wù)等
  3. 容器提供單例模式支持
  4. 容器提供了AOP技術(shù),利用它很容易實(shí)現(xiàn)如權(quán)限攔截,運(yùn)行期監(jiān)控等功能
  5. 容器提供了眾多的輔助類,能加快應(yīng)用的開(kāi)發(fā)
  6. spring對(duì)于主流的應(yīng)用框架提供了集成支持,如hibernate,JPA,struts等
  7. spring屬于低侵入式設(shè)計(jì),代碼的污染極低
  8. 獨(dú)立于各種應(yīng)用服務(wù)器
  9. spring的DI機(jī)制降低了業(yè)務(wù)對(duì)象替換的復(fù)雜性
  10. 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)。

  1. GlobalEscape(全局逃逸), 即一個(gè)對(duì)象的引用逃出了方法或者線程。例如,一個(gè)對(duì)象的引用是復(fù)制給了一個(gè)類變量,或者存儲(chǔ)在在一個(gè)已經(jīng)逃逸的對(duì)象當(dāng)中,或者這個(gè)對(duì)象的引用作為方法的返回值返回給了調(diào)用方法。
  2. ArgEscape(參數(shù)級(jí)逃逸),即在方法調(diào)用過(guò)程當(dāng)中傳遞對(duì)象的引用給一個(gè)方法。這種狀態(tài)可以通過(guò)分析被調(diào)方法的二進(jìn)制代碼確定。
  3. NoEscape(沒(méi)有逃逸),一個(gè)可以進(jìn)行標(biāo)量替換的對(duì)象??梢圆粚⑦@種對(duì)象分配在傳統(tǒng)的堆上。
    編譯器可以使用逃逸分析的結(jié)果,對(duì)程序進(jìn)行一下優(yōu)化。
  4. 堆分配對(duì)象變成棧分配對(duì)象。一個(gè)方法當(dāng)中的對(duì)象,對(duì)象的引用沒(méi)有發(fā)生逃逸,那么這個(gè)方法可能會(huì)被分配在棧內(nèi)存上而非常見(jiàn)的堆內(nèi)存上。
  5. 消除同步。線程同步的代價(jià)是相當(dāng)高的,同步的后果是降低并發(fā)性和性能。逃逸分析可以判斷出某個(gè)對(duì)象是否始終只被一個(gè)線程訪問(wèn),如果只被一個(gè)線程訪問(wèn),那么對(duì)該對(duì)象的同步操作就可以轉(zhuǎn)化成沒(méi)有同步保護(hù)的操作,這樣就能大大提高并發(fā)程度和性能。
  6. 矢量替代。逃逸分析方法如果發(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ū)別

  1. 函數(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)行刪除操作。

  2. 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é)思想:

  1. 一切皆文件;
  1. 體積小,目的單一的小程序組成;組合小程序,完成復(fù)雜的任務(wù);
  2. 盡量避免捕獲用戶接口;
  3. 通過(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ù)主要有:

  1. 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ì)退出。
  1. maxPoolSize 最大線程數(shù)
  • 當(dāng)線程數(shù)大于或等于核心線程,且任務(wù)隊(duì)列已滿時(shí),線程池會(huì)創(chuàng)建新的線程,直到線程數(shù)量達(dá)到maxPoolSize。
  • 如果線程數(shù)已等于maxPoolSize,且任務(wù)隊(duì)列已滿,則已超出線程池的處理能力,線程池會(huì)拒絕處理任務(wù)而拋出異常。
  1. keepAliveTime 線程空閑時(shí)間
  • 當(dāng)線程空閑時(shí)間達(dá)到keepAliveTime,該線程會(huì)退出,直到線程數(shù)量等于corePoolSize。
  • 如果allowCoreThreadTimeout設(shè)置為true,則所有線程均會(huì)退出直到線程數(shù)量為0。
  1. allowCoreThreadTimeout 允許核心線程超時(shí)
  • 是否允許核心線程空閑退出,默認(rèn)值為false
  1. queueCapacity 任務(wù)隊(duì)列容量
  • 當(dāng)核心線程數(shù)達(dá)到最大時(shí),新任務(wù)會(huì)放在隊(duì)列中排隊(duì)等待執(zhí)行
  1. 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ù):

  1. 當(dāng)線程數(shù)小于核心線程數(shù)時(shí),創(chuàng)建線程。
  2. 當(dāng)線程數(shù)大于等于核心線程數(shù),且任務(wù)隊(duì)列未滿時(shí),將任務(wù)放入任務(wù)隊(duì)列。
  3. 當(dāng)線程數(shù)大于等于核心線程數(shù),且任務(wù)隊(duì)列已滿
    1. 若線程數(shù)小于最大線程數(shù),創(chuàng)建線程
    2. 若線程數(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):

  1. 內(nèi)部類對(duì)象的創(chuàng)建依賴于外部類對(duì)象;
  2. 內(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.classOuter.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ò)以下幾步做到的:

  1. 編譯器自動(dòng)為內(nèi)部類添加一個(gè)成員變量, 這個(gè)成員變量的類型和外部類的類型相同, 這個(gè)成員變量就是指向外部類對(duì)象的引用;
  2. 編譯器自動(dòng)為內(nèi)部類的構(gòu)造方法添加一個(gè)參數(shù), 參數(shù)的類型是外部類的類型, 在構(gòu)造方法內(nèi)部使用這個(gè)參數(shù)為1中添加的成員變量賦值;
  3. 在調(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;
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容