日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java后台常见问题

發(fā)布時間:2024/1/17 编程问答 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java后台常见问题 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Java后臺面試 常見問題


Nginx負載均衡

  • 輪詢、輪詢是默認(rèn)的,每一個請求按順序逐一分配到不同的后端服務(wù)器,如果后端服務(wù)器down掉了,則能自動剔除

  • ip_hash、個請求按訪問IP的hash結(jié)果分配,這樣來自同一個IP的訪客固定訪問一個后端服務(wù)器,有效解決了動態(tài)網(wǎng)頁存在的session共享問題。

  • weight、weight是設(shè)置權(quán)重,用于后端服務(wù)器性能不均的情況,訪問比率約等于權(quán)重之比

  • fair(第三方)、這是比上面兩個更加智能的負載均衡算法。此種算法可以依據(jù)頁面大小和加載時間長短智能地進行負載均衡,也就是根據(jù)后端服務(wù)器的響應(yīng)時間來分配請求,響應(yīng)時間短的優(yōu)先分配。Nginx本身是不支持fair的,如果需要使用這種調(diào)度算法,必須下載Nginx的upstream_fair模塊。

  • url_hash(第三方)此方法按訪問url的hash結(jié)果來分配請求,使每個url定向到同一個后端服務(wù)器,可以進一步提高后端緩存服務(wù)器的效率。Nginx本身是不支持url_hash的,如果需要使用這種調(diào)度算法,必須安裝Nginx 的hash軟件包。

代理的概念

正向代理,也就是傳說中的代理, 簡單的說,我是一個用戶,我訪問不了某網(wǎng)站,但是我能訪問一個代理服務(wù)器,這個代理服務(wù)器呢,他能訪問那個我不能訪問的網(wǎng)站,于是我先連上代理服務(wù)器,告訴他我需要那個無法訪問網(wǎng)站的內(nèi)容,代理服務(wù)器去取回來,然后返回給我。從網(wǎng)站的角度,只在代理服務(wù)器來取內(nèi)容的時候有一次記錄,有時候并不知道是用戶的請求,也隱藏了用戶的資料,這取決于代理告不告訴網(wǎng)站。

反向代理: 結(jié)論就是,反向代理正好相反,對于客戶端而言它就像是原始服務(wù)器,并且客戶端不需要進行任何特別的設(shè)置。客戶端向反向代理的命名空間(name-space)中的內(nèi)容發(fā)送普通請求,接著反向代理將判斷向何處(原始服務(wù)器)轉(zhuǎn)交請求,并將獲得的內(nèi)容返回給客戶端,就像這些內(nèi)容原本就是它自己的一樣。

Volatile的特征:

A、原子性 :對任意單個volatile變量的讀/寫具有原子性,但類似于volatile++這種復(fù)合操作不具有原子性。
B、可見性:對一個volatile變量的讀,總是能看到(任意線程)對這個volatile變量最后的寫入。

Volatile的內(nèi)存語義:

當(dāng)寫一個volatile變量時,JMM會把線程對應(yīng)的本地內(nèi)存中的共享變量值刷新到主內(nèi)存。

這里寫圖片描述

當(dāng)讀一個volatile變量時,JMM會把線程對應(yīng)的本地內(nèi)存置為無效,線程接下來將從主內(nèi)存中讀取共享變量。

這里寫圖片描述

Volatile的重排序

1、當(dāng)?shù)诙€操作為volatile寫操做時,不管第一個操作是什么(普通讀寫或者volatile讀寫),都不能進行重排序。這個規(guī)則確保volatile寫之前的所有操作都不會被重排序到volatile之后;

2、當(dāng)?shù)谝粋€操作為volatile讀操作時,不管第二個操作是什么,都不能進行重排序。這個規(guī)則確保volatile讀之后的所有操作都不會被重排序到volatile之前;

3、當(dāng)?shù)谝粋€操作是volatile寫操作時,第二個操作是volatile讀操作,不能進行重排序。

這個規(guī)則和前面兩個規(guī)則一起構(gòu)成了:兩個volatile變量操作不能夠進行重排序;

除以上三種情況以外可以進行重排序。

比如:

1、第一個操作是普通變量讀/寫,第二個是volatile變量的讀;
2、第一個操作是volatile變量的寫,第二個是普通變量的讀/寫;


內(nèi)存屏障/內(nèi)存柵欄

內(nèi)存屏障(Memory Barrier,或有時叫做內(nèi)存柵欄,Memory Fence)是一種CPU指令,用于控制特定條件下的重排序和內(nèi)存可見性問題。Java編譯器也會根據(jù)內(nèi)存屏障的規(guī)則禁止重排序。(也就是讓一個CPU處理單元中的內(nèi)存狀態(tài)對其它處理單元可見的一項技術(shù)。)

內(nèi)存屏障可以被分為以下幾種類型:

LoadLoad屏障:對于這樣的語句Load1; LoadLoad; Load2,在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪問前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。

StoreStore屏障:對于這樣的語句Store1; StoreStore; Store2,在Store2及后續(xù)寫入操作執(zhí)行前,保證Store1的寫入操作對其它處理器可見。

LoadStore屏障:對于這樣的語句Load1; LoadStore; Store2,在Store2及后續(xù)寫入操作被刷出前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。

StoreLoad屏障:對于這樣的語句Store1; StoreLoad; Load2,在Load2及后續(xù)所有讀取操作執(zhí)行前,保證Store1的寫入對所有處理器可見。它的開銷是四種屏障中最大的。

在大多數(shù)處理器的實現(xiàn)中,這個屏障是個萬能屏障,兼具其它三種內(nèi)存屏障的功能。

內(nèi)存屏障阻礙了CPU采用優(yōu)化技術(shù)來降低內(nèi)存操作延遲,必須考慮因此帶來的性能損失。為了達到最佳性能,最好是把要解決的問題模塊化,這樣處理器可以按單元執(zhí)行任務(wù),然后在任務(wù)單元的邊界放上所有需要的內(nèi)存屏障。采用這個方法可以讓處理器不受限的執(zhí)行一個任務(wù)單元。合理的內(nèi)存屏障組合還有一個好處是:緩沖區(qū)在第一次被刷后開銷會減少,因為再填充改緩沖區(qū)不需要額外工作了。


happens-before原則

如果一個操作執(zhí)行的結(jié)果需要對另一個操作可見,那么這兩個操作之間必須要存在happens-before關(guān)系。

這里寫圖片描述

Java是如何實現(xiàn)跨平臺的?

跨平臺是怎樣實現(xiàn)的呢?這就要談及Java虛擬機(Java?Virtual Machine,簡稱 JVM)。

JVM也是一個軟件,不同的平臺有不同的版本。我們編寫的Java源碼,編譯后會生成一種 .class 文件,稱為字節(jié)碼文件。Java虛擬機就是負責(zé)將字節(jié)碼文件翻譯成特定平臺下的機器碼然后運行。也就是說,只要在不同平臺上安裝對應(yīng)的JVM,就可以運行字節(jié)碼文件,運行我們編寫的Java程序。

而這個過程中,我們編寫的Java程序沒有做任何改變,僅僅是通過JVM這一”中間層“,就能在不同平臺上運行,真正實現(xiàn)了”一次編譯,到處運行“的目的。

JVM是一個”橋梁“,是一個”中間件“,是實現(xiàn)跨平臺的關(guān)鍵,Java代碼首先被編譯成字節(jié)碼文件,再由JVM將字節(jié)碼文件翻譯成機器語言,從而達到運行Java程序的目的。

注意:編譯的結(jié)果不是生成機器碼,而是生成字節(jié)碼,字節(jié)碼不能直接運行,必須通過JVM翻譯成機器碼才能運行。不同平臺下編譯生成的字節(jié)碼是一樣的,但是由JVM翻譯成的機器碼卻不一樣。

所以,運行Java程序必須有JVM的支持,因為編譯的結(jié)果不是機器碼,必須要經(jīng)過JVM的再次翻譯才能執(zhí)行。即使你將Java程序打包成可執(zhí)行文件(例如 .exe),仍然需要JVM的支持。

注意:跨平臺的是Java程序,不是JVM。JVM是用C/C++開發(fā)的,是編譯后的機器碼,不能跨平臺,不同平臺下需要安裝不同版本的JVM。

垃圾搜集器

  • 按照線程數(shù)量來分:
  • 串行 串行垃圾回收器一次只使用一個線程進行垃圾回收
  • 并行 并行垃圾回收器一次將開啟多個線程同時進行垃圾回收。
  • 按照工作模式來分:
  • 并發(fā) 并發(fā)式垃圾回收器與應(yīng)用程序線程交替工作,以盡可能減少應(yīng)用程序的停頓時間
  • 獨占 一旦運行,就停止應(yīng)用程序中的其他所有線程,直到垃圾回收過程完全結(jié)束
  • 按照碎片處理方式:
  • 壓縮式 壓縮式垃圾回收器會在回收完成后,對存活對象進行壓縮整消除回收后的碎片;
  • 非壓縮式 非壓縮式的垃圾回收器不進行這步操作。
  • 按工作的內(nèi)存區(qū)間 可分為新生代垃圾回收器和老年代垃圾回收器
    • 新生代串行收集器 serial 它僅僅使用單線程進行垃圾回收;第二,它獨占式的垃圾回收。使用復(fù)制算法。

    • 老年代串行收集器 serial old 年代串行收集器使用的是標(biāo)記-壓縮算法。和新生代串行收集器一樣,它也是一個串行的、獨占式的垃圾回收器

    • 并行收集器 parnew 并行收集器是工作在新生代的垃圾收集器,它只簡單地將串行回收器多線程化。它的回收策略、算法以及參數(shù)和串行回收器一樣 并行回收器也是獨占式的回收器,在收集過程中,應(yīng)用程序會全部暫停。但由于并行回收器使用多線程進行垃圾回收,因此,在并發(fā)能力比較強的 CPU 上,它產(chǎn)生的停頓時間要短于串行回收器,而在單 CPU 或者并發(fā)能力較弱的系統(tǒng)中,并行回收器的效果不會比串行回收器好,由于多線程的壓力,它的實際表現(xiàn)很可能比串行回收器差。

    • 新生代并行回收 (Parallel Scavenge) 收集器 新生代并行回收收集器也是使用復(fù)制算法的收集器。從表面上看,它和并行收集器一樣都是多線程、獨占式的收集器。但是,并行回收收集器有一個重要的特點:它非常關(guān)注系統(tǒng)的吞吐量。

    • 老年代并行回收收集器 parallel old 老年代的并行回收收集器也是一種多線程并發(fā)的收集器。和新生代并行回收收集器一樣,它也是一種關(guān)注吞吐量的收集器。老年代并行回收收集器使用標(biāo)記-壓縮算法,JDK1.6 之后開始啟用。

    • CMS 收集器 CMS 收集器主要關(guān)注于系統(tǒng)停頓時間。CMS 是 Concurrent Mark Sweep 的縮寫,意為并發(fā)標(biāo)記清除,從名稱上可以得知,它使用的是標(biāo)記-清除算法,同時它又是一個使用多線程并發(fā)回收的垃圾收集器。

      • CMS 工作時,主要步驟有:初始標(biāo)記、并發(fā)標(biāo)記、重新標(biāo)記、并發(fā)清除和并發(fā)重置。其中初始標(biāo)記和重新標(biāo)記是獨占系統(tǒng)資源的,而并發(fā)標(biāo)記、并發(fā)清除和并發(fā)重置是可以和用戶線程一起執(zhí)行的。因此,從整體上來說,CMS 收集不是獨占式的,它可以在應(yīng)用程序運行過程中進行垃圾回收。

        根據(jù)標(biāo)記-清除算法,初始標(biāo)記、并發(fā)標(biāo)記和重新標(biāo)記都是為了標(biāo)記出需要回收的對象。并發(fā)清理則是在標(biāo)記完成后,正式回收垃圾對象;并發(fā)重置是指在垃圾回收完成后,重新初始化 CMS 數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù),為下一次垃圾回收做好準(zhǔn)備。并發(fā)標(biāo)記、并發(fā)清理和并發(fā)重置都是可以和應(yīng)用程序線程一起執(zhí)行的。

    • G1 收集器 G1 收集器是基于標(biāo)記-壓縮算法的。因此,它不會產(chǎn)生空間碎片,也沒有必要在收集完成后,進行一次獨占式的碎片整理工作。G1 收集器還可以進行非常精確的停頓控制。

    網(wǎng)絡(luò)基本概念

    OSI模型

    OSI 模型(Open System Interconnection model)是一個由國際標(biāo)準(zhǔn)化組織?提出的概念模型,試圖?供一個使各種不同的計算機和網(wǎng)絡(luò)在世界范圍內(nèi)實現(xiàn)互聯(lián)的標(biāo)準(zhǔn)框架。
    它將計算機網(wǎng)絡(luò)體系結(jié)構(gòu)劃分為七層,每層都可以?供抽象良好的接口。了解 OSI 模型有助于理解實際上互聯(lián)網(wǎng)絡(luò)的工業(yè)標(biāo)準(zhǔn)——TCP/IP 協(xié)議。
    OSI 模型各層間關(guān)系和通訊時的數(shù)據(jù)流向如圖所示:

    OSI 模型.png

    顯然、如果一個東西想包羅萬象、一般時不可能的;在實際的開發(fā)應(yīng)用中一般時在此模型的基礎(chǔ)上進行裁剪、整合!

    七層模型介紹

    • 物理層:
      物理層負責(zé)最后將信息編碼成電流脈沖或其它信號用于網(wǎng)上傳輸;
      eg:RJ45等將數(shù)據(jù)轉(zhuǎn)化成0和1;
    • 數(shù)據(jù)鏈路層:
      數(shù)據(jù)鏈路層通過物理網(wǎng)絡(luò)鏈路?供數(shù)據(jù)傳輸。不同的數(shù)據(jù)鏈路層定義了不同的網(wǎng)絡(luò)和協(xié) 議特征,其中包括物理編址、網(wǎng)絡(luò)拓撲結(jié)構(gòu)、錯誤校驗、數(shù)據(jù)幀序列以及流控;
      可以簡單的理解為:規(guī)定了0和1的分包形式,確定了網(wǎng)絡(luò)數(shù)據(jù)包的形式;
    • 網(wǎng)絡(luò)層
      網(wǎng)絡(luò)層負責(zé)在源和終點之間建立連接;
      可以理解為,此處需要確定計算機的位置,怎么確定?IPv4,IPv6!
    • 傳輸層
      傳輸層向高層?提供可靠的端到端的網(wǎng)絡(luò)數(shù)據(jù)流服務(wù)。
      可以理解為:每一個應(yīng)用程序都會在網(wǎng)卡注冊一個端口號,該層就是端口與端口的通信!常用的(TCP/IP)協(xié)議;
    • 會話層
      會話層建立、管理和終止表示層與實體之間的通信會話;
      建立一個連接(自動的手機信息、自動的網(wǎng)絡(luò)尋址);
    • 表示層:
      表示層?供多種功能用于應(yīng)用層數(shù)據(jù)編碼和轉(zhuǎn)化,以確保以一個系統(tǒng)應(yīng)用層發(fā)送的信息 可以被另一個系統(tǒng)應(yīng)用層識別;
      可以理解為:解決不同系統(tǒng)之間的通信,eg:Linux下的QQ和Windows下的QQ可以通信;
    • 應(yīng)用層:
      OSI 的應(yīng)用層協(xié)議包括文件的傳輸、訪問及管理協(xié)議(FTAM) ,以及文件虛擬終端協(xié)議(VIP)和公用管理系統(tǒng)信息(CMIP)等;
      規(guī)定數(shù)據(jù)的傳輸協(xié)議;

    常見的應(yīng)用層協(xié)議:

    常見的應(yīng)用層協(xié)議.png

    互聯(lián)網(wǎng)分層結(jié)構(gòu)的好處: 上層的變動完全不影響下層的結(jié)構(gòu)。

    TCP/IP 協(xié)議基本概念

    OSI 模型所分的七層,在實際應(yīng)用中,往往有一些層被整合,或者功能分散到其他層去。TCP/IP 沒有照搬 OSI 模型,也沒有 一個公認(rèn)的 TCP/IP 層級模型,一般劃分為三層到五層模型來?述 TCP/IP 協(xié)議。

    • 在此描述用一個通用的四層模型來描述,每一層都和 OSI 模型有較強的相關(guān)性但是又可能會有交叉。
    • TCP/IP 的設(shè)計,是吸取了分層模型的精華思想——封裝。每層對上一層?供服務(wù)的時 候,上一層的數(shù)據(jù)結(jié)構(gòu)是黑盒,直接作為本層的數(shù)據(jù),而不需要關(guān)心上一層協(xié)議的任何細節(jié)。

    TCP/IP 分層模型的分層以以太網(wǎng)上傳輸 UDP 數(shù)據(jù)包如圖所示;

    UDP 數(shù)據(jù)包.png

    數(shù)據(jù)包

    寬泛意義的數(shù)據(jù)包:每一個數(shù)據(jù)包都包含"標(biāo)頭"和"數(shù)據(jù)"兩個部分."標(biāo)頭"包含本數(shù)據(jù)包的一些說明."數(shù)據(jù)"則是本數(shù)據(jù)包的內(nèi)容.

    細分?jǐn)?shù)據(jù)包:

    • 應(yīng)用程序數(shù)據(jù)包: 標(biāo)頭部分規(guī)定應(yīng)用程序的數(shù)據(jù)格式.數(shù)據(jù)部分傳輸具體的數(shù)據(jù)內(nèi)容.** ——對應(yīng)上圖中的數(shù)據(jù)!**
    • TCP/UDP數(shù)據(jù)包:標(biāo)頭部分包含雙方的發(fā)出端口和接收端口. UDP數(shù)據(jù)包:'標(biāo)頭'長度:8個字節(jié),"數(shù)據(jù)包"總長度最大為65535字節(jié),正好放進一個IP數(shù)據(jù)包. TCP數(shù)據(jù)包:理論上沒有長度限制,但是,為了保證網(wǎng)絡(luò)傳輸效率,通常不會超過IP數(shù)據(jù)長度,確保單個包不會被分割.?——對應(yīng)上圖中的UDP數(shù)據(jù)!
    • IP數(shù)據(jù)包: 標(biāo)頭部分包含通信雙方的IP地址,協(xié)議版本,長度等信息. '標(biāo)頭'長度:20~60字節(jié),"數(shù)據(jù)包"總長度最大為65535字節(jié).?——對應(yīng)上圖中的IP數(shù)據(jù)
    • 以太網(wǎng)數(shù)據(jù)包: 最基礎(chǔ)的數(shù)據(jù)包.標(biāo)頭部分包含了通信雙方的MAC地址,數(shù)據(jù)類型等. '標(biāo)頭'長度:18字節(jié),'數(shù)據(jù)'部分長度:46~1500字節(jié).?——對應(yīng)上圖中的以太網(wǎng)數(shù)據(jù)

    四層模型

  • 網(wǎng)絡(luò)接口層
    網(wǎng)絡(luò)接口層包括用于協(xié)作IP數(shù)據(jù)在已有網(wǎng)絡(luò)介質(zhì)上傳輸?shù)膮f(xié)議。
    它定義像地址解析協(xié)議(Address Resolution Protocol,ARP)這樣的協(xié)議,?供 TCP/IP 協(xié)議的數(shù)據(jù)結(jié)構(gòu)和實際物理硬件之間的接口。
    可以理解為:確定了網(wǎng)絡(luò)數(shù)據(jù)包的形式。
  • 網(wǎng)間層
    網(wǎng)間層對應(yīng)于 OSI 七層參考模型的網(wǎng)絡(luò)層,本層包含 IP 協(xié)議、RIP 協(xié)議(Routing Information Protocol,路由信息協(xié)議),負責(zé)數(shù)據(jù)的包裝、尋址和路由。同時還包含網(wǎng)間控制報文協(xié)議(Internet Control Message Protocol,ICMP)用來?供網(wǎng)絡(luò)診斷信息;
    可以理解為:該層時確定計算機的位置。
  • 傳輸層
    傳輸層對應(yīng)于 OSI 七層參考模型的傳輸層,它?供兩種端到端的通信服務(wù)。其中 TCP 協(xié)議(Transmission Control Protocol)?供可靠的數(shù)據(jù)流運輸服務(wù),UDP 協(xié)議(Use Datagram Protocol)?供不可靠的用戶數(shù)據(jù)報服務(wù)。
    TCP:三次握手、四次揮手;UDP:只發(fā)不管別人收不收得到--任性哈
  • 應(yīng)用層
    應(yīng)用層對應(yīng)于 OSI 七層參考模型的應(yīng)用層和表達層;
    不明白的再看看7層參考模型的描述。
  • TCP/IP 協(xié)議族常用協(xié)議

    • 應(yīng)用層:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等
    • 傳輸層:TCP,UDP
    • 網(wǎng)絡(luò)層:IP,ICMP,OSPF,EIGRP,IGMP
    • 數(shù)據(jù)鏈路層:SLIP,CSLIP,PPP,MTU

    重要的 TCP/IP 協(xié)議族協(xié)議進行簡單介紹:

    • IP(Internet Protocol,網(wǎng)際協(xié)議)是網(wǎng)間層的主要協(xié)議,任務(wù)是在源地址和和目的地址之間傳輸數(shù)據(jù)。IP 協(xié)議只是盡最大努力來傳輸數(shù)據(jù)包,并不保證所有的包都可以傳輸 到目的地,也不保證數(shù)據(jù)包的順序和唯一。
      • IP 定義了 TCP/IP 的地址,尋址方法,以及路由規(guī)則。現(xiàn)在廣泛使用的 IP 協(xié)議有 IPv4 和 IPv6 兩種:IPv4 使用 32 位二進制整數(shù)做地址,一般使用點分十進制方式表示,比如 192.168.0.1。
      • IP 地址由兩部分組成,即網(wǎng)絡(luò)號和主機號。故一個完整的 IPv4 地址往往表示 為 192.168.0.1/24 或192.168.0.1/255.255.255.0 這種形式。
      • IPv6 是為了解決 IPv4 地址耗盡和其它一些問題而研發(fā)的最新版本的 IP。使用 128 位 整數(shù)表示地址,通常使用冒號分隔的十六進制來表示,并且可以省略其中一串連續(xù)的 0,如:fe80::200:1ff:fe00:1。
        目前使用并不多!
    • ICMP(Internet Control Message Protocol,網(wǎng)絡(luò)控制消息協(xié)議)是 TCP/IP 的 核心協(xié)議之一,用于在 IP 網(wǎng)絡(luò)中發(fā)送控制消息,?供通信過程中的各種問題反饋。 ICMP 直接使用 IP 數(shù)據(jù)包傳輸,但 ICMP 并不被視為 IP 協(xié)議的子協(xié)議。常見的聯(lián)網(wǎng)狀態(tài)診斷工具比如依賴于 ICMP 協(xié)議;
    • TCP(TransmissionControlProtocol,傳輸控制協(xié)議)是一種面向連接的,可靠的, 基于字節(jié)流傳輸?shù)耐ㄐ艆f(xié)議。TCP 具有端口號的概念,用來標(biāo)識同一個地址上的不 同應(yīng)用。?述 TCP 的標(biāo)準(zhǔn)文檔是 RFC793。
    • UDP(UserDatagramProtocol,用戶數(shù)據(jù)報協(xié)議)是一個面向數(shù)據(jù)報的傳輸層協(xié) 議。UDP 的傳輸是不可靠的,簡單的說就是發(fā)了不管,發(fā)送者不會知道目標(biāo)地址 的數(shù)據(jù)通路是否發(fā)生擁塞,也不知道數(shù)據(jù)是否到達,是否完整以及是否還是原來的 次序。它同 TCP 一樣有用來標(biāo)識本地應(yīng)用的端口號。所以應(yīng)用 UDP 的應(yīng)用,都能 夠容忍一定數(shù)量的錯誤和丟包,但是對傳輸性能敏感的,比如流媒體、DNS 等。
    • ECHO(EchoProtocol,回聲協(xié)議)是一個簡單的調(diào)試和檢測工具。服務(wù)器器會 原樣回發(fā)它收到的任何數(shù)據(jù),既可以使用 TCP 傳輸,也可以使用 UDP 傳輸。使用 端口號 7 。
    • DHCP(DynamicHostConfigrationProtocol,動態(tài)主機配置協(xié)議)是用于局域 網(wǎng)自動分配 IP 地址和主機配置的協(xié)議。可以使局域網(wǎng)的部署更加簡單。
    • DNS(DomainNameSystem,域名系統(tǒng))是互聯(lián)網(wǎng)的一項服務(wù),可以簡單的將用“.” 分隔的一般會有意義的域名轉(zhuǎn)換成不易記憶的 IP 地址。一般使用 UDP 協(xié)議傳輸, 也可以使用 TCP,默認(rèn)服務(wù)端口號 53。?
    • FTP(FileTransferProtocol,文件傳輸協(xié)議)是用來進行文件傳輸?shù)臉?biāo)準(zhǔn)協(xié)議。 FTP 基于 TCP 使用端口號 20 來傳輸數(shù)據(jù),21 來傳輸控制信息。
    • TFTP(Trivial File Transfer Protocol,簡單文件傳輸協(xié)議)是一個簡化的文 件傳輸協(xié)議,其設(shè)計非常簡單,通過少量存儲器就能輕松實現(xiàn),所以一般被用來通 過網(wǎng)絡(luò)引導(dǎo)計算機過程中傳輸引導(dǎo)文件等小文件;
    • SSH(SecureShell,安全Shell),因為傳統(tǒng)的網(wǎng)絡(luò)服務(wù)程序比如TELNET本質(zhì)上都極不安全,明文傳說數(shù)據(jù)和用戶信息包括密碼,SSH 被開發(fā)出來避免這些問題, 它其實是一個協(xié)議框架,有大量的擴展冗余能力,并且?供了加密壓縮的通道可以 為其他協(xié)議使用。
    • POP(PostOfficeProtocol,郵局協(xié)議)是支持通過客戶端訪問電子郵件的服務(wù), 現(xiàn)在版本是 POP3,也有加密的版本 POP3S。協(xié)議使用 TCP,端口 110。
    • SMTP(Simple Mail Transfer Protocol,簡單郵件傳輸協(xié)議)是現(xiàn)在在互聯(lián)網(wǎng) 上發(fā)送電子郵件的事實標(biāo)準(zhǔn)。使用 TCP 協(xié)議傳輸,端口號 25。
    • HTTP(HyperTextTransferProtocol,超文本傳輸協(xié)議)是現(xiàn)在廣為流行的WEB 網(wǎng)絡(luò)的基礎(chǔ),HTTPS 是 HTTP 的加密安全版本。協(xié)議通過 TCP 傳輸,HTTP 默認(rèn) 使用端口 80,HTTPS 使用 443。

    以上就是今天回顧的內(nèi)容。
    下篇回顧一下socket、TCP、UDP!

    線程池

    Executor 框架便是 Java 5 中引入的,其內(nèi)部使用了線程池機制

    好處

    第一:降低資源消耗 通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。

    第二:提高響應(yīng)速度。當(dāng)任務(wù)到達時,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。

    第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。但是要做到合理的利用線程池,必須對其原理了如指掌。

    Java線程間的通信方式

    wait()方法

    wait()方法使得當(dāng)前線程必須要等待,等到另外一個線程調(diào)用notify()或者notifyAll()方法。

    當(dāng)前的線程必須擁有當(dāng)前對象的monitor,也即lock,就是鎖。

    線程調(diào)用wait()方法,釋放它對鎖的擁有權(quán),然后等待另外的線程來通知它(通知的方式是notify()或者notifyAll()方法),這樣它才能重新獲得鎖的擁有權(quán)和恢復(fù)執(zhí)行。

    要確保調(diào)用wait()方法的時候擁有鎖,即,wait()方法的調(diào)用必須放在synchronized方法或synchronized塊中。

    一個小比較:

    當(dāng)線程調(diào)用了wait()方法時,它會釋放掉對象的鎖。

    另一個會導(dǎo)致線程暫停的方法:Thread.sleep(),它會導(dǎo)致線程睡眠指定的毫秒數(shù),但線程在睡眠的過程中是不會釋放掉對象的鎖的。

    notify()方法

    notify()方法會喚醒一個等待當(dāng)前對象的鎖的線程。

    如果多個線程在等待,它們中的一個將會選擇被喚醒。這種選擇是隨意的,和具體實現(xiàn)有關(guān)。(線程等待一個對象的鎖是由于調(diào)用了wait方法中的一個)。

    被喚醒的線程是不能被執(zhí)行的,需要等到當(dāng)前線程放棄這個對象的鎖。

    被喚醒的線程將和其他線程以通常的方式進行競爭,來獲得對象的鎖。也就是說,被喚醒的線程并沒有什么優(yōu)先權(quán),也沒有什么劣勢,對象的下一個線程還是需要通過一般性的競爭。

    notify()方法應(yīng)該是被擁有對象的鎖的線程所調(diào)用。

    (This method should only be called by a thread that is the owner of this object's monitor.)

    換句話說,和wait()方法一樣,notify方法調(diào)用必須放在synchronized方法或synchronized塊中。

    wait()和notify()方法要求在調(diào)用時線程已經(jīng)獲得了對象的鎖,因此對這兩個方法的調(diào)用需要放在synchronized方法或synchronized塊中。

      一個線程變?yōu)橐粋€對象的鎖的擁有者是通過下列三種方法:

    1.執(zhí)行這個對象的synchronized實例方法。

    2.執(zhí)行這個對象的synchronized語句塊。這個語句塊鎖的是這個對象。

    3.對于Class類的對象,執(zhí)行那個類的synchronized、static方法。


    Java 線程有哪些狀態(tài),這些狀態(tài)之間是如何轉(zhuǎn)化的?

    這里寫圖片描述
  • 新建(new):新創(chuàng)建了一個線程對象。
  • 可運行(runnable):線程對象創(chuàng)建后,其他線程(比如main線程)調(diào)用了該對象的start()方法。該狀態(tài)的線程位于可運行線程池中,等待被線程調(diào)度選中,獲取cpu 的使用權(quán) 。
  • 運行(running):可運行狀態(tài)(runnable)的線程獲得了cpu 時間片(timeslice) ,執(zhí)行程序代碼。
  • 阻塞(block):阻塞狀態(tài)是指線程因為某種原因放棄了cpu 使用權(quán),也即讓出了cpu timeslice,暫時停止運行。直到線程進入可運行(runnable)狀態(tài),才有機會再次獲得cpu timeslice 轉(zhuǎn)到運行(running)狀態(tài)。阻塞的情況分三種:
  • (一). 等待阻塞:運行(running)的線程執(zhí)行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue)中。同時釋放對象鎖

    (二). 同步阻塞:運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池(lock pool)中。

    (三). 其他阻塞:運行(running)的線程執(zhí)行Thread.sleep(long ms)或t.join()方法,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉(zhuǎn)入可運行(runnable)狀態(tài)。

  • 死亡(dead):線程run()、main() 方法執(zhí)行結(jié)束,或者因異常退出了run()方法,則該線程結(jié)束生命周期。死亡的線程不可再次復(fù)生。
  • List接口、Set接口和Map接口的區(qū)別

    1、List和Set接口自Collection接口,而Map不是繼承的Collection接口

    Collection表示一組對象,這些對象也稱為collection的元素;一些 collection允許有重復(fù)的元素,而另一些則不允許;一些collection是有序的,而另一些則是無序的;JDK中不提供此接口的任何直接實 現(xiàn),它提供更具體的子接口(如 Set 和 List)實現(xiàn);Map沒有繼承Collection接口,Map提供key到value的映射;一個Map中不能包含相同key,每個key只能映射一個value;Map接口提供3種集合的視圖,Map的內(nèi)容可以被當(dāng)做一組key集合,一組value集合,或者一組key-value映射;

    2、List接口

    元素有放入順序,元素可重復(fù) List接口有三個實現(xiàn)類:LinkedList,ArrayList,Vector LinkedList:底層基于鏈表實現(xiàn),鏈表內(nèi)存是散亂的,每一個元素存儲本身內(nèi)存地址的同時還存儲下一個元素的地址。鏈表增刪快,查找慢 ArrayList和Vector的區(qū)別:ArrayList是非線程安全的,效率高;Vector是基于線程安全的,效率低 List是一種有序的Collection,可以通過索引訪問集合中的數(shù)據(jù),List比Collection多了10個方法,主要是有關(guān)索引的方法。 1).所有的索引返回的方法都有可能拋出一個IndexOutOfBoundsException異常 2).subList(int fromIndex, int toIndex)返回的是包括fromIndex,不包括toIndex的視圖,該列表的size()=toIndex-fromIndex。 所有的List中只能容納單個不同類型的對象組成的表,而不是Key-Value鍵值對。例如:[ tom,1,c ]; 所有的List中可以有相同的元素,例如Vector中可以有 [ tom,koo,too,koo ]; 所有的List中可以有null元素,例如[ tom,null,1 ]; 基于Array的List(Vector,ArrayList)適合查詢,而LinkedList(鏈表)適合添加,刪除操作;

    3、Set接口

    元素?zé)o放入順序,元素不可重復(fù)(注意:元素雖然無放入順序,但是元素在set中的位置是有該元素的HashCode決定的,其位置其實是固定的) Set接口有兩個實現(xiàn)類:HashSet(底層由HashMap實現(xiàn)),LinkedHashSet SortedSet接口有一個實現(xiàn)類:TreeSet(底層由平衡二叉樹實現(xiàn))Query接口有一個實現(xiàn)類:LinkList Set具有與Collection完全一樣的接口,因此沒有任何額外的功能,不像前面有兩個不同的List。實際上Set就是Collection,只是行為不同。(這是繼承與多態(tài)思想的典型應(yīng)用:表現(xiàn)不同的行為。)Set不保存重復(fù)的元素(至于如何判斷元素相同則較為負責(zé)) Set : 存入Set的每個元素都必須是唯一的,因為Set不保存重復(fù)元素。加入Set的元素必須定義equals()方法以確保對象的唯一性。Set與Collection有完全一樣的接口。Set接口不保證維護元素的次序。 HashSet : 為快速查找設(shè)計的Set。存入HashSet的對象必須定義hashCode()。 TreeSet : 保存次序的Set, 底層為樹結(jié)構(gòu)。使用它可以從Set中提取有序的序列。 LinkedHashSet : 具有HashSet的查詢速度,且內(nèi)部使用鏈表維護元素的順序(插入的次序)。于是在使用迭代器遍歷Set時,結(jié)果會按元素插入的次序顯示。

    4、map接口

    以鍵值對的方式出現(xiàn)的 Map接口有三個實現(xiàn)類:HashMap,HashTable,LinkeHashMap HashMap非線程安全,高效,支持null; HashTable線程安全,低效,不支持null SortedMap有一個實現(xiàn)類:TreeMap

    Session機制

    一、術(shù)語session

    session,中文經(jīng)常翻譯為會話,其本來的含義是指有始有終的一系列動作/消息,比如打電話時從拿起電話撥號到掛斷電話這中間的一系列過程可以稱之為一個session。有時候我們可以看到這樣的話“在一個瀏覽器會話期間,...”,這里的會話一詞用的就是其本義,是指從一個瀏覽器窗口打開到關(guān)閉這個期間①。最混亂的是“用戶(客戶端)在一次會話期間”這樣一句話,它可能指用戶的一系列動作(一般情況下是同某個具體目的相關(guān)的一系列動作,比如從登錄到選購商品到結(jié)賬登出這樣一個網(wǎng)上購物的過程,有時候也被稱為一個transaction),然而有時候也可能僅僅是指一次連接,也有可能是指含義①,其中的差別只能靠上下文來推斷②。
    然而當(dāng)session一詞與網(wǎng)絡(luò)協(xié)議相關(guān)聯(lián)時,它又往往隱含了“面向連接”和/或“保持狀態(tài)”這樣兩個含義,“面向連接”指的是在通信雙方在通信之前要先建立一個通信的渠道,比如打電話,直到對方接了電話通信才能開始,與此相對的是寫信,在你把信發(fā)出去的時候你并不能確認(rèn)對方的地址是否正確,通信渠道不一定能建立,但對發(fā)信人來說,通信已經(jīng)開始了。“保持狀態(tài)”則是指通信的一方能夠把一系列的消息關(guān)聯(lián)起來,使得消息之間可以互相依賴,比如一個服務(wù)員能夠認(rèn)出再次光臨的老顧客并且記得上次這個顧客還欠店里一塊錢。這一類的例子有“一個TCP session”或者“一個POP3 session”③。
    而到了web服務(wù)器蓬勃發(fā)展的時代,session在web開發(fā)語境下的語義又有了新的擴展,它的含義是指一類用來在客戶端與服務(wù)器之間保持狀態(tài)的解決方案④。有時候session也用來指這種解決方案的存儲結(jié)構(gòu),如“把xxx保存在session里”⑤。由于各種用于web開發(fā)的語言在一定程度上都提供了對這種解決方案的支持,所以在某種特定語言的語境下,session也被用來指代該語言的解決方案,比如經(jīng)常把Java里提供的javax.servlet.http.HttpSession簡稱為session⑥。
    鑒于這種混亂已不可改變,本文中session一詞的運用也會根據(jù)上下文有不同的含義,請大家注意分辨。
    在本文中,使用中文“瀏覽器會話期間”來表達含義①,使用“session機制”來表達含義④,使用“session”表達含義⑤,使用具體的“HttpSession”來表達含義⑥
    ** 二、HTTP協(xié)議與狀態(tài)保持**
    HTTP協(xié)議本身是無狀態(tài)的,這與HTTP協(xié)議本來的目的是相符的,客戶端只需要簡單的向服務(wù)器請求下載某些文件,無論是客戶端還是服務(wù)器都沒有必要紀(jì)錄彼此過去的行為,每一次請求之間都是獨立的,好比一個顧客和一個自動售貨機或者一個普通的(非會員制)大賣場之間的關(guān)系一樣。
    然而聰明(或者貪心?)的人們很快發(fā)現(xiàn)如果能夠提供一些按需生成的動態(tài)信息會使web變得更加有用,就像給有線電視加上點播功能一樣。這種需求一方面迫使HTML逐步添加了表單、腳本、DOM等客戶端行為,另一方面在服務(wù)器端則出現(xiàn)了CGI規(guī)范以響應(yīng)客戶端的動態(tài)請求,作為傳輸載體的HTTP協(xié)議也添加了文件上載、cookie這些特性。其中cookie的作用就是為了解決HTTP協(xié)議無狀態(tài)的缺陷所作出的努力。至于后來出現(xiàn)的session機制則是又一種在客戶端與服務(wù)器之間保持狀態(tài)的解決方案。
    讓我們用幾個例子來描述一下cookie和session機制之間的區(qū)別與聯(lián)系。筆者曾經(jīng)常去的一家咖啡店有喝5杯咖啡免費贈一杯咖啡的優(yōu)惠,然而一次性消費5杯咖啡的機會微乎其微,這時就需要某種方式來紀(jì)錄某位顧客的消費數(shù)量。想象一下其實也無外乎下面的幾種方案:
    1、該店的店員很厲害,能記住每位顧客的消費數(shù)量,只要顧客一走進咖啡店,店員就知道該怎么對待了。這種做法就是協(xié)議本身支持狀態(tài)。
    2、發(fā)給顧客一張卡片,上面記錄著消費的數(shù)量,一般還有個有效期限。每次消費時,如果顧客出示這張卡片,則此次消費就會與以前或以后的消費相聯(lián)系起來。這種做法就是在客戶端保持狀態(tài)。
    3、發(fā)給顧客一張會員卡,除了卡號之外什么信息也不紀(jì)錄,每次消費時,如果顧客出示該卡片,則店員在店里的紀(jì)錄本上找到這個卡號對應(yīng)的紀(jì)錄添加一些消費信息。這種做法就是在服務(wù)器端保持狀態(tài)。
    由于HTTP協(xié)議是無狀態(tài)的,而出于種種考慮也不希望使之成為有狀態(tài)的,因此,后面兩種方案就成為現(xiàn)實的選擇。具體來說cookie機制采用的是在客戶端保持狀態(tài)的方案,而session機制采用的是在服務(wù)器端保持狀態(tài)的方案。同時我們也看到,由于采用服務(wù)器端保持狀態(tài)的方案在客戶端也需要保存一個標(biāo)識,所以session機制可能需要借助于cookie機制來達到保存標(biāo)識的目的,但實際上它還有其他選擇。
    **三、理解cookie機制 **
    cookie機制的基本原理就如上面的例子一樣簡單,但是還有幾個問題需要解決:“會員卡”如何分發(fā);“會員卡”的內(nèi)容;以及客戶如何使用“會員卡”。
    正統(tǒng)的cookie分發(fā)是通過擴展HTTP協(xié)議來實現(xiàn)的,服務(wù)器通過在HTTP的響應(yīng)頭中加上一行特殊的指示以提示瀏覽器按照指示生成相應(yīng)的cookie。然而純粹的客戶端腳本如JavaScript或者VBScript也可以生成cookie。
    而cookie的使用是由瀏覽器按照一定的原則在后臺自動發(fā)送給服務(wù)器的。瀏覽器檢查所有存儲的cookie,如果某個cookie所聲明的作用范圍大于等于將要請求的資源所在的位置,則把該cookie附在請求資源的HTTP請求頭上發(fā)送給服務(wù)器。意思是麥當(dāng)勞的會員卡只能在麥當(dāng)勞的店里出示,如果某家分店還發(fā)行了自己的會員卡,那么進這家店的時候除了要出示麥當(dāng)勞的會員卡,還要出示這家店的會員卡。
    cookie的內(nèi)容主要包括:名字,值,過期時間,路徑和域。
    其中域可以指定某一個域比如.google.com,相當(dāng)于總店招牌,比如寶潔公司,也可以指定一個域下的具體某臺機器比如www.google.com或者froogle.google.com,可以用飄柔來做比。
    路徑就是跟在域名后面的URL路徑,比如/或者/foo等等,可以用某飄柔專柜做比。路徑與域合在一起就構(gòu)成了cookie的作用范圍。如果不設(shè)置過期時間,則表示這個cookie的生命期為瀏覽器會話期間,只要關(guān)閉瀏覽器窗口,cookie就消失了。這種生命期為瀏覽器會話期的cookie被稱為會話cookie。會話cookie一般不存儲在硬盤上而是保存在內(nèi)存里,當(dāng)然這種行為并不是規(guī)范規(guī)定的。如果設(shè)置了過期時間,瀏覽器就會把cookie保存到硬盤上,關(guān)閉后再次打開瀏覽器,這些cookie仍然有效直到超過設(shè)定的過期時間。
    存儲在硬盤上的cookie可以在不同的瀏覽器進程間共享,比如兩個IE窗口。而對于保存在內(nèi)存里的cookie,不同的瀏覽器有不同的處理方式。對于IE,在一個打開的窗口上按Ctrl-N(或者從文件菜單)打開的窗口可以與原窗口共享,而使用其他方式新開的IE進程則不能共享已經(jīng)打開的窗口的內(nèi)存cookie;對于Mozilla Firefox0.8,所有的進程和標(biāo)簽頁都可以共享同樣的cookie。一般來說是用javascript的window.open打開的窗口會與原窗口共享內(nèi)存cookie。瀏覽器對于會話cookie的這種只認(rèn)cookie不認(rèn)人的處理方式經(jīng)常給采用session機制的web應(yīng)用程序開發(fā)者造成很大的困擾。

    Cookie和Session的區(qū)別

    HTTP請求是無狀態(tài)的。

    共同之處:

    cookie和session都是用來跟蹤瀏覽器用戶身份的會話方式。

    區(qū)別:

    • cookie數(shù)據(jù)保存在客戶端,session數(shù)據(jù)保存在服務(wù)器端。簡單的說,當(dāng)你登錄一個網(wǎng)站的時候, 如果web服務(wù)器端使用的是session,那么所有的數(shù)據(jù)都保存在服務(wù)器上,客戶端每次請求服務(wù)器的時候會發(fā)送當(dāng)前會話的sessionid,服務(wù)器根據(jù)當(dāng)前sessionid判斷相應(yīng)的用戶數(shù)據(jù)標(biāo)志,以確定用戶是否登錄或具有某種權(quán)限。由于數(shù)據(jù)是存儲在服務(wù)器上面,所以你不能偽造。
    • sessionid是服務(wù)器和客戶端鏈接時候隨機分配的. 如果瀏覽器使用的是cookie,那么所有的數(shù)據(jù)都保存在瀏覽器端,比如你登錄以后,服務(wù)器設(shè)置了cookie用戶名,那么當(dāng)你再次請求服務(wù)器的時候,瀏覽器會將用戶名一塊發(fā)送給服務(wù)器,這些變量有一定的特殊標(biāo)記。服務(wù)器會解釋為cookie變量,所以只要不關(guān)閉瀏覽器,那么cookie變量一直是有效的,所以能夠保證長時間不掉線。如果你能夠截獲某個用戶的 cookie變量,然后偽造一個數(shù)據(jù)包發(fā)送過去,那么服務(wù)器還是認(rèn)為你是合法的。所以,使用 cookie被攻擊的可能性比較大。

    如果設(shè)置了的有效時間,那么它會將 cookie保存在客戶端的硬盤上,下次再訪問該網(wǎng)站的時候,瀏覽器先檢查有沒有 cookie,如果有的話,就讀取該 cookie,然后發(fā)送給服務(wù)器。如果你在機器上面保存了某個論壇 cookie,有效期是一年,如果有人入侵你的機器,將你的 cookie拷走,然后放在他的瀏覽器的目錄下面,那么他登錄該網(wǎng)站的時候就是用你的的身份登錄的。所以 cookie是可以偽造的。當(dāng)然,偽造的時候需要主意,直接copy cookie文件到 cookie目錄,瀏覽器是不認(rèn)的,他有一個index.dat文件,存儲了 cookie文件的建立時間,以及是否有修改,所以你必須先要有該網(wǎng)站的 cookie文件,并且要從保證時間上騙過瀏覽器

    兩個都可以用來存私密的東西,同樣也都有有效期的說法,區(qū)別在于session是放在服務(wù)器上的,過期與否取決于服務(wù)期的設(shè)定,cookie是存在客戶端的,過去與否可以在cookie生成的時候設(shè)置進去。

    (1)cookie數(shù)據(jù)存放在客戶的瀏覽器上,session數(shù)據(jù)放在服務(wù)器上
    (2)cookie不是很安全,別人可以分析存放在本地的COOKIE并進行COOKIE欺騙,如果主要考慮到安全應(yīng)當(dāng)使用session
    (3)session會在一定時間內(nèi)保存在服務(wù)器上。當(dāng)訪問增多,會比較占用你服務(wù)器的性能,如果主要考慮到減輕服務(wù)器性能方面,應(yīng)當(dāng)使用COOKIE
    (4)單個cookie在客戶端的限制是3K,就是說一個站點在客戶端存放的COOKIE不能3K。
    (5)所以:將登陸信息等重要信息存放為SESSION;其他信息如果需要保留,可以放在COOKIE中

    Java中的equals和hashCode方法詳解

    equals()方法是用來判斷其他的對象是否和該對象相等.

    equals()方法在object類中定義如下:

    public boolean equals(Object obj) { return (this == obj); }

    很明顯是對兩個對象的地址值進行的比較(即比較引用是否相同)。但是我們知道,String 、Math、Integer、Double等這些封裝類在使用equals()方法時,已經(jīng)覆蓋了object類的equals()方法。

    比如在String類中如下:

    ?

    ?

    [ 復(fù)制代碼

    ](javascript:void(0);)

    public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = count; if (n == anotherString.count) { char v1[] = value; char v2[] = anotherString.value; int i = offset; int j = anotherString.offset; while (n– != 0) { if (v1[i++] != v2[j++]) return false; } return true; } } return false; }

    很明顯,這是進行的內(nèi)容比較,而已經(jīng)不再是地址的比較。依次類推Math、Integer、Double等這些類都是重寫了equals()方法的,從而進行的是內(nèi)容的比較。當(dāng)然,基本類型是進行值的比較。

    它的性質(zhì)有:

    • 自反性(reflexive)。對于任意不為null的引用值x,x.equals(x)一定是true。
    • 對稱性(symmetric)。對于任意不為null的引用值x和y,當(dāng)且僅當(dāng)x.equals(y)是true時,y.equals(x)也是true。
    • 傳遞性(transitive)。對于任意不為null的引用值x、y和z,如果x.equals(y)是true,同時y.equals(z)是true,那么x.equals(z)一定是true。
    • 一致性(consistent)。對于任意不為null的引用值x和y,如果用于equals比較的對象信息沒有被修改的話,多次調(diào)用時x.equals(y)要么一致地返回true要么一致地返回false。
    • 對于任意不為null的引用值x,x.equals(null)返回false。

    對于Object類來說,equals()方法在對象上實現(xiàn)的是差別可能性最大的等價關(guān)系,即,對于任意非null的引用值x和y,當(dāng)且僅當(dāng)x和y引用的是同一個對象,該方法才會返回true。

    需要注意的是當(dāng)equals()方法被override時,hashCode()也要被override。按照一般hashCode()方法的實現(xiàn)來說,相等的對象,它們的hash code一定相等。

    hashcode() 方法詳解

    hashCode()方法給對象返回一個hash code值。這個方法被用于hash tables,例如HashMap。

    它的性質(zhì)是:

    • 在一個Java應(yīng)用的執(zhí)行期間,如果一個對象提供給equals做比較的信息沒有被修改的話,該對象多次調(diào)用hashCode()方法,該方法必須始終如一返回同一個integer。
    • 如果兩個對象根據(jù)equals(Object)方法是相等的,那么調(diào)用二者各自的hashCode()方法必須產(chǎn)生同一個integer結(jié)果。
    • 并不要求根據(jù)equals(java.lang.Object)方法不相等的兩個對象,調(diào)用二者各自的hashCode()方法必須產(chǎn)生不同的integer結(jié)果。然而,程序員應(yīng)該意識到對于不同的對象產(chǎn)生不同的integer結(jié)果,有可能會提高hash table的性能。

    Java中CAS算法--樂觀鎖的一種實現(xiàn)方式

    悲觀者與樂觀者的做事方式完全不一樣,悲觀者的人生觀是一件事情我必須要百分之百完全控制才會去做,否則就認(rèn)為這件事情一定會出問題;而樂觀者的人生觀則相反,凡事不管最終結(jié)果如何,他都會先嘗試去做,大不了最后不成功。這就是悲觀鎖與樂觀鎖的區(qū)別,悲觀鎖會把整個對象加鎖占為自有后才去做操作,樂觀鎖不獲取鎖直接做操作,然后通過一定檢測手段決定是否更新數(shù)據(jù)。這一節(jié)將對樂觀鎖進行深入探討。

    上節(jié)討論的Synchronized互斥鎖屬于悲觀鎖,它有一個明顯的缺點,它不管數(shù)據(jù)存不存在競爭都加鎖,隨著并發(fā)量增加,且如果鎖的時間比較長,其性能開銷將會變得很大。有沒有辦法解決這個問題?答案是基于沖突檢測的樂觀鎖。這種模式下,已經(jīng)沒有所謂的鎖概念了,每條線程都直接先去執(zhí)行操作,計算完成后檢測是否與其他線程存在共享數(shù)據(jù)競爭,如果沒有則讓此操作成功,如果存在共享數(shù)據(jù)競爭則可能不斷地重新執(zhí)行操作和檢測,直到成功為止,可叫CAS自旋。

    樂觀鎖的核心算法是CAS(Compareand Swap,比較并交換),它涉及到三個操作數(shù):內(nèi)存值、預(yù)期值、新值。當(dāng)且僅當(dāng)預(yù)期值和內(nèi)存值相等時才將內(nèi)存值修改為新值。這樣處理的邏輯是,首先檢查某塊內(nèi)存的值是否跟之前我讀取時的一樣,如不一樣則表示期間此內(nèi)存值已經(jīng)被別的線程更改過,舍棄本次操作,否則說明期間沒有其他線程對此內(nèi)存值操作,可以把新值設(shè)置給此塊內(nèi)存。如圖2-5-4-1,有兩個線程可能會差不多同時對某內(nèi)存操作,線程二先讀取某內(nèi)存值作為預(yù)期值,執(zhí)行到某處時線程二決定將新值設(shè)置到內(nèi)存塊中,如果線程一在此期間修改了內(nèi)存塊,則通過CAS即可以檢測出來,假如檢測沒問題則線程二將新值賦予內(nèi)存塊。

    img

    圖2-5-4-1

    假如你足夠細心你可能會發(fā)現(xiàn)一個疑問,比較和交換,從字面上就有兩個操作了,更別說實際CAS可能會有更多的執(zhí)行指令,他們是原子性的嗎?如果非原子性又怎么保證CAS操作期間出現(xiàn)并發(fā)帶來的問題?我是不是需要用上節(jié)提到的互斥鎖來保證他的原子性操作?CAS肯定是具有原子性的,不然就談不上在并發(fā)中使用了,但這個原子性是由CPU硬件指令實現(xiàn)保證的,即使用JNI調(diào)用native方法調(diào)用由C++編寫的硬件級別指令,jdk中提供了Unsafe類執(zhí)行這些操作。另外,你可能想著CAS是通過互斥鎖來實現(xiàn)原子性的,這樣確實能實現(xiàn),但用這種方式來保證原子性顯示毫無意義。下面一個偽代碼加深對CAS的理解:

    public class AtomicInt { private volatile int value; public final int get() { return value; } public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } public final boolean compareAndSet(int expect, int update) { Unsafe類提供的硬件級別的compareAndSwapInt方法; } }

    其中最重要的方法是getAndIncrement方法,它里面實現(xiàn)了基于CAS的自旋。

    現(xiàn)在已經(jīng)了解樂觀鎖及CAS相關(guān)機制,樂觀鎖避免了悲觀鎖獨占對象的現(xiàn)象,同時也提高了并發(fā)性能,但它也有缺點:

    ① 觀鎖只能保證一個共享變量的原子操作。如上例子,自旋過程中只能保證value變量的原子性,這時如果多一個或幾個變量,樂觀鎖將變得力不從心,但互斥鎖能輕易解決,不管對象數(shù)量多少及對象顆粒度大小。

    ② 長時間自旋可能導(dǎo)致開銷大。假如CAS長時間不成功而一直自旋,會給CPU帶來很大的開銷。

    ③ ABA問題。CAS的核心思想是通過比對內(nèi)存值與預(yù)期值是否一樣而判斷內(nèi)存值是否被改過,但這個判斷邏輯不嚴(yán)謹(jǐn),假如內(nèi)存值原來是A,后來被一條線程改為B,最后又被改成了A,則CAS認(rèn)為此內(nèi)存值并沒有發(fā)生改變,但實際上是有被其他線程改過的,這種情況對依賴過程值的情景的運算結(jié)果影響很大。解決的思路是引入版本號,每次變量更新都把版本號加一。

    樂觀鎖是對悲觀鎖的改進,雖然它也有缺點,但它確實已經(jīng)成為提高并發(fā)性能的主要手段,而且jdk中的并發(fā)包也大量使用基于CAS的樂觀鎖。

    TimSort原理

    comparable與comparator的區(qū)別

    Comparable和Comparator的區(qū)別

    初次碰到這個問題是之前有一次電話面試,問了一個小時的問題,其中有一個問題就問到Comparable和Comparator的區(qū)別,當(dāng)時沒答出 來。之后是公司入職時候做的一套Java編程題,里面用JUnit跑用例的時候也用到了Comparator接口,再加上JDK的大量的類包括常見的 String、Byte、Char、Date等都實現(xiàn)了Comparable接口,因此要學(xué)習(xí)一下這兩個類的區(qū)別以及用法。

    Comparable

    Comparable可以認(rèn)為是一個內(nèi)比較器,實現(xiàn)了Comparable接口的類有一個特點,就是這些類是可以和自己比較的,至于具體和另一個實現(xiàn)了Comparable接口的類如何比較,則依賴compareTo方法的實現(xiàn),compareTo方法也被稱為自然比較方法。如果開發(fā)者add進入一個Collection的對象想要Collections的sort方法幫你自動進行排序的話,那么這個對象必須實現(xiàn)Comparable接口。compareTo方法的返回值是int,有三種情況:

    1、比較者大于被比較者(也就是compareTo方法里面的對象),那么返回正整數(shù)

    2、比較者等于被比較者,那么返回0

    3、比較者小于被比較者,那么返回負整數(shù)

    寫個很簡單的例子:

    public class Domain implements Comparable<Domain> { private String str; public Domain(String str) { this.str = str; } public int compareTo(Domain domain) { if (this.str.compareTo(domain.str) > 0) return 1; else if (this.str.compareTo(domain.str) == 0) return 0; else return -1; } public String getStr() { return str; } } public static void main(String[] args) { Domain d1 = new Domain("c"); Domain d2 = new Domain("c"); Domain d3 = new Domain("b"); Domain d4 = new Domain("d"); System.out.println(d1.compareTo(d2)); System.out.println(d1.compareTo(d3)); System.out.println(d1.compareTo(d4)); }

    運行結(jié)果為:

    0 1 -1

    注意一下,前面說實現(xiàn)Comparable接口的類是可以支持和自己比較的,但是其實代碼里面Comparable的泛型未必就一定要是Domain,將泛型指定為String或者指定為其他任何任何類型都可以----只要開發(fā)者指定了具體的比較算法就行。

    Comparator

    Comparator可以認(rèn)為是是一個外比較器,個人認(rèn)為有兩種情況可以使用實現(xiàn)Comparator接口的方式:

    1、一個對象不支持自己和自己比較(沒有實現(xiàn)Comparable接口),但是又想對兩個對象進行比較

    2、一個對象實現(xiàn)了Comparable接口,但是開發(fā)者認(rèn)為compareTo方法中的比較方式并不是自己想要的那種比較方式

    Comparator接口里面有一個compare方法,方法有兩個參數(shù)T o1和T o2,是泛型的表示方式,分別表示待比較的兩個對象,方法返回值和Comparable接口一樣是int,有三種情況:

    1、o1大于o2,返回正整數(shù)

    2、o1等于o2,返回0

    3、o1小于o3,返回負整數(shù)

    寫個很簡單的例子,上面代碼的Domain不變(假設(shè)這就是第2種場景,我對這個compareTo算法實現(xiàn)不滿意,要自己寫實現(xiàn)):

    public class DomainComparator implements Comparator<Domain> { public int compare(Domain domain1, Domain domain2) { if (domain1.getStr().compareTo(domain2.getStr()) > 0) return 1; else if (domain1.getStr().compareTo(domain2.getStr()) == 0) return 0; else return -1; } } public static void main(String[] args) { Domain d1 = new Domain("c"); Domain d2 = new Domain("c"); Domain d3 = new Domain("b"); Domain d4 = new Domain("d"); DomainComparator dc = new DomainComparator(); System.out.println(dc.compare(d1, d2)); System.out.println(dc.compare(d1, d3)); System.out.println(dc.compare(d1, d4)); }

    看一下運行結(jié)果:

    0 1 -1

    當(dāng)然因為泛型指定死了,所以實現(xiàn)Comparator接口的實現(xiàn)類只能是兩個相同的對象(不能一個Domain、一個String)進行比較了,因此實現(xiàn)Comparator接口的實現(xiàn)類一般都會以"待比較的實體類+Comparator"來命名

    總結(jié)

    總結(jié)一下,兩種比較器Comparable和Comparator,后者相比前者有如下優(yōu)點:

    1、如果實現(xiàn)類沒有實現(xiàn)Comparable接口,又想對兩個類進行比較(或者實現(xiàn)類實現(xiàn)了Comparable接口,但是對compareTo方法內(nèi)的比較算法不滿意),那么可以實現(xiàn)Comparator接口,自定義一個比較器,寫比較算法

    2、實現(xiàn)Comparable接口的方式比實現(xiàn)Comparator接口的耦合性 要強一些,如果要修改比較算法,要修改Comparable接口的實現(xiàn)類,而實現(xiàn)Comparator的類是在外部進行比較的,不需要對實現(xiàn)類有任何修 改。從這個角度說,其實有些不太好,尤其在我們將實現(xiàn)類的.class文件打成一個.jar文件提供給開發(fā)者使用的時候。實際上實現(xiàn)Comparator 接口的方式后面會寫到就是一種典型的策略模式。

    手寫單例模式(線程安全)

    解法一:只適合單線程環(huán)境(不好)

    package test; /** * @author xiaoping * */ public class Singleton { private static Singleton instance=null; private Singleton(){ } public static Singleton getInstance(){ if(instance==null){ instance=new Singleton(); } return instance; } }

    注解:Singleton的靜態(tài)屬性instance中,只有instance為null的時候才創(chuàng)建一個實例,構(gòu)造函數(shù)私有,確保每次都只創(chuàng)建一個,避免重復(fù)創(chuàng)建。
    缺點:只在單線程的情況下正常運行,在多線程的情況下,就會出問題。例如:當(dāng)兩個線程同時運行到判斷instance是否為空的if語句,并且instance確實沒有創(chuàng)建好時,那么兩個線程都會創(chuàng)建一個實例。

    解法二:多線程的情況可以用。(懶漢式,不好)

    public class Singleton { private static Singleton instance=null; private Singleton(){ } public static synchronized Singleton getInstance(){ if(instance==null){ instance=new Singleton(); } return instance; } }

    注解:在解法一的基礎(chǔ)上加上了同步鎖,使得在多線程的情況下可以用。例如:當(dāng)兩個線程同時想創(chuàng)建實例,由于在一個時刻只有一個線程能得到同步鎖,當(dāng)?shù)谝粋€線程加上鎖以后,第二個線程只能等待。第一個線程發(fā)現(xiàn)實例沒有創(chuàng)建,創(chuàng)建之。第一個線程釋放同步鎖,第二個線程才可以加上同步鎖,執(zhí)行下面的代碼。由于第一個線程已經(jīng)創(chuàng)建了實例,所以第二個線程不需要創(chuàng)建實例。保證在多線程的環(huán)境下也只有一個實例。
    缺點:每次通過getInstance方法得到singleton實例的時候都有一個試圖去獲取同步鎖的過程。而眾所周知,加鎖是很耗時的。能避免則避免。

    解法三:加同步鎖時,前后兩次判斷實例是否存在(可行)

    public class Singleton { private static Singleton instance=null; private Singleton(){ } public static Singleton getInstance(){ if(instance==null){ synchronized(Singleton.class){ if(instance==null){ instance=new Singleton(); } } } return instance; } }

    注解:只有當(dāng)instance為null時,需要獲取同步鎖,創(chuàng)建一次實例。當(dāng)實例被創(chuàng)建,則無需試圖加鎖。
    缺點:用雙重if判斷,復(fù)雜,容易出錯。

    解法四:餓漢式(建議使用)

    public class Singleton { private static Singleton instance=new Singleton(); private Singleton(){ } public static Singleton getInstance(){ return instance; } }

    注解:初試化靜態(tài)的instance創(chuàng)建一次。如果我們在Singleton類里面寫一個靜態(tài)的方法不需要創(chuàng)建實例,它仍然會早早的創(chuàng)建一次實例。而降低內(nèi)存的使用率。

    缺點:沒有l(wèi)azy loading的效果,從而降低內(nèi)存的使用率。

    解法五:靜態(tài)內(nèi)部內(nèi)。(建議使用)

    public class Singleton { private Singleton(){ } private static class SingletonHolder{ private final static Singleton instance=new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.instance; } }

    注解:定義一個私有的內(nèi)部類,在第一次用這個嵌套類時,會創(chuàng)建一個實例。而類型為SingletonHolder的類,只有在Singleton.getInstance()中調(diào)用,由于私有的屬性,他人無法使用SingleHolder,不調(diào)用Singleton.getInstance()就不會創(chuàng)建實例。
    優(yōu)點:達到了lazy loading的效果,即按需創(chuàng)建實例。

    JVM參數(shù)初始值

    初始堆大小:1/64內(nèi)存-Xms 最大堆大小:1/4內(nèi)存-Xmx

    初始永久代大小:1/64內(nèi)存-XX:PermSize 最大堆大小:1/4內(nèi)存-XX:MaxPermSize

    Java8的內(nèi)存分代改進

    JAVA 8持久代已經(jīng)被徹底刪除了

    取代它的是另一個內(nèi)存區(qū)域也被稱為元空間。

    元空間 —— 快速入門

    • 它是本地內(nèi)存中的一部分
    • 最直接的表現(xiàn)就是OOM(內(nèi)存溢出)問題將不復(fù)存在,因為直接利用的是本地內(nèi)存。
    • 它可以通過-XX:MetaspaceSize和-XX:MaxMetaspaceSize來進行調(diào)整
    • 當(dāng)?shù)竭_XX:MetaspaceSize所指定的閾值后會開始進行清理該區(qū)域
    • 如果本地空間的內(nèi)存用盡了會收到j(luò)ava.lang.OutOfMemoryError: Metadata space的錯誤信息。
    • 和持久代相關(guān)的JVM參數(shù)-XX:PermSize及-XX:MaxPermSize將會被忽略掉,并且在啟動的時候給出警告信息。
    • 充分利用了Java語言規(guī)范中的好處:類及相關(guān)的元數(shù)據(jù)的生命周期與類加載器的一致

    元空間 —— 內(nèi)存分配模型絕大多數(shù)的類元數(shù)據(jù)的空間都從本地內(nèi)存中分配。用來描述類元數(shù)據(jù)的類也被刪除了,分元數(shù)據(jù)分配了多個虛擬內(nèi)存空間給每個類加載器分配一個內(nèi)存塊的列表,只進行線性分配。塊的大小取決于類加載器的類型, sun/反射/代理對應(yīng)的類加載器的塊會小一些。不會單獨回收某個類,如果GC發(fā)現(xiàn)某個類加載器不再存活了,會把相關(guān)的空間整個回收掉。這樣減少了碎片,并節(jié)省GC掃描和壓縮的時間。

    元空間 —— 調(diào)優(yōu)使用-XX:MaxMetaspaceSize參數(shù)可以設(shè)置元空間的最大值,默認(rèn)是沒有上限的,也就是說你的系統(tǒng)內(nèi)存上限是多少它就是多少。使用-XX:MetaspaceSize選項指定的是元空間的初始大小,如果沒有指定的話,元空間會根據(jù)應(yīng)用程序運行時的需要動態(tài)地調(diào)整大小。 一旦類元數(shù)據(jù)的使用量達到了“MaxMetaspaceSize”指定的值,對于無用的類和類加載器,垃圾收集此時會觸發(fā)。為了控制這種垃圾收集的頻率和延遲,合適的監(jiān)控和調(diào)整Metaspace非常有必要。過于頻繁的Metaspace垃圾收集是類和類加載器發(fā)生內(nèi)存泄露的征兆,同時也說明你的應(yīng)用程序內(nèi)存大小不合適,需要調(diào)整。

    ** 快速過一遍JVM的內(nèi)存結(jié)構(gòu),JVM中的內(nèi)存分為5個虛擬的區(qū)域:(程序計數(shù)器、

    虛擬機棧、本地方法棧、堆區(qū)、方法區(qū))

    Java8的JVM持久代 - 何去何從?

    • 你的Java程序中所分配的每一個對象都需要存儲在內(nèi)存里。堆是這些實例化的對象所存儲的地方。是的——都怪new操作符,是它把你的Java堆都占滿了的!
    • 它由所有線程共享
    • 當(dāng)堆耗盡的時候,JVM會拋出java.lang.OutOfMemoryError 異常
    • 堆的大小可以通過JVM選項-Xms和-Xmx來進行調(diào)整

    堆被分為:

    • Eden區(qū) —— 新對象或者生命周期很短的對象會存儲在這個區(qū)域中,這個區(qū)的大小可以通過-XX:NewSize和-XX:MaxNewSize參數(shù)來調(diào)整。新生代GC(垃圾回收器)會清理這一區(qū)域。
    • Survivor區(qū) —— 那些歷經(jīng)了Eden區(qū)的垃圾回收仍能存活下來的依舊存在引用的對象會待在這個區(qū)域。這個區(qū)的大小可以由JVM參數(shù)-XX:SurvivorRatio來進行調(diào)節(jié)。
    • 老年代 —— 那些在歷經(jīng)了Eden區(qū)和Survivor區(qū)的多次GC后仍然存活下來的對象(當(dāng)然了,是拜那些揮之不去的引用所賜)會存儲在這個區(qū)里。這個區(qū)會由一個特殊的垃圾回收器來負責(zé)。年老代中的對象的回收是由老年代的GC(major GC)來進行的。

    方法區(qū)

    • 也被稱為非堆區(qū)域(在HotSpot JVM的實現(xiàn)當(dāng)中)
    • 它被分為兩個主要的子區(qū)域

    持久代 —— 這個區(qū)域會 存儲包括類定義,結(jié)構(gòu),字段,方法(數(shù)據(jù)及代碼)以及常量在內(nèi)的類相關(guān)數(shù)據(jù)。它可以通過-XX:PermSize及 -XX:MaxPermSize來進行調(diào)節(jié)。如果它的空間用完了,會導(dǎo)致java.lang.OutOfMemoryError: PermGen space的異常。

    代碼緩存——這個緩存區(qū)域是用來存儲編譯后的代碼。編譯后的代碼就是本地代碼(硬件相關(guān)的),它是由JIT(Just In Time)編譯器生成的,這個編譯器是Oracle HotSpot JVM所特有的。

    JVM棧

    • 和Java類中的方法密切相關(guān)
    • 它會存儲局部變量以及方法調(diào)用的中間結(jié)果及返回值
    • Java中的每個線程都有自己專屬的棧,這個棧是別的線程無法訪問的。
    • 可以通過JVM選項-Xss來進行調(diào)整

    本地棧

    • 用于本地方法(非Java代碼)
    • 按線程分配

    PC寄存器

    • 特定線程的程序計數(shù)器
    • 包含JVM正在執(zhí)行的指令的地址(如果是本地方法的話它的值則未定義)

    好吧,這就是JVM內(nèi)存分區(qū)的基礎(chǔ)知識了。現(xiàn)在再說說持久代這個話題吧。

    對Java內(nèi)存模型的理解以及其在并發(fā)當(dāng)中的作用

    概述

    Java平臺自動集成了線程以及多處理器技術(shù),這種集成程度比Java以前誕生的計算機語言要厲害很多,該語言針對多種異構(gòu)平臺的平臺獨立性而使用的多線程技術(shù)支持也是具有開拓性的一面,有時候在開發(fā)Java同步和線程安全要求很嚴(yán)格的程序時,往往容易混淆的一個概念就是內(nèi)存模型。究竟什么是內(nèi)存模型?內(nèi)存模型描述了程序中各個變量(實例域、靜態(tài)域和數(shù)組元素)之間的關(guān)系,以及在實際計算機系統(tǒng)中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細節(jié),對象最終是存儲在內(nèi)存里面的,這點沒有錯,但是編譯器、運行庫、處理器或者系統(tǒng)緩存可以有特權(quán)在變量指定內(nèi)存位置存儲或者取出變量的值。【JMM】(Java Memory Model的縮寫)允許編譯器和緩存以數(shù)據(jù)在處理器特定的緩存(或寄存器)和主存之間移動的次序擁有重要的特權(quán),除非程序員使用了final或synchronized明確請求了某些可見性的保證。在Java中應(yīng)為不同的目的可以將java劃分為兩種內(nèi)存模型:gc內(nèi)存模型。并發(fā)內(nèi)存模型。

    gc內(nèi)存模型

    java與c++之間有一堵由內(nèi)存動態(tài)分配與垃圾收集技術(shù)所圍成的“高墻”。墻外面的人想進去,墻里面的人想出來。java在執(zhí)行java程序的過程中會把它管理的內(nèi)存劃分若干個不同功能的數(shù)據(jù)管理區(qū)域。如圖:

    img img img

    hotspot中的gc內(nèi)存模型

    整體上。分為三部分:棧,堆,程序計數(shù)器,他們每一部分有其各自的用途;虛擬機棧保存著每一條線程的執(zhí)行程序調(diào)用堆棧;堆保存著類對象、數(shù)組的具體信息;程序計數(shù)器保存著每一條線程下一次執(zhí)行指令位置。這三塊區(qū)域中棧和程序計數(shù)器是線程私有的。也就是說每一個線程擁有其獨立的棧和程序計數(shù)器。我們可以看看具體結(jié)構(gòu):

    虛擬機/本地方法棧

    在棧中,會為每一個線程創(chuàng)建一個棧。線程越多,棧的內(nèi)存使用越大。對于每一個線程棧。當(dāng)一個方法在線程中執(zhí)行的時候,會在線程棧中創(chuàng)建一個棧幀(stack frame),用于存放該方法的上下文(局部變量表、操作數(shù)棧、方法返回地址等等)。每一個方法從調(diào)用到執(zhí)行完畢的過程,就是對應(yīng)著一個棧幀入棧出棧的過程。

    本地方法棧與虛擬機棧發(fā)揮的作用是類似的,他們之間的區(qū)別不過是虛擬機棧為虛擬機執(zhí)行java(字節(jié)碼)服務(wù)的,而本地方法棧是為虛擬機執(zhí)行native方法服務(wù)的。

    方法區(qū)/堆

    在hotspot的實現(xiàn)中,方法區(qū)就是在堆中稱為永久代的堆區(qū)域。幾乎所有的對象/數(shù)組的內(nèi)存空間都在堆上(有少部分在棧上)。在gc管理中,將虛擬機堆分為永久代、老年代、新生代。通過名字我們可以知道一個對象新建一般在新生代。經(jīng)過幾輪的gc。還存活的對象會被移到老年代。永久代用來保存類信息、代碼段等幾乎不會變的數(shù)據(jù)。堆中的所有數(shù)據(jù)是線程共享的。

    • 新生代:應(yīng)為gc具體實現(xiàn)的優(yōu)化的原因。hotspot又將新生代劃分為一個eden區(qū)和兩個survivor區(qū)。每一次新生代gc時候。只用到一個eden區(qū),一個survivor區(qū)。新生代一般的gc策略為mark-copy。
    • 老年代:當(dāng)新生代中的對象經(jīng)過若干輪gc后還存活/或survisor在gc內(nèi)存不夠的時候。會把當(dāng)前對象移動到老年代。老年代一般gc策略為mark-compact。
    • 永久代:永久代一般可以不參與gc。應(yīng)為其中保存的是一些代碼/常量數(shù)據(jù)/類信息。在永久代gc。清楚的是類信息以及常量池。

    JVM內(nèi)存模型中分兩大塊,一塊是 NEW Generation, 另一塊是Old Generation. 在New Generation中,有一個叫Eden的空間,主要是用來存放新生的對象,還有兩個Survivor Spaces(from,to), 它們用來存放每次垃圾回收后存活下來的對象。在Old Generation中,主要存放應(yīng)用程序中生命周期長的內(nèi)存對象,還有個Permanent Generation,主要用來放JVM自己的反射對象,比如類對象和方法對象等。

    程序計數(shù)器

    如同其名稱一樣。程序計數(shù)器用于記錄某個線程下次執(zhí)行指令位置。程序計數(shù)器也是線程私有的。

    并發(fā)內(nèi)存模型

    java試圖定義一個Java內(nèi)存模型(Java memory model jmm)來屏蔽掉各種硬件/操作系統(tǒng)的內(nèi)存訪問差異,以實現(xiàn)讓java程序在各個平臺下都能達到一致的內(nèi)存訪問效果。java內(nèi)存模型主要目標(biāo)是定義程序中各個變量的訪問規(guī)則,即在虛擬機中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細節(jié)。模型圖如下:

    img

    java并發(fā)內(nèi)存模型以及內(nèi)存操作規(guī)則

    java內(nèi)存模型中規(guī)定了所有變量都存貯到主內(nèi)存(如虛擬機物理內(nèi)存中的一部分)中。每一個線程都有一個自己的工作內(nèi)存(如cpu中的高速緩存)。線程中的工作內(nèi)存保存了該線程使用到的變量的主內(nèi)存的副本拷貝。線程對變量的所有操作(讀取、賦值等)必須在該線程的工作內(nèi)存中進行。不同線程之間無法直接訪問對方工作內(nèi)存中變量。線程間變量的值傳遞均需要通過主內(nèi)存來完成。

    關(guān)于主內(nèi)存與工作內(nèi)存之間的交互協(xié)議,即一個變量如何從主內(nèi)存拷貝到工作內(nèi)存。如何從工作內(nèi)存同步到主內(nèi)存中的實現(xiàn)細節(jié)。java內(nèi)存模型定義了8種操作來完成。這8種操作每一種都是原子操作。8種操作如下:

    • lock(鎖定):作用于主內(nèi)存,它把一個變量標(biāo)記為一條線程獨占狀態(tài);
    • unlock(解鎖):作用于主內(nèi)存,它將一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才能夠被其他線程鎖定;
    • read(讀取):作用于主內(nèi)存,它把變量值從主內(nèi)存?zhèn)魉偷骄€程的工作內(nèi)存中,以便隨后的load動作使用;
    • load(載入):作用于工作內(nèi)存,它把read操作的值放入工作內(nèi)存中的變量副本中;
    • use(使用):作用于工作內(nèi)存,它把工作內(nèi)存中的值傳遞給執(zhí)行引擎,每當(dāng)虛擬機遇到一個需要使用這個變量的指令時候,將會執(zhí)行這個動作;
    • assign(賦值):作用于工作內(nèi)存,它把從執(zhí)行引擎獲取的值賦值給工作內(nèi)存中的變量,每當(dāng)虛擬機遇到一個給變量賦值的指令時候,執(zhí)行該操作;
    • store(存儲):作用于工作內(nèi)存,它把工作內(nèi)存中的一個變量傳送給主內(nèi)存中,以備隨后的write操作使用;
    • write(寫入):作用于主內(nèi)存,它把store傳送值放到主內(nèi)存中的變量中。

    Java內(nèi)存模型還規(guī)定了執(zhí)行上述8種基本操作時必須滿足如下規(guī)則:

    • 不允許read和load、store和write操作之一單獨出現(xiàn),以上兩個操作必須按順序執(zhí)行,但沒有保證必須連續(xù)執(zhí)行,也就是說,read與load之間、store與write之間是可插入其他指令的。
    • 不允許一個線程丟棄它的最近的assign操作,即變量在工作內(nèi)存中改變了之后必須把該變化同步回主內(nèi)存。
    • 不允許一個線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存中。
    • 一個新的變量只能從主內(nèi)存中“誕生”,不允許在工作內(nèi)存中直接使用一個未被初始化(load或assign)的變量,換句話說就是對一個變量實施use和store操作之前,必須先執(zhí)行過了assign和load操作。
    • 一個變量在同一個時刻只允許一條線程對其執(zhí)行l(wèi)ock操作,但lock操作可以被同一個條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會被解鎖。
    • 如果對一個變量執(zhí)行l(wèi)ock操作,將會清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個變量前,需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值。
    • 如果一個變量實現(xiàn)沒有被lock操作鎖定,則不允許對它執(zhí)行unlock操作,也不允許去unlock一個被其他線程鎖定的變量。
    • 對一個變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存(執(zhí)行store和write操作)。

    volatile型變量的特殊規(guī)則

    關(guān)鍵字volatile可以說是Java虛擬機提供的最輕量級的同步機制,但是它并不容易完全被正確、完整的理解,以至于許多程序員都不習(xí)慣去使用它,遇到需要處理多線程的問題的時候一律使用synchronized來進行同步。了解volatile變量的語義對后面了解多線程操作的其他特性很有意義。Java內(nèi)存模型對volatile專門定義了一些特殊的訪問規(guī)則,當(dāng)一個變量被定義成volatile之后,他將具備兩種特性:

    • 保證此變量對所有線程的可見性。第一保證此變量對所有線程的可見性,這里的“可見性”是指當(dāng)一條線程修改了這個變量的值,新值對于其他線程來說是可以立即得知的。而普通變量是做不到這點,普通變量的值在線程在線程間傳遞均需要通過住內(nèi)存來完成,例如,線程A修改一個普通變量的值,然后向主內(nèi)存進行會寫,另外一個線程B在線程A回寫完成了之后再從主內(nèi)存進行讀取操作,新變量值才會對線程B可見。另外,java里面的運算并非原子操作,會導(dǎo)致volatile變量的運算在并發(fā)下一樣是不安全的。
    • 禁止指令重排序優(yōu)化。普通的變量僅僅會保證在該方法的執(zhí)行過程中所有依賴賦值結(jié)果的地方都能獲得正確的結(jié)果,而不能保證變量賦值操作的順序與程序中的執(zhí)行順序一致,在單線程中,我們是無法感知這一點的。

    由于volatile變量只能保證可見性,在不符合以下兩條規(guī)則的運算場景中,我們?nèi)匀灰ㄟ^加鎖來保證原子性。

    • 1.運算結(jié)果并不依賴變量的當(dāng)前值,或者能夠確保只有單一的線程修改變量的值。
    • 2.變量不需要與其他的狀態(tài)比阿尼浪共同參與不變約束。

    原子性、可見性與有序性

    Java內(nèi)存模型是圍繞著在并發(fā)過程中如何處理原子性、可見性和有序性這三個特征來建立的,我們逐個看下哪些操作實現(xiàn)了這三個特性。

    • 原子性(Atomicity):由Java內(nèi)存模型來直接保證的原子性變量包括read、load、assign、use、store和write,我們大致可以認(rèn)為基本數(shù)據(jù)類型的訪問讀寫是具備原子性的。如果應(yīng)用場景需要一個更大方位的原子性保證,Java內(nèi)存模型還提供了lock和unlock操作來滿足這種需求,盡管虛擬機未把lock和unlock操作直接開放給用戶使用,但是卻提供了更高層次的字節(jié)碼指令monitorenter和monitorexit來隱式的使用這兩個操作,這兩個字節(jié)碼指令反應(yīng)到Java代碼中就是同步塊--synchronized關(guān)鍵字,因此在synchronized塊之間的操作也具備原子性。
    • 可見性(Visibility):可見性是指當(dāng)一個線程修改了共享變量的值,其他線程能夠立即得知這個修改。上文在講解volatile變量的時候我們已詳細討論過這一點。Java內(nèi)存模型是通過在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存刷新變量值這種依賴主內(nèi)存作為傳遞媒介的方式來實現(xiàn)可見性的,無論是普通變量還是volatile變量都是如此,普通變量與volatile變量的區(qū)別是,volatile的特殊規(guī)則保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新。因此,可以說volatile保證了多線程操作時變量的可見性,而普通變量則不能保證這一點。除了volatile之外,Java還有兩個關(guān)鍵字能實現(xiàn)可見性,即synchronized和final.同步快的可見性是由“對一個變量執(zhí)行unlock操作前,必須先把此變量同步回主內(nèi)存”這條規(guī)則獲得的,而final關(guān)鍵字的可見性是指:被final修飾的字段在構(gòu)造器中一旦初始化完成,并且構(gòu)造器沒有把"this"的引用傳遞出去,那么在其他線程中就能看見final字段的值。
    • 有序性(Ordering):Java內(nèi)存模型的有序性在前面講解volatile時也詳細的討論過了,Java程序中天然的有序性可以總結(jié)為一句話:如果在本線程內(nèi)觀察,所有的操作都是有序的:如果在一個線程中觀察另外一個線程,所有的線程操作都是無序的。前半句是指“線程內(nèi)表現(xiàn)為串行的語義”,后半句是指“指令重排序”現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象。Java語言提供了volatile和synchronized兩個關(guān)鍵字來保證線程之間操作的有序性,volatile關(guān)鍵字本身就包含了禁止指令重排序的語義,而synchronized則是由“一個變量在同一個時刻只允許一條線程對其進行l(wèi)ock操作”這條規(guī)則獲得的,這條規(guī)則決定了持有同一個鎖的兩個同步塊只能串行的進入。

    Arrays和Collections 對于sort的不同實現(xiàn)原理

    1、Arrays.sort()
    該算法是一個經(jīng)過調(diào)優(yōu)的快速排序,此算法在很多數(shù)據(jù)集上提供N*log(N)的性能,這導(dǎo)致其他快速排序會降低二次型性能。

    2、Collections.sort()
    該算法是一個經(jīng)過修改的合并排序算法(其中,如果低子列表中的最高元素效益高子列表中的最低元素,則忽略合并)。此算法可提供保證的N*log(N)的性能,此實現(xiàn)將指定列表轉(zhuǎn)儲到一個數(shù)組中,然后再對數(shù)組進行排序,在重置數(shù)組中相應(yīng)位置處每個元素的列表上進行迭代。這避免了由于試圖原地對鏈接列表進行排序而產(chǎn)生的n2log(n)性能。

    Java中object常用方法

    1、clone()
    2、equals()
    3、finalize()
    4、getclass()
    5、hashcode()
    6、notify()
    7、notifyAll()
    8、toString()

    對于Java中多態(tài)的理解

    所謂多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時并不確定,而是在程序運行期間才確定,即一個引用變量到底會指向哪個類的實例對象,該引用變量發(fā)出的方法調(diào)用到底是哪個類中實現(xiàn)的方法,必須在由程序運行期間才能決定。因為在程序運行時才確定具體的類,這樣,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實現(xiàn)上,從而導(dǎo)致該引用調(diào)用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態(tài),這就是多態(tài)性。

    多態(tài)的定義:指允許不同類的對象對同一消息做出響應(yīng)。即同一消息可以根據(jù)發(fā)送對象的不同而采用多種不同的行為方式。(發(fā)送消息就是函數(shù)調(diào)用)

    Java實現(xiàn)多態(tài)有三個必要條件:繼承、重寫、父類引用指向子類對象。

    繼承:在多態(tài)中必須存在有繼承關(guān)系的子類和父類。

    重寫:子類對父類中某些方法進行重新定義,在調(diào)用這些方法時就會調(diào)用子類的方法。

    父類引用指向子類對象:在多態(tài)中需要將子類的引用賦給父類對象,只有這樣該引用才能夠具備技能調(diào)用父類的方法和子類的方法。

    實現(xiàn)多態(tài)的技術(shù)稱為:動態(tài)綁定(dynamic binding),是指在執(zhí)行期間判斷所引用對象的實際類型,根據(jù)其實際的類型調(diào)用其相應(yīng)的方法。

    多態(tài)的作用:消除類型之間的耦合關(guān)系。

    Java序列化與反序列化是什么?為什么需要序列化與反序列化?如何實現(xiàn)Java序列化與反序列化

    spring AOP 實現(xiàn)原理

    什么是AOP

    AOP(Aspect-OrientedProgramming,面向方面編程),可以說是OOP(Object-Oriented Programing,面向?qū)ο缶幊?#xff09;的補充和完善。OOP引入封裝、繼承和多態(tài)性等概念來建立一種對象層次結(jié)構(gòu),用以模擬公共行為的一個集合。當(dāng)我們需要為分散的對象引入公共行為的時候,OOP則顯得無能為力。也就是說,OOP允許你定義從上到下的關(guān)系,但并不適合定義從左到右的關(guān)系。例如日志功能。日志代碼往往水平地散布在所有對象層次中,而與它所散布到的對象的核心功能毫無關(guān)系。對于其他類型的代碼,如安全性、異常處理和透明的持續(xù)性也是如此。這種散布在各處的無關(guān)的代碼被稱為橫切(cross-cutting)代碼,在OOP設(shè)計中,它導(dǎo)致了大量代碼的重復(fù),而不利于各個模塊的重用。

    而AOP技術(shù)則恰恰相反,它利用一種稱為“橫切”的技術(shù),剖解開封裝的對象內(nèi)部,并將那些影響了多個類的公共行為封裝到一個可重用模塊,并將其名為“Aspect”,即方面。所謂“方面”,簡單地說,就是將那些與業(yè)務(wù)無關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任封裝起來,便于減少系統(tǒng)的重復(fù)代碼,降低模塊間的耦合度,并有利于未來的可操作性和可維護性。AOP代表的是一個橫向的關(guān)系,如果說“對象”是一個空心的圓柱體,其中封裝的是對象的屬性和行為;那么面向方面編程的方法,就仿佛一把利刃,將這些空心圓柱體剖開,以獲得其內(nèi)部的消息。而剖開的切面,也就是所謂的“方面”了。然后它又以巧奪天功的妙手將這些剖開的切面復(fù)原,不留痕跡。

    使用“橫切”技術(shù),AOP把軟件系統(tǒng)分為兩個部分:核心關(guān)注點和橫切關(guān)注點。業(yè)務(wù)處理的主要流程是核心關(guān)注點,與之關(guān)系不大的部分是橫切關(guān)注點。橫切關(guān)注點的一個特點是,他們經(jīng)常發(fā)生在核心關(guān)注點的多處,而各處都基本相似。比如權(quán)限認(rèn)證、日志、事務(wù)處理。Aop 的作用在于分離系統(tǒng)中的各種關(guān)注點,將核心關(guān)注點和橫切關(guān)注點分離開來。正如Avanade公司的高級方案構(gòu)架師Adam Magee所說,AOP的核心思想就是“將應(yīng)用程序中的商業(yè)邏輯同對其提供支持的通用服務(wù)進行分離。”

    實現(xiàn)AOP的技術(shù),主要分為兩大類:一是采用動態(tài)代理技術(shù),利用截取消息的方式,對該消息進行裝飾,以取代原有對象行為的執(zhí)行;二是采用靜態(tài)織入的方式,引入特定的語法創(chuàng)建“方面”,從而使得編譯器可以在編譯期間織入有關(guān)“方面”的代碼。

    AOP使用場景

    AOP用來封裝橫切關(guān)注點,具體可以在下面的場景中使用:

    Authentication 權(quán)限

    Caching 緩存

    Context passing 內(nèi)容傳遞

    Error handling 錯誤處理

    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相關(guān)概念

    方面(Aspect):一個關(guān)注點的模塊化,這個關(guān)注點實現(xiàn)可能另外橫切多個對象。事務(wù)管理是J2EE應(yīng)用中一個很好的橫切關(guān)注點例子。方面用spring的 Advisor或攔截器實現(xiàn)。

    連接點(Joinpoint): 程序執(zhí)行過程中明確的點,如方法的調(diào)用或特定的異常被拋出。

    通知(Advice): 在特定的連接點,AOP框架執(zhí)行的動作。各種類型的通知包括“around”、“before”和“throws”通知。通知類型將在下面討論。許多AOP框架包括Spring都是以攔截器做通知模型,維護一個“圍繞”連接點的攔截器鏈。Spring中定義了四個advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice

    切入點(Pointcut): 指定一個通知將被引發(fā)的一系列連接點的集合。AOP框架必須允許開發(fā)者指定切入點:例如,使用正則表達式。 Spring定義了Pointcut接口,用來組合MethodMatcher和ClassFilter,可以通過名字很清楚的理解, MethodMatcher是用來檢查目標(biāo)類的方法是否可以被應(yīng)用此通知,而ClassFilter是用來檢查Pointcut是否應(yīng)該應(yīng)用到目標(biāo)類上

    引入(Introduction): 添加方法或字段到被通知的類。 Spring允許引入新的接口到任何被通知的對象。例如,你可以使用一個引入使任何對象實現(xiàn) IsModified接口,來簡化緩存。Spring中要使用Introduction, 可有通過DelegatingIntroductionInterceptor來實現(xiàn)通知,通過DefaultIntroductionAdvisor來配置Advice和代理類要實現(xiàn)的接口

    目標(biāo)對象(Target Object): 包含連接點的對象。也被稱作被通知或被代理對象。POJO

    AOP代理(AOP Proxy): AOP框架創(chuàng)建的對象,包含通知。 在Spring中,AOP代理可以是JDK動態(tài)代理或者CGLIB代理。

    織入(Weaving): 組裝方面來創(chuàng)建一個被通知對象。這可以在編譯時完成(例如使用AspectJ編譯器),也可以在運行時完成。Spring和其他純Java?AOP框架一樣,在運行時完成織入。

    Spring AOP組件

    下面這種類圖列出了Spring中主要的AOP組件

    img

    如何使用Spring AOP

    可以通過配置文件或者編程的方式來使用Spring AOP。

    配置可以通過xml文件來進行,大概有四種方式:

    \1. 配置ProxyFactoryBean,顯式地設(shè)置advisors, advice, target等

  • 配置AutoProxyCreator,這種方式下,還是如以前一樣使用定義的bean,但是從容器中獲得的其實已經(jīng)是代理對象
  • 通過<aop:config>來配置
  • 通過<aop: aspectj-autoproxy>來配置,使用AspectJ的注解來標(biāo)識通知及切入點
  • 也可以直接使用ProxyFactory來以編程的方式使用Spring AOP,通過ProxyFactory提供的方法可以設(shè)置target對象, advisor等相關(guān)配置,最終通過 getProxy()方法來獲取代理對象

    具體使用的示例可以google. 這里略去

    Spring AOP代理對象的生成

    Spring提供了兩種方式來生成代理對象: JDKProxy和Cglib,具體使用哪種方式生成由AopProxyFactory根據(jù)AdvisedSupport對象的配置來決定。默認(rèn)的策略是如果目標(biāo)類是接口,則使用JDK動態(tài)代理技術(shù),否則使用Cglib來生成代理。下面我們來研究一下Spring如何使用JDK來生成代理對象,具體的生成代碼放在JdkDynamicAopProxy這個類中,直接上相關(guān)代碼:

    友情鏈接 :Spring AOP 實現(xiàn)原理

    /*** <ol>* <li>獲取代理類要實現(xiàn)的接口,除了Advised對象中配置的,還會加上SpringProxy, Advised(opaque=false)* <li>檢查上面得到的接口中有沒有定義 equals或者hashcode的接口* <li>調(diào)用Proxy.newProxyInstance創(chuàng)建代理對象* </ol>*/public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " +this.advised.getTargetSource()); } Class[] proxiedInterfaces =AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); }

    那這個其實很明了,注釋上我也已經(jīng)寫清楚了,不再贅述。

    下面的問題是,代理對象生成了,那切面是如何織入的?

    我們知道InvocationHandler是JDK動態(tài)代理的核心,生成的代理對象的方法調(diào)用都會委托到InvocationHandler.invoke()方法。而通過JdkDynamicAopProxy的簽名我們可以看到這個類其實也實現(xiàn)了InvocationHandler,下面我們就通過分析這個類中實現(xiàn)的invoke()方法來具體看下Spring AOP是如何織入切面的。

    Servlet 工作原理

    Servlet 工作原理解析

    從 Servlet 容器說起

    前面說了 Servlet 容器作為一個獨立發(fā)展的標(biāo)準(zhǔn)化產(chǎn)品,目前它的種類很多,但是它們都有自己的市場定位,很難說誰優(yōu)誰劣,各有特點。例如現(xiàn)在比較流行的 Jetty,在定制化和移動領(lǐng)域有不錯的發(fā)展,我們這里還是以大家最為熟悉 Tomcat 為例來介紹 Servlet 容器如何管理 Servlet。Tomcat 本身也很復(fù)雜,我們只從 Servlet 與 Servlet 容器的接口部分開始介紹,關(guān)于 Tomcat 的詳細介紹可以參考我的另外一篇文章《 Tomcat 系統(tǒng)架構(gòu)與模式設(shè)計分析》。

    Tomcat 的容器等級中,Context 容器是直接管理 Servlet 在容器中的包裝類 Wrapper,所以 Context 容器如何運行將直接影響 Servlet 的工作方式。

    圖 1 . Tomcat 容器模型

    從上圖可以看出 Tomcat 的容器分為四個等級,真正管理 Servlet 的容器是 Context 容器,一個 Context 對應(yīng)一個 Web 工程,在 Tomcat 的配置文件中可以很容易發(fā)現(xiàn)這一點,如下:

    清單 1 Context 配置參數(shù)
    <Context path="/projectOne " docBase="D:\projects\projectOne" reloadable="true" />

    下面詳細介紹一下 Tomcat 解析 Context 容器的過程,包括如何構(gòu)建 Servlet 的過程。

    Servlet 容器的啟動過程

    Tomcat7 也開始支持嵌入式功能,增加了一個啟動類 org.apache.catalina.startup.Tomcat。創(chuàng)建一個實例對象并調(diào)用 start 方法就可以很容易啟動 Tomcat,我們還可以通過這個對象來增加和修改 Tomcat 的配置參數(shù),如可以動態(tài)增加 Context、Servlet 等。下面我們就利用這個 Tomcat 類來管理新增的一個 Context 容器,我們就選擇 Tomcat7 自帶的 examples Web 工程,并看看它是如何加到這個 Context 容器中的。

    清單 2 . 給 Tomcat 增加一個 Web 工程
    Tomcat tomcat = getTomcatInstance(); File appDir = new File(getBuildDirectory(), "webapps/examples"); tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath()); tomcat.start(); ByteChunk res = getUrl("http://localhost:" + getPort() + "/examples/servlets/servlet/HelloWorldExample"); assertTrue(res.toString().indexOf("<h1>Hello World!</h1>") > 0);

    清單 1 的代碼是創(chuàng)建一個 Tomcat 實例并新增一個 Web 應(yīng)用,然后啟動 Tomcat 并調(diào)用其中的一個 HelloWorldExample Servlet,看有沒有正確返回預(yù)期的數(shù)據(jù)。

    Tomcat 的 addWebapp 方法的代碼如下:

    清單 3 .Tomcat.addWebapp
    public Context addWebapp(Host host, String url, String path) { silence(url); Context ctx = new StandardContext(); ctx.setPath( url ); ctx.setDocBase(path); if (defaultRealm == null) { initSimpleAuth(); } ctx.setRealm(defaultRealm); ctx.addLifecycleListener(new DefaultWebXmlListener()); ContextConfig ctxCfg = new ContextConfig(); ctx.addLifecycleListener(ctxCfg); ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML"); if (host == null) { getHost().addChild(ctx); } else { host.addChild(ctx); } return ctx; }

    前面已經(jīng)介紹了一個 Web 應(yīng)用對應(yīng)一個 Context 容器,也就是 Servlet 運行時的 Servlet 容器,添加一個 Web 應(yīng)用時將會創(chuàng)建一個 StandardContext 容器,并且給這個 Context 容器設(shè)置必要的參數(shù),url 和 path 分別代表這個應(yīng)用在 Tomcat 中的訪問路徑和這個應(yīng)用實際的物理路徑,這個兩個參數(shù)與清單 1 中的兩個參數(shù)是一致的。其中最重要的一個配置是 ContextConfig,這個類將會負責(zé)整個 Web 應(yīng)用配置的解析工作,后面將會詳細介紹。最后將這個 Context 容器加到父容器 Host 中。

    接下去將會調(diào)用 Tomcat 的 start 方法啟動 Tomcat,如果你清楚 Tomcat 的系統(tǒng)架構(gòu),你會容易理解 Tomcat 的啟動邏輯,Tomcat 的啟動邏輯是基于觀察者模式設(shè)計的,所有的容器都會繼承 Lifecycle 接口,它管理者容器的整個生命周期,所有容器的的修改和狀態(tài)的改變都會由它去通知已經(jīng)注冊的觀察者(Listener),關(guān)于這個設(shè)計模式可以參考《 Tomcat 的系統(tǒng)架構(gòu)與設(shè)計模式,第二部分:設(shè)計模式》。Tomcat 啟動的時序圖可以用圖 2 表示。

    圖 2. Tomcat 主要類的啟動時序圖(查看大圖)

    上圖描述了 Tomcat 啟動過程中,主要類之間的時序關(guān)系,下面我們將會重點關(guān)注添加 examples 應(yīng)用所對應(yīng)的 StandardContext 容器的啟動過程。

    當(dāng) Context 容器初始化狀態(tài)設(shè)為 init 時,添加在 Contex 容器的 Listener 將會被調(diào)用。ContextConfig 繼承了 LifecycleListener 接口,它是在調(diào)用清單 3 時被加入到 StandardContext 容器中。ContextConfig 類會負責(zé)整個 Web 應(yīng)用的配置文件的解析工作。

    ContextConfig 的 init 方法將會主要完成以下工作:

  • 創(chuàng)建用于解析 xml 配置文件的 contextDigester 對象
  • 讀取默認(rèn) context.xml 配置文件,如果存在解析它
  • 讀取默認(rèn) Host 配置文件,如果存在解析它
  • 讀取默認(rèn) Context 自身的配置文件,如果存在解析它
  • 設(shè)置 Context 的 DocBase
  • ContextConfig 的 init 方法完成后,Context 容器的會執(zhí)行 startInternal 方法,這個方法啟動邏輯比較復(fù)雜,主要包括如下幾個部分:

  • 創(chuàng)建讀取資源文件的對象
  • 創(chuàng)建 ClassLoader 對象
  • 設(shè)置應(yīng)用的工作目錄
  • 啟動相關(guān)的輔助類如:logger、realm、resources 等
  • 修改啟動狀態(tài),通知感興趣的觀察者(Web 應(yīng)用的配置)
  • 子容器的初始化
  • 獲取 ServletContext 并設(shè)置必要的參數(shù)
  • 初始化“l(fā)oad on startup”的 Servlet
  • Web 應(yīng)用的初始化工作

    Web 應(yīng)用的初始化工作是在 ContextConfig 的 configureStart 方法中實現(xiàn)的,應(yīng)用的初始化主要是要解析 web.xml 文件,這個文件描述了一個 Web 應(yīng)用的關(guān)鍵信息,也是一個 Web 應(yīng)用的入口。

    Tomcat 首先會找 globalWebXml 這個文件的搜索路徑是在 engine 的工作目錄下尋找以下兩個文件中的任一個 org/apache/catalin/startup/NO_DEFAULT_XML 或 conf/web.xml。接著會找 hostWebXml 這個文件可能會在 System.getProperty("catalina.base")/conf/${EngineName}/${HostName}/web.xml.default,接著尋找應(yīng)用的配置文件 examples/WEB-INF/web.xml。web.xml 文件中的各個配置項將會被解析成相應(yīng)的屬性保存在 WebXml 對象中。如果當(dāng)前應(yīng)用支持 Servlet3.0,解析還將完成額外 9 項工作,這個額外的 9 項工作主要是為 Servlet3.0 新增的特性,包括 jar 包中的 META-INF/web-fragment.xml 的解析以及對 annotations 的支持。

    接下去將會將 WebXml 對象中的屬性設(shè)置到 Context 容器中,這里包括創(chuàng)建 Servlet 對象、filter、listener 等等。這段代碼在 WebXml 的 configureContext 方法中。下面是解析 Servlet 的代碼片段:

    清單 4. 創(chuàng)建 Wrapper 實例
    for (ServletDef servlet : servlets.values()) { Wrapper wrapper = context.createWrapper(); String jspFile = servlet.getJspFile(); if (jspFile != null) { wrapper.setJspFile(jspFile); } if (servlet.getLoadOnStartup() != null) { wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); } if (servlet.getEnabled() != null) { wrapper.setEnabled(servlet.getEnabled().booleanValue()); } wrapper.setName(servlet.getServletName()); Map<String,String> params = servlet.getParameterMap(); for (Entry<String, String> entry : params.entrySet()) { wrapper.addInitParameter(entry.getKey(), entry.getValue()); } wrapper.setRunAs(servlet.getRunAs()); Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs(); for (SecurityRoleRef roleRef : roleRefs) { wrapper.addSecurityReference( roleRef.getName(), roleRef.getLink()); } wrapper.setServletClass(servlet.getServletClass()); MultipartDef multipartdef = servlet.getMultipartDef(); if (multipartdef != null) { if (multipartdef.getMaxFileSize() != null && multipartdef.getMaxRequestSize()!= null && multipartdef.getFileSizeThreshold() != null) { wrapper.setMultipartConfigElement(new MultipartConfigElement( multipartdef.getLocation(), Long.parseLong(multipartdef.getMaxFileSize()), Long.parseLong(multipartdef.getMaxRequestSize()), Integer.parseInt( multipartdef.getFileSizeThreshold()))); } else { wrapper.setMultipartConfigElement(new MultipartConfigElement( multipartdef.getLocation())); } } if (servlet.getAsyncSupported() != null) { wrapper.setAsyncSupported( servlet.getAsyncSupported().booleanValue()); } context.addChild(wrapper); }

    這段代碼清楚的描述了如何將 Servlet 包裝成 Context 容器中的 StandardWrapper,這里有個疑問,為什么要將 Servlet 包裝成 StandardWrapper 而不直接是 Servlet 對象。這里 StandardWrapper 是 Tomcat 容器中的一部分,它具有容器的特征,而 Servlet 為了一個獨立的 web 開發(fā)標(biāo)準(zhǔn),不應(yīng)該強耦合在 Tomcat 中。

    除了將 Servlet 包裝成 StandardWrapper 并作為子容器添加到 Context 中,其它的所有 web.xml 屬性都被解析到 Context 中,所以說 Context 容器才是真正運行 Servlet 的 Servlet 容器。一個 Web 應(yīng)用對應(yīng)一個 Context 容器,容器的配置屬性由應(yīng)用的 web.xml 指定,這樣我們就能理解 web.xml 到底起到什么作用了。

    回頁首

    創(chuàng)建 Servlet 實例

    前面已經(jīng)完成了 Servlet 的解析工作,并且被包裝成 StandardWrapper 添加在 Context 容器中,但是它仍然不能為我們工作,它還沒有被實例化。下面我們將介紹 Servlet 對象是如何創(chuàng)建的,以及如何被初始化的。

    創(chuàng)建 Servlet 對象

    如果 Servlet 的 load-on-startup 配置項大于 0,那么在 Context 容器啟動的時候就會被實例化,前面提到在解析配置文件時會讀取默認(rèn)的 globalWebXml,在 conf 下的 web.xml 文件中定義了一些默認(rèn)的配置項,其定義了兩個 Servlet,分別是:org.apache.catalina.servlets.DefaultServlet 和 org.apache.jasper.servlet.JspServlet 它們的 load-on-startup 分別是 1 和 3,也就是當(dāng) Tomcat 啟動時這兩個 Servlet 就會被啟動。

    創(chuàng)建 Servlet 實例的方法是從 Wrapper. loadServlet 開始的。loadServlet 方法要完成的就是獲取 servletClass 然后把它交給 InstanceManager 去創(chuàng)建一個基于 servletClass.class 的對象。如果這個 Servlet 配置了 jsp-file,那么這個 servletClass 就是 conf/web.xml 中定義的 org.apache.jasper.servlet.JspServlet 了。

    創(chuàng)建 Servlet 對象的相關(guān)類結(jié)構(gòu)圖如下:

    圖 3. 創(chuàng)建 Servlet 對象的相關(guān)類結(jié)構(gòu)

    初始化 Servlet

    初始化 Servlet 在 StandardWrapper 的 initServlet 方法中,這個方法很簡單就是調(diào)用 Servlet 的 init 的方法,同時把包裝了 StandardWrapper 對象的 StandardWrapperFacade 作為 ServletConfig 傳給 Servlet。Tomcat 容器為何要傳 StandardWrapperFacade 給 Servlet 對象將在后面做詳細解析。

    如果該 Servlet 關(guān)聯(lián)的是一個 jsp 文件,那么前面初始化的就是 JspServlet,接下去會模擬一次簡單請求,請求調(diào)用這個 jsp 文件,以便編譯這個 jsp 文件為 class,并初始化這個 class。

    這樣 Servlet 對象就初始化完成了,事實上 Servlet 從被 web.xml 中解析到完成初始化,這個過程非常復(fù)雜,中間有很多過程,包括各種容器狀態(tài)的轉(zhuǎn)化引起的監(jiān)聽事件的觸發(fā)、各種訪問權(quán)限的控制和一些不可預(yù)料的錯誤發(fā)生的判斷行為等等。我們這里只抓了一些關(guān)鍵環(huán)節(jié)進行闡述,試圖讓大家有個總體脈絡(luò)。

    下面是這個過程的一個完整的時序圖,其中也省略了一些細節(jié)。

    圖 4. 初始化 Servlet 的時序圖(查看大圖)

    回頁首

    Servlet 體系結(jié)構(gòu)

    我們知道 Java Web 應(yīng)用是基于 Servlet 規(guī)范運轉(zhuǎn)的,那么 Servlet 本身又是如何運轉(zhuǎn)的呢?為何要設(shè)計這樣的體系結(jié)構(gòu)。

    圖 5.Servlet 頂層類關(guān)聯(lián)圖

    從上圖可以看出 Servlet 規(guī)范就是基于這幾個類運轉(zhuǎn)的,與 Servlet 主動關(guān)聯(lián)的是三個類,分別是 ServletConfig、ServletRequest 和 ServletResponse。這三個類都是通過容器傳遞給 Servlet 的,其中 ServletConfig 是在 Servlet 初始化時就傳給 Servlet 了,而后兩個是在請求達到時調(diào)用 Servlet 時傳遞過來的。我們很清楚 ServletRequest 和 ServletResponse 在 Servlet 運行的意義,但是 ServletConfig 和 ServletContext 對 Servlet 有何價值?仔細查看 ServletConfig 接口中聲明的方法發(fā)現(xiàn),這些方法都是為了獲取這個 Servlet 的一些配置屬性,而這些配置屬性可能在 Servlet 運行時被用到。而 ServletContext 又是干什么的呢? Servlet 的運行模式是一個典型的“握手型的交互式”運行模式。所謂“握手型的交互式”就是兩個模塊為了交換數(shù)據(jù)通常都會準(zhǔn)備一個交易場景,這個場景一直跟隨個這個交易過程直到這個交易完成為止。這個交易場景的初始化是根據(jù)這次交易對象指定的參數(shù)來定制的,這些指定參數(shù)通常就會是一個配置類。所以對號入座,交易場景就由 ServletContext 來描述,而定制的參數(shù)集合就由 ServletConfig 來描述。而 ServletRequest 和 ServletResponse 就是要交互的具體對象了,它們通常都是作為運輸工具來傳遞交互結(jié)果。

    ServletConfig 是在 Servlet init 時由容器傳過來的,那么 ServletConfig 到底是個什么對象呢?

    下圖是 ServletConfig 和 ServletContext 在 Tomcat 容器中的類關(guān)系圖。

    圖 6. ServletConfig 在容器中的類關(guān)聯(lián)圖

    上圖可以看出 StandardWrapper 和 StandardWrapperFacade 都實現(xiàn)了 ServletConfig 接口,而 StandardWrapperFacade 是 StandardWrapper 門面類。所以傳給 Servlet 的是 StandardWrapperFacade 對象,這個類能夠保證從 StandardWrapper 中拿到 ServletConfig 所規(guī)定的數(shù)據(jù),而又不把 ServletConfig 不關(guān)心的數(shù)據(jù)暴露給 Servlet。

    同樣 ServletContext 也與 ServletConfig 有類似的結(jié)構(gòu),Servlet 中能拿到的 ServletContext 的實際對象也是 ApplicationContextFacade 對象。ApplicationContextFacade 同樣保證 ServletContex 只能從容器中拿到它該拿的數(shù)據(jù),它們都起到對數(shù)據(jù)的封裝作用,它們使用的都是門面設(shè)計模式。

    通過 ServletContext 可以拿到 Context 容器中一些必要信息,比如應(yīng)用的工作路徑,容器支持的 Servlet 最小版本等。

    Servlet 中定義的兩個 ServletRequest 和 ServletResponse 它們實際的對象又是什么呢?,我們在創(chuàng)建自己的 Servlet 類時通常使用的都是 HttpServletRequest 和 HttpServletResponse,它們繼承了 ServletRequest 和 ServletResponse。為何 Context 容器傳過來的 ServletRequest、ServletResponse 可以被轉(zhuǎn)化為 HttpServletRequest 和 HttpServletResponse 呢?

    圖 7.Request 相關(guān)類結(jié)構(gòu)圖

    上圖是 Tomcat 創(chuàng)建的 Request 和 Response 的類結(jié)構(gòu)圖。Tomcat 一接受到請求首先將會創(chuàng)建 org.apache.coyote.Request 和 org.apache.coyote.Response,這兩個類是 Tomcat 內(nèi)部使用的描述一次請求和相應(yīng)的信息類它們是一個輕量級的類,它們作用就是在服務(wù)器接收到請求后,經(jīng)過簡單解析將這個請求快速的分配給后續(xù)線程去處理,所以它們的對象很小,很容易被 JVM 回收。接下去當(dāng)交給一個用戶線程去處理這個請求時又創(chuàng)建 org.apache.catalina.connector. Request 和 org.apache.catalina.connector. Response 對象。這兩個對象一直穿越整個 Servlet 容器直到要傳給 Servlet,傳給 Servlet 的是 Request 和 Response 的門面類 RequestFacade 和 RequestFacade,這里使用門面模式與前面一樣都是基于同樣的目的——封裝容器中的數(shù)據(jù)。一次請求對應(yīng)的 Request 和 Response 的類轉(zhuǎn)化如下圖所示:

    圖 8.Request 和 Response 的轉(zhuǎn)變過程

    回頁首

    Servlet 如何工作

    我們已經(jīng)清楚了 Servlet 是如何被加載的、Servlet 是如何被初始化的,以及 Servlet 的體系結(jié)構(gòu),現(xiàn)在的問題就是它是如何被調(diào)用的。

    當(dāng)用戶從瀏覽器向服務(wù)器發(fā)起一個請求,通常會包含如下信息:http://hostname: port /contextpath/servletpath,hostname 和 port 是用來與服務(wù)器建立 TCP 連接,而后面的 URL 才是用來選擇服務(wù)器中那個子容器服務(wù)用戶的請求。那服務(wù)器是如何根據(jù)這個 URL 來達到正確的 Servlet 容器中的呢?

    Tomcat7.0 中這件事很容易解決,因為這種映射工作有專門一個類來完成的,這個就是 org.apache.tomcat.util.http.mapper,這個類保存了 Tomcat 的 Container 容器中的所有子容器的信息,當(dāng) org.apache.catalina.connector. Request 類在進入 Container 容器之前,mapper 將會根據(jù)這次請求的 hostnane 和 contextpath 將 host 和 context 容器設(shè)置到 Request 的 mappingData 屬性中。所以當(dāng) Request 進入 Container 容器之前,它要訪問那個子容器這時就已經(jīng)確定了。

    圖 9.Request 的 Mapper 類關(guān)系圖

    可能你有疑問,mapper 中怎么會有容器的完整關(guān)系,這要回到圖 2 中 19 步 MapperListener 類的初始化過程,下面是 MapperListener 的 init 方法代碼 :

    清單 5. MapperListener.init
    public void init() { findDefaultHost(); Engine engine = (Engine) connector.getService().getContainer(); engine.addContainerListener(this); Container[] conHosts = engine.findChildren(); for (Container conHost : conHosts) { Host host = (Host) conHost; if (!LifecycleState.NEW.equals(host.getState())) { host.addLifecycleListener(this); registerHost(host); } } }

    這段代碼的作用就是將 MapperListener 類作為一個監(jiān)聽者加到整個 Container 容器中的每個子容器中,這樣只要任何一個容器發(fā)生變化,MapperListener 都將會被通知,相應(yīng)的保存容器關(guān)系的 MapperListener 的 mapper 屬性也會修改。for 循環(huán)中就是將 host 及下面的子容器注冊到 mapper 中。

    圖 10.Request 在容器中的路由圖

    上圖描述了一次 Request 請求是如何達到最終的 Wrapper 容器的,我們現(xiàn)正知道了請求是如何達到正確的 Wrapper 容器,但是請求到達最終的 Servlet 還要完成一些步驟,必須要執(zhí)行 Filter 鏈,以及要通知你在 web.xml 中定義的 listener。

    接下去就要執(zhí)行 Servlet 的 service 方法了,通常情況下,我們自己定義的 servlet 并不是直接去實現(xiàn) javax.servlet.servlet 接口,而是去繼承更簡單的 HttpServlet 類或者 GenericServlet 類,我們可以有選擇的覆蓋相應(yīng)方法去實現(xiàn)我們要完成的工作。

    Servlet 的確已經(jīng)能夠幫我們完成所有的工作了,但是現(xiàn)在的 web 應(yīng)用很少有直接將交互全部頁面都用 servlet 來實現(xiàn),而是采用更加高效的 MVC 框架來實現(xiàn)。這些 MVC 框架基本的原理都是將所有的請求都映射到一個 Servlet,然后去實現(xiàn) service 方法,這個方法也就是 MVC 框架的入口。

    當(dāng) Servlet 從 Servlet 容器中移除時,也就表明該 Servlet 的生命周期結(jié)束了,這時 Servlet 的 destroy 方法將被調(diào)用,做一些掃尾工作。

    回頁首

    Session 與 Cookie

    前面我們已經(jīng)說明了 Servlet 如何被調(diào)用,我們基于 Servlet 來構(gòu)建應(yīng)用程序,那么我們能從 Servlet 獲得哪些數(shù)據(jù)信息呢?

    Servlet 能夠給我們提供兩部分?jǐn)?shù)據(jù),一個是在 Servlet 初始化時調(diào)用 init 方法時設(shè)置的 ServletConfig,這個類基本上含有了 Servlet 本身和 Servlet 所運行的 Servlet 容器中的基本信息。根據(jù)前面的介紹 ServletConfig 的實際對象是 StandardWrapperFacade,到底能獲得哪些容器信息可以看看這類提供了哪些接口。還有一部分?jǐn)?shù)據(jù)是由 ServletRequest 類提供,它的實際對象是 RequestFacade,從提供的方法中發(fā)現(xiàn)主要是描述這次請求的 HTTP 協(xié)議的信息。所以要掌握 Servlet 的工作方式必須要很清楚 HTTP 協(xié)議,如果你還不清楚趕緊去找一些參考資料。關(guān)于這一塊還有一個讓很多人迷惑的 Session 與 Cookie。

    Session 與 Cookie 不管是對 Java Web 的熟練使用者還是初學(xué)者來說都是一個令人頭疼的東西。Session 與 Cookie 的作用都是為了保持訪問用戶與后端服務(wù)器的交互狀態(tài)。它們有各自的優(yōu)點也有各自的缺陷。然而具有諷刺意味的是它們優(yōu)點和它們的使用場景又是矛盾的,例如使用 Cookie 來傳遞信息時,隨著 Cookie 個數(shù)的增多和訪問量的增加,它占用的網(wǎng)絡(luò)帶寬也很大,試想假如 Cookie 占用 200 個字節(jié),如果一天的 PV 有幾億的時候,它要占用多少帶寬。所以大訪問量的時候希望用 Session,但是 Session 的致命弱點是不容易在多臺服務(wù)器之間共享,所以這也限制了 Session 的使用。

    不管 Session 和 Cookie 有什么不足,我們還是要用它們。下面詳細講一下,Session 如何基于 Cookie 來工作。實際上有三種方式能可以讓 Session 正常工作:

  • 基于 URL Path Parameter,默認(rèn)就支持
  • 基于 Cookie,如果你沒有修改 Context 容器個 cookies 標(biāo)識的話,默認(rèn)也是支持的
  • 基于 SSL,默認(rèn)不支持,只有 connector.getAttribute("SSLEnabled") 為 TRUE 時才支持
  • 第一種情況下,當(dāng)瀏覽器不支持 Cookie 功能時,瀏覽器會將用戶的 SessionCookieName 重寫到用戶請求的 URL 參數(shù)中,它的傳遞格式如 /path/Servlet;name=value;name2=value2? Name3=value3,其中“Servlet;”后面的 K-V 對就是要傳遞的 Path Parameters,服務(wù)器會從這個 Path Parameters 中拿到用戶配置的 SessionCookieName。關(guān)于這個 SessionCookieName,如果你在 web.xml 中配置 session-config 配置項的話,其 cookie-config 下的 name 屬性就是這個 SessionCookieName 值,如果你沒有配置 session-config 配置項,默認(rèn)的 SessionCookieName 就是大家熟悉的“JSESSIONID”。接著 Request 根據(jù)這個 SessionCookieName 到 Parameters 拿到 Session ID 并設(shè)置到 request.setRequestedSessionId 中。

    請注意如果客戶端也支持 Cookie 的話,Tomcat 仍然會解析 Cookie 中的 Session ID,并會覆蓋 URL 中的 Session ID。

    如果是第三種情況的話將會根據(jù) javax.servlet.request.ssl_session 屬性值設(shè)置 Session ID。

    有了 Session ID 服務(wù)器端就可以創(chuàng)建 HttpSession 對象了,第一次觸發(fā)是通過 request. getSession() 方法,如果當(dāng)前的 Session ID 還沒有對應(yīng)的 HttpSession 對象那么就創(chuàng)建一個新的,并將這個對象加到 org.apache.catalina. Manager 的 sessions 容器中保存,Manager 類將管理所有 Session 的生命周期,Session 過期將被回收,服務(wù)器關(guān)閉,Session 將被序列化到磁盤等。只要這個 HttpSession 對象存在,用戶就可以根據(jù) Session ID 來獲取到這個對象,也就達到了狀態(tài)的保持。

    圖 11.Session 相關(guān)類圖

    上從圖中可以看出從 request.getSession 中獲取的 HttpSession 對象實際上是 StandardSession 對象的門面對象,這與前面的 Request 和 Servlet 是一樣的原理。下圖是 Session 工作的時序圖:

    圖 12.Session 工作的時序圖(查看大圖)

    還有一點與 Session 關(guān)聯(lián)的 Cookie 與其它 Cookie 沒有什么不同,這個配置的配置可以通過 web.xml 中的 session-config 配置項來指定。

    回頁首

    Servlet 中的 Listener

    整個 Tomcat 服務(wù)器中 Listener 使用的非常廣泛,它是基于觀察者模式設(shè)計的,Listener 的設(shè)計對開發(fā) Servlet 應(yīng)用程序提供了一種快捷的手段,能夠方便的從另一個縱向維度控制程序和數(shù)據(jù)。目前 Servlet 中提供了 5 種兩類事件的觀察者接口,它們分別是:4 個 EventListeners 類型的,ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttributeListener 和 2 個 LifecycleListeners 類型的,ServletContextListener、HttpSessionListener。如下圖所示:

    圖 13.Servlet 中的 Listener(查看大圖)

    它們基本上涵蓋了整個 Servlet 生命周期中,你感興趣的每種事件。這些 Listener 的實現(xiàn)類可以配置在 web.xml 中的 <listener> 標(biāo)簽中。當(dāng)然也可以在應(yīng)用程序中動態(tài)添加 Listener,需要注意的是 ServletContextListener 在容器啟動之后就不能再添加新的,因為它所監(jiān)聽的事件已經(jīng)不會再出現(xiàn)。掌握這些 Listener 的使用,能夠讓我們的程序設(shè)計的更加靈活

    Java NIO和IO的區(qū)別

    下表總結(jié)了Java NIO和IO之間的主要差別,我會更詳細地描述表中每部分的差異。

    復(fù)制代碼代碼如下:

    IO NIO
    面向流 面向緩沖
    阻塞IO 非阻塞IO
    無 選擇器

    面向流與面向緩沖

    Java NIO和IO之間第一個最大的區(qū)別是,IO是面向流的,NIO是面向緩沖區(qū)的。 Java IO面向流意味著每次從流中讀一個或多個字節(jié),直至讀取所有字節(jié),它們沒有被緩存在任何地方。此外,它不能前后移動流中的數(shù)據(jù)。如果需要前后移動從流中讀取的數(shù)據(jù),需要先將它緩存到一個緩沖區(qū)。 Java NIO的緩沖導(dǎo)向方法略有不同。數(shù)據(jù)讀取到一個它稍后處理的緩沖區(qū),需要時可在緩沖區(qū)中前后移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩沖區(qū)中包含所有您需要處理的數(shù)據(jù)。而且,需確保當(dāng)更多的數(shù)據(jù)讀入緩沖區(qū)時,不要覆蓋緩沖區(qū)里尚未處理的數(shù)據(jù)。

    阻塞與非阻塞IO

    Java IO的各種流是阻塞的。這意味著,當(dāng)一個線程調(diào)用read() 或 write()時,該線程被阻塞,直到有一些數(shù)據(jù)被讀取,或數(shù)據(jù)完全寫入。該線程在此期間不能再干任何事情了。 Java NIO的非阻塞模式,使一個線程從某通道發(fā)送請求讀取數(shù)據(jù),但是它僅能得到目前可用的數(shù)據(jù),如果目前沒有數(shù)據(jù)可用時,就什么都不會獲取。而不是保持線程阻塞,所以直至數(shù)據(jù)變的可以讀取之前,該線程可以繼續(xù)做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數(shù)據(jù)到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閑時間用于在其它通道上執(zhí)行IO操作,所以一個單獨的線程現(xiàn)在可以管理多個輸入和輸出通道(channel)。

    選擇器(Selectors)

    Java NIO的選擇器允許一個單獨的線程來監(jiān)視多個輸入通道,你可以注冊多個通道使用一個選擇器,然后使用一個單獨的線程來“選擇”通道:這些通道里已經(jīng)有可以處理的輸入,或者選擇已準(zhǔn)備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。

    NIO和IO如何影響應(yīng)用程序的設(shè)計

    無論您選擇IO或NIO工具箱,可能會影響您應(yīng)用程序設(shè)計的以下幾個方面:

    1.對NIO或IO類的API調(diào)用。
    2.數(shù)據(jù)處理。
    3.用來處理數(shù)據(jù)的線程數(shù)。

    API調(diào)用

    當(dāng)然,使用NIO的API調(diào)用時看起來與使用IO時有所不同,但這并不意外,因為并不是僅從一個InputStream逐字節(jié)讀取,而是數(shù)據(jù)必須先讀入緩沖區(qū)再處理。

    數(shù)據(jù)處理

    使用純粹的NIO設(shè)計相較IO設(shè)計,數(shù)據(jù)處理也受到影響。

    在IO設(shè)計中,我們從InputStream或 Reader逐字節(jié)讀取數(shù)據(jù)。假設(shè)你正在處理一基于行的文本數(shù)據(jù)流,例如:

    復(fù)制代碼代碼如下:

    Name: Anna
    Age: 25
    Email:?anna@mailserver.com
    Phone: 1234567890

    該文本行的流可以這樣處理:

    復(fù)制代碼代碼如下:

    BufferedReader reader = new BufferedReader(new InputStreamReader(input));
    String nameLine = reader.readLine();
    String ageLine = reader.readLine();
    String emailLine = reader.readLine();
    String phoneLine = reader.readLine();

    請注意處理狀態(tài)由程序執(zhí)行多久決定。換句話說,一旦reader.readLine()方法返回,你就知道肯定文本行就已讀完, readline()阻塞直到整行讀完,這就是原因。你也知道此行包含名稱;同樣,第二個readline()調(diào)用返回的時候,你知道這行包含年齡等。 正如你可以看到,該處理程序僅在有新數(shù)據(jù)讀入時運行,并知道每步的數(shù)據(jù)是什么。一旦正在運行的線程已處理過讀入的某些數(shù)據(jù),該線程不會再回退數(shù)據(jù)(大多如此)。下圖也說明了這條原則:

    img

    (Java IO: 從一個阻塞的流中讀數(shù)據(jù)) 而一個NIO的實現(xiàn)會有所不同,下面是一個簡單的例子:

    復(fù)制代碼代碼如下:

    ByteBuffer buffer = ByteBuffer.allocate(48);
    int bytesRead = inChannel.read(buffer);

    注意第二行,從通道讀取字節(jié)到ByteBuffer。當(dāng)這個方法調(diào)用返回時,你不知道你所需的所有數(shù)據(jù)是否在緩沖區(qū)內(nèi)。你所知道的是,該緩沖區(qū)包含一些字節(jié),這使得處理有點困難。
    假設(shè)第一次 read(buffer)調(diào)用后,讀入緩沖區(qū)的數(shù)據(jù)只有半行,例如,“Name:An”,你能處理數(shù)據(jù)嗎?顯然不能,需要等待,直到整行數(shù)據(jù)讀入緩存,在此之前,對數(shù)據(jù)的任何處理毫無意義。

    所以,你怎么知道是否該緩沖區(qū)包含足夠的數(shù)據(jù)可以處理呢?好了,你不知道。發(fā)現(xiàn)的方法只能查看緩沖區(qū)中的數(shù)據(jù)。其結(jié)果是,在你知道所有數(shù)據(jù)都在緩沖區(qū)里之前,你必須檢查幾次緩沖區(qū)的數(shù)據(jù)。這不僅效率低下,而且可以使程序設(shè)計方案雜亂不堪。例如:

    復(fù)制代碼代碼如下:

    ByteBuffer buffer = ByteBuffer.allocate(48);
    int bytesRead = inChannel.read(buffer);
    while(! bufferFull(bytesRead) ) {
    bytesRead = inChannel.read(buffer);
    }

    bufferFull()方法必須跟蹤有多少數(shù)據(jù)讀入緩沖區(qū),并返回真或假,這取決于緩沖區(qū)是否已滿。換句話說,如果緩沖區(qū)準(zhǔn)備好被處理,那么表示緩沖區(qū)滿了。

    bufferFull()方法掃描緩沖區(qū),但必須保持在bufferFull()方法被調(diào)用之前狀態(tài)相同。如果沒有,下一個讀入緩沖區(qū)的數(shù)據(jù)可能無法讀到正確的位置。這是不可能的,但卻是需要注意的又一問題。

    如果緩沖區(qū)已滿,它可以被處理。如果它不滿,并且在你的實際案例中有意義,你或許能處理其中的部分?jǐn)?shù)據(jù)。但是許多情況下并非如此。下圖展示了“緩沖區(qū)數(shù)據(jù)循環(huán)就緒”:

    img
    3) 用來處理數(shù)據(jù)的線程數(shù)

    ?

    NIO可讓您只使用一個(或幾個)單線程管理多個通道(網(wǎng)絡(luò)連接或文件),但付出的代價是解析數(shù)據(jù)可能會比從一個阻塞流中讀取數(shù)據(jù)更復(fù)雜。

    如果需要管理同時打開的成千上萬個連接,這些連接每次只是發(fā)送少量的數(shù)據(jù),例如聊天服務(wù)器,實現(xiàn)NIO的服務(wù)器可能是一個優(yōu)勢。同樣,如果你需要維持許多打開的連接到其他計算機上,如P2P網(wǎng)絡(luò)中,使用一個單獨的線程來管理你所有出站連接,可能是一個優(yōu)勢。一個線程多個連接的設(shè)計方案如

    img

    Java NIO: 單線程管理多個連接

    如果你有少量的連接使用非常高的帶寬,一次發(fā)送大量的數(shù)據(jù),也許典型的IO服務(wù)器實現(xiàn)可能非常契合。下圖說明了一個典型的IO服務(wù)器設(shè)計:

    img

    Java IO: 一個典型的IO服務(wù)器設(shè)計- 一個連接通過一個線程處理

    Java中堆內(nèi)存和棧內(nèi)存區(qū)別

    Java把內(nèi)存分成兩種,一種叫做棧內(nèi)存,一種叫做堆內(nèi)存

    在函數(shù)中定義的一些基本類型的變量和對象的引用變量都是在函數(shù)的棧內(nèi)存中分配。當(dāng)在一段代碼塊中定義一個變量時,java就在棧中為這個變量分配內(nèi)存空間,當(dāng)超過變量的作用域后,java會自動釋放掉為該變量分配的內(nèi)存空間,該內(nèi)存空間可以立刻被另作他用。

    堆內(nèi)存用于存放由new創(chuàng)建的對象和數(shù)組。在堆中分配的內(nèi)存,由java虛擬機自動垃圾回收器來管理。在堆中產(chǎn)生了一個數(shù)組或者對象后,還可以在棧中定義一個特殊的變量,這個變量的取值等于數(shù)組或者對象在堆內(nèi)存中的首地址,在棧中的這個特殊的變量就變成了數(shù)組或者對象的引用變量,以后就可以在程序中使用棧內(nèi)存中的引用變量來訪問堆中的數(shù)組或者對象,引用變量相當(dāng)于為數(shù)組或者對象起的一個別名,或者代號。

    引用變量是普通變量,定義時在棧中分配內(nèi)存,引用變量在程序運行到作用域外釋放。而數(shù)組&對象本身在堆中分配,即使程序運行到使用new產(chǎn)生數(shù)組和對象的語句所在地代碼塊之外,數(shù)組和對象本身占用的堆內(nèi)存也不會被釋放,數(shù)組和對象在沒有引用變量指向它的時候,才變成垃圾,不能再被使用,但是仍然占著內(nèi)存,在隨后的一個不確定的時間被垃圾回收器釋放掉。這個也是java比較占內(nèi)存的主要原因,********實際上,棧中的變量指向堆內(nèi)存中的變量,這就是 Java 中的指針!


    java中內(nèi)存分配策略及堆和棧的比較
      1 內(nèi)存分配策略
      按照編譯原理的觀點,程序運行時的內(nèi)存分配有三種策略,分別是靜態(tài)的,棧式的,和堆式的.
      靜態(tài)存儲分配是指在編譯時就能確定每個數(shù)據(jù)目標(biāo)在運行時刻的存儲空間需求,因而在編譯時就可以給他們分配固定的內(nèi)存空間.這種分配策略要求程序代碼中不允許有可變數(shù)據(jù)結(jié)構(gòu)(比如可變數(shù)組)的存在,也不允許有嵌套或者遞歸的結(jié)構(gòu)出現(xiàn),因為它們都會導(dǎo)致編譯程序無法計算準(zhǔn)確的存儲空間需求.
      棧式存儲分配也可稱為動態(tài)存儲分配,是由一個類似于堆棧的運行棧來實現(xiàn)的.和靜態(tài)存儲分配相反,在棧式存儲方案中,程序?qū)?shù)據(jù)區(qū)的需求在編譯時是完全未知的,只有到運行的時候才能夠知道,但是規(guī)定在運行中進入一個程序模塊時,必須知道該程序模塊所需的數(shù)據(jù)區(qū)大小才能夠為其分配內(nèi)存.和我們在數(shù)據(jù)結(jié)構(gòu)所熟知的棧一樣,棧式存儲分配按照先進后出的原則進行分配。
      靜態(tài)存儲分配要求在編譯時能知道所有變量的存儲要求,棧式存儲分配要求在過程的入口處必須知道所有的存儲要求,而堆式存儲分配則專門負責(zé)在編譯時或運行時模塊入口處都無法確定存儲要求的數(shù)據(jù)結(jié)構(gòu)的內(nèi)存分配,比如可變長度串和對象實例.堆由大片的可利用塊或空閑塊組成,堆中的內(nèi)存可以按照任意順序分配和釋放.
      2 堆和棧的比較
      上面的定義從編譯原理的教材中總結(jié)而來,除靜態(tài)存儲分配之外,都顯得很呆板和難以理解,下面撇開靜態(tài)存儲分配,集中比較堆和棧:
      從堆和棧的功能和作用來通俗的比較,堆主要用來存放對象的,棧主要是用來執(zhí)行程序的.而這種不同又主要是由于堆和棧的特點決定的:
      在編程中,例如C/C++中,所有的方法調(diào)用都是通過棧來進行的,所有的局部變量,形式參數(shù)都是從棧中分配內(nèi)存空間的。實際上也不是什么分配,只是從棧頂向上用就行,就好像工廠中的傳送帶(conveyor belt)一樣,Stack Pointer會自動指引你到放東西的位置,你所要做的只是把東西放下來就行.退出函數(shù)的時候,修改棧指針就可以把棧中的內(nèi)容銷毀.這樣的模式速度最快, 當(dāng)然要用來運行程序了.需要注意的是,在分配的時候,比如為一個即將要調(diào)用的程序模塊分配數(shù)據(jù)區(qū)時,應(yīng)事先知道這個數(shù)據(jù)區(qū)的大小,也就說是雖然分配是在程序運行時進行的,但是分配的大小多少是確定的,不變的,而這個"大小多少"是在編譯時確定的,不是在運行時.
      堆是應(yīng)用程序在運行的時候請求操作系統(tǒng)分配給自己內(nèi)存,由于從操作系統(tǒng)管理的內(nèi)存分配,所以在分配和銷毀時都要占用時間,因此用堆的效率非常低.但是堆的優(yōu)點在于,編譯器不必知道要從堆里分配多少存儲空間,也不必知道存儲的數(shù)據(jù)要在堆里停留多長的時間,因此,用堆保存數(shù)據(jù)時會得到更大的靈活性。事實上,面向?qū)ο蟮亩鄳B(tài)性,堆內(nèi)存分配是必不可少的,因為多態(tài)變量所需的存儲空間只有在運行時創(chuàng)建了對象之后才能確定.在C++中,要求創(chuàng)建一個對象時,只需用 new命令編制相關(guān)的代碼即可。執(zhí)行這些代碼時,會在堆里自動進行數(shù)據(jù)的保存.當(dāng)然,為達到這種靈活性,必然會付出一定的代價:在堆里分配存儲空間時會花掉更長的時間!這也正是導(dǎo)致我們剛才所說的效率低的原因,看來列寧同志說的好,人的優(yōu)點往往也是人的缺點,人的缺點往往也是人的優(yōu)點(暈~).
      3 JVM中的堆和棧
      JVM是基于堆棧的虛擬機.JVM為每個新創(chuàng)建的線程都分配一個堆棧.也就是說,對于一個Java程序來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態(tài)。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。
      我們知道,某個線程正在執(zhí)行的方法稱為此線程的當(dāng)前方法.我們可能不知道,當(dāng)前方法使用的幀稱為當(dāng)前幀。當(dāng)線程激活一個Java方法,JVM就會在線程的 Java堆棧里新壓入一個幀。這個幀自然成為了當(dāng)前幀.在此方法執(zhí)行期間,這個幀將用來保存參數(shù),局部變量,中間計算過程和其他數(shù)據(jù).這個幀在這里和編譯原理中的活動紀(jì)錄的概念是差不多的.
      從Java的這種分配機制來看,堆棧又可以這樣理解:堆棧(Stack)是操作系統(tǒng)在建立某個進程時或者線程(在支持多線程的操作系統(tǒng)中是線程)為這個線程建立的存儲區(qū)域,該區(qū)域具有先進后出的特性。
      每一個Java應(yīng)用都唯一對應(yīng)一個JVM實例,每一個實例唯一對應(yīng)一個堆。應(yīng)用程序在運行中所創(chuàng)建的所有類實例或數(shù)組都放在這個堆中,并由應(yīng)用所有的線程共享.跟C/C++不同,Java中分配堆內(nèi)存是自動初始化的。Java中所有對象的存儲空間都是在堆中分配的,但是這個對象的引用卻是在堆棧中分配,也就是說在建立一個對象時從兩個地方都分配內(nèi)存,在堆中分配的內(nèi)存實際建立這個對象,而在堆棧中分配的內(nèi)存只是一個指向這個堆對象的指針(引用)而已。
      Java 中的堆和棧
      Java把內(nèi)存劃分成兩種:一種是棧內(nèi)存,一種是堆內(nèi)存。
      在函數(shù)中定義的一些基本類型的變量和對象的引用變量都在函數(shù)的棧內(nèi)存中分配。
      當(dāng)在一段代碼塊定義一個變量時,Java就在棧中為這個變量分配內(nèi)存空間,當(dāng)超過變量的作用域后,Java會自動釋放掉為該變量所分配的內(nèi)存空間,該內(nèi)存空間可以立即被另作他用。
      堆內(nèi)存用來存放由new創(chuàng)建的對象和數(shù)組。
      在堆中分配的內(nèi)存,由Java虛擬機的自動垃圾回收器來管理。
      在堆中產(chǎn)生了一個數(shù)組或?qū)ο蠛?#xff0c;還可以在棧中定義一個特殊的變量,讓棧中這個變量的取值等于數(shù)組或?qū)ο笤诙褍?nèi)存中的首地址,棧中的這個變量就成了數(shù)組或?qū)ο蟮囊米兞俊?br />  引用變量就相當(dāng)于是為數(shù)組或?qū)ο笃鸬囊粋€名稱,以后就可以在程序中使用棧中的引用變量來訪問堆中的數(shù)組或?qū)ο蟆?br />  具體的說:
      棧與堆都是Java用來在Ram中存放數(shù)據(jù)的地方。與C++不同,Java自動管理棧和堆,程序員不能直接地設(shè)置棧或堆。
      Java的堆是一個運行時數(shù)據(jù)區(qū),類的(對象從中分配空間。這些對象通過new、newarray、anewarray和multianewarray等指令建立,它們不需要程序代碼來顯式的釋放。堆是由垃圾回收來負責(zé)的,堆的優(yōu)勢是可以動態(tài)地分配內(nèi)存大小,生存期也不必事先告訴編譯器,因為它是在運行時動態(tài)分配內(nèi)存的,Java的垃圾收集器會自動收走這些不再使用的數(shù)據(jù)。但缺點是,由于要在運行時動態(tài)分配內(nèi)存,存取速度較慢。
      棧的優(yōu)勢是,存取速度比堆要快,僅次于寄存器,棧數(shù)據(jù)可以共享。但缺點是,存在棧中的數(shù)據(jù)大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變量(,int, short, long, byte, float, double, boolean, char)和對象句柄。
      棧有一個很重要的特殊性,就是存在棧中的數(shù)據(jù)可以共享。假設(shè)我們同時定義:
      int a = 3;
      int b = 3;
      編譯器先處理int a = 3;首先它會在棧中創(chuàng)建一個變量為a的引用,然后查找棧中是否有3這個值,如果沒找到,就將3存放進來,然后將a指向3。接著處理int b = 3;在創(chuàng)建完b的引用變量后,因為在棧中已經(jīng)有3這個值,便將b直接指向3。這樣,就出現(xiàn)了a與b同時均指向3的情況。這時,如果再令a=4;那么編譯器會重新搜索棧中是否有4值,如果沒有,則將4存放進來,并令a指向4;如果已經(jīng)有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。要注意這種數(shù)據(jù)的共享與兩個對象的引用同時指向一個對象的這種共享是不同的,因為這種情況a的修改并不會影響到b, 它是由編譯器完成的,它有利于節(jié)省空間。而一個對象引用變量修改了這個對象的內(nèi)部狀態(tài),會影響到另一個對象引用變量

    反射講一講,主要是概念,都在哪需要反射機制,反射的性能,如何優(yōu)化

    反射機制的定義:

    是在運行狀態(tài)中,對于任意的一個類,都能夠知道這個類的所有屬性和方法,對任意一個對象都能夠通過反射機制調(diào)用一個類的任意方法,這種動態(tài)獲取類信息及動態(tài)調(diào)用類對象方法的功能稱為java的反射機制。

    反射的作用:

    1、動態(tài)地創(chuàng)建類的實例,將類綁定到現(xiàn)有的對象中,或從現(xiàn)有的對象中獲取類型。

    2、應(yīng)用程序需要在運行時從某個特定的程序集中載入一個特定的類

    如何保證RESTful API安全性

    友情鏈接:?如何設(shè)計好的RESTful API之安全性

    如何預(yù)防MySQL注入

    所謂SQL注入,就是通過把SQL命令插入到Web表單遞交或輸入域名或頁面請求的查詢字符串,最終達到欺騙服務(wù)器執(zhí)行惡意的SQL命令。

    我們永遠不要信任用戶的輸入,我們必須認(rèn)定用戶輸入的數(shù)據(jù)都是不安全的,我們都需要對用戶輸入的數(shù)據(jù)進行過濾處理。

    1.以下實例中,輸入的用戶名必須為字母、數(shù)字及下劃線的組合,且用戶名長度為 8 到 20 個字符之間:

    if (preg_match("/^\w{8,20}$/", $_GET['username'], $matches)) { $result = mysql_query("SELECT * FROM users WHERE username=$matches[0]"); } else { echo "username 輸入異常"; }

    讓我們看下在沒有過濾特殊字符時,出現(xiàn)的SQL情況:

    // 設(shè)定$name 中插入了我們不需要的SQL語句
    $name = "Qadir'; DELETE FROM users;";
    mysql_query("SELECT * FROM users WHERE name='{$name}'");

    以上的注入語句中,我們沒有對 $name 的變量進行過濾,$name 中插入了我們不需要的SQL語句,將刪除 users 表中的所有數(shù)據(jù)。

    2.在PHP中的 mysql_query() 是不允許執(zhí)行多個SQL語句的,但是在 SQLite 和 PostgreSQL 是可以同時執(zhí)行多條SQL語句的,所以我們對這些用戶的數(shù)據(jù)需要進行嚴(yán)格的驗證。

    防止SQL注入,我們需要注意以下幾個要點:

    1.永遠不要信任用戶的輸入。對用戶的輸入進行校驗,可以通過正則表達式,或限制長度;對單引號和 雙"-"進行轉(zhuǎn)換等。
    2.永遠不要使用動態(tài)拼裝sql,可以使用參數(shù)化的sql或者直接使用存儲過程進行數(shù)據(jù)查詢存取。
    3.永遠不要使用管理員權(quán)限的數(shù)據(jù)庫連接,為每個應(yīng)用使用單獨的權(quán)限有限的數(shù)據(jù)庫連接。
    4.不要把機密信息直接存放,加密或者hash掉密碼和敏感的信息。
    5.應(yīng)用的異常信息應(yīng)該給出盡可能少的提示,最好使用自定義的錯誤信息對原始錯誤信息進行包裝
    6.sql注入的檢測方法一般采取輔助軟件或網(wǎng)站平臺來檢測,軟件一般采用sql注入檢測工具jsky,網(wǎng)站平臺就有億思網(wǎng)站安全平臺檢測工具。MDCSOFT SCAN等。采用MDCSOFT-IPS可以有效的防御SQL注入,XSS攻擊等。

    3.防止SQL注入

    在腳本語言,如Perl和PHP你可以對用戶輸入的數(shù)據(jù)進行轉(zhuǎn)義從而來防止SQL注入。

    PHP的MySQL擴展提供了mysql_real_escape_string()函數(shù)來轉(zhuǎn)義特殊的輸入字符。

    if (get_magic_quotes_gpc()) { $name = stripslashes($name); } $name = mysql_real_escape_string($name); mysql_query("SELECT * FROM users WHERE name='{$name}'");

    4.Like語句中的注入

    like查詢時,如果用戶輸入的值有""和"%",則會出現(xiàn)這種情況:用戶本來只是想查詢"abcd",查詢結(jié)果中卻有"abcd_"、"abcde"、"abcdf"等等;用戶要查詢"30%"(注:百分之三十)時也會出現(xiàn)問題。

    在PHP腳本中我們可以使用addcslashes()函數(shù)來處理以上情況,如下實例:

    $sub = addcslashes(mysql_real_escape_string("%something_"), "%_"); // $sub == \%something\_ mysql_query("SELECT * FROM messages WHERE subject LIKE '{$sub}%'");

    addcslashes()函數(shù)在指定的字符前添加反斜杠。

    語法格式:

    addcslashes(string,characters)

    參數(shù) 描述
    string 必需。規(guī)定要檢查的字符串。
    characters 可選。規(guī)定受 addcslashes() 影響的字符或字符范圍。

    ThreadLocal(線程變量副本)

    Synchronized實現(xiàn)內(nèi)存共享,ThreadLocal為每個線程維護一個本地變量。

    采用空間換時間,它用于線程間的數(shù)據(jù)隔離,為每一個使用該變量的線程提供一個副本,每個線程都可以獨立地改變自己的副本,而不會和其他線程的副本沖突。

    ThreadLocal類中維護一個Map,用于存儲每一個線程的變量副本,Map中元素的鍵為線程對象,而值為對應(yīng)線程的變量副本。

    ThreadLocal在spring中發(fā)揮著巨大的作用,在管理Request作用域中的Bean、事務(wù)管理、任務(wù)調(diào)度、AOP等模塊都出現(xiàn)了它的身影。

    Spring中絕大部分Bean都可以聲明成Singleton作用域,采用ThreadLocal進行封裝,因此有狀態(tài)的Bean就能夠以singleton的方式在多線程中正常工作了。

    你能不能談?wù)?#xff0c;Java?GC是在什么時候,對什么東西,做了什么事情?

    在什么時候:

    1.新生代有一個Eden區(qū)和兩個survivor區(qū),首先將對象放入Eden區(qū),如果空間不足就向其中的一個survivor區(qū)上放,如果仍然放不下就會引發(fā)一次發(fā)生在新生代的minor GC,將存活的對象放入另一個survivor區(qū)中,然后清空Eden和之前的那個survivor區(qū)的內(nèi)存。在某次GC過程中,如果發(fā)現(xiàn)仍然又放不下的對象,就將這些對象放入老年代內(nèi)存里去。

    2.大對象以及長期存活的對象直接進入老年區(qū)。

    3.當(dāng)每次執(zhí)行minor GC的時候應(yīng)該對要晉升到老年代的對象進行分析,如果這些馬上要到老年區(qū)的老年對象的大小超過了老年區(qū)的剩余大小,那么執(zhí)行一次Full GC以盡可能地獲得老年區(qū)的空間。

    對什么東西:從GC Roots搜索不到,而且經(jīng)過一次標(biāo)記清理之后仍沒有復(fù)活的對象。

    做什么:
    新生代:復(fù)制清理;
    老年代:標(biāo)記-清除和標(biāo)記-壓縮算法;
    永久代:存放Java中的類和加載類的類加載器本身。

    GC Roots都有哪些:
    \1. 虛擬機棧中的引用的對象
    \2. 方法區(qū)中靜態(tài)屬性引用的對象,常量引用的對象
    \3. 本地方法棧中JNI(即一般說的Native方法)引用的對象。

    Volatile和Synchronized四個不同點:

    1 粒度不同,前者鎖對象和類,后者針對變量
    2 syn阻塞,volatile線程不阻塞
    3 syn保證三大特性,volatile不保證原子性
    4 syn編譯器優(yōu)化,volatile不優(yōu)化
    volatile具備兩種特性:
    \1. 保證此變量對所有線程的可見性,指一條線程修改了這個變量的值,新值對于其他線程來說是可見的,但并不是多線程安全的。
    \2. 禁止指令重排序優(yōu)化。
    Volatile如何保證內(nèi)存可見性:
    1.當(dāng)寫一個volatile變量時,JMM會把該線程對應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存。
    2.當(dāng)讀一個volatile變量時,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效。線程接下來將從主內(nèi)存中讀取共享變量。

    同步:就是一個任務(wù)的完成需要依賴另外一個任務(wù),只有等待被依賴的任務(wù)完成后,依賴任務(wù)才能完成。
    異步:不需要等待被依賴的任務(wù)完成,只是通知被依賴的任務(wù)要完成什么工作,只要自己任務(wù)完成了就算完成了,被依賴的任務(wù)是否完成會通知回來。(異步的特點就是通知)。
    打電話和發(fā)短信來比喻同步和異步操作。
    阻塞:CPU停下來等一個慢的操作完成以后,才會接著完成其他的工作。
    非阻塞:非阻塞就是在這個慢的執(zhí)行時,CPU去做其他工作,等這個慢的完成后,CPU才會接著完成后續(xù)的操作。
    非阻塞會造成線程切換增加,增加CPU的使用時間能不能補償系統(tǒng)的切換成本需要考慮。

    線程池的作用:

    在程序啟動的時候就創(chuàng)建若干線程來響應(yīng)處理,它們被稱為線程池,里面的線程叫工作線程
    第一:降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
    第二:提高響應(yīng)速度。當(dāng)任務(wù)到達時,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。
    第三:提高線程的可管理性。
    常用線程池:ExecutorService 是主要的實現(xiàn)類,其中常用的有
    Executors.newSingleThreadPool(),newFixedThreadPool(),newcachedTheadPool(),newScheduledThreadPool()。

    一致性哈希:

    Redis數(shù)據(jù)結(jié)構(gòu): String—字符串(key-value 類型)

    索引:B+,B-,全文索引

    MySQL的索引是一個數(shù)據(jù)結(jié)構(gòu),旨在使數(shù)據(jù)庫高效的查找數(shù)據(jù)。
    常用的數(shù)據(jù)結(jié)構(gòu)是B+Tree,每個葉子節(jié)點不但存放了索引鍵的相關(guān)信息還增加了指向相鄰葉子節(jié)點的指針,這樣就形成了帶有順序訪問指針的B+Tree,做這個優(yōu)化的目的是提高不同區(qū)間訪問的性能。
    什么時候使用索引:

  • 經(jīng)常出現(xiàn)在group by,order by和distinc關(guān)鍵字后面的字段
  • 經(jīng)常與其他表進行連接的表,在連接字段上應(yīng)該建立索引
  • 經(jīng)常出現(xiàn)在Where子句中的字段
  • 經(jīng)常出現(xiàn)用作查詢選擇的字段
  • Spring IOC AOP(控制反轉(zhuǎn),依賴注入)

    IOC容器:就是具有依賴注入功能的容器,是可以創(chuàng)建對象的容器,IOC容器負責(zé)實例化、定位、配置應(yīng)用程序中的對象及建立這些對象間的依賴。通常new一個實例,控制權(quán)由程序員控制,而"控制反轉(zhuǎn)"是指new實例工作不由程序員來做而是交給Spring容器來做。。在Spring中BeanFactory是IOC容器的實際代表者。

    DI(依賴注入Dependency injection)?:在容器創(chuàng)建對象后,處理對象的依賴關(guān)系。

    Spring支持三種依賴注入方式,分別是屬性(Setter方法)注入,構(gòu)造注入和接口注入。

    在Spring中,那些組成應(yīng)用的主體及由Spring IOC容器所管理的對象被稱之為Bean。

    Spring的IOC容器通過反射的機制實例化Bean并建立Bean之間的依賴關(guān)系。
    簡單地講,Bean就是由Spring IOC容器初始化、裝配及被管理的對象。
    獲取Bean對象的過程,首先通過Resource加載配置文件并啟動IOC容器,然后通過getBean方法獲取bean對象,就可以調(diào)用他的方法。
    Spring Bean的作用域:
    Singleton:Spring IOC容器中只有一個共享的Bean實例,一般都是Singleton作用域。
    Prototype:每一個請求,會產(chǎn)生一個新的Bean實例。
    Request:每一次http請求會產(chǎn)生一個新的Bean實例。

    AOP就是縱向的編程,如業(yè)務(wù)1和業(yè)務(wù)2都需要一個共同的操作,與其往每個業(yè)務(wù)中都添加同樣的代碼,不如寫一遍代碼,讓兩個業(yè)務(wù)共同使用這段代碼。在日常有訂單管理、商品管理、資金管理、庫存管理等業(yè)務(wù),都會需要到類似日志記錄、事務(wù)控制、****權(quán)限控制、性能統(tǒng)計、異常處理及事務(wù)處理等。AOP把所有共有代碼全部抽取出來,放置到某個地方集中管理,然后在具體運行時,再由容器動態(tài)織入這些共有代碼。

    Spring AOP應(yīng)用場景
    性能檢測,訪問控制,日志管理,事務(wù)等。
    默認(rèn)的策略是如果目標(biāo)類實現(xiàn)接口,則使用JDK動態(tài)代理技術(shù),如果目標(biāo)對象沒有實現(xiàn)接口,則默認(rèn)會采用CGLIB代理

    友情鏈接:?Spring框架IOC容器和AOP解析

    友情鏈接:淺談Spring框架注解的用法分析

    友情鏈接:關(guān)于Spring的69個面試問答——終極列表

    代理的共有優(yōu)點:業(yè)務(wù)類只需要關(guān)注業(yè)務(wù)邏輯本身,保證了業(yè)務(wù)類的重用性。

    Java靜態(tài)代理:
    代理對象和目標(biāo)對象實現(xiàn)了相同的接口,目標(biāo)對象作為代理對象的一個屬性,具體接口實現(xiàn)中,代理對象可以在調(diào)用目標(biāo)對象相應(yīng)方法前后加上其他業(yè)務(wù)處理邏輯。
    缺點:一個代理類只能代理一個業(yè)務(wù)類。如果業(yè)務(wù)類增加方法時,相應(yīng)的代理類也要增加方法。
    Java動態(tài)代理:
    Java動態(tài)代理是寫一個類實現(xiàn)InvocationHandler接口,重寫Invoke方法,在Invoke方法可以進行增強處理的邏輯的編寫,這個公共代理類在運行的時候才能明確自己要代理的對象,同時可以實現(xiàn)該被代理類的方法的實現(xiàn),然后在實現(xiàn)類方法的時候可以進行增強處理。
    實際上:代理對象的方法 = 增強處理 + 被代理對象的方法

    JDK和CGLIB生成動態(tài)代理類的區(qū)別:
    JDK動態(tài)代理只能針對實現(xiàn)了接口的類生成代理(實例化一個類)。此時代理對象和目標(biāo)對象實現(xiàn)了相同的接口,目標(biāo)對象作為代理對象的一個屬性,具體接口實現(xiàn)中,可以在調(diào)用目標(biāo)對象相應(yīng)方法前后加上其他業(yè)務(wù)處理邏輯
    CGLIB是針對類實現(xiàn)代理,主要是對指定的類生成一個子類(沒有實例化一個類),覆蓋其中的方法 。

    SpringMVC運行原理

    \1. 客戶端請求提交到DispatcherServlet
    \2. 由DispatcherServlet控制器查詢HandlerMapping,找到并分發(fā)到指定的Controller中。
    \4. Controller調(diào)用業(yè)務(wù)邏輯處理后,返回ModelAndView
    \5. DispatcherServlet查詢一個或多個ViewResoler視圖解析器,找到ModelAndView指定的視圖
    \6. 視圖負責(zé)將結(jié)果顯示到客戶端

    友情鏈接:Spring:基于注解的Spring MVC(上)

    友情鏈接:?Spring:基于注解的Spring MVC(下)

    友情鏈接:SpringMVC與Struts2區(qū)別與比較總結(jié)

    友情鏈接:SpringMVC與Struts2的對比

    TCP三次握手,四次揮手

    TCP作為一種可靠傳輸控制協(xié)議,其核心思想:既要保證數(shù)據(jù)可靠傳輸,又要提高傳輸?shù)男?#xff0c;而用三次恰恰可以滿足以上兩方面的需求!****雙方都需要確認(rèn)自己的發(fā)信和收信功能正常,收信功能通過接收對方信息得到確認(rèn),發(fā)信功能需要發(fā)出信息—>對方回復(fù)信息得到確認(rèn)。

    三次握手過程:

  • 第一次握手:建立連接。客戶端發(fā)送連接請求報文段,將SYN位置為1,Sequence Number為x;然后,客戶端進入SYN_SEND狀態(tài),等待服務(wù)器的確認(rèn);
  • 第二次握手:服務(wù)器收到客戶端的SYN報文段,需要對這個SYN報文段進行確認(rèn),設(shè)置ACK為x+1(Sequence Number+1);同時,自己還要發(fā)送SYN請求信息,將SYN位置為1,Sequence Number為y;服務(wù)器端將上述所有信息放到一個報文段(即SYN+ACK報文段)中,一并發(fā)送給客戶端,此時服務(wù)器進入SYN_RECV狀態(tài);
  • 第三次握手:客戶端收到服務(wù)器的SYN+ACK報文段。然后將Acknowledgment Number設(shè)置為y+1,向服務(wù)器發(fā)送ACK報文段,這個報文段發(fā)送完畢以后,客戶端和服務(wù)器端都進入ESTABLISHED狀態(tài),完成TCP三次握手。
  • TCP工作在網(wǎng)絡(luò)OSI的七層模型中的第四層——Transport層,IP在第三層——Network層
    �ARP在第二層——Data Link層;在第二層上的數(shù)據(jù),我們把它叫Frame,在第三層上的數(shù)據(jù)叫Packet,第四層的數(shù)據(jù)叫Segment。

    四次揮手過程:

  • 第一次分手:主機1(可以使客戶端,也可以是服務(wù)器端),設(shè)置Sequence Number和Acknowledgment Number,向主機2發(fā)送一個FIN報文段;此時,主機1進入FIN_WAIT_1狀態(tài);這表示主機1沒有數(shù)據(jù)要發(fā)送給主機2了;
  • 第二次分手:主機2收到了主機1發(fā)送的FIN報文段,向主機1回一個ACK報文段,Acknowledgment Number為Sequence Number加1;主機1進入FIN_WAIT_2狀態(tài);主機2告訴主機1,我“同意”你的關(guān)閉請求;
  • 第三次分手:主機2向主機1發(fā)送FIN報文段,請求關(guān)閉連接,同時主機2進入LAST_ACK狀態(tài);
  • 第四次分手:主機1收到主機2發(fā)送的FIN報文段,向主機2發(fā)送ACK報文段,然后主機1進入TIME_WAIT狀態(tài);主機2收到主機1的ACK報文段以后,就關(guān)閉連接;此時,主機1等待2MSL后依然沒有收到回復(fù),則證明Server端已正常關(guān)閉,那好,主機1也可以關(guān)閉連接了。
  • (2)而關(guān)閉連接卻是四次揮手呢?這是因為服務(wù)端在LISTEN狀態(tài)下,收到建立連接請求的SYN報文后,把ACK和SYN放在一個報文里發(fā)送給客戶端。

    為什么建立連接是三次握手

    這是因為服務(wù)端在LISTEN狀態(tài)下,收到建立連接請求的SYN報文后,把ACK和SYN放在一個報文里發(fā)送給客戶端。

    關(guān)閉連接卻是四次揮手呢

    而關(guān)閉連接時,當(dāng)收到對方的FIN報文時,僅僅表示對方不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù),己方也未必全部數(shù)據(jù)都發(fā)送給對方了,所以己方可以立即close,也可以發(fā)送一些數(shù)據(jù)給對方后,再發(fā)送FIN報文給對方來表示同意現(xiàn)在關(guān)閉連接,因此,己方ACK和FIN一般都會分開發(fā)送。

    HTTPS和HTTP 為什么更安全,先看這些

    http默認(rèn)端口是80 https是443

    http是HTTP協(xié)議運行在TCP之上。所有傳輸?shù)膬?nèi)容都是明文,客戶端和服務(wù)器端都無法驗證對方的身份。

    https是HTTP運行在SSL/TLS之上,SSL/TLS運行在TCP之上。所有傳輸?shù)膬?nèi)容都經(jīng)過加密,加密采用對稱加密,但對稱加密的密鑰用服務(wù)器方的證書進行了非對稱加密。此外客戶端可以驗證服務(wù)器端的身份,如果配置了客戶端驗證,服務(wù)器方也可以驗證客戶端的身份。HTTP(應(yīng)用層) 和TCP(傳輸層)之間插入一個SSL協(xié)議,

    一個Http請求

    DNS域名解析 –> 發(fā)起TCP的三次握手 –> 建立TCP連接后發(fā)起http請求 –> 服務(wù)器響應(yīng)http請求,瀏覽器得到html代碼 –> 瀏覽器解析html代碼,并請求html代碼中的資源(如js、css、圖片等) –> 瀏覽器對頁面進行渲染呈現(xiàn)給用戶

    友情鏈接:?HTTP與HTTPS的區(qū)別

    友情鏈接:?HTTPS 為什么更安全,先看這些

    友情鏈接:?HTTP請求報文和HTTP響應(yīng)報文

    友情鏈接:?HTTP 請求方式: GET和POST的比較

    Mybatis

    每一個Mybatis的應(yīng)用程序都以一個SqlSessionFactory對象的實例為核心。首先用字節(jié)流通過Resource將配置文件讀入,然后通過SqlSessionFactoryBuilder().build方法創(chuàng)建SqlSessionFactory,然后再通過sqlSessionFactory.openSession()方法創(chuàng)建一個sqlSession為每一個數(shù)據(jù)庫事務(wù)服務(wù)。
    經(jīng)歷了Mybatis初始化 –>創(chuàng)建SqlSession –>運行SQL語句 返回結(jié)果三個過程

    Servlet和Filter的區(qū)別:

    整的流程是:Filter對用戶請求進行預(yù)處理,接著將請求交給Servlet進行處理并生成響應(yīng),最后Filter再對服務(wù)器響應(yīng)進行后處理。

    Filter有如下幾個用處:
    Filter可以進行對特定的url請求和相應(yīng)做預(yù)處理和后處理。
    在HttpServletRequest到達Servlet之前,攔截客戶的HttpServletRequest。
    根據(jù)需要檢查HttpServletRequest,也可以修改HttpServletRequest頭和數(shù)據(jù)。
    在HttpServletResponse到達客戶端之前,攔截HttpServletResponse。
    根據(jù)需要檢查HttpServletResponse,也可以修改HttpServletResponse頭和數(shù)據(jù)。

    實際上Filter和Servlet極其相似,區(qū)別只是Filter不能直接對用戶生成響應(yīng)。實際上Filter里doFilter()方法里的代碼就是從多個Servlet的service()方法里抽取的通用代碼,通過使用Filter可以實現(xiàn)更好的復(fù)用。

    Filter和Servlet的生命周期:
    1.Filter在web服務(wù)器啟動時初始化
    2.如果某個Servlet配置了 1 ,該Servlet也是在Tomcat(Servlet容器)啟動時初始化。
    3.如果Servlet沒有配置1 ,該Servlet不會在Tomcat啟動時初始化,而是在請求到來時初始化。
    4.每次請求,?Request都會被初始化,響應(yīng)請求后,請求被銷毀。
    5.Servlet初始化后,將不會隨著請求的結(jié)束而注銷。
    6.關(guān)閉Tomcat時,Servlet、Filter依次被注銷。

    HashMap和TreeMap區(qū)別

    HashMap:基于哈希表實現(xiàn)。使用HashMap要求添加的鍵類明確定義了hashCode()和equals()[可以重寫hashCode()和equals()],為了優(yōu)化HashMap空間的使用,您可以調(diào)優(yōu)初始容量和負載因子。 適合查找和刪除
    (1)HashMap(): 構(gòu)建一個空的哈希映像
    (2)HashMap(Map m): 構(gòu)建一個哈希映像,并且添加映像m的所有映射
    (3)HashMap(int initialCapacity): 構(gòu)建一個擁有特定容量的空的哈希映像
    (4)HashMap(int initialCapacity, float loadFactor): 構(gòu)建一個擁有特定容量和加載因子的空的哈希映像
    TreeMap:基于紅黑樹實現(xiàn)。TreeMap沒有調(diào)優(yōu)選項,因為該樹總處于平衡狀態(tài)。 適合按照自然順序或者自定義的順序排序遍歷key
    (1)TreeMap():構(gòu)建一個空的映像樹
    (2)TreeMap(Map m): 構(gòu)建一個映像樹,并且添加映像m中所有元素
    (3)TreeMap(Comparator c): 構(gòu)建一個映像樹,并且使用特定的比較器對關(guān)鍵字進行排序
    (4)TreeMap(SortedMap s): 構(gòu)建一個映像樹,添加映像樹s中所有映射,并且使用與有序映像s相同的比較器排序

    友情鏈接:?Java中HashMap和TreeMap的區(qū)別深入理解

    HashMap沖突

    友情鏈接:?HashMap沖突的解決方法以及原理分析

    友情鏈接:?HashMap的工作原理

    友情鏈接:?HashMap和Hashtable的區(qū)別

    友情鏈接:?2種辦法讓HashMap線程安全

    HashMap,ConcurrentHashMap與LinkedHashMap的區(qū)別

  • ConcurrentHashMap是使用了鎖分段技術(shù)技術(shù)來保證線程安全的,鎖分段技術(shù):首先將數(shù)據(jù)分成一段一段的存儲,然后給每一段數(shù)據(jù)配一把鎖,當(dāng)一個線程占用鎖訪問其中一個段數(shù)據(jù)的時候,其他段的數(shù)據(jù)也能被其他線程訪問

  • ConcurrentHashMap 是在每個段(segment)中線程安全的

  • LinkedHashMap維護一個雙鏈表,可以將里面的數(shù)據(jù)按寫入的順序讀出

  • ConcurrentHashMap應(yīng)用場景

  • 1:ConcurrentHashMap的應(yīng)用場景是高并發(fā),但是并不能保證線程安全,而同步的HashMap和HashMap的是鎖住整個容器,而加鎖之后ConcurrentHashMap不需要鎖住整個容器,只需要鎖住對應(yīng)的Segment就好了,所以可以保證高并發(fā)同步訪問,提升了效率。

    2:可以多線程寫。
    ConcurrentHashMap把HashMap分成若干個Segmenet
    1.get時,不加鎖,先定位到segment然后在找到頭結(jié)點進行讀取操作。而value是volatile變量,所以可以保證在競爭條件時保證讀取最新的值,如果讀到的value是null,則可能正在修改,那么久調(diào)用ReadValueUnderLock函數(shù),加鎖保證讀到的數(shù)據(jù)是正確的。
    2.Put時會加鎖,一律添加到hash鏈的頭部。
    3.Remove時也會加鎖,由于next是final類型不可改變,所以必須把刪除的節(jié)點之前的節(jié)點都復(fù)制一遍。
    4.ConcurrentHashMap允許多個修改操作并發(fā)進行,其關(guān)鍵在于使用了鎖分離技術(shù)。它使用了多個鎖來控制對Hash表的不同Segment進行的修改。

    ConcurrentHashMap的應(yīng)用場景是高并發(fā),但是并不能保證線程安全,而同步的HashMap和HashTable的是鎖住整個容器,而加鎖之后ConcurrentHashMap不需要鎖住整個容器,只需要鎖住對應(yīng)的segment就好了,所以可以保證高并發(fā)同步訪問,提升了效率。

    友情鏈接:Java集合—ConcurrentHashMap原理分析

    ThreadPoolExecutor 的內(nèi)部工作原理

    進程間的通信方式

  • 管道( pipe ):管道是一種半雙工的通信方式,數(shù)據(jù)只能單向流動,而且只能在具有親緣關(guān)系的進程間使用。進程的親緣關(guān)系通常是指父子進程關(guān)系。
  • 有名管道 (named pipe) : 有名管道也是半雙工的通信方式,但是它允許無親緣關(guān)系進程間的通信。
    3.信號量( semophore ) : 信號量是一個計數(shù)器,可以用來控制多個進程對共享資源的訪問。它常作為一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作為進程間以及同一進程內(nèi)不同線程之間的同步手段。
  • 消息隊列( message queue ) : 消息隊列是由消息的鏈表,存放在內(nèi)核中并由消息隊列標(biāo)識符標(biāo)識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限等缺點。
    5.信號 ( sinal ) : 信號是一種比較復(fù)雜的通信方式,用于通知接收進程某個事件已經(jīng)發(fā)生。
    6.共享內(nèi)存( shared memory ) :共享內(nèi)存就是映射一段能被其他進程所訪問的內(nèi)存,這段共享內(nèi)存由一個進程創(chuàng)建,但多個進程都可以訪問。共享內(nèi)存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設(shè)計的。它往往與其他通信機制,如信號量,配合使用,來實現(xiàn)進程間的同步和通信。
    7.套接字( socket ) : 套解口也是一種進程間通信機制,與其他通信機制不同的是,它可用于不同機器間的進程通信。
  • 死鎖的必要條件

  • 互斥 至少有一個資源處于非共享狀態(tài)
  • 占有并等待
  • 非搶占
  • 循環(huán)等待
    解決死鎖,第一個是死鎖預(yù)防,就是不讓上面的四個條件同時成立。二是,合理分配資源。
    三是使用銀行家算法,如果該進程請求的資源操作系統(tǒng)剩余量可以滿足,那么就分配。
  • 轉(zhuǎn)載于:https://www.cnblogs.com/lenglangjin/p/10531240.html

    總結(jié)

    以上是生活随笔為你收集整理的java后台常见问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。