java後臺常見問題

Java後臺面試 常見問題


Nginx負載均衡

  • 輪詢、輪詢是默認的,每個請求按順序逐一分配到不一樣的後端服務器,若是後端服務器down掉了,則能自動剔除javascript

  • ip_hash、個請求按訪問IP的hash結果分配,這樣來自同一個IP的訪客固定訪問一個後端服務器,有效解決了動態網頁存在的session共享問題。php

  • weight、weight是設置權重,用於後端服務器性能不均的狀況,訪問比率約等於權重之比css

  • fair(第三方)、這是比上面兩個更加智能的負載均衡算法。此種算法能夠依據頁面大小和加載時間長短智能地進行負載均衡,也就是根據後端服務器的響應時間來分配請求,響應時間短的優先分配。Nginx自己是不支持fair的,若是須要使用這種調度算法,必須下載Nginx的upstream_fair模塊。html

  • url_hash(第三方)此方法按訪問url的hash結果來分配請求,使每一個url定向到同一個後端服務器,能夠進一步提升後端緩存服務器的效率。Nginx自己是不支持url_hash的,若是須要使用這種調度算法,必須安裝Nginx 的hash軟件包。java

代理的概念

正向代理,也就是傳說中的代理, 簡單的說,我是一個用戶,我訪問不了某網站,可是我能訪問一個代理服務器,這個代理服務器呢,他能訪問那個我不能訪問的網站,因而我先連上代理服務器,告訴他我須要那個沒法訪問網站的內容,代理服務器去取回來,而後返回給我。從網站的角度,只在代理服務器來取內容的時候有一次記錄,有時候並不知道是用戶的請求,也隱藏了用戶的資料,這取決於代理告不告訴網站。mysql

反向代理: 結論就是,反向代理正好相反,對於客戶端而言它就像是原始服務器,而且客戶端不須要進行任何特別的設置。客戶端向反向代理的命名空間(name-space)中的內容發送普通請求,接着反向代理將判斷向何處(原始服務器)轉交請求,並將得到的內容返回給客戶端,就像這些內容本來就是它本身的同樣。c++

Volatile的特徵:

A、原子性 :對任意單個volatile變量的讀/寫具備原子性,但相似於volatile++這種複合操做不具備原子性。
B、可見性:對一個volatile變量的讀,老是能看到(任意線程)對這個volatile變量最後的寫入。程序員

Volatile的內存語義:

當寫一個volatile變量時,JMM會把線程對應的本地內存中的共享變量值刷新到主內存。web

 
這裏寫圖片描述

當讀一個volatile變量時,JMM會把線程對應的本地內存置爲無效,線程接下來將從主內存中讀取共享變量。面試

 
這裏寫圖片描述

Volatile的重排序

一、當第二個操做爲volatile寫操作時,無論第一個操做是什麼(普通讀寫或者volatile讀寫),都不能進行重排序。這個規則確保volatile寫以前的全部操做都不會被重排序到volatile以後;

二、當第一個操做爲volatile讀操做時,無論第二個操做是什麼,都不能進行重排序。這個規則確保volatile讀以後的全部操做都不會被重排序到volatile以前;

三、當第一個操做是volatile寫操做時,第二個操做是volatile讀操做,不能進行重排序。

這個規則和前面兩個規則一塊兒構成了:兩個volatile變量操做不可以進行重排序;

除以上三種狀況之外能夠進行重排序。

好比:

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


內存屏障/內存柵欄

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

內存屏障能夠被分爲如下幾種類型:

LoadLoad屏障:對於這樣的語句Load1; LoadLoad; Load2,在Load2及後續讀取操做要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。

StoreStore屏障:對於這樣的語句Store1; StoreStore; Store2,在Store2及後續寫入操做執行前,保證Store1的寫入操做對其它處理器可見。

LoadStore屏障:對於這樣的語句Load1; LoadStore; Store2,在Store2及後續寫入操做被刷出前,保證Load1要讀取的數據被讀取完畢。

StoreLoad屏障:對於這樣的語句Store1; StoreLoad; Load2,在Load2及後續全部讀取操做執行前,保證Store1的寫入對全部處理器可見。它的開銷是四種屏障中最大的。

在大多數處理器的實現中,這個屏障是個萬能屏障,兼具其它三種內存屏障的功能。

內存屏障阻礙了CPU採用優化技術來下降內存操做延遲,必須考慮所以帶來的性能損失。爲了達到最佳性能,最好是把要解決的問題模塊化,這樣處理器能夠按單元執行任務,而後在任務單元的邊界放上全部須要的內存屏障。採用這個方法可讓處理器不受限的執行一個任務單元。合理的內存屏障組合還有一個好處是:緩衝區在第一次被刷後開銷會減小,由於再填充改緩衝區不須要額外工做了。


happens-before原則

若是一個操做執行的結果須要對另外一個操做可見,那麼這兩個操做之間必需要存在happens-before關係。

 
這裏寫圖片描述

Java是如何實現跨平臺的?

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

JVM也是一個軟件,不一樣的平臺有不一樣的版本。咱們編寫的Java源碼,編譯後會生成一種 .class 文件,稱爲字節碼文件。Java虛擬機就是負責將字節碼文件翻譯成特定平臺下的機器碼而後運行。也就是說,只要在不一樣平臺上安裝對應的JVM,就能夠運行字節碼文件,運行咱們編寫的Java程序。

而這個過程當中,咱們編寫的Java程序沒有作任何改變,僅僅是經過JVM這一」中間層「,就能在不一樣平臺上運行,真正實現了」一次編譯,處處運行「的目的。

JVM是一個」橋樑「,是一個」中間件「,是實現跨平臺的關鍵,Java代碼首先被編譯成字節碼文件,再由JVM將字節碼文件翻譯成機器語言,從而達到運行Java程序的目的。

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

因此,運行Java程序必須有JVM的支持,由於編譯的結果不是機器碼,必需要通過JVM的再次翻譯才能執行。即便你將Java程序打包成可執行文件(例如 .exe),仍然須要JVM的支持。

注意:跨平臺的是Java程序,不是JVM。JVM是用C/C++開發的,是編譯後的機器碼,不能跨平臺,不一樣平臺下須要安裝不一樣版本的JVM。

垃圾蒐集器

  1. 按照線程數量來分:
    1. 串行 串行垃圾回收器一次只使用一個線程進行垃圾回收
    2. 並行 並行垃圾回收器一次將開啓多個線程同時進行垃圾回收。
  2. 按照工做模式來分:
    1. 併發 併發式垃圾回收器與應用程序線程交替工做,以儘量減小應用程序的停頓時間
    2. 獨佔 一旦運行,就中止應用程序中的其餘全部線程,直到垃圾回收過程徹底結束
  3. 按照碎片處理方式:
    1. 壓縮式 壓縮式垃圾回收器會在回收完成後,對存活對象進行壓縮整消除回收後的碎片;
    2. 非壓縮式 非壓縮式的垃圾回收器不進行這步操做。
  4. 按工做的內存區間 可分爲新生代垃圾回收器和老年代垃圾回收器
  • 新生代串行收集器 serial 它僅僅使用單線程進行垃圾回收;第二,它獨佔式的垃圾回收。使用複製算法。

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

  • 並行收集器 parnew 並行收集器是工做在新生代的垃圾收集器,它只簡單地將串行回收器多線程化。它的回收策略、算法以及參數和串行回收器同樣 並行回收器也是獨佔式的回收器,在收集過程當中,應用程序會所有暫停。但因爲並行回收器使用多線程進行垃圾回收,所以,在併發能力比較強的 CPU 上,它產生的停頓時間要短於串行回收器,而在單 CPU 或者併發能力較弱的系統中,並行回收器的效果不會比串行回收器好,因爲多線程的壓力,它的實際表現極可能比串行回收器差。

  • 新生代並行回收 (Parallel Scavenge) 收集器 新生代並行回收收集器也是使用複製算法的收集器。從表面上看,它和並行收集器同樣都是多線程、獨佔式的收集器。可是,並行回收收集器有一個重要的特色:它很是關注系統的吞吐量。

  • 老年代並行回收收集器 parallel old 老年代的並行回收收集器也是一種多線程併發的收集器。和新生代並行回收收集器同樣,它也是一種關注吞吐量的收集器。老年代並行回收收集器使用標記-壓縮算法,JDK1.6 以後開始啓用。

  • CMS 收集器 CMS 收集器主要關注於系統停頓時間。CMS 是 Concurrent Mark Sweep 的縮寫,意爲併發標記清除,從名稱上能夠得知,它使用的是標記-清除算法,同時它又是一個使用多線程併發回收的垃圾收集器。

    • CMS 工做時,主要步驟有:初始標記、併發標記、從新標記、併發清除和併發重置。其中初始標記和從新標記是獨佔系統資源的,而併發標記、併發清除和併發重置是能夠和用戶線程一塊兒執行的。所以,從總體上來講,CMS 收集不是獨佔式的,它能夠在應用程序運行過程當中進行垃圾回收。

      根據標記-清除算法,初始標記、併發標記和從新標記都是爲了標記出須要回收的對象。併發清理則是在標記完成後,正式回收垃圾對象;併發重置是指在垃圾回收完成後,從新初始化 CMS 數據結構和數據,爲下一次垃圾回收作好準備。併發標記、併發清理和併發重置都是能夠和應用程序線程一塊兒執行的。

  • G1 收集器 G1 收集器是基於標記-壓縮算法的。所以,它不會產生空間碎片,也沒有必要在收集完成後,進行一次獨佔式的碎片整理工做。G1 收集器還能夠進行很是精確的停頓控制。

網絡基本概念

OSI模型

OSI 模型(Open System Interconnection model)是一個由國際標準化組織􏰁提出的概念模型,試圖􏰁供一個使各類不一樣的計算機和網絡在世界範圍內實現互聯的標準框架。
它將計算機網絡體系結構劃分爲七層,每層均可以􏰁供抽象良好的接口。瞭解 OSI 模型有助於理解實際上互聯網絡的工業標準——TCP/IP 協議。
OSI 模型各層間關係和通信時的數據流向如圖所示:

OSI 模型.png

顯然、若是一個東西想一應俱全、通常時不可能的;在實際的開發應用中通常時在此模型的基礎上進行裁剪、整合!

七層模型介紹

  • 物理層:
    物理層負責最後將信息編碼成電流脈衝或其它信號用於網上傳輸;
    eg:RJ45等將數據轉化成0和1;
  • 數據鏈路層:
    數據鏈路層經過物理網絡鏈路􏰁供數據傳輸。不一樣的數據鏈路層定義了不一樣的網絡和協 議特徵,其中包括物理編址、網絡拓撲結構、錯誤校驗、數據幀序列以及流控;
    能夠簡單的理解爲:規定了0和1的分包形式,肯定了網絡數據包的形式;
  • 網絡層
    網絡層負責在源和終點之間創建鏈接;
    能夠理解爲,此處須要肯定計算機的位置,怎麼肯定?IPv4,IPv6!
  • 傳輸層
    傳輸層向高層􏰁提供可靠的端到端的網絡數據流服務。
    能夠理解爲:每個應用程序都會在網卡註冊一個端口號,該層就是端口與端口的通訊!經常使用的(TCP/IP)協議;
  • 會話層
    會話層創建、管理和終止表示層與實體之間的通訊會話;
    創建一個鏈接(自動的手機信息、自動的網絡尋址);
  • 表示層:
    表示層􏰁供多種功能用於應用層數據編碼和轉化,以確保以一個系統應用層發送的信息 能夠被另外一個系統應用層識別;
    能夠理解爲:解決不一樣系統之間的通訊,eg:Linux下的QQ和Windows下的QQ能夠通訊;
  • 應用層:
    OSI 的應用層協議包括文件的傳輸、訪問及管理協議(FTAM) ,以及文件虛擬終端協議(VIP)和公用管理系統信息(CMIP)等;
    規定數據的傳輸協議;

常見的應用層協議:

常見的應用層協議.png

互聯網分層結構的好處: 上層的變更徹底不影響下層的結構。

TCP/IP 協議基本概念

OSI 模型所分的七層,在實際應用中,每每有一些層被整合,或者功能分散到其餘層去。TCP/IP 沒有照搬 OSI 模型,也沒有 一個公認的 TCP/IP 層級模型,通常劃分爲三層到五層模型來􏰂述 TCP/IP 協議。

  • 在此描述用一個通用的四層模型來描述,每一層都和 OSI 模型有較強的相關性可是又可能會有交叉。
  • TCP/IP 的設計,是吸收了分層模型的精華思想——封裝。每層對上一層􏰁供服務的時 候,上一層的數據結構是黑盒,直接做爲本層的數據,而不須要關心上一層協議的任何細節。

TCP/IP 分層模型的分層以以太網上傳輸 UDP 數據包如圖所示;

UDP 數據包.png

數據包

寬泛意義的數據包:每個數據包都包含"標頭"和"數據"兩個部分."標頭"包含本數據包的一些說明."數據"則是本數據包的內容.

細分數據包:

  • 應用程序數據包: 標頭部分規定應用程序的數據格式.數據部分傳輸具體的數據內容.** ——對應上圖中的數據!**
  • TCP/UDP數據包:標頭部分包含雙方的發出端口和接收端口. UDP數據包:'標頭'長度:8個字節,"數據包"總長度最大爲65535字節,正好放進一個IP數據包. TCP數據包:理論上沒有長度限制,可是,爲了保證網絡傳輸效率,一般不會超過IP數據長度,確保單個包不會被分割. ——對應上圖中的UDP數據!
  • IP數據包: 標頭部分包含通訊雙方的IP地址,協議版本,長度等信息. '標頭'長度:20~60字節,"數據包"總長度最大爲65535字節. ——對應上圖中的IP數據
  • 以太網數據包: 最基礎的數據包.標頭部分包含了通訊雙方的MAC地址,數據類型等. '標頭'長度:18字節,'數據'部分長度:46~1500字節. ——對應上圖中的以太網數據

四層模型

  1. 網絡接口層
    網絡接口層包括用於協做IP數據在已有網絡介質上傳輸的協議。
    它定義像地址解析協議(Address Resolution Protocol,ARP)這樣的協議,􏰁供 TCP/IP 協議的數據結構和實際物理硬件之間的接口。
    能夠理解爲:肯定了網絡數據包的形式
  2. 網間層
    網間層對應於 OSI 七層參考模型的網絡層,本層包含 IP 協議、RIP 協議(Routing Information Protocol,路由信息協議),負責數據的包裝、尋址和路由。同時還包含網間控制報文協議(Internet Control Message Protocol,ICMP)用來􏰁供網絡診斷信息;
    能夠理解爲:該層時肯定計算機的位置
  3. 傳輸層
    傳輸層對應於 OSI 七層參考模型的傳輸層,它􏰁供兩種端到端的通訊服務。其中 TCP 協議(Transmission Control Protocol)􏰁供可靠的數據流運輸服務,UDP 協議(Use Datagram Protocol)􏰁供不可靠的用戶數據報服務。
    TCP:三次握手、四次揮手;UDP:只發無論別人收不收穫得--任性哈
  4. 應用層
    應用層對應於 OSI 七層參考模型的應用層和表達層;
    不明白的再看看7層參考模型的描述

TCP/IP 協議族經常使用協議

  • 應用層:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等
  • 傳輸層:TCP,UDP
  • 網絡層:IP,ICMP,OSPF,EIGRP,IGMP
  • 數據鏈路層:SLIP,CSLIP,PPP,MTU

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

  • IP(Internet Protocol,網際協議)是網間層的主要協議,任務是在源地址和和目的地址之間傳輸數據。IP 協議只是盡最大努力來傳輸數據包,並不保證全部的包均可以傳輸 到目的地,也不保證數據包的順序和惟一。
    • IP 定義了 TCP/IP 的地址,尋址方法,以及路由規則。如今普遍使用的 IP 協議有 IPv4 和 IPv6 兩種:IPv4 使用 32 位二進制整數作地址,通常使用點分十進制方式表示,好比 192.168.0.1。
    • IP 地址由兩部分組成,即網絡號和主機號。故一個完整的 IPv4 地址每每表示 爲 192.168.0.1/24 或192.168.0.1/255.255.255.0 這種形式。
    • IPv6 是爲了解決 IPv4 地址耗盡和其它一些問題而研發的最新版本的 IP。使用 128 位 整數表示地址,一般使用冒號分隔的十六進制來表示,而且能夠省略其中一串連續的 0,如:fe80::200:1ff:fe00:1。
      目前使用並很少!
  • ICMP(Internet Control Message Protocol,網絡控制消息協議)是 TCP/IP 的 核心協議之一,用於在 IP 網絡中發送控制消息,􏰁供通訊過程當中的各類問題反饋。 ICMP 直接使用 IP 數據包傳輸,但 ICMP 並不被視爲 IP 協議的子協議。常見的聯網狀態診斷工具好比依賴於 ICMP 協議;
  • TCP(TransmissionControlProtocol,傳輸控制協議)是一種面向鏈接的,可靠的, 基於字節流傳輸的通訊協議。TCP 具備端口號的概念,用來標識同一個地址上的不 同應用。􏰂述 TCP 的標準文檔是 RFC793。
  • UDP(UserDatagramProtocol,用戶數據報協議)是一個面向數據報的傳輸層協 議。UDP 的傳輸是不可靠的,簡單的說就是發了無論,發送者不會知道目標地址 的數據通路是否發生擁塞,也不知道數據是否到達,是否完整以及是否仍是原來的 次序。它同 TCP 同樣有用來標識本地應用的端口號。因此應用 UDP 的應用,都能 夠容忍必定數量的錯誤和丟包,可是對傳輸性能敏感的,好比流媒體、DNS 等。
  • ECHO(EchoProtocol,回聲協議)是一個簡單的調試和檢測工具。服務器器會 原樣回發它收到的任何數據,既能夠使用 TCP 傳輸,也能夠使用 UDP 傳輸。使用 端口號 7 。
  • DHCP(DynamicHostConfigrationProtocol,動態主機配置協議)是用於局域 網自動分配 IP 地址和主機配置的協議。能夠使局域網的部署更加簡單。
  • DNS(DomainNameSystem,域名系統)是互聯網的一項服務,能夠簡單的將用「.」 分隔的通常會有意義的域名轉換成不易記憶的 IP 地址。通常使用 UDP 協議傳輸, 也能夠使用 TCP,默認服務端口號 53。􏰂
  • FTP(FileTransferProtocol,文件傳輸協議)是用來進行文件傳輸的標準協議。 FTP 基於 TCP 使用端口號 20 來傳輸數據,21 來傳輸控制信息。
  • TFTP(Trivial File Transfer Protocol,簡單文件傳輸協議)是一個簡化的文 件傳輸協議,其設計很是簡單,經過少許存儲器就能輕鬆實現,因此通常被用來通 過網絡引導計算機過程當中傳輸引導文件等小文件;
  • SSH(SecureShell,安全Shell),由於傳統的網絡服務程序好比TELNET本質上都極不安全,明文傳說數據和用戶信息包括密碼,SSH 被開發出來避免這些問題, 它實際上是一個協議框架,有大量的擴展冗餘能力,而且􏰁供了加密壓縮的通道能夠 爲其餘協議使用。
  • POP(PostOfficeProtocol,郵局協議)是支持經過客戶端訪問電子郵件的服務, 如今版本是 POP3,也有加密的版本 POP3S。協議使用 TCP,端口 110。
  • SMTP(Simple Mail Transfer Protocol,簡單郵件傳輸協議)是如今在互聯網 上發送電子郵件的事實標準。使用 TCP 協議傳輸,端口號 25。
  • HTTP(HyperTextTransferProtocol,超文本傳輸協議)是如今廣爲流行的WEB 網絡的基礎,HTTPS 是 HTTP 的加密安全版本。協議經過 TCP 傳輸,HTTP 默認 使用端口 80,HTTPS 使用 443。

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

線程池

Executor 框架即是 Java 5 中引入的,其內部使用了線程池機制

好處

第一:下降資源消耗 經過重複利用已建立的線程下降線程建立和銷燬形成的消耗。

第二:提升響應速度。當任務到達時,任務能夠不須要等到線程建立就能當即執行。

第三:提升線程的可管理性。線程是稀缺資源,若是無限制的建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一的分配,調優和監控。可是要作到合理的利用線程池,必須對其原理了如指掌。

Java線程間的通訊方式

wait()方法

wait()方法使得當前線程必需要等待,等到另一個線程調用notify()或者notifyAll()方法。

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

線程調用wait()方法,釋放它對鎖的擁有權,而後等待另外的線程來通知它(通知的方式是notify()或者notifyAll()方法),這樣它才能從新得到鎖的擁有權和恢復執行。

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

一個小比較:

當線程調用了wait()方法時,它會釋放掉對象的鎖。

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

notify()方法

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

若是多個線程在等待,它們中的一個將會選擇被喚醒。這種選擇是隨意的,和具體實現有關。(線程等待一個對象的鎖是因爲調用了wait方法中的一個)。

被喚醒的線程是不能被執行的,須要等到當前線程放棄這個對象的鎖。

被喚醒的線程將和其餘線程以一般的方式進行競爭,來得到對象的鎖。也就是說,被喚醒的線程並無什麼優先權,也沒有什麼劣勢,對象的下一個線程仍是須要經過通常性的競爭。

notify()方法應該是被擁有對象的鎖的線程所調用。

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

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

wait()和notify()方法要求在調用時線程已經得到了對象的鎖,所以對這兩個方法的調用須要放在synchronized方法或synchronized塊中。

  一個線程變爲一個對象的鎖的擁有者是經過下列三種方法:

1.執行這個對象的synchronized實例方法。

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

3.對於Class類的對象,執行那個類的synchronized、static方法。


Java 線程有哪些狀態,這些狀態之間是如何轉化的?

 
這裏寫圖片描述
  1. 新建(new):新建立了一個線程對象。
  2. 可運行(runnable):線程對象建立後,其餘線程(好比main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取cpu 的使用權 。
  3. 運行(running):可運行狀態(runnable)的線程得到了cpu 時間片(timeslice) ,執行程序代碼。
  4. 阻塞(block):阻塞狀態是指線程由於某種緣由放棄了cpu 使用權,也即讓出了cpu timeslice,暫時中止運行。直到線程進入可運行(runnable)狀態,纔有機會再次得到cpu timeslice 轉到運行(running)狀態。阻塞的狀況分三種:

(一). 等待阻塞:運行(running)的線程執行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue)中。同時釋放對象鎖

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

(三). 其餘阻塞:運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入可運行(runnable)狀態。

  1. 死亡(dead):線程run()、main() 方法執行結束,或者因異常退出了run()方法,則該線程結束生命週期。死亡的線程不可再次復生。

List接口、Set接口和Map接口的區別

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

Collection表示一組對象,這些對象也稱爲collection的元素;一些 collection容許有重複的元素,而另外一些則不容許;一些collection是有序的,而另外一些則是無序的;JDK中不提供此接口的任何直接實 現,它提供更具體的子接口(如 Set 和 List)實現;Map沒有繼承Collection接口,Map提供key到value的映射;一個Map中不能包含相同key,每一個key只能映射一個value;Map接口提供3種集合的視圖,Map的內容能夠被當作一組key集合,一組value集合,或者一組key-value映射; 

二、List接口

元素有放入順序,元素可重複 
List接口有三個實現類:LinkedList,ArrayList,Vector LinkedList:底層基於鏈表實現,鏈表內存是散亂的,每個元素存儲自己內存地址的同時還存儲下一個元素的地址。鏈表增刪快,查找慢 ArrayList和Vector的區別:ArrayList是非線程安全的,效率高;Vector是基於線程安全的,效率低 List是一種有序的Collection,能夠經過索引訪問集合中的數據,List比Collection多了10個方法,主要是有關索引的方法。 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(鏈表)適合添加,刪除操做; 

三、Set接口

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

四、map接口

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

Session機制

1、術語session

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

Cookie和Session的區別

HTTP請求是無狀態的。

共同之處:

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

區別:

  • cookie數據保存在客戶端,session數據保存在服務器端。簡單的說,當你登陸一個網站的時候, 若是web服務器端使用的是session,那麼全部的數據都保存在服務器上,客戶端每次請求服務器的時候會發送當前會話的sessionid,服務器根據當前sessionid判斷相應的用戶數據標誌,以肯定用戶是否登陸或具備某種權限。因爲數據是存儲在服務器上面,因此你不能僞造。
  • sessionid是服務器和客戶端連接時候隨機分配的. 若是瀏覽器使用的是cookie,那麼全部的數據都保存在瀏覽器端,好比你登陸之後,服務器設置了cookie用戶名,那麼當你再次請求服務器的時候,瀏覽器會將用戶名一塊發送給服務器,這些變量有必定的特殊標記。服務器會解釋爲cookie變量,因此只要不關閉瀏覽器,那麼cookie變量一直是有效的,因此可以保證長時間不掉線。若是你可以截獲某個用戶的 cookie變量,而後僞造一個數據包發送過去,那麼服務器仍是認爲你是合法的。因此,使用 cookie被攻擊的可能性比較大。

若是設置了的有效時間,那麼它會將 cookie保存在客戶端的硬盤上,下次再訪問該網站的時候,瀏覽器先檢查有沒有 cookie,若是有的話,就讀取該 cookie,而後發送給服務器。若是你在機器上面保存了某個論壇 cookie,有效期是一年,若是有人入侵你的機器,將你的 cookie拷走,而後放在他的瀏覽器的目錄下面,那麼他登陸該網站的時候就是用你的的身份登陸的。因此 cookie是能夠僞造的。固然,僞造的時候須要主意,直接copy cookie文件到 cookie目錄,瀏覽器是不認的,他有一個index.dat文件,存儲了 cookie文件的創建時間,以及是否有修改,因此你必須先要有該網站的 cookie文件,而且要從保證時間上騙過瀏覽器

兩個均可以用來存私密的東西,一樣也都有有效期的說法,區別在於session是放在服務器上的,過時與否取決於服務期的設定,cookie是存在客戶端的,過去與否能夠在cookie生成的時候設置進去。

(1)cookie數據存放在客戶的瀏覽器上,session數據放在服務器上
(2)cookie不是很安全,別人能夠分析存放在本地的COOKIE並進行COOKIE欺騙,若是主要考慮到安全應當使用session
(3)session會在必定時間內保存在服務器上。當訪問增多,會比較佔用你服務器的性能,若是主要考慮到減輕服務器性能方面,應當使用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()方法時,已經覆蓋了object類的equals()方法。

好比在String類中以下:

 

 

[
 
複製代碼

](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; } 

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

它的性質有:

  • 自反性(reflexive)。對於任意不爲null的引用值x,x.equals(x)必定是true
  • 對稱性(symmetric)。對於任意不爲null的引用值xy,當且僅當x.equals(y)true時,y.equals(x)也是true
  • 傳遞性(transitive)。對於任意不爲null的引用值xyz,若是x.equals(y)true,同時y.equals(z)true,那麼x.equals(z)必定是true
  • 一致性(consistent)。對於任意不爲null的引用值xy,若是用於equals比較的對象信息沒有被修改的話,屢次調用時x.equals(y)要麼一致地返回true要麼一致地返回false
  • 對於任意不爲null的引用值xx.equals(null)返回false

對於Object類來講,equals()方法在對象上實現的是差異可能性最大的等價關係,即,對於任意非null的引用值xy,當且僅當xy引用的是同一個對象,該方法纔會返回true

須要注意的是當equals()方法被override時,hashCode()也要被override。按照通常hashCode()方法的實現來講,相等的對象,它們的hash code必定相等。

hashcode() 方法詳解

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

它的性質是:

  • 在一個Java應用的執行期間,若是一個對象提供給equals作比較的信息沒有被修改的話,該對象屢次調用hashCode()方法,該方法必須始終如一返回同一個integer。
  • 若是兩個對象根據equals(Object)方法是相等的,那麼調用兩者各自的hashCode()方法必須產生同一個integer結果。
  • 並不要求根據equals(java.lang.Object)方法不相等的兩個對象,調用兩者各自的hashCode()方法必須產生不一樣的integer結果。然而,程序員應該意識到對於不一樣的對象產生不一樣的integer結果,有可能會提升hash table的性能。

Java中CAS算法--樂觀鎖的一種實現方式

悲觀者與樂觀者的作事方式徹底不同,悲觀者的人生觀是一件事情我必需要百分之百徹底控制纔會去作,不然就認爲這件事情必定會出問題;而樂觀者的人生觀則相反,凡事無論最終結果如何,他都會先嚐試去作,大不了最後不成功。這就是悲觀鎖與樂觀鎖的區別,悲觀鎖會把整個對象加鎖佔爲自有後纔去作操做,樂觀鎖不獲取鎖直接作操做,而後經過必定檢測手段決定是否更新數據。這一節將對樂觀鎖進行深刻探討。

上節討論的Synchronized互斥鎖屬於悲觀鎖,它有一個明顯的缺點,它無論數據存不存在競爭都加鎖,隨着併發量增長,且若是鎖的時間比較長,其性能開銷將會變得很大。有沒有辦法解決這個問題?答案是基於衝突檢測的樂觀鎖。這種模式下,已經沒有所謂的鎖概念了,每條線程都直接先去執行操做,計算完成後檢測是否與其餘線程存在共享數據競爭,若是沒有則讓此操做成功,若是存在共享數據競爭則可能不斷地從新執行操做和檢測,直到成功爲止,可叫CAS自旋。

樂觀鎖的核心算法是CAS(Compareand Swap,比較並交換),它涉及到三個操做數:內存值、預期值、新值。當且僅當預期值和內存值相等時纔將內存值修改成新值。這樣處理的邏輯是,首先檢查某塊內存的值是否跟以前我讀取時的同樣,如不同則表示期間此內存值已經被別的線程更改過,捨棄本次操做,不然說明期間沒有其餘線程對此內存值操做,能夠把新值設置給此塊內存。如圖2-5-4-1,有兩個線程可能會差很少同時對某內存操做,線程二先讀取某內存值做爲預期值,執行到某處時線程二決定將新值設置到內存塊中,若是線程一在此期間修改了內存塊,則經過CAS便可以檢測出來,假如檢測沒問題則線程二將新值賦予內存塊。

 
img

圖2-5-4-1

假如你足夠細心你可能會發現一個疑問,比較和交換,從字面上就有兩個操做了,更別說實際CAS可能會有更多的執行指令,他們是原子性的嗎?若是非原子性又怎麼保證CAS操做期間出現併發帶來的問題?我是否是須要用上節提到的互斥鎖來保證他的原子性操做?CAS確定是具備原子性的,否則就談不上在併發中使用了,但這個原子性是由CPU硬件指令實現保證的,即便用JNI調用native方法調用由C++編寫的硬件級別指令,jdk中提供了Unsafe類執行這些操做。另外,你可能想着CAS是經過互斥鎖來實現原子性的,這樣確實能實現,但用這種方式來保證原子性顯示毫無心義。下面一個僞代碼加深對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方法,它裏面實現了基於CAS的自旋。

如今已經瞭解樂觀鎖及CAS相關機制,樂觀鎖避免了悲觀鎖獨佔對象的現象,同時也提升了併發性能,但它也有缺點:

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

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

③ ABA問題。CAS的核心思想是經過比對內存值與預期值是否同樣而判斷內存值是否被改過,但這個判斷邏輯不嚴謹,假如內存值原來是A,後來被一條線程改成B,最後又被改爲了A,則CAS認爲此內存值並無發生改變,但其實是有被其餘線程改過的,這種狀況對依賴過程值的情景的運算結果影響很大。解決的思路是引入版本號,每次變量更新都把版本號加一。

樂觀鎖是對悲觀鎖的改進,雖然它也有缺點,但它確實已經成爲提升併發性能的主要手段,並且jdk中的併發包也大量使用基於CAS的樂觀鎖。

TimSort原理

comparable與comparator的區別

Comparable和Comparator的區別

初次碰到這個問題是以前有一次電話面試,問了一個小時的問題,其中有一個問題就問到Comparable和Comparator的區別,當時沒答出 來。以後是公司入職時候作的一套Java編程題,裏面用JUnit跑用例的時候也用到了Comparator接口,再加上JDK的大量的類包括常見的 String、Byte、Char、Date等都實現了Comparable接口,所以要學習一下這兩個類的區別以及用法。

Comparable

Comparable能夠認爲是一個內比較器,實現了Comparable接口的類有一個特色,就是這些類是能夠和本身比較的,至於具體和另外一個實現了Comparable接口的類如何比較,則依賴compareTo方法的實現,compareTo方法也被稱爲天然比較方法。若是開發者add進入一個Collection的對象想要Collections的sort方法幫你自動進行排序的話,那麼這個對象必須實現Comparable接口。compareTo方法的返回值是int,有三種狀況:

一、比較者大於被比較者(也就是compareTo方法裏面的對象),那麼返回正整數

二、比較者等於被比較者,那麼返回0

三、比較者小於被比較者,那麼返回負整數

寫個很簡單的例子:

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)); } 

運行結果爲:

0
1
-1 

注意一下,前面說實現Comparable接口的類是能夠支持和本身比較的,可是其實代碼裏面Comparable的泛型未必就必定要是Domain,將泛型指定爲String或者指定爲其餘任何任何類型均可以----只要開發者指定了具體的比較算法就行。

Comparator

Comparator能夠認爲是是一個外比較器,我的認爲有兩種狀況能夠使用實現Comparator接口的方式:

一、一個對象不支持本身和本身比較(沒有實現Comparable接口),可是又想對兩個對象進行比較

二、一個對象實現了Comparable接口,可是開發者認爲compareTo方法中的比較方式並非本身想要的那種比較方式

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

一、o1大於o2,返回正整數

二、o1等於o2,返回0

三、o1小於o3,返回負整數

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

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)); } 

看一下運行結果:

0
1
-1 

固然由於泛型指定死了,因此實現Comparator接口的實現類只能是兩個相同的對象(不能一個Domain、一個String)進行比較了,所以實現Comparator接口的實現類通常都會以"待比較的實體類+Comparator"來命名

總結

總結一下,兩種比較器Comparable和Comparator,後者相比前者有以下優勢:

一、若是實現類沒有實現Comparable接口,又想對兩個類進行比較(或者實現類實現了Comparable接口,可是對compareTo方法內的比較算法不滿意),那麼能夠實現Comparator接口,自定義一個比較器,寫比較算法

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

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

解法一:只適合單線程環境(很差)

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的靜態屬性instance中,只有instance爲null的時候才建立一個實例,構造函數私有,確保每次都只建立一個,避免重複建立。
缺點:只在單線程的狀況下正常運行,在多線程的狀況下,就會出問題。例如:當兩個線程同時運行到判斷instance是否爲空的if語句,而且instance確實沒有建立好時,那麼兩個線程都會建立一個實例。

解法二:多線程的狀況能夠用。(懶漢式,很差)

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

註解:在解法一的基礎上加上了同步鎖,使得在多線程的狀況下能夠用。例如:當兩個線程同時想建立實例,因爲在一個時刻只有一個線程能獲得同步鎖,當第一個線程加上鎖之後,第二個線程只能等待。第一個線程發現實例沒有建立,建立之。第一個線程釋放同步鎖,第二個線程才能夠加上同步鎖,執行下面的代碼。因爲第一個線程已經建立了實例,因此第二個線程不須要建立實例。保證在多線程的環境下也只有一個實例。
缺點:每次經過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; } } 

註解:只有當instance爲null時,須要獲取同步鎖,建立一次實例。當實例被建立,則無需試圖加鎖。
缺點:用雙重if判斷,複雜,容易出錯。

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

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

註解:初試化靜態的instance建立一次。若是咱們在Singleton類裏面寫一個靜態的方法不須要建立實例,它仍然會早早的建立一次實例。而下降內存的使用率。

缺點:沒有lazy loading的效果,從而下降內存的使用率。

解法五:靜態內部內。(建議使用)

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

註解:定義一個私有的內部類,在第一次用這個嵌套類時,會建立一個實例。而類型爲SingletonHolder的類,只有在Singleton.getInstance()中調用,因爲私有的屬性,他人沒法使用SingleHolder,不調用Singleton.getInstance()就不會建立實例。
優勢:達到了lazy loading的效果,即按需建立實例。

JVM參數初始值

初始堆大小:1/64內存-Xms 最大堆大小:1/4內存-Xmx

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

Java8的內存分代改進

JAVA 8持久代已經被完全刪除了

取代它的是另外一個內存區域也被稱爲元空間。

元空間 —— 快速入門

  • 它是本地內存中的一部分
  • 最直接的表現就是OOM(內存溢出)問題將不復存在,由於直接利用的是本地內存。
  • 它能夠經過-XX:MetaspaceSize和-XX:MaxMetaspaceSize來進行調整
  • 當到達XX:MetaspaceSize所指定的閾值後會開始進行清理該區域
  • 若是本地空間的內存用盡了會收到java.lang.OutOfMemoryError: Metadata space的錯誤信息。
  • 和持久代相關的JVM參數-XX:PermSize及-XX:MaxPermSize將會被忽略掉,而且在啓動的時候給出警告信息。
  • 充分利用了Java語言規範中的好處:類及相關的元數據的生命週期與類加載器的一致

元空間 —— 內存分配模型絕大多數的類元數據的空間都從本地內存中分配。用來描述類元數據的類也被刪除了,分元數據分配了多個虛擬內存空間給每一個類加載器分配一個內存塊的列表,只進行線性分配。塊的大小取決於類加載器的類型, sun/反射/代理對應的類加載器的塊會小一些。不會單獨回收某個類,若是GC發現某個類加載器再也不存活了,會把相關的空間整個回收掉。這樣減小了碎片,並節省GC掃描和壓縮的時間。

元空間 —— 調優使用-XX:MaxMetaspaceSize參數能夠設置元空間的最大值,默認是沒有上限的,也就是說你的系統內存上限是多少它就是多少。使用-XX:MetaspaceSize選項指定的是元空間的初始大小,若是沒有指定的話,元空間會根據應用程序運行時的須要動態地調整大小。 一旦類元數據的使用量達到了「MaxMetaspaceSize」指定的值,對於無用的類和類加載器,垃圾收集此時會觸發。爲了控制這種垃圾收集的頻率和延遲,合適的監控和調整Metaspace很是有必要。過於頻繁的Metaspace垃圾收集是類和類加載器發生內存泄露的徵兆,同時也說明你的應用程序內存大小不合適,須要調整。

** 快速過一遍JVM的內存結構,JVM中的內存分爲5個虛擬的區域:(程序計數器、

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

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

  • 你的Java程序中所分配的每個對象都須要存儲在內存裏。堆是這些實例化的對象所存儲的地方。是的——都怪new操做符,是它把你的Java堆都佔滿了的!
  • 它由全部線程共享
  • 當堆耗盡的時候,JVM會拋出java.lang.OutOfMemoryError 異常
  • 堆的大小能夠經過JVM選項-Xms和-Xmx來進行調整

堆被分爲:

  • Eden區 —— 新對象或者生命週期很短的對象會存儲在這個區域中,這個區的大小能夠經過-XX:NewSize和-XX:MaxNewSize參數來調整。新生代GC(垃圾回收器)會清理這一區域。
  • Survivor區 —— 那些歷經了Eden區的垃圾回收仍能存活下來的依舊存在引用的對象會待在這個區域。這個區的大小能夠由JVM參數-XX:SurvivorRatio來進行調節。
  • 老年代 —— 那些在歷經了Eden區和Survivor區的屢次GC後仍然存活下來的對象(固然了,是拜那些揮之不去的引用所賜)會存儲在這個區裏。這個區會由一個特殊的垃圾回收器來負責。年老代中的對象的回收是由老年代的GC(major GC)來進行的。

方法區

  • 也被稱爲非堆區域(在HotSpot JVM的實現當中)
  • 它被分爲兩個主要的子區域

持久代 —— 這個區域會 存儲包括類定義,結構,字段,方法(數據及代碼)以及常量在內的類相關數據。它能夠經過-XX:PermSize及 -XX:MaxPermSize來進行調節。若是它的空間用完了,會致使java.lang.OutOfMemoryError: PermGen space的異常。

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

JVM棧

  • 和Java類中的方法密切相關
  • 它會存儲局部變量以及方法調用的中間結果及返回值
  • Java中的每一個線程都有本身專屬的棧,這個棧是別的線程沒法訪問的。
  • 能夠經過JVM選項-Xss來進行調整

本地棧

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

PC寄存器

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

好吧,這就是JVM內存分區的基礎知識了。如今再說說持久代這個話題吧。

對Java內存模型的理解以及其在併發當中的做用

概述

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

gc內存模型

java與c++之間有一堵由內存動態分配與垃圾收集技術所圍成的「高牆」。牆外面的人想進去,牆裏面的人想出來。java在執行java程序的過程當中會把它管理的內存劃分若干個不一樣功能的數據管理區域。如圖:

 
img
 
img
 
img

hotspot中的gc內存模型

總體上。分爲三部分:棧,堆,程序計數器,他們每一部分有其各自的用途;虛擬機棧保存着每一條線程的執行程序調用堆棧;堆保存着類對象、數組的具體信息;程序計數器保存着每一條線程下一次執行指令位置。這三塊區域中棧和程序計數器是線程私有的。也就是說每個線程擁有其獨立的棧和程序計數器。咱們能夠看看具體結構:

虛擬機/本地方法棧

在棧中,會爲每個線程建立一個棧。線程越多,棧的內存使用越大。對於每個線程棧。當一個方法在線程中執行的時候,會在線程棧中建立一個棧幀(stack frame),用於存放該方法的上下文(局部變量表、操做數棧、方法返回地址等等)。每個方法從調用到執行完畢的過程,就是對應着一個棧幀入棧出棧的過程。

本地方法棧與虛擬機棧發揮的做用是相似的,他們之間的區別不過是虛擬機棧爲虛擬機執行java(字節碼)服務的,而本地方法棧是爲虛擬機執行native方法服務的。

方法區/堆

在hotspot的實現中,方法區就是在堆中稱爲永久代的堆區域。幾乎全部的對象/數組的內存空間都在堆上(有少部分在棧上)。在gc管理中,將虛擬機堆分爲永久代、老年代、新生代。經過名字咱們能夠知道一個對象新建通常在新生代。通過幾輪的gc。還存活的對象會被移到老年代。永久代用來保存類信息、代碼段等幾乎不會變的數據。堆中的全部數據是線程共享的。

  • 新生代:應爲gc具體實現的優化的緣由。hotspot又將新生代劃分爲一個eden區和兩個survivor區。每一次新生代gc時候。只用到一個eden區,一個survivor區。新生代通常的gc策略爲mark-copy。
  • 老年代:當新生代中的對象通過若干輪gc後還存活/或survisor在gc內存不夠的時候。會把當前對象移動到老年代。老年代通常gc策略爲mark-compact。
  • 永久代:永久代通常能夠不參與gc。應爲其中保存的是一些代碼/常量數據/類信息。在永久代gc。清楚的是類信息以及常量池。

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

程序計數器

如同其名稱同樣。程序計數器用於記錄某個線程下次執行指令位置。程序計數器也是線程私有的。

併發內存模型

java試圖定義一個Java內存模型(Java memory model jmm)來屏蔽掉各類硬件/操做系統的內存訪問差別,以實現讓java程序在各個平臺下都能達到一致的內存訪問效果。java內存模型主要目標是定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。模型圖以下:

 
img

java併發內存模型以及內存操做規則

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

關於主內存與工做內存之間的交互協議,即一個變量如何從主內存拷貝到工做內存。如何從工做內存同步到主內存中的實現細節。java內存模型定義了8種操做來完成。這8種操做每一種都是原子操做。8種操做以下:

  • lock(鎖定):做用於主內存,它把一個變量標記爲一條線程獨佔狀態;
  • unlock(解鎖):做用於主內存,它將一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其餘線程鎖定;
  • read(讀取):做用於主內存,它把變量值從主內存傳送到線程的工做內存中,以便隨後的load動做使用;
  • load(載入):做用於工做內存,它把read操做的值放入工做內存中的變量副本中;
  • use(使用):做用於工做內存,它把工做內存中的值傳遞給執行引擎,每當虛擬機遇到一個須要使用這個變量的指令時候,將會執行這個動做;
  • assign(賦值):做用於工做內存,它把從執行引擎獲取的值賦值給工做內存中的變量,每當虛擬機遇到一個給變量賦值的指令時候,執行該操做;
  • store(存儲):做用於工做內存,它把工做內存中的一個變量傳送給主內存中,以備隨後的write操做使用;
  • write(寫入):做用於主內存,它把store傳送值放到主內存中的變量中。

Java內存模型還規定了執行上述8種基本操做時必須知足以下規則:

  • 不容許read和load、store和write操做之一單獨出現,以上兩個操做必須按順序執行,但沒有保證必須連續執行,也就是說,read與load之間、store與write之間是可插入其餘指令的。
  • 不容許一個線程丟棄它的最近的assign操做,即變量在工做內存中改變了以後必須把該變化同步回主內存。
  • 不容許一個線程無緣由地(沒有發生過任何assign操做)把數據從線程的工做內存同步回主內存中。
  • 一個新的變量只能從主內存中「誕生」,不容許在工做內存中直接使用一個未被初始化(load或assign)的變量,換句話說就是對一個變量實施use和store操做以前,必須先執行過了assign和load操做。
  • 一個變量在同一個時刻只容許一條線程對其執行lock操做,但lock操做能夠被同一個條線程重複執行屢次,屢次執行lock後,只有執行相同次數的unlock操做,變量纔會被解鎖。
  • 若是對一個變量執行lock操做,將會清空工做內存中此變量的值,在執行引擎使用這個變量前,須要從新執行load或assign操做初始化變量的值。
  • 若是一個變量實現沒有被lock操做鎖定,則不容許對它執行unlock操做,也不容許去unlock一個被其餘線程鎖定的變量。
  • 對一個變量執行unlock操做以前,必須先把此變量同步回主內存(執行store和write操做)。

volatile型變量的特殊規則

關鍵字volatile能夠說是Java虛擬機提供的最輕量級的同步機制,可是它並不容易徹底被正確、完整的理解,以致於許多程序員都不習慣去使用它,遇到須要處理多線程的問題的時候一概使用synchronized來進行同步。瞭解volatile變量的語義對後面瞭解多線程操做的其餘特性頗有意義。Java內存模型對volatile專門定義了一些特殊的訪問規則,當一個變量被定義成volatile以後,他將具有兩種特性:

  • 保證此變量對全部線程的可見性。第一保證此變量對全部線程的可見性,這裏的「可見性」是指當一條線程修改了這個變量的值,新值對於其餘線程來講是能夠當即得知的。而普通變量是作不到這點,普通變量的值在線程在線程間傳遞均須要經過住內存來完成,例如,線程A修改一個普通變量的值,而後向主內存進行會寫,另一個線程B在線程A回寫完成了以後再從主內存進行讀取操做,新變量值纔會對線程B可見。另外,java裏面的運算並不是原子操做,會致使volatile變量的運算在併發下同樣是不安全的。
  • 禁止指令重排序優化。普通的變量僅僅會保證在該方法的執行過程當中全部依賴賦值結果的地方都能得到正確的結果,而不能保證變量賦值操做的順序與程序中的執行順序一致,在單線程中,咱們是沒法感知這一點的。

因爲volatile變量只能保證可見性,在不符合如下兩條規則的運算場景中,咱們仍然要經過加鎖來保證原子性。

  • 1.運算結果並不依賴變量的當前值,或者可以確保只有單一的線程修改變量的值。
  • 2.變量不須要與其餘的狀態比阿尼浪共同參與不變約束。

原子性、可見性與有序性

Java內存模型是圍繞着在併發過程當中如何處理原子性、可見性和有序性這三個特徵來創建的,咱們逐個看下哪些操做實現了這三個特性。

  • 原子性(Atomicity):由Java內存模型來直接保證的原子性變量包括read、load、assign、use、store和write,咱們大體能夠認爲基本數據類型的訪問讀寫是具有原子性的。若是應用場景須要一個更大方位的原子性保證,Java內存模型還提供了lock和unlock操做來知足這種需求,儘管虛擬機未把lock和unlock操做直接開放給用戶使用,可是卻提供了更高層次的字節碼指令monitorenter和monitorexit來隱式的使用這兩個操做,這兩個字節碼指令反應到Java代碼中就是同步塊--synchronized關鍵字,所以在synchronized塊之間的操做也具有原子性。
  • 可見性(Visibility):可見性是指當一個線程修改了共享變量的值,其餘線程可以當即得知這個修改。上文在講解volatile變量的時候咱們已詳細討論過這一點。Java內存模型是經過在變量修改後將新值同步回主內存,在變量讀取前從主內存刷新變量值這種依賴主內存做爲傳遞媒介的方式來實現可見性的,不管是普通變量仍是volatile變量都是如此,普通變量與volatile變量的區別是,volatile的特殊規則保證了新值能當即同步到主內存,以及每次使用前當即從主內存刷新。所以,能夠說volatile保證了多線程操做時變量的可見性,而普通變量則不能保證這一點。除了volatile以外,Java還有兩個關鍵字能實現可見性,即synchronized和final.同步快的可見性是由「對一個變量執行unlock操做前,必須先把此變量同步回主內存」這條規則得到的,而final關鍵字的可見性是指:被final修飾的字段在構造器中一旦初始化完成,而且構造器沒有把"this"的引用傳遞出去,那麼在其餘線程中就能看見final字段的值。
  • 有序性(Ordering):Java內存模型的有序性在前面講解volatile時也詳細的討論過了,Java程序中自然的有序性能夠總結爲一句話:若是在本線程內觀察,全部的操做都是有序的:若是在一個線程中觀察另一個線程,全部的線程操做都是無序的。前半句是指「線程內表現爲串行的語義」,後半句是指「指令重排序」現象和「工做內存與主內存同步延遲」現象。Java語言提供了volatile和synchronized兩個關鍵字來保證線程之間操做的有序性,volatile關鍵字自己就包含了禁止指令重排序的語義,而synchronized則是由「一個變量在同一個時刻只容許一條線程對其進行lock操做」這條規則得到的,這條規則決定了持有同一個鎖的兩個同步塊只能串行的進入。

Arrays和Collections 對於sort的不一樣實現原理

一、Arrays.sort()
該算法是一個通過調優的快速排序,此算法在不少數據集上提供N*log(N)的性能,這致使其餘快速排序會下降二次型性能。

二、Collections.sort()
該算法是一個通過修改的合併排序算法(其中,若是低子列表中的最高元素效益高子列表中的最低元素,則忽略合併)。此算法可提供保證的N*log(N)的性能,此實現將指定列表轉儲到一個數組中,而後再對數組進行排序,在重置數組中相應位置處每一個元素的列表上進行迭代。這避免了因爲試圖原地對連接列表進行排序而產生的n2log(n)性能。

Java中object經常使用方法

一、clone()
二、equals()
三、finalize()
四、getclass()
五、hashcode()
六、notify()
七、notifyAll()
八、toString()

對於Java中多態的理解

所謂多態就是指程序中定義的引用變量所指向的具體類型和經過該引用變量發出的方法調用在編程時並不肯定,而是在程序運行期間才肯定,即一個引用變量到底會指向哪一個類的實例對象,該引用變量發出的方法調用究竟是哪一個類中實現的方法,必須在由程序運行期間才能決定。由於在程序運行時才肯定具體的類,這樣,不用修改源程序代碼,就可讓引用變量綁定到各類不一樣的類實現上,從而致使該引用調用的具體方法隨之改變,即不修改程序代碼就能夠改變程序運行時所綁定的具體代碼,讓程序能夠選擇多個運行狀態,這就是多態性。

多態的定義:指容許不一樣類的對象對同一消息作出響應。即同一消息能夠根據發送對象的不一樣而採用多種不一樣的行爲方式。(發送消息就是函數調用)

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

繼承:在多態中必須存在有繼承關係的子類和父類。

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

父類引用指向子類對象:在多態中須要將子類的引用賦給父類對象,只有這樣該引用纔可以具有技能調用父類的方法和子類的方法。

實現多態的技術稱爲:動態綁定(dynamic binding),是指在執行期間判斷所引用對象的實際類型,根據其實際的類型調用其相應的方法。

多態的做用:消除類型之間的耦合關係。

Java序列化與反序列化是什麼?爲何須要序列化與反序列化?如何實現Java序列化與反序列化

spring AOP 實現原理

什麼是AOP

AOP(Aspect-OrientedProgramming,面向方面編程),能夠說是OOP(Object-Oriented Programing,面向對象編程)的補充和完善。OOP引入封裝、繼承和多態性等概念來創建一種對象層次結構,用以模擬公共行爲的一個集合。當咱們須要爲分散的對象引入公共行爲的時候,OOP則顯得無能爲力。也就是說,OOP容許你定義從上到下的關係,但並不適合定義從左到右的關係。例如日誌功能。日誌代碼每每水平地散佈在全部對象層次中,而與它所散佈到的對象的核心功能毫無關係。對於其餘類型的代碼,如安全性、異常處理和透明的持續性也是如此。這種散佈在各處的無關的代碼被稱爲橫切(cross-cutting)代碼,在OOP設計中,它致使了大量代碼的重複,而不利於各個模塊的重用。

而AOP技術則偏偏相反,它利用一種稱爲「橫切」的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其名爲「Aspect」,即方面。所謂「方面」,簡單地說,就是將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降模塊間的耦合度,並有利於將來的可操做性和可維護性。AOP表明的是一個橫向的關係,若是說「對象」是一個空心的圓柱體,其中封裝的是對象的屬性和行爲;那麼面向方面編程的方法,就彷彿一把利刃,將這些空心圓柱體剖開,以得到其內部的消息。而剖開的切面,也就是所謂的「方面」了。而後它又以巧奪天功的妙手將這些剖開的切面復原,不留痕跡。

使用「橫切」技術,AOP把軟件系統分爲兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特色是,他們常常發生在覈心關注點的多處,而各處都基本類似。好比權限認證、日誌、事務處理。Aop 的做用在於分離系統中的各類關注點,將核心關注點和橫切關注點分離開來。正如Avanade公司的高級方案構架師Adam Magee所說,AOP的核心思想就是「將應用程序中的商業邏輯同對其提供支持的通用服務進行分離。」

實現AOP的技術,主要分爲兩大類:一是採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行爲的執行;二是採用靜態織入的方式,引入特定的語法建立「方面」,從而使得編譯器能夠在編譯期間織入有關「方面」的代碼。

AOP使用場景

AOP用來封裝橫切關注點,具體能夠在下面的場景中使用:

Authentication 權限

Caching 緩存

Context passing 內容傳遞

Error handling 錯誤處理

Lazy loading 懶加載

Debugging  調試

logging, tracing, profiling and monitoring 記錄跟蹤 優化 校準

Performance optimization 性能優化

Persistence  持久化

Resource pooling 資源池

Synchronization 同步

Transactions 事務

AOP相關概念

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

鏈接點(Joinpoint): 程序執行過程當中明確的點,如方法的調用或特定的異常被拋出。

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

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

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

目標對象(Target Object): 包含鏈接點的對象。也被稱做被通知或被代理對象。POJO

AOP代理(AOP Proxy): AOP框架建立的對象,包含通知。 在Spring中,AOP代理能夠是JDK動態代理或者CGLIB代理。

織入(Weaving): 組裝方面來建立一個被通知對象。這能夠在編譯時完成(例如使用AspectJ編譯器),也能夠在運行時完成。Spring和其餘純Java AOP框架同樣,在運行時完成織入。

Spring AOP組件

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

 
img

如何使用Spring AOP

能夠經過配置文件或者編程的方式來使用Spring AOP。

配置能夠經過xml文件來進行,大概有四種方式:

\1. 配置ProxyFactoryBean,顯式地設置advisors, advice, target等

  1. 配置AutoProxyCreator,這種方式下,仍是如之前同樣使用定義的bean,可是從容器中得到的其實已是代理對象
  2. 經過<aop:config>來配置 
  3. 經過<aop: aspectj-autoproxy>來配置,使用AspectJ的註解來標識通知及切入點 

也能夠直接使用ProxyFactory來以編程的方式使用Spring AOP,經過ProxyFactory提供的方法能夠設置target對象, advisor等相關配置,最終經過 getProxy()方法來獲取代理對象

具體使用的示例能夠google. 這裏略去

Spring AOP代理對象的生成

Spring提供了兩種方式來生成代理對象: JDKProxy和Cglib,具體使用哪一種方式生成由AopProxyFactory根據AdvisedSupport對象的配置來決定。默認的策略是若是目標類是接口,則使用JDK動態代理技術,不然使用Cglib來生成代理。下面咱們來研究一下Spring如何使用JDK來生成代理對象,具體的生成代碼放在JdkDynamicAopProxy這個類中,直接上相關代碼:

友情連接 :Spring AOP 實現原理

/** * <ol> * <li>獲取代理類要實現的接口,除了Advised對象中配置的,還會加上SpringProxy, Advised(opaque=false) * <li>檢查上面獲得的接口中有沒有定義 equals或者hashcode的接口 * <li>調用Proxy.newProxyInstance建立代理對象 * </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); } 

那這個其實很明瞭,註釋上我也已經寫清楚了,再也不贅述。

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

咱們知道InvocationHandler是JDK動態代理的核心,生成的代理對象的方法調用都會委託到InvocationHandler.invoke()方法。而經過JdkDynamicAopProxy的簽名咱們能夠看到這個類其實也實現了InvocationHandler,下面咱們就經過分析這個類中實現的invoke()方法來具體看下Spring AOP是如何織入切面的。

 
  

Servlet 工做原理

Servlet 工做原理解析

從 Servlet 容器提及

前面說了 Servlet 容器做爲一個獨立發展的標準化產品,目前它的種類不少,可是它們都有本身的市場定位,很難說誰優誰劣,各有特色。例如如今比較流行的 Jetty,在定製化和移動領域有不錯的發展,咱們這裏仍是以你們最爲熟悉 Tomcat 爲例來介紹 Servlet 容器如何管理 Servlet。Tomcat 自己也很複雜,咱們只從 Servlet 與 Servlet 容器的接口部分開始介紹,關於 Tomcat 的詳細介紹能夠參考個人另一篇文章《 Tomcat 系統架構與模式設計分析》。

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

圖 1 . Tomcat 容器模型

從上圖能夠看出 Tomcat 的容器分爲四個等級,真正管理 Servlet 的容器是 Context 容器,一個 Context 對應一個 Web 工程,在 Tomcat 的配置文件中能夠很容易發現這一點,以下:

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

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

Servlet 容器的啓動過程

Tomcat7 也開始支持嵌入式功能,增長了一個啓動類 org.apache.catalina.startup.Tomcat。建立一個實例對象並調用 start 方法就能夠很容易啓動 Tomcat,咱們還能夠經過這個對象來增長和修改 Tomcat 的配置參數,如能夠動態增長 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 的代碼是建立一個 Tomcat 實例並新增一個 Web 應用,而後啓動 Tomcat 並調用其中的一個 HelloWorldExample Servlet,看有沒有正確返回預期的數據。

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; } 

前面已經介紹了一個 Web 應用對應一個 Context 容器,也就是 Servlet 運行時的 Servlet 容器,添加一個 Web 應用時將會建立一個 StandardContext 容器,而且給這個 Context 容器設置必要的參數,url 和 path 分別表明這個應用在 Tomcat 中的訪問路徑和這個應用實際的物理路徑,這個兩個參數與清單 1 中的兩個參數是一致的。其中最重要的一個配置是 ContextConfig,這個類將會負責整個 Web 應用配置的解析工做,後面將會詳細介紹。最後將這個 Context 容器加到父容器 Host 中。

接下去將會調用 Tomcat 的 start 方法啓動 Tomcat,若是你清楚 Tomcat 的系統架構,你會容易理解 Tomcat 的啓動邏輯,Tomcat 的啓動邏輯是基於觀察者模式設計的,全部的容器都會繼承 Lifecycle 接口,它管理者容器的整個生命週期,全部容器的的修改和狀態的改變都會由它去通知已經註冊的觀察者(Listener),關於這個設計模式能夠參考《 Tomcat 的系統架構與設計模式,第二部分:設計模式》。Tomcat 啓動的時序圖能夠用圖 2 表示。

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

上圖描述了 Tomcat 啓動過程當中,主要類之間的時序關係,下面咱們將會重點關注添加 examples 應用所對應的 StandardContext 容器的啓動過程。

當 Context 容器初始化狀態設爲 init 時,添加在 Contex 容器的 Listener 將會被調用。ContextConfig 繼承了 LifecycleListener 接口,它是在調用清單 3 時被加入到 StandardContext 容器中。ContextConfig 類會負責整個 Web 應用的配置文件的解析工做。

ContextConfig 的 init 方法將會主要完成如下工做:

  1. 建立用於解析 xml 配置文件的 contextDigester 對象
  2. 讀取默認 context.xml 配置文件,若是存在解析它
  3. 讀取默認 Host 配置文件,若是存在解析它
  4. 讀取默認 Context 自身的配置文件,若是存在解析它
  5. 設置 Context 的 DocBase

ContextConfig 的 init 方法完成後,Context 容器的會執行 startInternal 方法,這個方法啓動邏輯比較複雜,主要包括以下幾個部分:

  1. 建立讀取資源文件的對象
  2. 建立 ClassLoader 對象
  3. 設置應用的工做目錄
  4. 啓動相關的輔助類如:logger、realm、resources 等
  5. 修改啓動狀態,通知感興趣的觀察者(Web 應用的配置)
  6. 子容器的初始化
  7. 獲取 ServletContext 並設置必要的參數
  8. 初始化「load on startup」的 Servlet

Web 應用的初始化工做

Web 應用的初始化工做是在 ContextConfig 的 configureStart 方法中實現的,應用的初始化主要是要解析 web.xml 文件,這個文件描述了一個 Web 應用的關鍵信息,也是一個 Web 應用的入口。

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

接下去將會將 WebXml 對象中的屬性設置到 Context 容器中,這裏包括建立 Servlet 對象、filter、listener 等等。這段代碼在 WebXml 的 configureContext 方法中。下面是解析 Servlet 的代碼片斷:

清單 4. 建立 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 開發標準,不該該強耦合在 Tomcat 中。

除了將 Servlet 包裝成 StandardWrapper 並做爲子容器添加到 Context 中,其它的全部 web.xml 屬性都被解析到 Context 中,因此說 Context 容器纔是真正運行 Servlet 的 Servlet 容器。一個 Web 應用對應一個 Context 容器,容器的配置屬性由應用的 web.xml 指定,這樣咱們就能理解 web.xml 到底起到什麼做用了。

回頁首

建立 Servlet 實例

前面已經完成了 Servlet 的解析工做,而且被包裝成 StandardWrapper 添加在 Context 容器中,可是它仍然不能爲咱們工做,它尚未被實例化。下面咱們將介紹 Servlet 對象是如何建立的,以及如何被初始化的。

建立 Servlet 對象

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

建立 Servlet 實例的方法是從 Wrapper. loadServlet 開始的。loadServlet 方法要完成的就是獲取 servletClass 而後把它交給 InstanceManager 去建立一個基於 servletClass.class 的對象。若是這個 Servlet 配置了 jsp-file,那麼這個 servletClass 就是 conf/web.xml 中定義的 org.apache.jasper.servlet.JspServlet 了。

建立 Servlet 對象的相關類結構圖以下:

圖 3. 建立 Servlet 對象的相關類結構

初始化 Servlet

初始化 Servlet 在 StandardWrapper 的 initServlet 方法中,這個方法很簡單就是調用 Servlet 的 init 的方法,同時把包裝了 StandardWrapper 對象的 StandardWrapperFacade 做爲 ServletConfig 傳給 Servlet。Tomcat 容器爲什麼要傳 StandardWrapperFacade 給 Servlet 對象將在後面作詳細解析。

若是該 Servlet 關聯的是一個 jsp 文件,那麼前面初始化的就是 JspServlet,接下去會模擬一次簡單請求,請求調用這個 jsp 文件,以便編譯這個 jsp 文件爲 class,並初始化這個 class。

這樣 Servlet 對象就初始化完成了,事實上 Servlet 從被 web.xml 中解析到完成初始化,這個過程很是複雜,中間有不少過程,包括各類容器狀態的轉化引發的監聽事件的觸發、各類訪問權限的控制和一些不可預料的錯誤發生的判斷行爲等等。咱們這裏只抓了一些關鍵環節進行闡述,試圖讓你們有個整體脈絡。

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

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

回頁首

Servlet 體系結構

咱們知道 Java Web 應用是基於 Servlet 規範運轉的,那麼 Servlet 自己又是如何運轉的呢?爲什麼要設計這樣的體系結構。

圖 5.Servlet 頂層類關聯圖

從上圖能夠看出 Servlet 規範就是基於這幾個類運轉的,與 Servlet 主動關聯的是三個類,分別是 ServletConfig、ServletRequest 和 ServletResponse。這三個類都是經過容器傳遞給 Servlet 的,其中 ServletConfig 是在 Servlet 初始化時就傳給 Servlet 了,然後兩個是在請求達到時調用 Servlet 時傳遞過來的。咱們很清楚 ServletRequest 和 ServletResponse 在 Servlet 運行的意義,可是 ServletConfig 和 ServletContext 對 Servlet 有何價值?仔細查看 ServletConfig 接口中聲明的方法發現,這些方法都是爲了獲取這個 Servlet 的一些配置屬性,而這些配置屬性可能在 Servlet 運行時被用到。而 ServletContext 又是幹什麼的呢? Servlet 的運行模式是一個典型的「握手型的交互式」運行模式。所謂「握手型的交互式」就是兩個模塊爲了交換數據一般都會準備一個交易場景,這個場景一直跟隨個這個交易過程直到這個交易完成爲止。這個交易場景的初始化是根據此次交易對象指定的參數來定製的,這些指定參數一般就會是一個配置類。因此對號入座,交易場景就由 ServletContext 來描述,而定製的參數集合就由 ServletConfig 來描述。而 ServletRequest 和 ServletResponse 就是要交互的具體對象了,它們一般都是做爲運輸工具來傳遞交互結果。

ServletConfig 是在 Servlet init 時由容器傳過來的,那麼 ServletConfig 究竟是個什麼對象呢?

下圖是 ServletConfig 和 ServletContext 在 Tomcat 容器中的類關係圖。

圖 6. ServletConfig 在容器中的類關聯圖

上圖能夠看出 StandardWrapper 和 StandardWrapperFacade 都實現了 ServletConfig 接口,而 StandardWrapperFacade 是 StandardWrapper 門面類。因此傳給 Servlet 的是 StandardWrapperFacade 對象,這個類可以保證從 StandardWrapper 中拿到 ServletConfig 所規定的數據,而又不把 ServletConfig 不關心的數據暴露給 Servlet。

一樣 ServletContext 也與 ServletConfig 有相似的結構,Servlet 中能拿到的 ServletContext 的實際對象也是 ApplicationContextFacade 對象。ApplicationContextFacade 一樣保證 ServletContex 只能從容器中拿到它該拿的數據,它們都起到對數據的封裝做用,它們使用的都是門面設計模式。

經過 ServletContext 能夠拿到 Context 容器中一些必要信息,好比應用的工做路徑,容器支持的 Servlet 最小版本等。

Servlet 中定義的兩個 ServletRequest 和 ServletResponse 它們實際的對象又是什麼呢?,咱們在建立本身的 Servlet 類時一般使用的都是 HttpServletRequest 和 HttpServletResponse,它們繼承了 ServletRequest 和 ServletResponse。爲什麼 Context 容器傳過來的 ServletRequest、ServletResponse 能夠被轉化爲 HttpServletRequest 和 HttpServletResponse 呢?

圖 7.Request 相關類結構圖

上圖是 Tomcat 建立的 Request 和 Response 的類結構圖。Tomcat 一接受到請求首先將會建立 org.apache.coyote.Request 和 org.apache.coyote.Response,這兩個類是 Tomcat 內部使用的描述一次請求和相應的信息類它們是一個輕量級的類,它們做用就是在服務器接收到請求後,通過簡單解析將這個請求快速的分配給後續線程去處理,因此它們的對象很小,很容易被 JVM 回收。接下去當交給一個用戶線程去處理這個請求時又建立 org.apache.catalina.connector. Request 和 org.apache.catalina.connector. Response 對象。這兩個對象一直穿越整個 Servlet 容器直到要傳給 Servlet,傳給 Servlet 的是 Request 和 Response 的門面類 RequestFacade 和 RequestFacade,這裏使用門面模式與前面同樣都是基於一樣的目的——封裝容器中的數據。一次請求對應的 Request 和 Response 的類轉化以下圖所示:

圖 8.Request 和 Response 的轉變過程

回頁首

Servlet 如何工做

咱們已經清楚了 Servlet 是如何被加載的、Servlet 是如何被初始化的,以及 Servlet 的體系結構,如今的問題就是它是如何被調用的。

當用戶從瀏覽器向服務器發起一個請求,一般會包含以下信息:http://hostname: port /contextpath/servletpath,hostname 和 port 是用來與服務器創建 TCP 鏈接,然後面的 URL 纔是用來選擇服務器中那個子容器服務用戶的請求。那服務器是如何根據這個 URL 來達到正確的 Servlet 容器中的呢?

Tomcat7.0 中這件事很容易解決,由於這種映射工做有專門一個類來完成的,這個就是 org.apache.tomcat.util.http.mapper,這個類保存了 Tomcat 的 Container 容器中的全部子容器的信息,當 org.apache.catalina.connector. Request 類在進入 Container 容器以前,mapper 將會根據此次請求的 hostnane 和 contextpath 將 host 和 context 容器設置到 Request 的 mappingData 屬性中。因此當 Request 進入 Container 容器以前,它要訪問那個子容器這時就已經肯定了。

圖 9.Request 的 Mapper 類關係圖

可能你有疑問,mapper 中怎麼會有容器的完整關係,這要回到圖 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 類做爲一個監聽者加到整個 Container 容器中的每一個子容器中,這樣只要任何一個容器發生變化,MapperListener 都將會被通知,相應的保存容器關係的 MapperListener 的 mapper 屬性也會修改。for 循環中就是將 host 及下面的子容器註冊到 mapper 中。

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

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

接下去就要執行 Servlet 的 service 方法了,一般狀況下,咱們本身定義的 servlet 並非直接去實現 javax.servlet.servlet 接口,而是去繼承更簡單的 HttpServlet 類或者 GenericServlet 類,咱們能夠有選擇的覆蓋相應方法去實現咱們要完成的工做。

Servlet 的確已經可以幫咱們完成全部的工做了,可是如今的 web 應用不多有直接將交互所有頁面都用 servlet 來實現,而是採用更加高效的 MVC 框架來實現。這些 MVC 框架基本的原理都是將全部的請求都映射到一個 Servlet,而後去實現 service 方法,這個方法也就是 MVC 框架的入口。

當 Servlet 從 Servlet 容器中移除時,也就代表該 Servlet 的生命週期結束了,這時 Servlet 的 destroy 方法將被調用,作一些掃尾工做。

回頁首

Session 與 Cookie

前面咱們已經說明了 Servlet 如何被調用,咱們基於 Servlet 來構建應用程序,那麼咱們能從 Servlet 得到哪些數據信息呢?

Servlet 可以給咱們提供兩部分數據,一個是在 Servlet 初始化時調用 init 方法時設置的 ServletConfig,這個類基本上含有了 Servlet 自己和 Servlet 所運行的 Servlet 容器中的基本信息。根據前面的介紹 ServletConfig 的實際對象是 StandardWrapperFacade,到底能得到哪些容器信息能夠看看這類提供了哪些接口。還有一部分數據是由 ServletRequest 類提供,它的實際對象是 RequestFacade,從提供的方法中發現主要是描述此次請求的 HTTP 協議的信息。因此要掌握 Servlet 的工做方式必需要很清楚 HTTP 協議,若是你還不清楚趕忙去找一些參考資料。關於這一塊還有一個讓不少人迷惑的 Session 與 Cookie。

Session 與 Cookie 無論是對 Java Web 的熟練使用者仍是初學者來講都是一個使人頭疼的東西。Session 與 Cookie 的做用都是爲了保持訪問用戶與後端服務器的交互狀態。它們有各自的優勢也有各自的缺陷。然而具備諷刺意味的是它們優勢和它們的使用場景又是矛盾的,例如使用 Cookie 來傳遞信息時,隨着 Cookie 個數的增多和訪問量的增長,它佔用的網絡帶寬也很大,試想假如 Cookie 佔用 200 個字節,若是一天的 PV 有幾億的時候,它要佔用多少帶寬。因此大訪問量的時候但願用 Session,可是 Session 的致命弱點是不容易在多臺服務器之間共享,因此這也限制了 Session 的使用。

無論 Session 和 Cookie 有什麼不足,咱們仍是要用它們。下面詳細講一下,Session 如何基於 Cookie 來工做。實際上有三種方式能可讓 Session 正常工做:

  1. 基於 URL Path Parameter,默認就支持
  2. 基於 Cookie,若是你沒有修改 Context 容器個 cookies 標識的話,默認也是支持的
  3. 基於 SSL,默認不支持,只有 connector.getAttribute("SSLEnabled") 爲 TRUE 時才支持

第一種狀況下,當瀏覽器不支持 Cookie 功能時,瀏覽器會將用戶的 SessionCookieName 重寫到用戶請求的 URL 參數中,它的傳遞格式如 /path/Servlet;name=value;name2=value2? Name3=value3,其中「Servlet;」後面的 K-V 對就是要傳遞的 Path Parameters,服務器會從這個 Path Parameters 中拿到用戶配置的 SessionCookieName。關於這個 SessionCookieName,若是你在 web.xml 中配置 session-config 配置項的話,其 cookie-config 下的 name 屬性就是這個 SessionCookieName 值,若是你沒有配置 session-config 配置項,默認的 SessionCookieName 就是你們熟悉的「JSESSIONID」。接着 Request 根據這個 SessionCookieName 到 Parameters 拿到 Session ID 並設置到 request.setRequestedSessionId 中。

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

若是是第三種狀況的話將會根據 javax.servlet.request.ssl_session 屬性值設置 Session ID。

有了 Session ID 服務器端就能夠建立 HttpSession 對象了,第一次觸發是經過 request. getSession() 方法,若是當前的 Session ID 尚未對應的 HttpSession 對象那麼就建立一個新的,並將這個對象加到 org.apache.catalina. Manager 的 sessions 容器中保存,Manager 類將管理全部 Session 的生命週期,Session 過時將被回收,服務器關閉,Session 將被序列化到磁盤等。只要這個 HttpSession 對象存在,用戶就能夠根據 Session ID 來獲取到這個對象,也就達到了狀態的保持。

圖 11.Session 相關類圖

上從圖中能夠看出從 request.getSession 中獲取的 HttpSession 對象其實是 StandardSession 對象的門面對象,這與前面的 Request 和 Servlet 是同樣的原理。下圖是 Session 工做的時序圖:

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

還有一點與 Session 關聯的 Cookie 與其它 Cookie 沒有什麼不一樣,這個配置的配置能夠經過 web.xml 中的 session-config 配置項來指定。

回頁首

Servlet 中的 Listener

整個 Tomcat 服務器中 Listener 使用的很是普遍,它是基於觀察者模式設計的,Listener 的設計對開發 Servlet 應用程序提供了一種快捷的手段,可以方便的從另外一個縱向維度控制程序和數據。目前 Servlet 中提供了 5 種兩類事件的觀察者接口,它們分別是:4 個 EventListeners 類型的,ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttributeListener 和 2 個 LifecycleListeners 類型的,ServletContextListener、HttpSessionListener。以下圖所示:

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

它們基本上涵蓋了整個 Servlet 生命週期中,你感興趣的每種事件。這些 Listener 的實現類能夠配置在 web.xml 中的 <listener> 標籤中。固然也能夠在應用程序中動態添加 Listener,須要注意的是 ServletContextListener 在容器啓動以後就不能再添加新的,由於它所監聽的事件已經不會再出現。掌握這些 Listener 的使用,可以讓咱們的程序設計的更加靈活

Java NIO和IO的區別

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

複製代碼代碼以下:

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

面向流與面向緩衝

Java NIO和IO之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。 Java IO面向流意味着每次從流中讀一個或多個字節,直至讀取全部字節,它們沒有被緩存在任何地方。此外,它不能先後移動流中的數據。若是須要先後移動從流中讀取的數據,須要先將它緩存到一個緩衝區。 Java NIO的緩衝導向方法略有不一樣。數據讀取到一個它稍後處理的緩衝區,須要時可在緩衝區中先後移動。這就增長了處理過程當中的靈活性。可是,還須要檢查是否該緩衝區中包含全部您須要處理的數據。並且,需確保當更多的數據讀入緩衝區時,不要覆蓋緩衝區裏還沒有處理的數據。

阻塞與非阻塞IO

Java IO的各類流是阻塞的。這意味着,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據徹底寫入。該線程在此期間不能再幹任何事情了。 Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,可是它僅能獲得目前可用的數據,若是目前沒有數據可用時,就什麼都不會獲取。而不是保持線程阻塞,因此直至數據變的能夠讀取以前,該線程能夠繼續作其餘的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不須要等待它徹底寫入,這個線程同時能夠去作別的事情。 線程一般將非阻塞IO的空閒時間用於在其它通道上執行IO操做,因此一個單獨的線程如今能夠管理多個輸入和輸出通道(channel)。

選擇器(Selectors)

Java NIO的選擇器容許一個單獨的線程來監視多個輸入通道,你能夠註冊多個通道使用一個選擇器,而後使用一個單獨的線程來「選擇」通道:這些通道里已經有能夠處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。

NIO和IO如何影響應用程序的設計

不管您選擇IO或NIO工具箱,可能會影響您應用程序設計的如下幾個方面:

1.對NIO或IO類的API調用。
2.數據處理。
3.用來處理數據的線程數。

API調用

固然,使用NIO的API調用時看起來與使用IO時有所不一樣,但這並不意外,由於並非僅從一個InputStream逐字節讀取,而是數據必須先讀入緩衝區再處理。

數據處理

使用純粹的NIO設計相較IO設計,數據處理也受到影響。

在IO設計中,咱們從InputStream或 Reader逐字節讀取數據。假設你正在處理一基於行的文本數據流,例如:

複製代碼代碼以下:

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

該文本行的流能夠這樣處理:

複製代碼代碼以下:

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

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

 
img

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

複製代碼代碼以下:

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

注意第二行,從通道讀取字節到ByteBuffer。當這個方法調用返回時,你不知道你所需的全部數據是否在緩衝區內。你所知道的是,該緩衝區包含一些字節,這使得處理有點困難。
假設第一次 read(buffer)調用後,讀入緩衝區的數據只有半行,例如,「Name:An」,你能處理數據嗎?顯然不能,須要等待,直到整行數據讀入緩存,在此以前,對數據的任何處理毫無心義。

因此,你怎麼知道是否該緩衝區包含足夠的數據能夠處理呢?好了,你不知道。發現的方法只能查看緩衝區中的數據。其結果是,在你知道全部數據都在緩衝區裏以前,你必須檢查幾回緩衝區的數據。這不只效率低下,並且能夠使程序設計方案雜亂不堪。例如:

複製代碼代碼以下:

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

bufferFull()方法必須跟蹤有多少數據讀入緩衝區,並返回真或假,這取決於緩衝區是否已滿。換句話說,若是緩衝區準備好被處理,那麼表示緩衝區滿了。

bufferFull()方法掃描緩衝區,但必須保持在bufferFull()方法被調用以前狀態相同。若是沒有,下一個讀入緩衝區的數據可能沒法讀到正確的位置。這是不可能的,但倒是須要注意的又一問題。

若是緩衝區已滿,它能夠被處理。若是它不滿,而且在你的實際案例中有意義,你或許能處理其中的部分數據。可是許多狀況下並不是如此。下圖展現了「緩衝區數據循環就緒」:

 
img

3) 用來處理數據的線程數

 

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

若是須要管理同時打開的成千上萬個鏈接,這些鏈接每次只是發送少許的數據,例如聊天服務器,實現NIO的服務器多是一個優點。一樣,若是你須要維持許多打開的鏈接到其餘計算機上,如P2P網絡中,使用一個單獨的線程來管理你全部出站鏈接,多是一個優點。一個線程多個鏈接的設計方案如

 
img

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

若是你有少許的鏈接使用很是高的帶寬,一次發送大量的數據,也許典型的IO服務器實現可能很是契合。下圖說明了一個典型的IO服務器設計:

 
img

Java IO: 一個典型的IO服務器設計- 一個鏈接經過一個線程處理

Java中堆內存和棧內存區別

Java把內存分紅兩種,一種叫作棧內存,一種叫作堆內存

在函數中定義的一些基本類型的變量和對象的引用變量都是在函數的棧內存中分配。當在一段代碼塊中定義一個變量時,java就在棧中爲這個變量分配內存空間,當超過變量的做用域後,java會自動釋放掉爲該變量分配的內存空間,該內存空間能夠馬上被另做他用。

堆內存用於存放由new建立的對象和數組。在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。在堆中產生了一個數組或者對象後,還能夠在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,在棧中的這個特殊的變量就變成了數組或者對象的引用變量,之後就能夠在程序中使用棧內存中的引用變量來訪問堆中的數組或者對象,引用變量至關於爲數組或者對象起的一個別名,或者代號。

引用變量是普通變量,定義時在棧中分配內存,引用變量在程序運行到做用域外釋放。而數組&對象自己在堆中分配,即便程序運行到使用new產生數組和對象的語句所在地代碼塊以外,數組和對象自己佔用的堆內存也不會被釋放,數組和對象在沒有引用變量指向它的時候,才變成垃圾,不能再被使用,可是仍然佔着內存,在隨後的一個不肯定的時間被垃圾回收器釋放掉。這個也是java比較佔內存的主要緣由,********實際上,棧中的變量指向堆內存中的變量,這就是 Java 中的指針!


java中內存分配策略及堆和棧的比較
  1 內存分配策略
  按照編譯原理的觀點,程序運行時的內存分配有三種策略,分別是靜態的,棧式的,和堆式的.
  靜態存儲分配是指在編譯時就能肯定每一個數據目標在運行時刻的存儲空間需求,於是在編譯時就能夠給他們分配固定的內存空間.這種分配策略要求程序代碼中不容許有可變數據結構(好比可變數組)的存在,也不容許有嵌套或者遞歸的結構出現,由於它們都會致使編譯程序沒法計算準確的存儲空間需求.
  棧式存儲分配也可稱爲動態存儲分配,是由一個相似於堆棧的運行棧來實現的.和靜態存儲分配相反,在棧式存儲方案中,程序對數據區的需求在編譯時是徹底未知的,只有到運行的時候纔可以知道,可是規定在運行中進入一個程序模塊時,必須知道該程序模塊所需的數據區大小纔可以爲其分配內存.和咱們在數據結構所熟知的棧同樣,棧式存儲分配按照先進後出的原則進行分配。
  靜態存儲分配要求在編譯時能知道全部變量的存儲要求,棧式存儲分配要求在過程的入口處必須知道全部的存儲要求,而堆式存儲分配則專門負責在編譯時或運行時模塊入口處都沒法肯定存儲要求的數據結構的內存分配,好比可變長度串和對象實例.堆由大片的可利用塊或空閒塊組成,堆中的內存能夠按照任意順序分配和釋放.
  2 堆和棧的比較
  上面的定義從編譯原理的教材中總結而來,除靜態存儲分配以外,都顯得很呆板和難以理解,下面撇開靜態存儲分配,集中比較堆和棧:
  從堆和棧的功能和做用來通俗的比較,堆主要用來存放對象的,棧主要是用來執行程序的.而這種不一樣又主要是因爲堆和棧的特色決定的:
  在編程中,例如C/C++中,全部的方法調用都是經過棧來進行的,全部的局部變量,形式參數都是從棧中分配內存空間的。實際上也不是什麼分配,只是從棧頂向上用就行,就好像工廠中的傳送帶(conveyor belt)同樣,Stack Pointer會自動指引你到放東西的位置,你所要作的只是把東西放下來就行.退出函數的時候,修改棧指針就能夠把棧中的內容銷燬.這樣的模式速度最快, 固然要用來運行程序了.須要注意的是,在分配的時候,好比爲一個即將要調用的程序模塊分配數據區時,應事先知道這個數據區的大小,也就說是雖然分配是在程序運行時進行的,可是分配的大小多少是肯定的,不變的,而這個"大小多少"是在編譯時肯定的,不是在運行時.
  堆是應用程序在運行的時候請求操做系統分配給本身內存,因爲從操做系統管理的內存分配,因此在分配和銷燬時都要佔用時間,所以用堆的效率很是低.可是堆的優勢在於,編譯器沒必要知道要從堆裏分配多少存儲空間,也沒必要知道存儲的數據要在堆裏停留多長的時間,所以,用堆保存數據時會獲得更大的靈活性。事實上,面向對象的多態性,堆內存分配是必不可少的,由於多態變量所需的存儲空間只有在運行時建立了對象以後才能肯定.在C++中,要求建立一個對象時,只需用 new命令編制相關的代碼便可。執行這些代碼時,會在堆裏自動進行數據的保存.固然,爲達到這種靈活性,必然會付出必定的代價:在堆裏分配存儲空間時會花掉更長的時間!這也正是致使咱們剛纔所說的效率低的緣由,看來列寧同志說的好,人的優勢每每也是人的缺點,人的缺點每每也是人的優勢(暈~).
  3 JVM中的堆和棧
  JVM是基於堆棧的虛擬機.JVM爲每一個新建立的線程都分配一個堆棧.也就是說,對於一個Java程序來講,它的運行就是經過對堆棧的操做來完成的。堆棧以幀爲單位保存線程的狀態。JVM對堆棧只進行兩種操做:以幀爲單位的壓棧和出棧操做。
  咱們知道,某個線程正在執行的方法稱爲此線程的當前方法.咱們可能不知道,當前方法使用的幀稱爲當前幀。當線程激活一個Java方法,JVM就會在線程的 Java堆棧裏新壓入一個幀。這個幀天然成爲了當前幀.在此方法執行期間,這個幀將用來保存參數,局部變量,中間計算過程和其餘數據.這個幀在這裏和編譯原理中的活動紀錄的概念是差很少的.
  從Java的這種分配機制來看,堆棧又能夠這樣理解:堆棧(Stack)是操做系統在創建某個進程時或者線程(在支持多線程的操做系統中是線程)爲這個線程創建的存儲區域,該區域具備先進後出的特性。
  每個Java應用都惟一對應一個JVM實例,每個實例惟一對應一個堆。應用程序在運行中所建立的全部類實例或數組都放在這個堆中,並由應用全部的線程共享.跟C/C++不一樣,Java中分配堆內存是自動初始化的。Java中全部對象的存儲空間都是在堆中分配的,可是這個對象的引用倒是在堆棧中分配,也就是說在創建一個對象時從兩個地方都分配內存,在堆中分配的內存實際創建這個對象,而在堆棧中分配的內存只是一個指向這個堆對象的指針(引用)而已。
  Java 中的堆和棧
  Java把內存劃分紅兩種:一種是棧內存,一種是堆內存。
  在函數中定義的一些基本類型的變量和對象的引用變量都在函數的棧內存中分配。
  當在一段代碼塊定義一個變量時,Java就在棧中爲這個變量分配內存空間,當超過變量的做用域後,Java會自動釋放掉爲該變量所分配的內存空間,該內存空間能夠當即被另做他用。
  堆內存用來存放由new建立的對象和數組。
  在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。
  在堆中產生了一個數組或對象後,還能夠在棧中定義一個特殊的變量,讓棧中這個變量的取值等於數組或對象在堆內存中的首地址,棧中的這個變量就成了數組或對象的引用變量。
  引用變量就至關因而爲數組或對象起的一個名稱,之後就能夠在程序中使用棧中的引用變量來訪問堆中的數組或對象。
  具體的說:
  棧與堆都是Java用來在Ram中存放數據的地方。與C++不一樣,Java自動管理棧和堆,程序員不能直接地設置棧或堆。
  Java的堆是一個運行時數據區,類的(對象從中分配空間。這些對象經過new、newarray、anewarray和multianewarray等指令創建,它們不須要程序代碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優點是能夠動態地分配內存大小,生存期也沒必要事先告訴編譯器,由於它是在運行時動態分配內存的,Java的垃圾收集器會自動收走這些再也不使用的數據。但缺點是,因爲要在運行時動態分配內存,存取速度較慢。
  棧的優點是,存取速度比堆要快,僅次於寄存器,棧數據能夠共享。但缺點是,存在棧中的數據大小與生存期必須是肯定的,缺少靈活性。棧中主要存放一些基本類型的變量(,int, short, long, byte, float, double, boolean, char)和對象句柄。
  棧有一個很重要的特殊性,就是存在棧中的數據能夠共享。假設咱們同時定義:
  int a = 3;
  int b = 3;
  編譯器先處理int a = 3;首先它會在棧中建立一個變量爲a的引用,而後查找棧中是否有3這個值,若是沒找到,就將3存放進來,而後將a指向3。接着處理int b = 3;在建立完b的引用變量後,由於在棧中已經有3這個值,便將b直接指向3。這樣,就出現了a與b同時均指向3的狀況。這時,若是再令a=4;那麼編譯器會從新搜索棧中是否有4值,若是沒有,則將4存放進來,並令a指向4;若是已經有了,則直接將a指向這個地址。所以a值的改變不會影響到b的值。要注意這種數據的共享與兩個對象的引用同時指向一個對象的這種共享是不一樣的,由於這種狀況a的修改並不會影響到b, 它是由編譯器完成的,它有利於節省空間。而一個對象引用變量修改了這個對象的內部狀態,會影響到另外一個對象引用變量

反射講一講,主要是概念,都在哪須要反射機制,反射的性能,如何優化

反射機制的定義:

是在運行狀態中,對於任意的一個類,都可以知道這個類的全部屬性和方法,對任意一個對象都可以經過反射機制調用一個類的任意方法,這種動態獲取類信息及動態調用類對象方法的功能稱爲java的反射機制。

反射的做用:

一、動態地建立類的實例,將類綁定到現有的對象中,或從現有的對象中獲取類型。

二、應用程序須要在運行時從某個特定的程序集中載入一個特定的類

如何保證RESTful API安全性

友情連接: 如何設計好的RESTful API之安全性

如何預防MySQL注入

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

咱們永遠不要信任用戶的輸入,咱們必須認定用戶輸入的數據都是不安全的,咱們都須要對用戶輸入的數據進行過濾處理。

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

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

讓咱們看下在沒有過濾特殊字符時,出現的SQL狀況:

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

以上的注入語句中,咱們沒有對 $name 的變量進行過濾,$name 中插入了咱們不須要的SQL語句,將刪除 users 表中的全部數據。

2.在PHP中的 mysql_query() 是不容許執行多個SQL語句的,可是在 SQLite 和 PostgreSQL 是能夠同時執行多條SQL語句的,因此咱們對這些用戶的數據須要進行嚴格的驗證。

防止SQL注入,咱們須要注意如下幾個要點:

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

3.防止SQL注入

在腳本語言,如Perl和PHP你能夠對用戶輸入的數據進行轉義從而來防止SQL注入。

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

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查詢時,若是用戶輸入的值有""和"%",則會出現這種狀況:用戶原本只是想查詢"abcd",查詢結果中卻有"abcd_"、"abcde"、"abcdf"等等;用戶要查詢"30%"(注:百分之三十)時也會出現問題。

在PHP腳本中咱們能夠使用addcslashes()函數來處理以上狀況,以下實例:

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

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

語法格式:

addcslashes(string,characters)

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

ThreadLocal(線程變量副本)

Synchronized實現內存共享,ThreadLocal爲每一個線程維護一個本地變量。

採用空間換時間,它用於線程間的數據隔離,爲每個使用該變量的線程提供一個副本,每一個線程均可以獨立地改變本身的副本,而不會和其餘線程的副本衝突。

ThreadLocal類中維護一個Map,用於存儲每個線程的變量副本,Map中元素的鍵爲線程對象,而值爲對應線程的變量副本。

ThreadLocal在spring中發揮着巨大的做用,在管理Request做用域中的Bean、事務管理、任務調度、AOP等模塊都出現了它的身影。

Spring中絕大部分Bean均可以聲明成Singleton做用域,採用ThreadLocal進行封裝,所以有狀態的Bean就可以以singleton的方式在多線程中正常工做了。

你能不能談談,Java GC是在何時,對什麼東西,作了什麼事情?

在何時:

1.新生代有一個Eden區和兩個survivor區,首先將對象放入Eden區,若是空間不足就向其中的一個survivor區上放,若是仍然放不下就會引起一次發生在新生代的minor GC,將存活的對象放入另外一個survivor區中,而後清空Eden和以前的那個survivor區的內存。在某次GC過程當中,若是發現仍然又放不下的對象,就將這些對象放入老年代內存裏去。

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

3.當每次執行minor GC的時候應該對要晉升到老年代的對象進行分析,若是這些立刻要到老年區的老年對象的大小超過了老年區的剩餘大小,那麼執行一次Full GC以儘量地得到老年區的空間。

對什麼東西:從GC Roots搜索不到,並且通過一次標記清理以後仍沒有復活的對象。

作什麼:
新生代:複製清理;
老年代:標記-清除和標記-壓縮算法
永久代:存放Java中的類和加載類的類加載器自己。

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

Volatile和Synchronized四個不一樣點:

1 粒度不一樣,前者鎖對象和類,後者針對變量
2 syn阻塞,volatile線程不阻塞
3 syn保證三大特性,volatile不保證原子性
4 syn編譯器優化,volatile不優化
volatile具有兩種特性:
\1. 保證此變量對全部線程的可見性,指一條線程修改了這個變量的值,新值對於其餘線程來講是可見的,但並非多線程安全的。
\2. 禁止指令重排序優化。
Volatile如何保證內存可見性:
1.當寫一個volatile變量時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存。
2.當讀一個volatile變量時,JMM會把該線程對應的本地內存置爲無效。線程接下來將從主內存中讀取共享變量。

同步:就是一個任務的完成須要依賴另一個任務,只有等待被依賴的任務完成後,依賴任務才能完成。
異步:不須要等待被依賴的任務完成,只是通知被依賴的任務要完成什麼工做,只要本身任務完成了就算完成了,被依賴的任務是否完成會通知回來。(異步的特色就是通知)。
打電話和發短信來比喻同步和異步操做。
阻塞:CPU停下來等一個慢的操做完成之後,纔會接着完成其餘的工做。
非阻塞:非阻塞就是在這個慢的執行時,CPU去作其餘工做,等這個慢的完成後,CPU纔會接着完成後續的操做。
非阻塞會形成線程切換增長,增長CPU的使用時間能不能補償系統的切換成本須要考慮。

線程池的做用:

在程序啓動的時候就建立若干線程來響應處理,它們被稱爲線程池,裏面的線程叫工做線程
第一:下降資源消耗。經過重複利用已建立的線程下降線程建立和銷燬形成的消耗。
第二:提升響應速度。當任務到達時,任務能夠不須要等到線程建立就能當即執行。
第三:提升線程的可管理性。
經常使用線程池:ExecutorService 是主要的實現類,其中經常使用的有
Executors.newSingleThreadPool(),newFixedThreadPool(),newcachedTheadPool(),newScheduledThreadPool()。

一致性哈希:

Redis數據結構: String—字符串(key-value 類型)

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

MySQL的索引是一個數據結構,旨在使數據庫高效的查找數據。
經常使用的數據結構是B+Tree,每一個葉子節點不但存放了索引鍵的相關信息還增長了指向相鄰葉子節點的指針,這樣就造成了帶有順序訪問指針的B+Tree,作這個優化的目的是提升不一樣區間訪問的性能。
何時使用索引:

  1. 常常出如今group by,order by和distinc關鍵字後面的字段
  2. 常常與其餘表進行鏈接的表,在鏈接字段上應該創建索引
  3. 常常出如今Where子句中的字段
  4. 常常出現用做查詢選擇的字段

Spring IOC AOP(控制反轉,依賴注入)

IOC容器:就是具備依賴注入功能的容器,是能夠建立對象的容器,IOC容器負責實例化、定位、配置應用程序中的對象及創建這些對象間的依賴。一般new一個實例,控制權由程序員控制,而"控制反轉"是指new實例工做不禁程序員來作而是交給Spring容器來作。。在Spring中BeanFactory是IOC容器的實際表明者。

DI(依賴注入Dependency injection) :在容器建立對象後,處理對象的依賴關係。

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

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

Spring的IOC容器經過反射的機制實例化Bean並創建Bean之間的依賴關係。
簡單地講,Bean就是由Spring IOC容器初始化、裝配及被管理的對象。
獲取Bean對象的過程,首先經過Resource加載配置文件並啓動IOC容器,而後經過getBean方法獲取bean對象,就能夠調用他的方法。
Spring Bean的做用域:
Singleton:Spring IOC容器中只有一個共享的Bean實例,通常都是Singleton做用域。
Prototype:每個請求,會產生一個新的Bean實例。
Request:每一次http請求會產生一個新的Bean實例。

AOP就是縱向的編程,如業務1和業務2都須要一個共同的操做,與其往每一個業務中都添加一樣的代碼,不如寫一遍代碼,讓兩個業務共同使用這段代碼。在平常有訂單管理、商品管理、資金管理、庫存管理等業務,都會須要到相似日誌記錄、事務控制、****權限控制、性能統計、異常處理及事務處理等。AOP把全部共有代碼所有抽取出來,放置到某個地方集中管理,而後在具體運行時,再由容器動態織入這些共有代碼。

Spring AOP應用場景
性能檢測,訪問控制,日誌管理,事務等。
默認的策略是若是目標類實現接口,則使用JDK動態代理技術,若是目標對象沒有實現接口,則默認會採用CGLIB代理

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

友情連接:淺談Spring框架註解的用法分析

友情連接:關於Spring的69個面試問答——終極列表

代理的共有優勢:業務類只須要關注業務邏輯自己,保證了業務類的重用性。

Java靜態代理:
代理對象和目標對象實現了相同的接口,目標對象做爲代理對象的一個屬性,具體接口實現中,代理對象能夠在調用目標對象相應方法先後加上其餘業務處理邏輯。
缺點:一個代理類只能代理一個業務類。若是業務類增長方法時,相應的代理類也要增長方法。
Java動態代理:
Java動態代理是寫一個類實現InvocationHandler接口,重寫Invoke方法,在Invoke方法能夠進行加強處理的邏輯的編寫,這個公共代理類在運行的時候才能明確本身要代理的對象,同時能夠實現該被代理類的方法的實現,而後在實現類方法的時候能夠進行加強處理。
實際上:代理對象的方法 = 加強處理 + 被代理對象的方法

JDK和CGLIB生成動態代理類的區別:
JDK動態代理只能針對實現了接口的類生成代理(實例化一個類)。此時代理對象和目標對象實現了相同的接口,目標對象做爲代理對象的一個屬性,具體接口實現中,能夠在調用目標對象相應方法先後加上其餘業務處理邏輯
CGLIB是針對類實現代理,主要是對指定的類生成一個子類(沒有實例化一個類),覆蓋其中的方法 。

SpringMVC運行原理

\1. 客戶端請求提交到DispatcherServlet
\2. 由DispatcherServlet控制器查詢HandlerMapping,找到並分發到指定的Controller中。
\4. Controller調用業務邏輯處理後,返回ModelAndView
\5. DispatcherServlet查詢一個或多個ViewResoler視圖解析器,找到ModelAndView指定的視圖
\6. 視圖負責將結果顯示到客戶端

友情連接:Spring:基於註解的Spring MVC(上)

友情連接: Spring:基於註解的Spring MVC(下)

友情連接:SpringMVC與Struts2區別與比較總結

友情連接:SpringMVC與Struts2的對比

TCP三次握手,四次揮手

TCP做爲一種可靠傳輸控制協議,其核心思想:既要保證數據可靠傳輸,又要提升傳輸的效率,而用三次偏偏能夠知足以上兩方面的需求!****雙方都須要確認本身的發信和收信功能正常,收信功能經過接收對方信息獲得確認,發信功能須要發出信息—>對方回覆信息獲得確認。

三次握手過程:

  1. 第一次握手:創建鏈接。客戶端發送鏈接請求報文段,將SYN位置爲1,Sequence Number爲x;而後,客戶端進入SYN_SEND狀態,等待服務器的確認;
  2. 第二次握手:服務器收到客戶端的SYN報文段,須要對這個SYN報文段進行確認,設置ACK爲x+1(Sequence Number+1);同時,本身還要發送SYN請求信息,將SYN位置爲1,Sequence Number爲y;服務器端將上述全部信息放到一個報文段(即SYN+ACK報文段)中,一併發送給客戶端,此時服務器進入SYN_RECV狀態;
  3. 第三次握手:客戶端收到服務器的SYN+ACK報文段。而後將Acknowledgment Number設置爲y+1,向服務器發送ACK報文段,這個報文段發送完畢之後,客戶端和服務器端都進入ESTABLISHED狀態,完成TCP三次握手。

TCP工做在網絡OSI的七層模型中的第四層——Transport層,IP在第三層——Network層
�ARP在第二層——Data Link層;在第二層上的數據,咱們把它叫Frame,在第三層上的數據叫Packet,第四層的數據叫Segment。

四次揮手過程:

  1. 第一次分手:主機1(能夠使客戶端,也能夠是服務器端),設置Sequence NumberAcknowledgment Number,向主機2發送一個FIN報文段;此時,主機1進入FIN_WAIT_1狀態;這表示主機1沒有數據要發送給主機2了;
  2. 第二次分手:主機2收到了主機1發送的FIN報文段,向主機1回一個ACK報文段,Acknowledgment NumberSequence Number加1;主機1進入FIN_WAIT_2狀態;主機2告訴主機1,我「贊成」你的關閉請求;
  3. 第三次分手:主機2向主機1發送FIN報文段,請求關閉鏈接,同時主機2進入LAST_ACK狀態;
  4. 第四次分手:主機1收到主機2發送的FIN報文段,向主機2發送ACK報文段,而後主機1進入TIME_WAIT狀態;主機2收到主機1的ACK報文段之後,就關閉鏈接;此時,主機1等待2MSL後依然沒有收到回覆,則證實Server端已正常關閉,那好,主機1也能夠關閉鏈接了。
(2)而關閉鏈接倒是四次揮手呢?
    這是由於服務端在LISTEN狀態下,收到創建鏈接請求的SYN報文後,把ACK和SYN放在一個報文裏發送給客戶端。

爲何創建鏈接是三次握手

這是由於服務端在LISTEN狀態下,收到創建鏈接請求的SYN報文後,把ACK和SYN放在一個報文裏發送給客戶端。

關閉鏈接倒是四次揮手呢

而關閉鏈接時,當收到對方的FIN報文時,僅僅表示對方再也不發送數據了可是還能接收數據,己方也未必所有數據都發送給對方了,因此己方能夠當即close,也能夠發送一些數據給對方後,再發送FIN報文給對方來表示贊成如今關閉鏈接,所以,己方ACK和FIN通常都會分開發送。

HTTPS和HTTP 爲何更安全,先看這些

http默認端口是80 https是443

http是HTTP協議運行在TCP之上。全部傳輸的內容都是明文,客戶端和服務器端都沒法驗證對方的身份。

https是HTTP運行在SSL/TLS之上,SSL/TLS運行在TCP之上。全部傳輸的內容都通過加密,加密採用對稱加密,但對稱加密的密鑰用服務器方的證書進行了非對稱加密。此外客戶端能夠驗證服務器端的身份,若是配置了客戶端驗證,服務器方也能夠驗證客戶端的身份。HTTP(應用層) 和TCP(傳輸層)之間插入一個SSL協議,

一個Http請求

DNS域名解析 –> 發起TCP的三次握手 –> 創建TCP鏈接後發起http請求 –> 服務器響應http請求,瀏覽器獲得html代碼 –> 瀏覽器解析html代碼,並請求html代碼中的資源(如js、css、圖片等) –> 瀏覽器對頁面進行渲染呈現給用戶

友情連接: HTTP與HTTPS的區別

友情連接: HTTPS 爲何更安全,先看這些

友情連接: HTTP請求報文和HTTP響應報文

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

Mybatis

每個Mybatis的應用程序都以一個SqlSessionFactory對象的實例爲核心。首先用字節流經過Resource將配置文件讀入,而後經過SqlSessionFactoryBuilder().build方法建立SqlSessionFactory,而後再經過sqlSessionFactory.openSession()方法建立一個sqlSession爲每個數據庫事務服務。
經歷了Mybatis初始化 –>建立SqlSession –>運行SQL語句 返回結果三個過程

Servlet和Filter的區別:

整的流程是:Filter對用戶請求進行預處理,接着將請求交給Servlet進行處理並生成響應,最後Filter再對服務器響應進行後處理。

Filter有以下幾個用處:
Filter能夠進行對特定的url請求和相應作預處理和後處理。
在HttpServletRequest到達Servlet以前,攔截客戶的HttpServletRequest。
根據須要檢查HttpServletRequest,也能夠修改HttpServletRequest頭和數據。
在HttpServletResponse到達客戶端以前,攔截HttpServletResponse。
根據須要檢查HttpServletResponse,也能夠修改HttpServletResponse頭和數據。

實際上Filter和Servlet極其類似,區別只是Filter不能直接對用戶生成響應。實際上Filter裏doFilter()方法裏的代碼就是從多個Servlet的service()方法裏抽取的通用代碼,經過使用Filter能夠實現更好的複用。

Filter和Servlet的生命週期:
1.Filter在web服務器啓動時初始化
2.若是某個Servlet配置了 1 ,該Servlet也是在Tomcat(Servlet容器)啓動時初始化。
3.若是Servlet沒有配置1 ,該Servlet不會在Tomcat啓動時初始化,而是在請求到來時初始化。
4.每次請求, Request都會被初始化,響應請求後,請求被銷燬。
5.Servlet初始化後,將不會隨着請求的結束而註銷。
6.關閉Tomcat時,Servlet、Filter依次被註銷。

HashMap和TreeMap區別

HashMap:基於哈希表實現。使用HashMap要求添加的鍵類明肯定義了hashCode()和equals()[能夠重寫hashCode()和equals()],爲了優化HashMap空間的使用,您能夠調優初始容量和負載因子。 適合查找和刪除
(1)HashMap(): 構建一個空的哈希映像
(2)HashMap(Map m): 構建一個哈希映像,而且添加映像m的全部映射
(3)HashMap(int initialCapacity): 構建一個擁有特定容量的空的哈希映像
(4)HashMap(int initialCapacity, float loadFactor): 構建一個擁有特定容量和加載因子的空的哈希映像
TreeMap:基於紅黑樹實現。TreeMap沒有調優選項,由於該樹總處於平衡狀態。 適合按照天然順序或者自定義的順序排序遍歷key
(1)TreeMap():構建一個空的映像樹
(2)TreeMap(Map m): 構建一個映像樹,而且添加映像m中全部元素
(3)TreeMap(Comparator c): 構建一個映像樹,而且使用特定的比較器對關鍵字進行排序
(4)TreeMap(SortedMap s): 構建一個映像樹,添加映像樹s中全部映射,而且使用與有序映像s相同的比較器排序

友情連接: Java中HashMap和TreeMap的區別深刻理解

HashMap衝突

友情連接: HashMap衝突的解決方法以及原理分析

友情連接: HashMap的工做原理

友情連接: HashMap和Hashtable的區別

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

HashMap,ConcurrentHashMap與LinkedHashMap的區別

  1. ConcurrentHashMap是使用了鎖分段技術技術來保證線程安全的,鎖分段技術:首先將數據分紅一段一段的存儲,而後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其餘段的數據也能被其餘線程訪問

  2. ConcurrentHashMap 是在每一個段(segment)中線程安全的

  3. LinkedHashMap維護一個雙鏈表,能夠將裏面的數據按寫入的順序讀出

  4. ConcurrentHashMap應用場景

1:ConcurrentHashMap的應用場景是高併發,可是並不能保證線程安全,而同步的HashMap和HashMap的是鎖住整個容器,而加鎖以後ConcurrentHashMap不須要鎖住整個容器,只須要鎖住對應的Segment就行了,因此能夠保證高併發同步訪問,提高了效率。

2:能夠多線程寫。
ConcurrentHashMap把HashMap分紅若干個Segmenet
1.get時,不加鎖,先定位到segment而後在找到頭結點進行讀取操做。而value是volatile變量,因此能夠保證在競爭條件時保證讀取最新的值,若是讀到的value是null,則可能正在修改,那麼久調用ReadValueUnderLock函數,加鎖保證讀到的數據是正確的。
2.Put時會加鎖,一概添加到hash鏈的頭部。
3.Remove時也會加鎖,因爲next是final類型不可改變,因此必須把刪除的節點以前的節點都複製一遍。
4.ConcurrentHashMap容許多個修改操做併發進行,其關鍵在於使用了鎖分離技術。它使用了多個鎖來控制對Hash表的不一樣Segment進行的修改。

ConcurrentHashMap的應用場景是高併發,可是並不能保證線程安全,而同步的HashMap和HashTable的是鎖住整個容器,而加鎖以後ConcurrentHashMap不須要鎖住整個容器,只須要鎖住對應的segment就行了,因此能夠保證高併發同步訪問,提高了效率。

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

ThreadPoolExecutor 的內部工做原理

進程間的通訊方式

  1. 管道( pipe ):管道是一種半雙工的通訊方式,數據只能單向流動,並且只能在具備親緣關係的進程間使用。進程的親緣關係一般是指父子進程關係。
  2. 有名管道 (named pipe) : 有名管道也是半雙工的通訊方式,可是它容許無親緣關係進程間的通訊。
    3.信號量( semophore ) : 信號量是一個計數器,能夠用來控制多個進程對共享資源的訪問。它常做爲一種鎖機制,防止某進程正在訪問共享資源時,其餘進程也訪問該資源。所以,主要做爲進程間以及同一進程內不一樣線程之間的同步手段。
  3. 消息隊列( message queue ) : 消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點。
    5.信號 ( sinal ) : 信號是一種比較複雜的通訊方式,用於通知接收進程某個事件已經發生。
    6.共享內存( shared memory ) :共享內存就是映射一段能被其餘進程所訪問的內存,這段共享內存由一個進程建立,但多個進程均可以訪問。共享內存是最快的 IPC 方式,它是針對其餘進程間通訊方式運行效率低而專門設計的。它每每與其餘通訊機制,如信號量,配合使用,來實現進程間的同步和通訊。
    7.套接字( socket ) : 套解口也是一種進程間通訊機制,與其餘通訊機制不一樣的是,它可用於不一樣機器間的進程通訊。

死鎖的必要條件

  1. 互斥 至少有一個資源處於非共享狀態
  2. 佔有並等待
  3. 非搶佔
  4. 循環等待
    解決死鎖,第一個是死鎖預防,就是不讓上面的四個條件同時成立。二是,合理分配資源。
    三是使用銀行家算法,若是該進程請求的資源操做系統剩餘量能夠知足,那麼就分配。
相關文章
相關標籤/搜索