static 靜態修飾關鍵字,能夠修飾 變量,程序塊,類的方法;javascript
當你定義一個static的變量的時候jvm會將將其分配在內存堆上,全部程序對它的引用都會指向這一個地址而不會從新分配內存;css
修飾一個程序塊的時候(也就是直接將代碼寫在static{...}中)時候,虛擬機就會優先加載靜態塊中代碼,這主要用於系統初始化;html
當修飾一個類方法時候你就能夠直接經過類來調用而不須要新建對象。前端
final 只能賦值一次;修飾變量、方法及類,當你定義一個final變量時,jvm會將其分配到常量池中,程序不可改變其值;當你定義一個方法時,改方法在子類中將不能被重寫;當你修飾一個類時,該類不能被繼承。java
transientnode
類型修飾符,只能用來修飾字段,若是用transient聲明一個實例變量,當對象存儲時,它的值不須要維持。換句話來講就是,用transient關鍵字標記的成員變量不參與序列化過程。mysql
static和final使用範圍:類、方法、變量。web
List初始化話大小爲10,擴容增量:原容量的 0.5倍+1面試
List<String> strList = new ArrayList<>();redis
使用for-each循環,
Iterator<String> it = strList.iterator();
while(it.hasNext()){
String obj = it.next();
System.out.println(obj);
}
使用迭代器更加線程安全,由於它能夠確保,在當前遍歷的集合元素被更改的時候,它會拋出ConcurrentModificationException。
ArrayList:
線程不安全,查詢速度快,底層數據結構是數組結構,
擴容增量:原容量的 0.5倍+1 如 ArrayList的容量爲10,一次擴容後是容量爲16
Set(集) 元素無序的、不可重複。
底層實現是一個HashMap(保存數據),實現Set接口默認初始容量爲16(爲什麼是16,見下方對HashMap的描述)加載因子爲0.75:即當 元素個數 超過 容量長度的0.75倍 時,進行擴容擴容增量:原容量的 1 倍如 HashSet的容量爲16,一次擴容後是容量爲32
Iterator的fail-fast屬性與當前的集合共同起做用,所以它不會受到集合中任何改動的影響。
Java.util包中的全部集合類都被設計爲fail-fast的,而java.util.concurrent中的集合類都爲fail-safe的。
Fail-fast迭代器拋出ConcurrentModificationException,而fail-safe迭代器從不拋出ConcurrentModificationException
在遍歷一個集合的時候,咱們能夠使用併發集合類來避免ConcurrentModificationException,
好比使用CopyOnWriteArrayList,而不是ArrayList。
1.HashMap容許key和value爲null,而HashTable不容許。
2.HashTable是同步的,而HashMap不是。因此HashMap適合單線程環境,HashTable適合多線程環境。
3.在Java1.4中引入了LinkedHashMap,HashMap的一個子類,假如你想要遍歷順序,你很容易從HashMap轉向LinkedHashMap,可是HashTable不是這樣的,它的順序是不可預知的。
4.HashMap提供對key的Set進行遍歷,所以它是fail-fast的,但HashTable提供對key的Enumeration進行遍歷,它不支持fail-fast。
5.HashTable被認爲是個遺留的類,若是你尋求在迭代的時候修改Map,你應該使用CocurrentHashMap。
ArrayList和LinkedList二者都實現了List接口,可是它們之間有些不一樣。
1.ArrayList是由Array所支持的基於一個索引的數據結構,因此它提供對元素的隨機訪問,複雜度爲O(1),但LinkedList存儲一系列的節點數據,每一個節點都與前一個和下一個節點相鏈接。因此,儘管有使用索引獲取元素的方法,內部實現是從起始點開始遍歷,遍歷到索引的節點而後返回元素,時間複雜度爲O(n),比ArrayList要慢。
2.與ArrayList相比,在LinkedList中插入、添加和刪除一個元素會更快,由於在一個元素被插入到中間的時候,不會涉及改變數組的大小,或更新索引。
3.LinkedList比ArrayList消耗更多的內存,由於LinkedList中的每一個節點存儲了先後節點的引用。
Vector、HashTable、Properties和Stack是同步類,因此它們是線程安全的,能夠在多線程環境下使用。
Java1.5併發API包括一些集合類,容許迭代時修改,由於它們都工做在集合的克隆上,因此它們在多線程環境中是安全的。
棧和隊列二者都被用來預存儲數據。java.util.Queue是一個接口,它的實現類在Java併發包中。
隊列容許先進先出(FIFO)檢索元素,但並不是老是這樣。Deque接口容許從兩端檢索元素。
棧與隊列很類似,但它容許對元素進行後進先出(LIFO)進行檢索。
Stack是一個擴展自Vector的類,而Queue是一個接口。
1.等號(==):
對比對象實例的內存地址(也即對象實例的ID),來判斷是不是同一對象實例;又能夠說是判斷對象實例是否物理相等;
2.equals():
對比兩個對象實例是否相等。
當對象所屬的類沒有重寫根類Object的equals()方法時,equals()判斷的是對象實例的ID(內存地址),
是不是同一對象實例;該方法就是使用的等號(==)的判斷結果,如Object類的源代碼所示:
public boolean equals(Object obj) {
return (this == obj);
}
3.hashCode():
計算出對象實例的哈希碼,並返回哈希碼,又稱爲散列函數。
根類Object的hashCode()方法的計算依賴於對象實例的D(內存地址),故每一個Object對象的hashCode都是惟一的;固然,當對象所對應的類重寫了hashCode()方法時,結果就大相徑庭了。
總的來講,Java中的集合(Collection)有兩類,一類是List,再有一類是Set。你知道它們的區別嗎?前者集合內的元素是有序的,元素能夠重複;
後者元素無序,但元素不可重複。那麼這裏就有一個比較嚴重的問題了:要想保證元素不重複,可兩個元素是否重複應該依據什麼來判斷呢?
這就是 Object.equals方法了。可是,若是每增長一個元素就檢查一次,那麼當元素不少時,後添加到集合中的元素比較的次數就很是多了。
也就是說,若是集合中如今已經有1000個元素,那麼第1001個元素加入集合時,它就要調用1000次equals方法。這顯然會大大下降效率。
因而,Java採用了哈希表的原理。哈希算法也稱爲散列算法,當集合要添加新的元素時,將對象經過哈希算法計算獲得哈希值(正整數),而後將哈希值和集合(數組)長度進行&運算,獲得該對象在該數組存放的位置索引。若是這個位置上沒有元素,它就能夠直接存儲在這個位置上,不用再進行任何比較了;若是這個位置上已經有元素了,就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就表示發生衝突了,散列表對於衝突有具體的解決辦法,但最終還會將新元素保存在適當的位置。這樣一來,實際調用equals方法比較的次數就大大下降了,幾乎只須要一兩次。 簡而言之,在集合查找時,hashcode能大大下降對象比較次數,提升查找效率!
Java 把內存劃分紅兩種:一種是棧內存,另外一種是堆內存。在函數中定義的一些基本類型的變量和對象的引用變量都是在函數的棧內存中分配。
堆內存用來存放由 new 建立的對象和數組,在堆中分配的內存,由 Java 虛擬機的自動垃圾回收器來管理。
在堆中產生了一個數組或者對象以後,還能夠在棧中定義一個特殊的變量,讓棧中的這個變量的取值等於數組或對象在堆內存中的首地址,
棧中的這個變量就成了數組或對象的引用變量,之後就能夠在程序中使用棧中的引用變量來訪問堆中的數組或者對象,
引用變量就至關因而爲數組或者對象起的一個名稱。引用變量是普通的變量,定義時在棧中分配,引用變量在程序運行到其做用域以外後被釋放。
而數組和對象自己在堆中分配,即便程序運行到使用 new 產生數組或者對象的語句所在的代碼塊以外,數組和對象自己佔據的內存不會被釋放,
數組和對象在沒有引用變量指向它的時候,才變爲垃圾,不能在被使用,但仍然佔據內存空間不放,在隨後的一個不肯定的時間被垃圾回收器收走(釋放掉)。這也是 Java 比較佔內存的緣由,實際上,棧中的變量指向堆內存中的變量,這就是 Java 中的指針!
1. 首先會執行類中static代碼塊(無論代碼塊是否在類的開頭仍是末尾處),若是這個類有父類,一樣會優先查找父類中的static代碼塊,而後是當前類的static。
2..而後會從父類的第一行開始執行,直至代碼末尾處,中間無論是有賦值仍是method調用,都會按順序一一執行(method),普通代碼塊{ }
3.其次纔是父類的構造函數,執行帶參數或不帶參數的構造函數,依賴於實例化的類的構造函數有沒有super父類的帶參或不帶參的構造函數,上邊試驗二三已經證實
4..而後會從子類(當前類)的第一行開始執行,直至代碼末尾處,中間無論是有賦值仍是method調用,都會按順序一一執行(method),普通代碼塊{ }
5.其次會是子類(當前類)的構造函數,按順序執行。
6.最後是類方法的調用執行,若是子類覆蓋了父類的method,執行時會先執行子類覆蓋的method,method內若是有super.method(),纔會調用父類的同名method,不然不會。
class.forName()除了將類的.class文件加載到jvm中以外,還會對類進行解釋,執行類中的static塊。
classLoader只幹一件事情,就是將.class文件加載到jvm中,不會執行static中的內容,只有在newInstance纔會去執行static塊.
動態代理有兩種實現方式,分別是:jdk動態代理和cglib動態代理。
jdk動態代理的前提是目標類必須實現一個接口,代理對象跟目標類實現一個接口,從而避過虛擬機的校驗。
cglib動態代理是繼承並重寫目標類,因此目標類和方法不能被聲明成final。
2. JVM的內存結構,Eden和Survivor比例。
HashMap實現原理(數組+鏈表/紅黑樹):
數組:存儲區間連續,佔用內存嚴重,尋址容易,插入刪除困難(須要元素移位)
鏈表:存儲區間離散,佔用內存比較寬鬆,尋址困難,插入刪除容易(只需改動元素中的指針)
用一個數組來存儲元素(數組有默認長度),可是這個數組存儲的不是基本數據類型。HashMap實現巧妙的地方就在這裏,數組存儲的元素是一個Entry類,這個類有三個數據域,key、value(鍵值對),next(指向下一個Entry);而這個Entry應該放在數組的哪個位置上(這個位置一般稱爲位桶或者hash桶,即hash值相同的Entry會放在同一位置,用鏈表相連),是經過key的hashCode來計算的。
HashTable已算廢棄:hashtable的線程安全機制效率是很是差的,如今能找到很是多的替代方案,好比Collections.synchronizedMap,courrenthashmap等
在jdk1.8中ConcurrentHashMap主要作了2方面的改進
改進一:取消segments字段,直接採用transient volatile HashEntry<K,V>[] table保存數據,採用table數組元素做爲鎖,從而實現了對每一行數據進行加鎖,進一步減小併發衝突的機率。
改進二:將原先table數組+單向鏈表的數據結構,變動爲table數組+單向鏈表+紅黑樹的結構。對於hash表來講,最核心的能力在於將key hash以後能均勻的分佈在數組中。若是hash以後散列的很均勻,那麼table數組中的每一個隊列長度主要爲0或者1。但實際狀況並不是老是如此理想,雖然ConcurrentHashMap類默認的加載因子爲0.75,可是在數據量過大或者運氣不佳的狀況下,仍是會存在一些隊列長度過長的狀況,若是仍是採用單向列表方式,那麼查詢某個節點的時間複雜度爲O(n);所以,對於個數超過8(默認值)的列表,jdk1.8中採用了紅黑樹的結構,那麼查詢的時間複雜度能夠下降到O(logN),能夠改進性能。
答:數組+鏈表
數組裏面放的是鏈表的第一個值
鏈表放的是hash後產生衝突的值
1.put(a,b)作了那幾個操做?
首先計算a的hashing值,而後根據a的hashing值找到a所對象的桶(busket),取出當前桶裏面所存放的鏈表,遍歷鏈表,看是否存在key爲a的對象,若是有,就將原來的值返回,並覆蓋上新值b。最後將entry加入到鏈頂。
2.爲何hashmap是無序的?
由於hashmap的默認容量是16,負載因子是0.75。就是當hashmap填充了75%的busket是就會擴容,最小的可能性是(16*0.75),通常爲原內存的2倍,而後rehash,這時候致使了hashmap的無序性。
3.concurrentHashMap和hashtable的區別?
hashtable是將整個類加了鎖,因此整個類的操做性能都比較低,而concurrentHashMap是在busket上面加了鎖,實現了鎖的細化,能夠同時支持16個線程的併發。
4.concurrentHashMap是如何保證線程安全的:
1.就是key和entry都是用final修飾的,從而保證告終構的不可變形
2.value是用volatile修飾的,從而保證了線程的可見性
3.busket操做的時候加了鎖(reentrantlock)
①攔截器是基於java的反射機制的,而過濾器是基於函數回調。
②攔截器不依賴與servlet容器,過濾器依賴與servlet容器。
③攔截器只能對action請求起做用,而過濾器則能夠對幾乎全部的請求起做用。
④攔截器能夠訪問action上下文、值棧裏的對象,而過濾器不能訪問。
⑤在action的生命週期中,攔截器能夠屢次被調用,而過濾器只能在容器初始化時被調用一次。
⑥攔截器能夠獲取IOC容器中的各個bean,而過濾器就不行,這點很重要,在攔截器裏注入一個service,能夠調用業務邏輯。
1、使用數據庫鏈接池和線程池
這兩個池都是用於重用對象的,前者能夠避免頻繁地打開和關閉鏈接,後者能夠避免頻繁地建立和銷燬線程
2、使用同步代碼塊替代同步方法
這點在多線程模塊中的synchronized鎖方法塊一文中已經講得很清楚了,除非能肯定一整個方法都是須要進行同步的,不然儘可能使用同步代碼塊,避免對那些不須要進行同步的代碼也進行了同步,影響了代碼執行效率。
3、不要讓public方法中有太多的形參
public方法即對外提供的方法,若是給這些方法太多形參的話主要有兩點壞處:
② 違反了面向對象的編程思想,Java講求一切都是對象,太多的形參,和麪向對象的編程思想並不契合
② 參數太多勢必致使方法調用的出錯機率增長
4、不要將數組聲明爲public static final
由於這毫無心義,這樣只是定義了引用爲static final,數組的內容仍是能夠隨意改變的,將數組聲明爲public更是一個安全漏洞,這意味着這個數組能夠被外部類所改變
5、不要在循環中使用try…catch…,應該把其放在最外層
除非不得已。若是毫無理由地這麼寫了,只要你的領導資深一點、有強迫症一點,八成就要罵你爲何寫出這種垃圾代碼來了
6、及時關閉流
Java編程過程當中,進行數據庫鏈接、I/O流操做時務必當心,在使用完畢後,及時關閉以釋放資源。由於對這些大對象的操做會形成系統大的開銷,稍有不慎,將會致使嚴重的後果。
7、慎用異常
異常對性能不利。拋出異常首先要建立一個新的對象,Throwable接口的構造函數調用名爲fillInStackTrace()的本地同步方法,fillInStackTrace()方法檢查堆棧,收集調用跟蹤信息。只要有異常被拋出,Java虛擬機就必須調整調用堆棧,由於在處理過程當中建立了一個新的對象。異常只能用於錯誤處理,不該該用來控制程序流程。
8、儘可能重用對象
特別是String對象的使用,出現字符串鏈接時應該使用StringBuilder/StringBuffer代替。因爲Java虛擬機不只要花時間生成對象,之後可能還須要花時間對這些對象進行垃圾回收和處理,所以,生成過多的對象將會給程序的性能帶來很大的影響。
9、及時清除再也不須要的會話
爲了清除再也不活動的會話,許多應用服務器都有默認的會話超時時間,通常爲30分鐘。當應用服務器須要保存更多的會話時,若是內存不足,那麼操做系統會把部分數據轉移到磁盤,應用服務器也可能根據MRU(最近最頻繁使用)算法把部分不活躍的會話轉儲到磁盤,甚至可能拋出內存不足的異常。若是會話要被轉儲到磁盤,那麼必需要先被序列化,在大規模集羣中,對對象進行序列化的代價是很昂貴的。所以,當會話再也不須要時,應當及時調用HttpSession的invalidate()方法清除會話。
10、公用的集合類中不使用的數據必定要及時remove掉
若是一個集合類是公用的(也就是說不是方法裏面的屬性),那麼這個集合裏面的元素是不會自動釋放的,由於始終有引用指向它們。因此,若是公用集合裏面的某些數據不使用而不去remove掉它們,那麼將會形成這個公用集合不斷增大,使得系統有內存泄露的隱患。
1.加載:jvm把class字節碼文件加載到內存中,並將這些靜態數據轉換成方法區中的運行時數據結構,並在堆內存中生成一個表明這個類的java.lang.class對象,做爲方法區類數據的訪問入口。
2.連接
2.1 驗證:
確保jvm加載的類信息符合jvm規範,沒有安全方面的問題;
2.2準備:
正式爲類變量(static變量)分配內存並設置類變量初始值的階段,這些內存都將在方法區中進行分配;
2.3解析
虛擬機常量池內的符號引用替換爲直接引用的過程。(好比String s ="aaa",轉化爲 s的地址指向「aaa」的地址)
3.初始化:
初始化階段是執行類構造器方法的過程。類構造器方法是由編譯器自動收集類中的全部類變量的賦值動做和靜態語句塊(static塊)中的語句合併產生的。
當初始化一個類的時候,若是發現其父類尚未進行過初始化,則須要先初始化其父類的初始化
虛擬機會保證一個類的構造器方法在多線程環境中被正確加鎖和同步
當訪問一個java類的靜態域時,只有真正聲明這個靜態變量的類纔會被初始化。
1. 避免構造函數重排序、
2. 保證final變量的初始化,必定在構造函數返回以前完成!
private static Sington instance; new Instance();
底層能夠分爲3個操做: 分配內存,在內存上初始化成員變量,把instance指向內存。
這3個操做,可能重排序,即先把instance指向內存,再初始化成員變量。
自旋鎖: 線程拿不到鎖的時候,CPU空轉,不放棄CPU,等待別的線程釋放鎖。前面所講的CAS樂觀鎖,便是自旋鎖的典型例子。
很顯然,自旋鎖只有在多CPU狀況下,纔可能使用。若是單CPU,佔着不放,其餘程序就沒辦法調度了。
阻塞鎖: 線程拿不到鎖的時候,放棄CPU,進入阻塞狀態。等別的線程釋放鎖以後,再幻醒此線程,調度到CPU上執行。
自旋鎖相比阻塞鎖,少了線程切換的時間,所以性能可能更高。但若是自旋不少次,也拿不到鎖,則會浪費CPU的資源。
獨佔鎖:Synchronized/ReentrantLock都是獨佔鎖,或者叫「排他鎖」。1個線程拿到鎖以後,其餘線程只能等待。 「讀」與「讀」互斥,「讀」與「寫」互斥,「寫」與「寫」互斥
共享鎖:在筆者看來,共享鎖和「讀寫鎖」就是一個概念,讀寫分離。「讀」與「讀」不互斥,「讀」與「寫」互斥,「寫」與「寫」互斥。由於「讀」與「讀」不互斥,因此1個線程拿到「讀」鎖以後,其餘線程也能夠拿到「讀」鎖,所以「讀」鎖是共享的。但1個線程拿到」讀「鎖以後,另1個線程,拿不到」寫「鎖;反之亦然!
所謂可重入,就是指某個線程在拿到鎖以後,在鎖的代碼塊內部,再次去拿該鎖,仍可拿到。
synchronized關鍵字, Lock都是可重入鎖。所以你能夠在synchronized方法內部,調用另一個synchronized方法;在lock.lock()的代碼塊裏面,再次調用lock.lock()。
(1)有公平/非公平策略
(2)有tryLock(),非阻塞方式。Synchronized只有阻塞方式
(3)有lockInterruptibly,能夠響應中斷。Synchronized不能響應中斷
(4)Lock對應的Condition,其使用方式要比Synchronized對應的wait()/notify()更加靈活。
(5) 2者加鎖後,thread所處的狀態是不同的:synchronized加鎖,thread是處於Blocked狀態,Lock枷鎖,thread是處於Waiting狀態!
(6) synchronized關鍵字內部有自旋機制,先自旋,超過次數,再阻塞;Lock裏面沒有用自旋機制。
(7) synchronized是在JVM層面上實現的,不但能夠經過一些監控工具監控synchronized的鎖定,並且在代碼執行時出現異常,
JVM會自動釋放鎖定;可是使用Lock則不行,lock是經過代碼實現的,要保證鎖定必定會被釋放,就必須將unLock()放到finally{}中;
一般的Queue,一邊是生產者,一邊是消費者。一邊進,一邊出,有一個判空函數,一個判滿函數。
而所謂的BlockingQueue,就是指當爲空的時候,阻塞消費者線程;當爲滿的時候,阻塞生產者線程。
1.爲何會有線程不安全?
答:由於方法在執行的時候,爲了方便回從堆裏面將變量copy到棧中,由於棧是線程私有的,因此致使了數據的不一致。
2.線程安全的三要素是什麼?
答:可見性,原子性,順序性。
3.cas 的原理是?
答:cas是根據裏面的一個標誌位status來判斷是否鎖被佔用了。
Volatile:本質是在告訴jvm當前變量在寄存器(工做內存)中的值是不肯定的,須要從主存中讀取; synchronized:則是鎖定當前變量,只有當前線程能夠訪問該變量,其餘線程被阻塞住。
volatile僅能使用在變量級別;synchronized則能夠使用在變量、方法、和類級別的
volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則能夠保證變量的修改可見性和原子性
volatile不會形成線程的阻塞;synchronized可能會形成線程的阻塞。
volatile標記的變量不會被編譯器優化;synchronized標記的變量能夠被編譯器優化
多線程的異步執行方式,雖然可以最大限度發揮多核計算機的計算能力,可是若是不加控制,反而會對系統形成負擔。線程自己也要佔用內存空間,大量的線程會佔用內存資源而且可能會致使Out of Memory。即使沒有這樣的狀況,大量的線程回收也會給GC帶來很大的壓力。
爲了不重複的建立線程,線程池的出現可讓線程進行復用。通俗點講,當有工做來,就會向線程池拿一個線程,當工做完成後,並非直接關閉線程,而是將這個線程歸還給線程池供其餘任務使用。
線程池處理流程:
按照策略處理沒法執行新的任務
建立線程執行任務
將任務存儲在隊列裏
建立線程執行任務
結合上面的流程圖來逐行解析,首先前面進行空指針檢查,
wonrkerCountOf()方法可以取得當前線程池中的線程的總數,取得當前線程數與核心池大小比較,
若是小於,將經過addWorker()方法調度執行。
若是大於核心池大小,那麼就提交到等待隊列。
若是進入等待隊列失敗,則會將任務直接提交給線程池。
若是線程數達到最大線程數,那麼就提交失敗,執行拒絕策略。
addWorker共有四種傳參方式。execute使用了其中三種,分別爲:
1.addWorker(paramRunnable, true)
線程數小於corePoolSize時,放一個須要處理的task進Workers Set。若是Workers Set長度超過corePoolSize,就返回false.
2.addWorker(null, false)
放入一個空的task進workers Set,長度限制是maximumPoolSize。這樣一個task爲空的worker在線程執行的時候會去任務隊列裏拿任務,這樣就至關於建立了一個新的線程,只是沒有立刻分配任務。
3.addWorker(paramRunnable, false)
當隊列被放滿時,就嘗試將這個新來的task直接放入Workers Set,而此時Workers Set的長度限制是maximumPoolSize。若是線程池也滿了的話就返回false.
還有一種狀況是execute()方法沒有使用的addWorker(null, true)
這個方法就是放一個null的task進Workers Set,並且是在小於corePoolSize時,若是此時Set中的數量已經達到corePoolSize那就返回false,什麼也不幹。實際使用中是在prestartAllCoreThreads()方法,這個方法用來爲線程池預先啓動corePoolSize個worker等待從workQueue中獲取任務執行。
執行流程:
1、判斷線程池當前是否爲能夠添加worker線程的狀態,能夠則繼續下一步,不能夠return false:
A、線程池狀態>shutdown,可能爲stop、tidying、terminated,不能添加worker線程
B、線程池狀態==shutdown,firstTask不爲空,不能添加worker線程,由於shutdown狀態的線程池不接收新任務
C、線程池狀態==shutdown,firstTask==null,workQueue爲空,不能添加worker線程,由於firstTask爲空是爲了添加一個沒有任務的線程再從workQueue獲取task,而workQueue爲 空,說明添加無任務線程已經沒有意義
2、線程池當前線程數量是否超過上限(corePoolSize 或 maximumPoolSize),超過了return false,沒超過則對workerCount+1,繼續下一步
3、在線程池的ReentrantLock保證下,向Workers Set中添加新建立的worker實例,添加完成後解鎖,並啓動worker線程,若是這一切都成功了,return true,若是添加worker入Set失敗或啓動失敗,調用addWorkerFailed()邏輯
newFixedThreadPool固定大小的線程池
public static ExecutorService newFixedThreadPool(int var0) {
return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
}
public static ExecutorService newFixedThreadPool(int var0, ThreadFactory var1) {
return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var1);
}
固定大小的線程池,能夠指定線程池的大小,該線程池corePoolSize和maximumPoolSize相等,阻塞隊列使用的是LinkedBlockingQueue,大小爲整數最大值。
該線程池中的線程數量始終不變,當有新任務提交時,線程池中有空閒線程則會當即執行,若是沒有,則會暫存到阻塞隊列。對於固定大小的線程池,不存在線程數量的變化。同時使用無界的LinkedBlockingQueue來存放執行的任務。當任務提交十分頻繁的時候,LinkedBlockingQueue
迅速增大,存在着耗盡系統資源的問題。並且在線程池空閒時,即線程池中沒有可運行任務時,它也不會釋放工做線程,還會佔用必定的系統資源,須要shutdown。
newSingleThreadExecutor單個線程線程池
public static ExecutorService newSingleThreadExecutor() {
return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory var0) {
return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var0));
}
單個線程線程池,只有一個線程的線程池,阻塞隊列使用的是LinkedBlockingQueue,如有多餘的任務提交到線程池中,則會被暫存到阻塞隊列,待空閒時再去執行。按照先入先出的順序執行任務。
newCachedThreadPool緩存線程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}
public static ExecutorService newCachedThreadPool(ThreadFactory var0) {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), var0);
}
緩存線程池,緩存的線程默認存活60秒。線程的核心池corePoolSize大小爲0,核心池最大爲Integer.MAX_VALUE,阻塞隊列使用的是SynchronousQueue。是一個直接提交的阻塞隊列, 他總會迫使線程池增長新的線程去執行新的任務。在沒有任務執行時,當線程的空閒時間超過keepAliveTime(60秒),則工做線程將會終止被回收,當提交新任務時,若是沒有空閒線程,則建立新線程執行任務,會致使必定的系統開銷。若是同時又大量任務被提交,並且任務執行的時間不是特別快,那麼線程池便會新增出等量的線程池處理任務,這極可能會很快耗盡系統的資源。
newScheduledThreadPool定時線程池
public static ScheduledExecutorService newScheduledThreadPool(int var0) {
return new ScheduledThreadPoolExecutor(var0);
}
public static ScheduledExecutorService newScheduledThreadPool(int var0, ThreadFactory var1) {
return new ScheduledThreadPoolExecutor(var0, var1);
}
定時線程池,該線程池可用於週期性地去執行任務,一般用於週期性的同步數據。
scheduleAtFixedRate:是以固定的頻率去執行任務,週期是指每次執行任務成功執行之間的間隔。
schedultWithFixedDelay:是以固定的延時去執行任務,延時是指上一次執行成功以後和下一次開始執行的以前的時間。
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);
corePoolSize(線程池的基本大小):當提交一個任務到線程池時,線程池會建立一個線程來執行任務,即便其餘空閒的基本線程可以執行新任務也會建立線程,等到須要執行的任務數大於線程池基本大小時就再也不建立。若是調用了線程池的prestartAllCoreThreads方法,線程池會提早建立並啓動全部基本線程。
runnableTaskQueue(任務隊列):用於保存等待執行的任務的阻塞隊列。 能夠選擇如下幾個阻塞隊列。
ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)原則對元素進行排序。
LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO (先進先出) 排序元素,吞吐量一般要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。
SynchronousQueue:一個不存儲元素的阻塞隊列。每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量一般要高於LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個隊列。
PriorityBlockingQueue:一個具備優先級的無限阻塞隊列。
maximumPoolSize(線程池最大大小):線程池容許建立的最大線程數。若是隊列滿了,而且已建立的線程數小於最大線程數,則線程池會再建立新的線程執行任務。值得注意的是若是使用了無界的任務隊列這個參數就沒什麼效果。
ThreadFactory:用於設置建立線程的工廠,能夠經過線程工廠給每一個建立出來的線程設置更有意義的名字。
RejectedExecutionHandler(飽和策略):當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採起一種策略處理提交的新任務。這個策略默認狀況下是AbortPolicy,表示沒法處理新任務時拋出異常。如下是JDK1.5提供的四種策略。
AbortPolicy:直接拋出異常。
CallerRunsPolicy:只用調用者所在線程來運行任務。
DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。
DiscardPolicy:不處理,丟棄掉。
固然也能夠根據應用場景須要來實現RejectedExecutionHandler接口自定義策略。如記錄日誌或持久化不能處理的任務。
keepAliveTime(線程活動保持時間):線程池的工做線程空閒後,保持存活的時間。因此若是任務不少,而且每一個任務執行的時間比較短,能夠調大這個時間,提升線程的利用率。
TimeUnit(線程活動保持時間的單位):可選的單位有天(DAYS),小時(HOURS),分鐘(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
線程池的大小決定着系統的性能,過大或者太小的線程池數量都沒法發揮最優的系統性能。
固然線程池的大小也不須要作的太過於精確,只須要避免過大和太小的狀況。通常來講,肯定線程池的大小須要考慮CPU的數量,內存大小,任務是計算密集型仍是IO密集型等因素
NCPU = CPU的數量
UCPU = 指望對CPU的使用率 0 ≤ UCPU ≤ 1
W/C = 等待時間與計算時間的比率
若是但願處理器達到理想的使用率,那麼線程池的最優大小爲:
線程池大小=NCPU *UCPU(1+W/C)
在Java中使用
int ncpus = Runtime.getRuntime().availableProcessors();
線程池工廠
Executors的線程池若是不指定線程工廠會使用Executors中的DefaultThreadFactory,默認線程池工廠建立的線程都是非守護線程。
使用自定義的線程工廠能夠作不少事情,好比能夠跟蹤線程池在什麼時候建立了多少線程,也能夠自定義線程名稱和優先級。若是將新建的線程都設置成守護線程,當主線程退出後,將會強制銷燬線程池。
下面這個例子,記錄了線程的建立,並將全部的線程設置成守護線程。
public class ThreadFactoryDemo {
public static class MyTask1 implements Runnable{
@Override
public void run() {
System.out.println(System.currentTimeMillis()+"Thrad ID:"+Thread.currentThread().getId());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args){
MyTask1 task = new MyTask1();
ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MICROSECONDS, new SynchronousQueue<Runnable>(), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
System.out.println("建立線程"+t);
return t;
}
});
for (int i = 0;i<=4;i++){
es.submit(task);
}
}
}
擴展線程池
ThreadPoolExecutor是能夠拓展的,它提供了幾個能夠在子類中改寫的方法:beforeExecute,afterExecute和terimated。
在執行任務的線程中將調用beforeExecute和afterExecute,這些方法中還能夠添加日誌,計時,監視或統計收集的功能,還能夠用來輸出有用的調試信息,幫助系統診斷故障。
如下阿里編碼規範裏面說的一段話:
線程池不容許使用Executors去建立,而是經過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同窗更加明確線程池的運行規則,規避資源耗盡的風險。 說明:Executors各個方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
主要問題是堆積的請求處理隊列可能會耗費很是大的內存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
主要問題是線程數最大數是Integer.MAX_VALUE,可能會建立數量很是多的線程,甚至OOM。
1.任務獨立。如何任務依賴於其餘任務,那麼可能產生死鎖。例如某個任務等待另外一個任務的返回值或執行結果,那麼除非線程池足夠大,不然將發生線程飢餓死鎖。
2.合理配置阻塞時間過長的任務。若是任務阻塞時間過長,那麼即便不出現死鎖,線程池的性能也會變得很糟糕。在Java併發包裏可阻塞方法都同時定義了限時方式和不限時方式。例如
Thread.join,BlockingQueue.put,CountDownLatch.await等,若是任務超時,則標識任務失敗,而後停止任務或者將任務放回隊列以便隨後執行,這樣,不管任務的最終結果是否成功,這種辦法都可以保證任務總能繼續執行下去。
3.設置合理的線程池大小。只須要避免過大或者太小的狀況便可,上文的公式線程池大小=NCPU *UCPU(1+W/C)。
4.選擇合適的阻塞隊列。newFixedThreadPool和newSingleThreadExecutor都使用了無界的阻塞隊列,無界阻塞隊列會有消耗很大的內存,若是使用了有界阻塞隊列,它會規避內存佔用過大的問題,可是當任務填滿有界阻塞隊列,新的任務該怎麼辦?在使用有界隊列是,須要選擇合適的拒絕策略,隊列的大小和線程池的大小必須一塊兒調節。對於很是大的或者無界的線程池,能夠使用SynchronousQueue來避免任務排隊,以直接將任務從生產者提交到工做者線程。
下面是Thrift框架處理socket任務所使用的一個線程池,能夠看一下FaceBook的工程師是如何自定義線程池的。
private static ExecutorService createDefaultExecutorService(Args args) {
SynchronousQueue executorQueue = new SynchronousQueue();
return new ThreadPoolExecutor(args.minWorkerThreads, args.maxWorkerThreads, 60L, TimeUnit.SECONDS,
executorQueue);
}
Semaphore又稱信號量,是操做系統中的一個概念,在Java併發編程中,信號量控制的是線程併發的數量。
package concurrent.semaphore;
import java.util.concurrent.Semaphore;
public class Driver {
// 控制線程的數目爲1,也就是單線程
private Semaphore semaphore = new Semaphore(1);
public void driveCar() {
try {
// 從信號量中獲取一個容許機會
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " start at " + System.currentTimeMillis());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " stop at " + System.currentTimeMillis());
// 釋放容許,將佔有的信號量歸還
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
倒計時器是一個很是強大的多線程控制器,好比在放飛火箭的時候,必須先確保各類儀器的檢查,引擎才能點火,這種狀況就適合使用CountDownLatch。
public CountDownLatch(int count)//構造器接受的這個參數就是這個計時器的計數個數
循環柵欄是另一種多線程併發控制工具,和倒計時器的做用相似,可是功能比倒計時器強大並且複雜。
static final CountDownLatch end = new CountDownLatch(10);
end.countDown();
end.await();
public CyclicBarrier(int parties, Runnable barrierAction)
barrierAction就是當計數器一次計數完成後,系統會執行的動做
await()
線程安全:虛擬機棧、本地方法棧、程序計數器。
非線程安全:堆,方法區
虛擬機棧:每一個方法被執行時,都會在內存中建立一個空間用來存儲方法中的局部變量,方法的出入口等信息。
本地方法棧:每一個本地方法被執行時,都會建立一個內存空間,用來存儲本地方法中的局部變量,方法的出入口等信息。
程序計數器:是當前程序所執行的class文件的行號指示器,經過改變行號來決定下一段要執行的字節碼指令,跳轉,循環,異常處理
堆:每個對象的建立跟分配都是在堆上面進行的,堆分爲新生代,老生代。新生代有一個Eden和兩個Survivor組成,默認比例是8:2。也能夠使用-XXSurvivorRatio來改變百分比。
方法區:用來存放類的版本,類的方法還有static修飾的對象等信息。
對象晉升老生代一共有三個可能:
1.當對象達到成年,經歷過15次GC(默認15次,可配置),對象就晉升爲老生代
2.大的對象會直接在老生代建立
3.新生代跟倖存區內存不足時,對象可能晉升到老生代
你知道哪幾種垃圾收集器,各自的優缺點,重點講下cms,包括原理,流程,優缺點。
串行垃圾收集器:收集時間長,停頓時間久
併發垃圾收集器:碎片空間多
CMS:併發標記清除。他的主要步驟有:初始收集,併發標記,從新標記,併發清除(刪除),重置
G1:主要步驟:初始標記,併發標記,從新標記,複製清除(整理)
CMS的缺點是對cpu的要求比較高。G1是將內存化成了多塊,全部對內段的大小有很大的要求
CMS是清除,因此會存在不少的內存碎片。G1是整理,因此碎片空間較小
垃圾回收算法的實現原理。
經常使用的垃圾回收算法有兩種: 引用計數和可達性分析
1.引用計數是增長一個字段來標識當前的引用次數,引用計數爲0的就是能夠GC的。可是引用計數不能解決循環引用的問題。
2. 可達性分析:就是經過一系列GC ROOT的對象做爲起點,向下搜索,搜索全部沒有與當前對象GC ROOT 有引用關係的對象。這些對象就是能夠GC的。
Java使用一個主內存來保存變量當前值,而每一個線程則有其獨立的工做內存。線程訪問變量的時候會將變量的值拷貝到本身的工做內存中,這樣,當線程對本身工做內存中的變量進行操做以後,就形成了工做內存中的變量拷貝的值與主內存中的變量值不一樣。
Java語言規範中指出:爲了得到最佳速度,容許線程保存共享成員變量的私有拷貝,並且只當線程進入或者離開同步代碼塊時才與共享成員變量的原始值對比。
這樣當多個線程同時與某個對象交互時,就必需要注意到要讓線程及時的獲得共享成員變量的變化。
而volatile關鍵字就是提示VM:對於這個成員變量不能保存它的私有拷貝,而應直接與共享成員變量交互。
使用建議:在兩個或者更多的線程訪問的成員變量上使用volatile。當要訪問的變量已在synchronized代碼塊中,或者爲常量時,沒必要使用。
因爲使用volatile屏蔽掉了VM中必要的代碼優化,因此在效率上比較低,所以必定在必要時才使用此關鍵字。
串行收集器 暫停全部應用的線程來工做:單線程
並行收集器 默認的垃圾收集器。暫停全部應用;多線程
G1收集器 用於大堆區域。堆內存分割,併發回收
CMS收集器 多線程掃描,標記須要回收的實例,清除
強引用:new出的對象之類的引用,
只要強引用還在,永遠不會回收
軟引用:引用但非必須的對象,內存溢出異常以前,回收
弱引用:非必須的對象,對象能生存到下一次垃圾收集發生以前。
虛引用:對生存時間無影響,在垃圾回收時獲得通知。
CMS收集器:一款以獲取最短回收停頓時間爲目標的收集器,是基於「標記-清除」算法實現的,分爲4個步驟:初始標記、併發標記、從新標記、併發清除。
G1收集器:面向服務端應用的垃圾收集器,過程:初始標記;併發標記;最終標記;篩選回收。總體上看是「標記-整理」,局部看是「複製」,不會產生內存碎片。
吞吐量優先的並行收集器:以到達必定的吞吐量爲目標,適用於科學技術和後臺處理等。
響應時間優先的併發收集器:保證系統的響應時間,減小垃圾收集時的停頓時間。適用於應用服務器、電信領域等。
設定堆最小內存大小-Xms。 -Xmx:堆內存最大限制。
設定新生代大小。新生代不宜過小,不然會有大量對象涌入老年代。
-XX:NewSize:新生代大小
-XX:NewRatio 新生代和老生代佔比
-XX:SurvivorRatio:伊甸園空間和倖存者空間的佔比
設定垃圾回收器
年輕代用 -XX:+UseParNewGC (串行) 年老代用-XX:+UseConcMarkSweepGC (CMS)
設定鎖的使用
多線程下關閉偏向鎖,比較浪費資源
g1 和 cms 區別,吞吐量優先和響應優先的垃圾收集器選擇
CMS是一種以最短停頓時間爲目標的收集器
響應優先選擇CMS,吞吐量高選擇G1
當出現了內存溢出,你怎麼排錯
用jmap看內存狀況,而後用 jstack主要用來查看某個Java進程內的線程堆棧信息
從永久代到元空間,在小範圍自動擴展永生代避免溢出
分代垃圾回收機制:不一樣的對象生命週期不一樣。把不一樣生命週期的對象放在不一樣代上,不一樣代上採用最合適它的垃圾回收方式進行回收。
JVM中共劃分爲三個代:年輕代、年老代和持久代,
年輕代:存放全部新生成的對象;
年老代:在年輕代中經歷了N次垃圾回收仍然存活的對象,將被放到年老代中,故都是一些生命週期較長的對象;
持久代:用於存放靜態文件,如Java類、方法等。
新生代的垃圾收集器命名爲「minor gc」,老生代的GC命名爲」Full Gc 或者Major GC」.其中用System.gc()強制執行的是Full Gc.
判斷對象是否須要回收的方法有兩種:
1.引用計數
當某對象的引用數爲0時,即可以進行垃圾收集。
2.對象引用遍歷
果某對象不能從這些根對象的一個(至少一個)到達,則將它做爲垃圾收集。在對象遍歷階段,gc必須記住哪些對象能夠到達,以便刪除不可到達的對象,這稱爲標記(marking)對象。
觸發GC(Garbage Collector)的條件:
1)GC在優先級最低的線程中運行,通常在應用程序空閒即沒有應用線程在運行時被調用。
2)Java堆內存不足時,GC會被調用。
默認比例8:1。
大部分對象都是朝生夕死。
複製算法的基本思想就是將內存分爲兩塊,每次只用其中一塊,當這一塊內存用完,就將還活着的對象複製到另一塊上面。複製算法不會產生內存碎片。
8. 深刻分析了Classloader,雙親委派機制
ClassLoader:類加載器(class loader)用來加載 Java 類到 Java 虛擬機中。Java 源程序(.java 文件)在通過 Java 編譯器編譯以後就被轉換成 Java 字節代碼(.class 文件)。類加載器負責讀取 Java 字節代碼,並轉換成 java.lang.Class 類的一個實例。
雙親委派機制:某個特定的類加載器在接到加載類的請求時,首先將加載任務委託給父類加載器,依次遞歸,若是父類加載器能夠完成類加載任務,就成功返回;只有父類加載器沒法完成此加載任務時,才本身去加載。
ClassLoader:類加載器(class loader)用來加載 Java 類到 Java 虛擬機中。Java 源程序(.java 文件)在通過 Java 編譯器編譯以後就被轉換成 Java 字節代碼(.class 文件)。類加載器負責讀取 Java 字節代碼,並轉換成 java.lang.Class 類的一個實例。
雙親委派機制:某個特定的類加載器在接到加載類的請求時,首先將加載任務委託給父類加載器,依次遞歸,若是父類加載器能夠完成類加載任務,就成功返回;只有父類加載器沒法完成此加載任務時,才本身去加載。
CAS是英文單詞Compare And Swap的縮寫,翻譯過來就是比較並替換。
CAS機制當中使用了3個基本操做數:內存地址V,舊的預期值A,要修改的新值B。
更新一個變量的時候,只有當變量的預期值A和內存地址V當中的實際值相同時,纔會將內存地址V對應的值修改成B。
這樣說或許有些抽象,咱們來看一個例子:
內存地址V
內存地址V
線程1: A=10 B=11
內存地址
線程1: A=10 B=11
線程2: 把變量值更新爲11
內存地址V
線程1: A=10 B=11 A!=V的值(10!=11)提交失敗
線程2: 把變量值更新爲11
內存地址V
線程1: A=11 B=12
內存地址V
線程1: A=11 B=12 A==V的值(11==11)
、、
內存地址V
線程1: A=11 B=12 A==V的值(11==11)
地址V的值更新爲12
從思想上來講,Synchronized屬於悲觀鎖,悲觀地認爲程序中的併發狀況嚴重,因此嚴防死守。CAS屬於樂觀鎖,樂觀地認爲程序中的併發狀況不那麼嚴重,因此讓線程不斷去嘗試更新。
1.CPU開銷較大
在併發量比較高的狀況下,若是許多線程反覆嘗試更新某一個變量,卻又一直更新不成功,循環往復,會給CPU帶來很大的壓力。
2.不能保證代碼塊的原子性
CAS機制所保證的只是一個變量的原子性操做,而不能保證整個代碼塊的原子性。好比須要保證3個變量共同進行原子性的更新,就不得不使用Synchronized了。
3.ABA問題
這是CAS機制最大的問題所在。
利用unsafe提供了原子性操做方法。
首先看一看AtomicInteger當中經常使用的自增方法 incrementAndGet:
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
private volatile int value;
public final int get() {
return value;
}
這段代碼是一個無限循環,也就是CAS的自旋。循環體當中作了三件事:
1.獲取當前值。
2.當前值+1,計算出目標值。
3.進行CAS操做,若是成功則跳出循環,若是失敗則重複上述步驟。
這裏須要注意的重點是 get 方法,這個方法的做用是獲取變量的當前值。
如何保證得到的當前值是內存中的最新值呢?很簡單,用volatile關鍵字來保證。
接下來看一看compareAndSet方法的實現,以及方法所依賴對象的來歷:
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception var1) {
throw new Error(var1);
}
}
compareAndSet方法的實現很簡單,只有一行代碼。這裏涉及到兩個重要的對象,一個是unsafe,一個是valueOffset。
什麼是unsafe呢?Java語言不像C,C++那樣能夠直接訪問底層操做系統,可是JVM爲咱們提供了一個後門,這個後門就是unsafe。unsafe爲咱們提供了硬件級別的原子操做。
至於valueOffset對象,是經過unsafe.objectFieldOffset方法獲得,所表明的是AtomicInteger對象value成員變量在內存中的偏移量。咱們能夠簡單地把valueOffset理解爲value變量的內存地址。
咱們在上一期說過,CAS機制當中使用了3個基本操做數:內存地址V,舊的預期值A,要修改的新值B。
而unsafe的compareAndSwapInt方法參數包括了這三個基本元素:valueOffset參數表明了V,expect參數表明了A,update參數表明了B。
正是unsafe的compareAndSwapInt方法保證了Compare和Swap操做之間的原子性操做。
當一個值從A更新成B,又更新會A,普通CAS機制會誤判經過檢測。
利用版本號比較能夠有效解決ABA問題。
什麼是ABA呢?假設內存中有一個值爲A的變量,存儲在地址V當中。
內存地址
此時有三個線程想使用CAS的方式更新這個變量值,每一個線程的執行時間有略微的誤差。線程1和線程2已經得到當前值,線程3還未得到當前值。
內存地址V
線程1: 獲取當前值A,指望更新爲B
線程2: 獲取當前值A,指望更新爲B
線程3: 指望更新爲A
接下來,線程1先一步執行成功,把當前值成功從A更新爲B;同時線程2由於某種緣由被阻塞住,沒有作更新操做;線程3在線程1更新以後,得到了當前值B。
內存地址
線程1: 獲取當前值A,成功更新爲B
線程2: 獲取當前值A,指望更新爲B,BLOCK
線程3: 獲取當前值B,指望更新爲A
再以後,線程2仍然處於阻塞狀態,線程3繼續執行,成功把當前值從B更新成了A。
內存地址V
線程1: 獲取當前值A,成功更新爲B,已返回
線程2: 獲取當前值A,指望更新爲B,BLOCK
線程3: 獲取當前值B,成功更新爲A
最後,線程2終於恢復了運行狀態,因爲阻塞以前已經得到了「當前值」A,而且通過compare檢測,內存地址V中的實際值也是A,因此成功把變量值A更新成了B。
內存地址
線程1: 獲取當前值A,成功更新爲B,已返回
線程2: 獲取「當前值「 A,成功更新爲B
線程3: 獲取當前值B,成功更新爲A,已返回
這個過程當中,線程2獲取到的變量值A是一箇舊值,儘管和當前的實際值相同,但內存地址V中的變量已經經歷了A->B->A的改變。
當咱們舉一個提款機的例子。假設有一個遵循CAS原理的提款機,小灰有100元存款,要用這個提款機來提款50元。
因爲提款機硬件出了點小問題,小灰的提款操做被同時提交兩次,開啓了兩個線程,兩個線程都是獲取當前值100元,要更新成50元。
理想狀況下,應該一個線程更新成功,另外一個線程更新失敗,小灰的存款只被扣一次。
線程1首先執行成功,把餘額從100改爲50。線程2由於某種緣由阻塞了。這時候,小灰的媽媽恰好給小灰匯款50元。
線程2仍然是阻塞狀態,線程3執行成功,把餘額從50改爲100。
線程2恢復運行,因爲阻塞以前已經得到了「當前值」100,而且通過compare檢測,此時存款實際值也是100,因此成功把變量值100更新成了50。
本來線程2應當提交失敗,小灰的正確餘額應該保持爲100元,結果因爲ABA問題提交成功了。
什麼意思呢?真正要作到嚴謹的CAS機制,咱們在Compare階段不只要比較指望值A和地址V中的實際值,還要比較變量的版本號是否一致。
咱們仍然以最初的例子來講明一下,假設地址V中存儲着變量值A,當前版本號是01。線程1得到了當前值A和版本號01,想要更新爲B,可是被阻塞了。
版本號01
內存地址V
線程1: 獲取當前值A,版本號01,指望更新爲B
A。這時候,內存地址V中的變量發生了屢次改變,版本號提高爲03,可是變量值仍然是A。
版本號03
內存地址V
線程1: 獲取當前值A,版本號01,指望更新爲B
隨後線程1恢復運行,進行Compare操做。通過比較,線程1所得到的值和地址V的實際值都是A,可是版本號不相等,因此這一次更新失敗。
版本號03
內存地址V
線程1: 獲取當前值A,版本號01,指望更新爲B
A==A 01!=03 更新失敗!
在Java當中,AtomicStampedReference類就實現了用版本號作比較的CAS機制。
1 語法分析 分析語句的語法是否符合規範,衡量語句中各表達式的意義。
2 語義分析 檢查語句中涉及的全部數據庫對象是否存在,且用戶有相應的權限。
3 視圖轉換 將涉及視圖的查詢語句轉換爲相應的對基表查詢語句。
4 表達式轉換 將複雜的SQL表達式轉換爲較簡單的等效鏈接表達式。
5 選擇優化器 不一樣的優化器通常產生不一樣的「執行計劃」
6 選擇鏈接方式 Oracle有三種鏈接方式,對多表鏈接Oracle可選擇適當的鏈接方式。
7 選擇鏈接順序 對多表鏈接Oracle選擇哪一對錶先鏈接,選擇這兩表中哪一個表作爲源數據表。
8 選擇數據的搜索路徑 根據以上條件選擇合適的數據搜索路徑,如是選用全表搜索仍是利用索引或是其餘的方式。
9 運行「執行計劃」
普通索引:最基本的索引,沒有任何限制
惟一索引:與"普通索引"相似,不一樣的就是:索引列的值必須惟一,但容許有空值。
主鍵索引:它 是一種特殊的惟一索引,不容許有空值。
全文索引:僅可用於 MyISAM 表,針對較大的數據,生成全文索引很耗時好空間。
組合索引:爲了更多的提升mysql效率可創建組合索引,遵循」最左前綴「原則。建立複合索引時應該將最經常使用 (頻率)做限制條件的列放在最左邊,依次遞減。
組合索引最左字段用in是能夠用到索引的,最好explain一下select。
①:建立必要的索引
在常常須要進行檢索的字段上建立索引,好比要按照姓名進行檢索,那麼就應該在姓名字段上建立索引,若是常常要按照員工部門和員工崗位級別進行檢索,那麼就應該在員工部門和員工崗位級別這兩個字段上建立索引。建立索引給檢索帶來的性能提高每每是巨大的,所以在發現檢索速度過慢的時候應該首先想到的就是建立索引。
②:使用預編譯查詢
程序中一般是根據用戶的輸入來動態執行SQL,這時應該儘可能使用參數化SQL,這樣不只能夠避免SQL注入漏洞攻擊,最重要數據庫會對這些參數化SQL進行預編譯,這樣第一次執行的時候DBMS會爲這個SQL語句進行查詢優化而且執行預編譯,這樣之後再執行這個SQL的時候就直接使用預編譯的結果,這樣能夠大大提升執行的速度。
③:調整Where字句中的鏈接順序
DBMS通常採用自下而上的順序解析where字句,根據這個原理錶鏈接最好寫在其餘where條件以前,那些能夠過濾掉最大數量記錄。
④:儘可能將多條SQL語句壓縮到一句SQL中
每次執行SQL的時候都要創建網絡鏈接、進行權限校驗、進行SQL語句的查詢優化、發送執行結果,這個過程是很是耗時的,所以應該儘可能避免過多的執行SQL語句,可以壓縮到一句SQL執行的語句就不要用多條來執行。
⑤:用where字句替換HAVING字句
避免使用HAVING字句,由於HAVING只會在檢索出全部記錄以後纔對結果集進行過濾,而where則是在聚合前刷選記錄,若是能經過where字句限制記錄的數目,那就能減小這方面的開銷。HAVING中的條件通常用於聚合函數的過濾,除此以外,應該將條件寫在where字句中。
⑥:使用表的別名
當在SQL語句中鏈接多個表時,請使用表的別名並把別名前綴於每一個列名上。這樣就能夠減小解析的時間並減小哪些友列名歧義引發的語法錯誤。
⑦:在in和exists中一般狀況下使用EXISTS,由於in不走索引。
⑧:避免在索引上使用計算
在where字句中,若是索引列是計算或者函數的一部分,DBMS的優化器將不會使用索引而使用全表查詢,函數屬於計算的一種效率低:select * from person where salary*12>25000(salary是索引列)效率高:select * from person where salary>25000/12(salary是索引列)
⑨:用union all替換union
當SQL語句須要union兩個查詢結果集合時,即便檢索結果中不會有重複的記錄,若是使用union這兩個結果集一樣會嘗試進行合併,而後在輸出最終結果前進行排序,所以若是能夠判斷檢索結果中不會有重複的記錄時候,應該用union all,這樣效率就會所以獲得提升。
⑩:避免SQL中出現隱式類型轉換
當某一張表中的索引字段在做爲where條件的時候,若是進行了隱式類型轉換,則此索引字段將會不被識別,由於隱式類型轉換也屬於計算,因此此時DBMS會使用全表掃面。最後須要注意的是:防止檢索範圍過寬若是DBMS優化器認爲檢索範圍過寬,那麼將放棄索引查找而使用全表掃描。下面幾種可能形成檢索範圍過寬的狀況。
a、使用is not null或者不等於判斷,可能形成優化器假設匹配的記錄數太多。
b、使用like運算符的時候,「a%」將會使用索引,而「a%c」和「%a」則會使用全表掃描,所以「a%c」和「%a」不能被有效的評估匹配的數量。
1) 表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的機率最高,併發度最低。
2) 行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的機率最低,併發度也最高。
3) 頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度通常
悲觀鎖(Pessimistic Lock), 顧名思義,就是很悲觀,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關係型數據庫裏邊就用到了不少這種鎖機制,好比行鎖,表鎖等,讀鎖,寫鎖等,都是在作操做以前先上鎖。
樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,可是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,能夠使用版本號等機制。樂觀鎖適用於多讀的應用類型,這樣能夠提升吞吐量,像數據庫若是提供相似於write_condition機制的其實都是提供的樂觀鎖。
B+樹:
(1)根節點和枝節點很簡單,分別記錄每一個葉子節點的最小值,並用一個指針指向葉子節點。
葉子節點裏每一個鍵值都指向真正的數據塊(如Oracle裏的RowID),每一個葉子節點都有前指針和後指針,這是爲了作範圍查詢時,葉子節點間能夠直接跳轉,從而避免再去回溯至枝和跟節點,B+樹做爲索引,減小磁盤IO數量。
(2)B+樹最大的性能問題是會產生大量的隨機IO,隨着新數據的插入,葉子節點會慢慢分裂,邏輯上連續的葉子節點在物理上每每不連續,甚至分離的很遠,但作範圍查詢時,會產生大量讀隨機IO對於大量的隨機寫也同樣。舉一個插入key跨度很大的例子,如7->1000->3->2000 ... 新插入的數據存儲在磁盤上相隔很遠,會產生大量的隨機寫IO.從上面能夠看出,低下的磁盤尋道速度嚴重影響性能(近些年來,磁盤尋道速度的發展幾乎處於停滯的狀態)。
LSM樹:存儲引擎和B樹存儲引擎同樣,LSM樹分爲兩個部分,一部分在磁盤一部分在內存,當內存空間逐漸被佔滿以後,LSM會把這些有序的鍵刷新到磁盤,同時和磁盤中的LSM樹合併成一個文件。讀取是更新最新操做到磁盤,讀取慢(先取內存,而後讀磁盤),犧牲讀性能,提升寫性能,磁盤順序寫,週期調整磁盤文件。
總結:
(1)B+樹的特色決定了可以對主鍵進行高效的查找和刪除,B+樹可以提供高效的的範圍掃描功能得益於相互鏈接且按主鍵有序,掃描時避免了耗時的遍歷操做。
LSM樹在查找時先查找內存的存儲,若是在內存中未命中就去磁盤文件中查找文件,找到key以後返回最新的版本。
B樹和LSM樹最主要的區別在於他們的結構和如何利用硬件,特別是磁盤。
(2)在沒有太多的修改時,B+樹表現得很好,由於修改要求執行高代價的優化操做以保證查詢能在有限的時間內完成。LSM以磁盤傳輸速率工做,並能較好地擴展以處理大量數據,他們使用日誌文件和內存存儲來將隨機寫轉換成順序寫,所以也可以保證穩定的數據插入速率。因爲讀寫分離,兩個操做也不存在衝突的問題。
(3)LSM樹的主要目標是快速的創建索引,B樹是創建索引的通用技術,可是在大併發插入數據的狀況下,B樹須要大量的隨機IO,這些隨機IO嚴重影響索引創建速度。LSM經過磁盤序列寫,來達到最優的寫性能,由於這個下降了磁盤的尋道次數,一次IO能夠寫入多個索引塊。
BeanFactory 能夠理解爲含有bean集合的工廠類。BeanFactory 包含了種bean的定義,以便在接收到客戶端請求時將對應的bean實例化。
BeanFactory還能在實例化對象的時生成協做類之間的關係。此舉將bean自身與bean客戶端的配置中解放出來。BeanFactory還包含了bean生命週期的控制,調用客戶端的初始化方法(initialization methods)和銷燬方法(destruction methods)。
從表面上看,application context如同bean factory同樣具備bean定義、bean關聯關係的設置,根據請求分發bean的功能。但application context在此基礎上還提供了其餘的功能。
提供了支持國際化的文本消息
統一的資源文件讀取方式
已在監聽器中註冊的bean的事件
如下是三種較常見的 ApplicationContext 實現方式:
1.ClassPathXmlApplicationContext:從classpath的XML配置文件中讀取上下文,並生成上下文定義。應用程序上下文從程序環境變量中取得。
ApplicationContext context = new ClassPathXmlApplicationContext(「bean.xml」);
2.FileSystemXmlApplicationContext :由文件系統中的XML配置文件讀取上下文。
ApplicationContext context = new FileSystemXmlApplicationContext(「bean.xml」);
3. XmlWebApplicationContext:由Web應用的XML文件讀取上下文。
將Spring配置到應用開發中有如下三種方式:
基於XML的配置
基於註解的配置
基於Java的配置
singleton:這種bean範圍是默認的,這種範圍確保無論接受到多少個請求,每一個容器中只有一個bean的實例,單例的模式由bean factory自身來維護。
prototype:原形範圍與單例範圍相反,爲每個bean請求提供一個實例。
request:在請求bean範圍內會每個來自客戶端的網絡請求建立一個實例,在請求完成之後,bean會失效並被垃圾回收器回收。
Session:與請求範圍相似,確保每一個session中有一個bean的實例,在session過時後,bean會隨之失效。
global-session:global-session和Portlet應用相關。當你的應用部署在Portlet容器中工做時,它包含不少portlet。若是你想要聲明讓全部的portlet共用全局的存儲變量的話,那麼這全局變量須要存儲在global-session中。全局做用域與Servlet中的session做用域效果相同。
1.spring mvc的全部請求都提交給DispatcherServlet,它會委託應用系統的其餘模塊負責對請求進行真正的處理工做。
2.DispatcherServlet查詢一個或多個HandlerMapping,找處處理請求的Controller.
3.DispatcherServlet請求提交到目標Controller
4.Controller進行業務邏輯處理後,會返回一個ModelAndView
5.Dispathcher查詢一個或多個ViewResolver視圖解析器,找到ModelAndView對象指定的視圖對象
6.視圖對象負責渲染返回給客戶端。
1、事務的基本原理
Spring事務 的本質其實就是數據庫對事務的支持,沒有數據庫的事務支持,spring是沒法提供事務功能的。對於純JDBC操做數據庫,想要用到事務,能夠按照如下步驟進行:
獲取鏈接 Connection con = DriverManager.getConnection()
開啓事務con.setAutoCommit(true/false);
執行CRUD
提交事務/回滾事務 con.commit() / con.rollback();
關閉鏈接 conn.close();
2、Spring 事務的傳播屬性
所謂spring事務的傳播屬性,就是定義在存在多個事務同時存在的時候,spring應該如何處理這些事務的行爲。這些屬性在TransactionDefinition中定義 ,
PROPAGATION_REQUIRED 支持當前事務, 沒有事務,就新建一個事務。這是最多見的
選擇,也是 Spring 默認的事務的傳播。
PROPAGATION_REQUIRES_NEW 新建事務,若是當前存在事務,把當前事務掛起。新建的事務將和被掛起的事務沒有任何關係,是兩個獨立的事務,外層事務失敗回滾以後,不能回滾內層事務執行的結果,內層事務失敗拋出異常,外層事務捕獲,也能夠不處理回滾操做
PROPAGATION_SUPPORTS 支持當前事務,若是當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY 支持當前事務,若是當前沒有事務,就拋出異常。
PROPAGATION_NOT_SUPPORTED 以非事務方式執行操做,若是當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER 以非事務方式執行,若是當前存在事務,則拋出異常。
PROPAGATION_NESTED
若是一個活動的事務存在,則運行在一個嵌套的事務中。若是沒有活動事務,則按REQUIRED屬性執行。它使用了一個單獨的事務,這個事務擁有多個能夠回滾的保存點。內部事務的回滾不會對外部事務形成影響。它只對DataSourceTransactionManager事務管理器起效。
3、數據庫隔離級別
隔離級別 隔離級別的值 致使的問題
Read-Uncommitted 0 致使髒讀
Read-Committed 1 避免髒讀,容許不可重複讀和幻讀
Repeatable-Read 2 避免髒讀,不可重複讀,容許幻讀
Serializable 3 串行化讀,事務只能一個一個執行,避免了髒讀、不可重複讀、幻讀。執行效率慢,使用時慎重
髒讀: 一事務對數據進行了增刪改,但未提交,另外一事務能夠讀取到未提交的數據。若是第一個事務這時候回滾了,那麼第二個事務就讀到了髒數據。
不可重複讀:一個事務中發生了兩次讀操做,第一次讀操做和第二次操做之間,另一個事務對數據進行了修改,這時候兩次讀取的數據是不一致的。
幻讀:第一個事務對必定範圍的數據進行批量修改,第二個事務在這個範圍增長一條數據,這時候第一個事務就會丟失對新增數據的修改。
總結:
隔離級別越高,越能保證數據的完整性和一致性,可是對併發性能的影響也越大。
大多數的數據庫默認隔離級別爲 Read Commited,好比 SqlServer、Oracle
少數數據庫默認隔離級別爲:Repeatable Read 好比: MySQL InnoDB
4、Spring中的隔離級別
常量 解釋
ISOLATION_DEFAULT 這是個 PlatfromTransactionManager 默認的隔離級別,使用數據庫默認的事務隔離級別。另外四個與 JDBC 的隔離級別相對應。
ISOLATION_READ_UNCOMMITTED 這是事務最低的隔離級別,它充許另一個事務能夠看到這個事務未提交的數據。這種隔離級別會產生髒讀,不可重複讀和幻像讀。
ISOLATION_READ_COMMITTED 保證一個事務修改的數據提交後才能被另一個事務讀取。另一個事務不能讀取該事務未提交的數據。
ISOLATION_REPEATABLE_READ 這種事務隔離級別能夠防止髒讀,不可重複讀。可是可能出現幻像讀。
ISOLATION_SERIALIZABLE 這是花費最高代價可是最可靠的事務隔離級別。事務被處理爲順序執行。
當異常被捕獲catch的時候,spring的事物則不會回滾
爲何不會滾呢??
spring aop 異常捕獲原理:被攔截的方法需顯式拋出異常,並不能經任何處理,這樣aop代理才能捕獲到方法的異常,才能進行回滾,默認狀況下aop只捕獲runtimeexception的異常;
解決方案:
RuntimeException()語句,以便讓aop捕獲異常再去回滾,而且在service上層(webservice客戶端,view層action)要繼續捕獲這個異常並處理
catch (Exception e) {
json.setStatus(StatusCode.ERROR);
json.setMessage(e.getMessage());
System.out.println("添加用戶異常,報錯信息:"+e.getMessage());
//繼續拋出異常
throw new RuntimeException();
}
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();語句,手動回滾,這樣上層就無需去處理異常(如今項目的作法)
形如:@Transactional(readOnly = true, rollbackFor = Exception.class)。
註解注入顧名思義就是經過註解來實現注入,Spring和注入相關的常見註解有Autowired、Resource、Qualifier、Service、Controller、Repository、Component。
Autowired是自動注入,自動從spring的上下文找到合適的bean來注入。
Resource用來指定名稱注入。
Qualifier和Autowired配合使用,指定bean的名稱。
Service,用於標註業務層組件。
Controller用於標註控制層組件(如struts中的action)。
Repository 用於標註數據訪問組件,即DAO組件。
Component泛指組件,,當組件很差歸類的時候,咱們能夠使用這個註解進行標註。
spring掃描Service、Controller、Repository 、Component註解配置時,會標記這些類要生成bean。
1、 @Autowired與@Resource均可以用來裝配bean.均可以寫在字段上,或寫在setter方法上。
2、 @Autowired默認按類型裝配(這個註解是屬業spring的),默認狀況下必需要求依賴對象必須存在,若是要容許null值,能夠設置它的required屬性爲false,如:@Autowired(required=false),若是咱們想使用名稱裝配能夠結合@Qualifier註解進行使用,以下:
@Autowired() @Qualifier("baseDao")
private BaseDao baseDao;
3、@Resource(這個註解屬於J2EE的),默認安裝名稱進行裝配,名稱能夠經過name屬性進行指定,若是沒有指定name屬性,當註解寫在字段上時,默認取字段名進行安裝名稱查找,若是註解寫在setter方法上默認取屬性名進行裝配。當找不到與名稱匹配的bean時才按照類型進行裝配。可是須要注意的是,若是name屬性一旦指定,就只會按照名稱進行裝配。
當一個類實現了這個接口以後,這個類就能夠方便地得到 ApplicationContext 中的全部bean。換句話說,就是這個類能夠直接獲取Spring配置文件中,全部有引用到的bean對象。
1、定義一個工具類,實現 ApplicationContextAware,實現 setApplicationContext方法
public class SpringContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context)
throws BeansException {
SpringContextUtils.context = context;
}
public static ApplicationContext getContext(){
return context;
}
}
2、在Spring配置文件中註冊該工具類
<!--Spring中bean獲取的工具類-->
<bean id="springContextUtils" class="com.zker.common.util.SpringContextUtils" />
3、編寫方法進行使用
UserDao userDao = (UserDao)SpringContextUtils.getContext().getBean("userDao");
1. 創建事件類,繼承 ApplicationEvent 父類
2. 創建監聽類,實現 ApplicationListener 接口
3. 在配置文件 bean.xml 中註冊寫好的全部 事件類 和 監聽類
4. 須要發佈事件的類 要實現 ApplicationContextAware 接口,並獲取 ApplicationContext 參數
ActionEvent event = new ActionEvent(username);
SpringContextUtils.applicationContext.publishEvent(event);
1. ThreadPoolTaskExecutor配置
<!-- spring thread pool executor -->
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 線程池維護線程的最少數量 -->
<property name="corePoolSize" value="5" />
<!-- 容許的空閒時間 -->
<property name="keepAliveSeconds" value="200" />
<!-- 線程池維護線程的最大數量 -->
<property name="maxPoolSize" value="10" />
<!-- 緩存隊列 -->
<property name="queueCapacity" value="20" />
<!-- 對拒絕task的處理策略 -->
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
</property>
</bean>
屬性字段說明
corePoolSize:線程池維護線程的最少數量
keepAliveSeconds:容許的空閒時間
maxPoolSize:線程池維護線程的最大數量
queueCapacity:緩存隊列
rejectedExecutionHandler:對拒絕task的處理策略
2. execute(Runable)方法執行過程
若是此時線程池中的數量小於corePoolSize,即便線程池中的線程都處於空閒狀態,也要建立新的線程來處理被添加的任務。
若是此時線程池中的數量等於 corePoolSize,可是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。
若是此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,而且線程池中的數量小於maxPoolSize,建新的線程來處理被添加的任務。
若是此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,而且線程池中的數量等於maxPoolSize,那麼經過handler所指定的策略來處理此任務。也就是:處理任務的優先級爲:核心線程corePoolSize、任務隊列workQueue、最大線程 maximumPoolSize,若是三者都滿了,使用handler處理被拒絕的任務。
當線程池中的線程數量大於corePoolSize時,若是某線程空閒時間超過keepAliveTime,線程將被終止。這樣,線程池能夠動態的調整池中的線程數。
1.速度快,由於數據存在內存中,相似於HashMap,HashMap的優點就是查找和操做的時間複雜度都是O(1)
2.支持豐富數據類型,支持string,list,set,sorted set,hash
3.支持事務,操做都是原子性,所謂的原子性就是對數據的更改要麼所有執行,要麼所有不執行
4.豐富的特性:可用於緩存,消息,按key設置過時時間,過時後將會自動刪除
1.memcached全部的值均是簡單的字符串,redis做爲其替代者,支持更爲豐富的數據類型
2.redis的速度比memcached快不少
3.redis能夠持久化其數據
4.Redis支持數據的備份,即master-slave模式的數據備份。
5.使用底層模型不一樣, 它們之間底層實現方式 以及與客戶端之間通訊的應用協議不同。
Redis直接本身構建了VM 機制 ,由於通常的系統調用系統函數的話,會浪費必定的時間去移動和請求。
6.value大小:redis最大能夠達到1GB,而memcache只有1MB
(1) Master最好不要作任何持久化工做,如RDB內存快照和AOF日誌文件
(2) 若是數據比較重要,某個Slave開啓AOF備份數據,策略設置爲每秒同步一次
(3) 爲了主從複製的速度和鏈接的穩定性,Master和Slave最好在同一個局域網內
(4) 儘可能避免在壓力很大的主庫上增長從庫
(5) 主從複製不要用圖狀結構,用單向鏈表結構更爲穩定,即:Master <- Slave1 <- Slave2 <- Slave3...
這樣的結構方便解決單點故障問題,實現Slave對Master的替換。若是Master掛了,能夠馬上啓用Slave1作Master,其餘不變。
volatile-lru:從已設置過時時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
volatile-ttl:從已設置過時時間的數據集(server.db[i].expires)中挑選將要過時的數據淘汰
volatile-random:從已設置過時時間的數據集(server.db[i].expires)中任意選擇數據淘汰
allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰
allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
no-enviction(驅逐):禁止驅逐數據
注意這裏的6種機制,volatile和allkeys規定了是對已設置過時時間的數據集淘汰數據仍是從所有數據集淘汰數據,後面的lru、ttl以及random是三種不一樣的淘汰策略,再加上一種no-enviction永不回收的策略。
使用策略規則:
1、若是數據呈現冪律分佈,也就是一部分數據訪問頻率高,一部分數據訪問頻率低,則使用allkeys-lru
2、若是數據呈現平等分佈,也就是全部的數據訪問頻率都相同,則使用allkeys-random
一:快照模式
或許在用Redis之初的時候,就據說過redis有兩種持久化模式,第一種是SNAPSHOTTING模式,仍是一種是AOF模式,並且在實戰場景下用的最多的莫過於SNAPSHOTTING模式,這個不須要反駁吧,並且你可能還知道,使用SNAPSHOTTING模式,須要在redis.conf中設置配置參數,好比下面這樣:
save 900 1
save 300 10
save 60 10000
上面三組命令也是很是好理解的,就是說900指的是「秒數」,1指的是「change次數」,接下來若是在「900s「內有1次更改,那麼就執行save保存,一樣的道理,若是300s內有10次change,60s內有1w次change,那麼也會執行save操做,就這麼簡單,看了我剛纔說了這麼幾句話,是否是有種直覺在告訴你,有兩個問題是否是要澄清一下:
固然能夠進行手工操做,redis提供了兩個操做命令:save,bgsave,這兩個命令都會強制將數據刷新到硬盤中,以下:
先設值set name jack 後執行save 命令
先設值set age 20 後執行bgsave 命令
Redis 持久化:
提供了多種不一樣級別的持久化方式:一種是RDB,另外一種是AOF.
RDB 持久化能夠在指定的時間間隔內生成數據集的時間點快照(point-in-time snapshot)。
AOF 持久化記錄服務器執行的全部寫操做命令,並在服務器啓動時,經過從新執行這些命令來還原數據集。 AOF 文件中的命令所有以 Redis 協議的格式來保存,新命令會被追加到文件的末尾。 Redis 還能夠在後臺對 AOF 文件進行重寫(rewrite),使得 AOF 文件的體積不會超出保存數據集狀態所需的實際大小。Redis 還能夠同時使用 AOF 持久化和 RDB 持久化。 在這種狀況下, 當 Redis 重啓時, 它會優先使用 AOF 文件來還原數據集, 由於 AOF 文件保存的數據集一般比 RDB 文件所保存的數據集更完整。你甚至能夠關閉持久化功能,讓數據只在服務器運行時存在。
RDB 的優勢:
RDB 是一個很是緊湊(compact)的文件,它保存了 Redis 在某個時間點上的數據集。 這種文件很是適合用於進行備份: 好比說,你能夠在最近的 24 小時內,每小時備份一次 RDB 文件,而且在每月的每一天,也備份一個 RDB 文件。 這樣的話,即便趕上問題,也能夠隨時將數據集還原到不一樣的版本。RDB 很是適用於災難恢復(disaster recovery):它只有一個文件,而且內容都很是緊湊,能夠(在加密後)將它傳送到別的數據中心,或者亞馬遜 S3 中。RDB 能夠最大化 Redis 的性能:父進程在保存 RDB 文件時惟一要作的就是 fork 出一個子進程,而後這個子進程就會處理接下來的全部保存工做,父進程無須執行任何磁盤 I/O 操做。RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。
RDB 的缺點:
若是你須要儘可能避免在服務器故障時丟失數據,那麼 RDB 不適合你。 雖然 Redis 容許你設置不一樣的保存點(save point)來控制保存 RDB 文件的頻率, 可是, 由於RDB 文件須要保存整個數據集的狀態, 因此它並非一個輕鬆的操做。 所以你可能會至少 5 分鐘才保存一次 RDB 文件。 在這種狀況下, 一旦發生故障停機, 你就可能會丟失好幾分鐘的數據。每次保存 RDB 的時候,Redis 都要 fork() 出一個子進程,並由子進程來進行實際的持久化工做。 在數據集比較龐大時, fork() 可能會很是耗時,形成服務器在某某毫秒內中止處理客戶端; 若是數據集很是巨大,而且 CPU 時間很是緊張的話,那麼這種中止時間甚至可能會長達整整一秒。 雖然 AOF 重寫也須要進行 fork() ,但不管 AOF 重寫的執行間隔有多長,數據的耐久性都不會有任何損失。
AOF 的優勢:
使用 AOF 持久化會讓 Redis 變得很是耐久(much more durable):你能夠設置不一樣的 fsync 策略,好比無 fsync ,每秒鐘一次 fsync ,或者每次執行寫入命令時 fsync 。 AOF 的默認策略爲每秒鐘 fsync 一次,在這種配置下,Redis 仍然能夠保持良好的性能,而且就算髮生故障停機,也最多隻會丟失一秒鐘的數據( fsync 會在後臺線程執行,因此主線程能夠繼續努力地處理命令請求)。AOF 文件是一個只進行追加操做的日誌文件(append only log), 所以對 AOF 文件的寫入不須要進行 seek , 即便日誌由於某些緣由而包含了未寫入完整的命令(好比寫入時磁盤已滿,寫入中途停機,等等), redis-check-aof 工具也能夠輕易地修復這種問題。
Redis 能夠在 AOF 文件體積變得過大時,自動地在後臺對 AOF 進行重寫: 重寫後的新 AOF 文件包含了恢復當前數據集所需的最小命令集合。 整個重寫操做是絕對安全的,由於 Redis 在建立新 AOF 文件的過程當中,會繼續將命令追加到現有的 AOF 文件裏面,即便重寫過程當中發生停機,現有的 AOF 文件也不會丟失。 而一旦新 AOF 文件建立完畢,Redis 就會從舊 AOF 文件切換到新 AOF 文件,並開始對新 AOF 文件進行追加操做。AOF 文件有序地保存了對數據庫執行的全部寫入操做, 這些寫入操做以 Redis 協議的格式保存, 所以 AOF 文件的內容很是容易被人讀懂, 對文件進行分析(parse)也很輕鬆。 導出(export) AOF 文件也很是簡單: 舉個例子, 若是你不當心執行了 FLUSHALL 命令, 但只要 AOF 文件未被重寫, 那麼只要中止服務器, 移除 AOF 文件末尾的 FLUSHALL 命令, 並重啓 Redis , 就能夠將數據集恢復到 FLUSHALL 執行以前的狀態。
AOF 的缺點:
對於相同的數據集來講,AOF 文件的體積一般要大於 RDB 文件的體積。根據所使用的 fsync 策略,AOF 的速度可能會慢於 RDB 。 在通常狀況下, 每秒 fsync 的性能依然很是高, 而關閉 fsync 可讓 AOF 的速度和 RDB 同樣快, 即便在高負荷之下也是如此。 不過在處理巨大的寫入載入時,RDB 能夠提供更有保證的最大延遲時間(latency)。AOF 在過去曾經發生過這樣的 bug : 由於個別命令的緣由,致使 AOF 文件在從新載入時,沒法將數據集恢復成保存時的原樣。 (舉個例子,阻塞命令 BRPOPLPUSH 就曾經引發過這樣的 bug 。) 測試套件裏爲這種狀況添加了測試: 它們會自動生成隨機的、複雜的數據集, 並經過從新載入這些數據來確保一切正常。 雖然這種 bug 在 AOF 文件中並不常見, 可是對比來講, RDB 幾乎是不可能出現這種 bug 的。
通常來講,若是想達到足以媲美 PostgreSQL 的數據安全性, 你應該同時使用兩種持久化功能。若是你很是關心你的數據,但仍然能夠承受數分鐘之內的數據丟失, 那麼你能夠只使用 RDB 持久化。有不少用戶都只使用 AOF 持久化, 但咱們並不推薦這種方式: 由於定時生成 RDB 快照(snapshot)很是便於進行數據庫備份, 而且 RDB 恢復數據集的速度也要比 AOF 恢復的速度要快, 除此以外, 使用 RDB 還能夠避免以前提到的 AOF 程序的 bug 。由於以上提到的種種緣由, 將來咱們可能會將 AOF 和 RDB 整合成單個持久化模型。 (這是一個長期計劃。)
RDB 快照:
在默認狀況下, Redis 將數據庫快照保存在名字爲 dump.rdb 的二進制文件中。你能夠對 Redis 進行設置, 讓它在「 N 秒內數據集至少有 M 個改動」這一條件被知足時, 自動保存一次數據集。你也能夠經過調用 SAVE 或者 BGSAVE , 手動讓 Redis 進行數據集保存操做。好比說, 如下設置會讓 Redis 在知足「 60 秒內有至少有 1000 個鍵被改動」這一條件時, 自動保存一次數據集:
save 60 1000
這種持久化方式被稱爲快照(snapshot)。
當 Redis 須要保存 dump.rdb 文件時, 服務器執行如下操做:
Redis 調用 fork() ,同時擁有父進程和子進程。
子進程將數據集寫入到一個臨時 RDB 文件中。
當子進程完成對新 RDB 文件的寫入時,Redis 用新 RDB 文件替換原來的 RDB 文件,並刪除舊的 RDB 文件。
這種工做方式使得 Redis 能夠從寫時複製(copy-on-write)機制中獲益。
只進行追加操做的文件(append-only file,AOF)
快照功能並非很是耐久(durable): 若是 Redis 由於某些緣由而形成故障停機, 那麼服務器將丟失最近寫入、且仍未保存到快照中的那些數據。儘管對於某些程序來講, 數據的耐久性並非最重要的考慮因素, 可是對於那些追求徹底耐久能力(full durability)的程序來講, 快照功能就不太適用了。
從 1.1 版本開始, Redis 增長了一種徹底耐久的持久化方式: AOF 持久化。
你能夠經過修改配置文件來打開 AOF 功能:
appendonly yes
從如今開始, 每當 Redis 執行一個改變數據集的命令時(好比 SET), 這個命令就會被追加到 AOF 文件的末尾。
這樣的話, 當 Redis 從新啓時, 程序就能夠經過從新執行 AOF 文件中的命令來達到重建數據集的目的。
由於 AOF 的運做方式是不斷地將命令追加到文件的末尾, 因此隨着寫入命令的不斷增長, AOF 文件的體積也會變得愈來愈大。舉個例子, 若是你對一個計數器調用了 100 次 INCR , 那麼僅僅是爲了保存這個計數器的當前值, AOF 文件就須要使用 100 條記錄(entry)。然而在實際上, 只使用一條 SET 命令已經足以保存計數器的當前值了, 其他 99 條記錄實際上都是多餘的。爲了處理這種狀況, Redis 支持一種有趣的特性: 能夠在不打斷服務客戶端的狀況下, 對 AOF 文件進行重建(rebuild)。執行 BGREWRITEAOF 命令, Redis 將生成一個新的 AOF 文件, 這個文件包含重建當前數據集所需的最少命令。
你能夠配置 Redis 多久纔將數據 fsync 到磁盤一次。
有三個選項:
每次有新命令追加到 AOF 文件時就執行一次 fsync :很是慢,也很是安全。
每秒 fsync 一次:足夠快(和使用 RDB 持久化差很少),而且在故障時只會丟失 1 秒鐘的數據。
從不 fsync :將數據交給操做系統來處理。更快,也更不安全的選擇。
推薦(而且也是默認)的措施爲每秒 fsync 一次, 這種 fsync 策略能夠兼顧速度和安全性。
老是 fsync 的策略在實際使用中很是慢, 即便在 Redis 2.0 對相關的程序進行了改進以後還是如此 —— 頻繁調用 fsync 註定了這種策略不可能快得起來。
服務器可能在程序正在對 AOF 文件進行寫入時停機, 若是停機形成了 AOF 文件出錯(corrupt), 那麼 Redis 在重啓時會拒絕載入這個 AOF 文件, 從而確保數據的一致性不會被破壞。
當發生這種狀況時, 能夠用如下方法來修復出錯的 AOF 文件:
爲現有的 AOF 文件建立一個備份。
使用 Redis 附帶的 redis-check-aof 程序,對原來的 AOF 文件進行修復。
$ redis-check-aof --fix
(可選)使用 diff -u 對比修復後的 AOF 文件和原始 AOF 文件的備份,查看兩個文件之間的不一樣之處。
重啓 Redis 服務器,等待服務器載入修復後的 AOF 文件,並進行數據恢復。
AOF 的運做方式
AOF 重寫和 RDB 建立快照同樣,都巧妙地利用了寫時複製機制。
Redis 執行 fork() ,如今同時擁有父進程和子進程。
子進程開始將新 AOF 文件的內容寫入到臨時文件。對於全部新執行的寫入命令,父進程一邊將它們累積到一個內存緩存中,一邊將這些改動追加到現有 AOF 文件的末尾: 這樣即便在重寫的中途發生停機,現有的 AOF 文件也仍是安全的。當子進程完成重寫工做時,它給父進程發送一個信號,父進程在接收到信號以後,將內存緩存中的全部數據追加到新 AOF 文件的末尾。如今 Redis 原子地用新文件替換舊文件,以後全部命令都會直接追加到新 AOF 文件的末尾。
爲最新的 dump.rdb 文件建立一個備份。
將備份放到一個安全的地方。
執行如下兩條命令:
redis-cli> CONFIG SET appendonly yes
redis-cli> CONFIG SET save ""
確保命令執行以後,數據庫的鍵的數量沒有改變。
確保寫命令會被正確地追加到 AOF 文件的末尾。
步驟 3 執行的第一條命令開啓了 AOF 功能: Redis 會阻塞直到初始 AOF 文件建立完成爲止, 以後 Redis 會繼續處理命令請求, 並開始將寫入命令追加到 AOF 文件末尾。
步驟 3 執行的第二條命令用於關閉 RDB 功能。 這一步是可選的, 若是你願意的話, 也能夠同時使用 RDB 和 AOF 這兩種持久化功能。
別忘了在 redis.conf 中打開 AOF 功能! 不然的話, 服務器重啓以後, 以前經過 CONFIG SET 設置的配置就會被遺忘, 程序會按原來的配置來啓動服務器。
在版本號大於等於 2.4 的 Redis 中, BGSAVE 執行的過程當中, 不能夠執行 BGREWRITEAOF 。 反過來講, 在 BGREWRITEAOF 執行的過程當中, 也不能夠執行 BGSAVE 。
這能夠防止兩個 Redis 後臺進程同時對磁盤進行大量的 I/O 操做。
若是 BGSAVE 正在執行, 而且用戶顯示地調用 BGREWRITEAOF 命令, 那麼服務器將向用戶回覆一個 OK 狀態, 並告知用戶, BGREWRITEAOF 已經被預約執行: 一旦 BGSAVE 執行完畢, BGREWRITEAOF 就會正式開始。當 Redis 啓動時, 若是 RDB 持久化和 AOF 持久化都被打開了, 那麼程序會優先使用 AOF 文件來恢復數據集, 由於 AOF 文件所保存的數據一般是最完整的。
Redis 對於數據備份是很是友好的, 由於你能夠在服務器運行的時候對 RDB 文件進行復制: RDB 文件一旦被建立, 就不會進行任何修改。 當服務器要建立一個新的 RDB 文件時, 它先將文件的內容保存在一個臨時文件裏面, 當臨時文件寫入完畢時, 程序才使用 原子地用臨時文件替換原來的 RDB 文件。這也就是說, 不管什麼時候, 複製 RDB 文件都是絕對安全的。
flusall 清除reids全部褲
flushdb 清楚當前褲
dbsize 查看當前數據庫的key數量大小 dbsize
select 切換數據庫 select 1
move 把當前key 移動到哪一個庫 move key 1
expire 設置當前key過時,單位秒 expire k1 10
ttl 查看當前key的過時時間,-1表示永不過時,-2表示已過時 ttl k1
type 查看當前key的數據類型 type k1
exists 判斷某個key是否存在 exists key1
incr 對數字類型相加 incr k1
decr
incrby
decrby
save 立刻進行快照
RDB是整個內存的壓縮過的Snapshot,RDB的數據結構,key配置符合的快照觸發條件,
默認:
是1分鐘內改了1萬次
或5分鐘內改了了10次
或15分鐘內改了1次
1.failover cluster
失敗自動切換,當出現失敗,重試其餘服務器(缺省),一般用於讀操做,但重試會帶來更長的延時,可經過retries=「2」來設置重試次數(不含第一次)
<dubbo:service retries="2">
或者
<dubbo:reference retries="2">
或者
<dubbo:reference>
<dubbo:method name="findFoo" retries=2>
<dubbo:reference/>
2.failfast cluster
快速失效,只發起一次調用,失敗當即報錯。一般用於非冪等性寫操做,好比說新增記錄
<dubbo:service cluster="failfast">
或者
<dubbo:reference cluster="failfast"
3.failsaft cluster
失敗安全,出現異常時,直接忽略,一般用於寫入審計日誌等操做
<dubbo:service cluster="failsafe">
或者
<dubbo:reference cluster="failsafe">
4.failback cluster
失敗自動恢復,後臺記錄失敗請求,定時重發,一般用於消息通知操做
<dubbo:service cluster="failback">
或者
<dubbo:reference cluster="failback">
5.forking cluster
並行調用多個服務器,只要一個成功即返回。一般用於實時性要求較高的讀操做,但須要浪費更多的服務器資源。可經過forks=「2」來設置最大並行數。
<dubbo:service cluster="forking">
或者
<dubbo:reference cluster="forking">
Random LoadBalance
隨機,按權重設置隨機機率。
在一個截面上碰撞的機率高,但調用量越大分佈越均勻,並且按機率使用權重後也比較均勻,有利於動態調整提供者權重。
RoundRobin LoadBalance
輪循,按公約後的權重設置輪循比率。
存在慢的提供者累積請求問題,好比:第二臺機器很慢,但沒掛,當請求調到第二臺時就卡在那,長此以往,全部請求都卡在調到第二臺上。
解決辦法 :結合權重,把第二臺機(性能低的)的權重設置低一點
LeastActive LoadBalance
最少活躍調用數,相同活躍數的隨機,活躍數指調用先後計數差。
使慢的提供者收到更少請求,由於越慢的提供者的調用先後計數差會越大。
ConsistentHash LoadBalance
一致性Hash,相同參數的請求老是發到同一提供者。
當某一臺提供者掛時,本來發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引發劇烈變更。
算法參見:http://en.wikipedia.org/wiki/Consistent_hashing。
缺省只對第一個參數Hash,若是要修改,請配置<dubbo:parameter key="hash.arguments" value="0,1" />
缺省用160份虛擬節點,若是要修改,請配置<dubbo:parameter key="hash.nodes" value="320" />
事件處理線程說明:
若是事件處理的邏輯能迅速完成,而且不會發起新的IO請求,好比只是在內存中記個標識,則直接在IO線程上處理更快,由於減小了線程池調度。
但若是事件處理邏輯較慢,或者須要發起新的IO請求,好比須要查詢數據庫,則必須派發到線程池,不然IO線程阻塞,將致使不能接收其它請求。
若是用IO線程處理事件,又在事件處理過程當中發起新的IO請求,好比在鏈接事件中發起登陸請求,會報「可能引起死鎖」
Dispatcher
all 全部消息都派發到線程池,包括請求,響應,鏈接事件,斷開事件,心跳等。
direct 全部消息都不派發到線程池,所有在IO線程上直接執行。
message 只有請求響應消息派發到線程池,其它鏈接斷開事件,心跳等消息,直接在IO線程上執行。
execution 只請求消息派發到線程池,不含響應,響應和其它鏈接斷開事件,心跳等消息,直接在IO線程上執行。
connection 在IO線程上,將鏈接斷開事件放入隊列,有序逐個執行,其它消息派發到線程池。
ThreadPool
fixed 固定大小線程池,啓動時創建線程,不關閉,一直持有。(缺省)
cached 緩存線程池,空閒一分鐘自動刪除,須要時重建。
limited 可伸縮線程池,但池中的線程數只會增加不會收縮。(爲避免收縮時忽然來了大流量引發的性能問題)。
<dubbo:protocolname="dubbo"dispatcher="all"threadpool="fixed"threads="100"/>(默認配置)
(儘可能不要使用 root 用戶來部署應用程序,避免資源耗盡後沒法登陸操做系統。
由於root用戶默認沒有限制線程數,若是線程過多,會使資源佔用不少,致使不能關機,只能硬關機)
Provider: 暴露服務的提供方。
Consumer:調用遠程服務的服務消費方。
Registry: 服務註冊中心和發現中心。
Monitor: 統計服務和調用次數,調用時間監控中心。(dubbo的控制檯頁面中能夠顯示)
Container:服務運行的容器。
調用關係:
1.服務器負責啓動,加載,運行提供者(例如在tomcat容器中,啓動dubbo服務端)。
2.提供者在啓動時,向註冊中心註冊本身提供的服務。
3.消費者啓動時,向註冊中心訂閱本身所需的服務。
4.註冊中心返回提供者地址列表給消費者,若是有變動,註冊中心將基於長鏈接推送變動數據給消費者。
5.消費者,從遠程接口列表中,調用遠程接口,dubbo會基於負載均衡算法,選一臺提供者進行調用,若是調用失敗則選擇另外一臺.
dubbo主要核心部件
Remoting:網絡通訊框架,實現了sync-over-async和request-response消息機制。
RPC:一個遠程過程調用的抽象,支持負載均衡、容災和集羣功能。
Registry:服務目錄框架用於服務的註冊和服務事件發佈和訂閱。(相似第一篇文章中的點菜寶)
1 直連(適用開發)
在開發及測試環境下,常常須要繞過註冊中心,只測試指定服務提供者,這時候可能須要點對點直連,點對點直聯方式,將以服務接口爲單位,忽略註冊中心的提供者列表,A接口配置點對點,不影響B接口從註冊中心獲取列表。
<dubbo:reference interface="com.changhf.service.DeptmentService" id="deptmentService" check="false" url="dubbo://192.168.1.1:20881"/>
2 只訂閱
爲方便開發測試,常常會在線下共用一個全部服務可用的註冊中心,這時,若是一個正在開發中的服務提供者註冊,可能會影響消費者不能正常運行。
解決方案:
可讓服務提供者開發方,只訂閱服務(開發的服務可能依賴其它服務),而不註冊正在開發的服務,經過直連測試正在開發的服務。
<dubbo:registry protocol="zookeeper" address="${dubbo.registry.address}" register="false"/>
3 只註冊
若是有兩個鏡像環境(例如環境A、B),兩個註冊中心,有一個服務(例如D)只在其中一個註冊中心有部署,另外一個註冊中心還沒來得及部署,而兩個註冊中心的其它應用都須要依賴此服務,因此須要將服務同時註冊到兩個註冊中心,但卻不能讓此服務同時依賴兩個註冊中心的其它服務(其它服務:例如服務A)。
解決方案:
可讓服務提供者方,只註冊服務到另外一註冊中心,而不從另外一註冊中心訂閱服務。
<dubbo:registry id="hzRegistry" address="10.20.153.10:9090" />
<dubbo:registry id="qdRegistry" address="10.20.141.150:9090" subscribe="false" />
或:
<dubbo:registry id="hzRegistry" address="10.20.153.10:9090" />
<dubbo:registry id="qdRegistry" address="10.20.141.150:9090?subscribe=false" />
BIO:一個鏈接一個線程,客戶端有鏈接請求時服務器端就須要啓動一個線程進行處理。線程開銷大。
僞異步IO:將請求鏈接放入線程池,一對多,但線程仍是很寶貴的資源。
NIO:一個請求一個線程,但客戶端發送的鏈接請求都會註冊到多路複用器上,多路複用器輪詢到鏈接有I/O請求時才啓動一個線程進行處理。
AIO:一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啓動線程進行處理,
BIO是面向流的,NIO是面向緩衝區的;BIO的各類流是阻塞的。而NIO是非阻塞的;BIO的Stream是單向的,而NIO的channel是雙向的。
注意:Java NIO(New IO)和 Linux NIO(non-blocking IO)不同,Java NIO爲多路複用IO模型。
下面這張圖就表示了五種Linux IO模型的處理流程:
BIO(blocking IO): 同步阻塞IO,阻塞整個步驟,若是鏈接少,他的延遲是最低的,由於一個線程只處理一 個鏈接,適用於少鏈接且延遲低的場景,好比說數據庫鏈接。
NIO(non-blocking IO): 同步非阻塞IO,阻塞業務處理但不阻塞數據接收,適用於高併發且處理簡單的場景, 好比聊天軟件,注意這裏所說的NIO並不是Java的NIO(New IO)庫。
多路複用IO: 他的兩個步驟處理是分開的,也就是說,一個鏈接可能他的數據接收是線程a完成的, 數據處理是線程b完成的。相比NIO和(多線程+ BIO)鏈接處理量越大越有優點。即經典的 Reactor設計模式,Java中的Selector和Linux中的epoll都是這種模型。
信號驅動IO: 這種IO模型主要用在嵌入式開發,不參與討論。
異步IO: 他的數據請求和數據處理都是異步的,數據請求一次返回一次,適用於長鏈接的業務場景。即經 典的Proactor設計模式,也稱爲異步非阻塞IO。
在Reactor模式中,事件分發器等待某個事件或者可應用或個操做的狀態發生,事件分發器就把這個事件傳給事先註冊的事件處理函數或者回調函數,由後者來作實際的讀寫操做。如在Reactor中實現讀:註冊讀就緒事件和相應的事件處理器、事件分發器等待事件、事件到來,激活分發器,分發器調用事件對應的處理器、事件處理器完成實際的讀操做,處理讀到的數據,註冊新的事件,而後返還控制權。
上圖對於一次NIO訪問(以read舉例),數據會先被拷貝到操做系統內核的緩衝區中,而後纔會從操做系統內核的緩衝區拷貝到應用程序的地址空間。因此說,當一個read操做發生時,它會經歷兩個階段:
1. 等待數據準備 (Waiting for the data to be ready)
2. 將數據從內核拷貝到進程中 (Copying the data from the kernel to the process)
同步:無論是BIO,NIO,仍是IO多路複用,第二步數據從內核緩存寫入用戶緩存必定是由用戶線程自行讀 取數據,處理數據。
異步:第二步數據是內核寫入的,並放在了用戶線程指定的緩存區,寫入完畢後通知用戶線程。
Netty經過Reactor模型基於多路複用器接收並處理用戶請求,內部實現了兩個線程池,boss線程池和work線程池,其中boss線程池的線程負責處理請求的accept事件,當接收到accept事件的請求時,把對應的socket封裝到一個NioSocketChannel中,並交給work線程池,其中work線程池負責請求的read和write事件,由對應的Handler處理。
單線程模型:全部I/O操做都由一個線程完成,即多路複用、事件分發和處理都是在一個Reactor線程上完成的。既要接收客戶端的鏈接請求,向服務端發起鏈接,又要發送/讀取請求或應答/響應消息。一個NIO 線程同時處理成百上千的鏈路,性能上沒法支撐,速度慢,若線程進入死循環,整個程序不可用,對於高負載、大併發的應用場景不合適。
多線程模型:有一個NIO 線程(Acceptor) 只負責監聽服務端,接收客戶端的TCP 鏈接請求;NIO 線程池負責網絡IO 的操做,即消息的讀取、解碼、編碼和發送;1 個NIO 線程能夠同時處理N 條鏈路,可是1 個鏈路只對應1 個NIO 線程,這是爲了防止發生併發操做問題。但在併發百萬客戶端鏈接或須要安全認證時,一個Acceptor 線程可能會存在性能不足問題。
主從多線程模型:Acceptor 線程用於綁定監聽端口,接收客戶端鏈接,將SocketChannel 從主線程池的Reactor 線程的多路複用器上移除,從新註冊到Sub 線程池的線程上,用於處理I/O 的讀寫等操做,從而保證mainReactor只負責接入認證、握手等操做;
Linux 操做系統的標準 I/O 接口是基於數據拷貝操做的,即 I/O 操做會致使數據在操做系統內核地址空間的緩衝區和應用程序地址空間定義的緩衝區之間進行傳輸。這樣作最大的好處是能夠減小磁盤 I/O 的操做,由於若是所請求的數據已經存放在操做系統的高速緩衝存儲器中,那麼就不須要再進行實際的物理磁盤 I/O 操做。可是數據傳輸過程當中的數據拷貝操做卻致使了極大的 CPU 開銷,限制了操做系統有效進行數據傳輸操做的能力。
零拷貝( zero-copy )這種技術能夠有效地改善數據傳輸的性能,在內核驅動程序(好比網絡堆棧或者磁盤存儲驅動程序)處理 I/O 數據的時候,零拷貝技術能夠在某種程度上減小甚至徹底避免沒必要要 CPU 數據拷貝操做。現代的 CPU 和存儲體系結構提供了不少特徵能夠有效地實現零拷貝技術,可是由於存儲體系結構很是複雜,並且網絡協議棧有時須要對數據進行必要的處理,因此零拷貝技術有可能會產生不少負面的影響,甚至會致使零拷貝技術自身的優勢徹底喪失。
Java的內存有堆內存、棧內存和字符串常量池等等,其中堆內存是佔用內存空間最大的一塊,也是Java對象存放的地方,通常咱們的數據若是須要從IO讀取到堆內存,中間須要通過Socket緩衝區,也就是說一個數據會被拷貝兩次才能到達他的的終點,若是數據量大,就會形成沒必要要的資源浪費。
零拷貝,當他須要接收數據的時候,他會在堆內存以外開闢一塊內存,數據就直接從IO讀到了那塊內存中去,在netty裏面經過ByteBuf能夠直接對這些數據進行直接操做,從而加快了傳輸速度。
Tomcat內存優化主要是對 tomcat 啓動參數優化,咱們能夠在 tomcat 的啓動腳本 catalina.sh 中設置 java_OPTS 參數。
JAVA_OPTS參數說明
-server 啓用jdk 的 server 版;
-Xms java虛擬機初始化時的最小內存;
-Xmx java虛擬機可以使用的最大內存;
-XX: PermSize 內存永久保留區域
-XX:MaxPermSize 內存最大永久保留區域
服務器參數配置
現公司服務器內存通常均可以加到最大2G ,因此能夠採起如下配置:
JAVA_OPTS=’-Xms1024m -Xmx2048m -XX: PermSize=256M -XX:MaxNewSize=256m -XX:MaxPermSize=256m’
配置完成後可重啓Tomcat ,經過如下命令進行查看配置是否生效:
首先查看Tomcat 進程號:
udo lsof -i:9027
咱們能夠看到Tomcat 進程號是 12222 。
查看是否配置生效:
sudo jmap – heap 12222
咱們能夠看到MaxHeapSize 等參數已經生效。
1.Tomcat鏈接相關參數
在Tomcat 配置文件 server.xml 中的
<Connector port="9027" protocol="HTTP/1.1" maxHttpHeaderSize="8192" minProcessors="100"
maxProcessors="1000" acceptCount="1000" redirectPort="8443"
2.調整鏈接器connector的併發處理能力
1>參數說明
maxThreads 客戶請求最大線程數
minSpareThreads Tomcat初始化時建立的 socket 線程數
maxSpareThreads Tomcat鏈接器的最大空閒 socket 線程數
enableLookups 若設爲true, 則支持域名解析,可把 ip 地址解析爲主機名
redirectPort 在須要基於安全通道的場合,把客戶請求轉發到基於SSL 的 redirectPort 端口
acceptAccount 監聽端口隊列最大數,滿了以後客戶請求會被拒絕(不能小於maxSpareThreads )
connectionTimeout 鏈接超時
minProcessors 服務器建立時的最小處理線程數
maxProcessors 服務器同時最大處理線程數
URIEncoding URL統一編碼
2>Tomcat中的配置示例
<Connector port="9027" protocol="HTTP/1.1" maxHttpHeaderSize="8192" maxThreads="1000"
minSpareThreads="100" maxSpareThreads="1000" minProcessors="100" maxProcessors="1000"
enableLookups="false" URIEncoding="utf-8" acceptCount="1000"
redirectPort="8443" disableUploadTimeout="true"/>
1參數說明
c ompression 打開壓縮功能
compressionMinSize 啓用壓縮的輸出內容大小,這裏面默認爲2KB
compressableMimeType 壓縮類型
connectionTimeout 定義創建客戶鏈接超時的時間. 若是爲 -1, 表示不限制創建客戶鏈接的時間
2 Tomcat中的配置示例
<Connector port="9027" protocol="HTTP/1.1" maxHttpHeaderSize="8192" maxThreads="1000"
minSpareThreads="100" maxSpareThreads="1000" minProcessors="100" maxProcessors="1000"
enableLookups="false" compression="on" compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain"
connectionTimeout="20000" URIEncoding="utf-8" acceptCount="1000" redirectPort="8443"
disableUploadTimeout="true"/>
答案是mybatis經過JDK的動態代理方式,在啓動加載配置文件時,根據配置mapper的xml去生成。.
1、mapper代理類是如何生成的.
1.若是不是集成spring的,會去讀取<mappers>節點,去加載mapper的xml配置
<mappers>
<mapper resource="com/xixicat/dao/CommentMapper.xml"/>
</mappers>
2.若是是集成spring的,會去讀spring的sqlSessionFactory的xml配置中的mapperLocations,而後去解析mapper的xml.
<mappers>
<mapper resource="com/xixicat/dao/CommentMapper.xml"/>
</mappers>
3. 而後綁定namespace(XMLMapperBuilder)
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
這裏先去判斷該namespace能不能找到對應的class,若能夠則調用configuration.addMapper(boundType);
4. configuration委託給MapperRegistry:
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
5. 生成該mapper的代理工廠(MapperRegistry)
這裏的重點就是MapperProxyFactory類:
6. getMapper的時候生成mapper代理類
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
最多見的就是多線程,儘量提升程序的併發度。
好比屢次rpc順序調用,經過異步rpc轉化爲併發調用;
好比數據分片,你的一個Job要掃描全表,跑幾個小時,數據分片,用多線程,性能會加快好幾倍。
緩存你們都不陌生,遇到性能問題,你們首先想到的就是緩存。
關於緩存,一個關鍵點就是:緩存的粒度問題。
好比Tweet的架構,緩存的粒度從小到大,有Row Cache, Vector Cache, Fragment Cache, Page Cache。
粒度越小,重用性越好,但查詢須要屢次,須要數據拼裝;
粒度越大,越容易會失效,任何一個小的地方改動,均可能形成緩存的失效。
批量其實也是在線/離線的一種思想,把實時問題,轉化爲一個批量處理的問題,從而下降對系統吞吐量的壓力
好比Kafka中的批量發消息;
好比廣告扣費系統中,把屢次點擊累積在一塊兒扣費;
一樣,對傳統的單機Mysql數據庫,讀和寫是徹底同步的。寫進去的內容,立馬就能夠讀到。
但在不少業務場景下,讀和寫並不須要徹底同步。這個時候,就能夠分開存儲,寫到一個地方,再異步的同步到另外一個地方。這樣就能夠實現讀寫分離。
好比Mysql的Master/Slave就是個典型,Slave上面的數據並非和Master實時同步的;
再好比各類報表分析,OLTP/OLAP,線上/線下數據分離,線上數據按期同步到Hive集羣,再作分析
動靜分離的典型例子就是網站的前端,動態的頁面,放在web服務器上;靜態的css/jss/img,直接放到CDN上,這樣既提升性能,也極大的下降服務器壓力。
按照這個思路,不少大型網站都致力於動態內容的靜態化,靜態化以後,就能夠很容易的緩存。
好比按期把mysql中的歷史數據作備份到離線數據庫等。
服務降級是系統的最後一道保險。在一個複雜系統內部,一個系統每每會調用其它很大系統的服務。在大流量的狀況下,咱們可能會在保證主流程能正常工做的狀況下,對其它服務作降級。
所謂降級,也就是當某個服務不可用時,乾脆就別讓其提供服務了,直接返回一個缺省的結果。雖然這個服務不可用,但它不至於讓整個主流程癱瘓,這就能夠最大限度的保證核心系統可用。
在分佈式系統中,由於數據的分拆,服務的分拆,強一致性就很難保證。這個時候,用的最多的就是「最終一致性「。
強一致性,弱一致性,最終一致性,是一致性的幾個不一樣的等級。在傳統的關係型數據庫中,經過事務來保證強一致性。
但在分佈式系統中,一般都會把強一致性折中成最終一致性,從而變相的解決分佈式事務問題。
典型的轉賬的例子,A給B轉賬1萬塊錢,A的帳號扣1萬,B的帳號加1萬。但這2步未必須要同時發生, A的扣完以後,B的帳號上面未必立馬就有,但只要保證B最終能夠收到就能夠了。
最終一致性的實現,一般都須要一個高可靠的消息隊列。
線程池的做用:
線程池做用就是限制系統中執行線程的數量。
根據系統的環境狀況,能夠自動或手動設置線程數量,達到運行的最佳效果;少了浪費了系統資源,多了形成系統擁擠效率不高。用線程池控制線程數量,其餘線程排隊等候。一個任務執行完畢,再從隊列的中取最前面的任務開始執行。若隊列中沒有等待進程,線程池的這一資源處於等待。當一個新任務須要運行時,若是線程池中有等待的工做線程,就能夠開始運行了;不然進入等待隊列。
爲何要用線程池:
1.減小了建立和銷燬線程的次數,每一個工做線程均可以被重複利用,可執行多個任務。
2.能夠根據系統的承受能力,調整線程池中工做線線程的數目,防止由於消耗過多的內存,而把服務器累趴下(每一個線程須要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。
Java裏面線程池的頂級接口是Executor,可是嚴格意義上講Executor並非一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService。
在實際的業務需求中,並非全部須要都須要徹底實時的:
好比內部針對產品、運營開發的各類報表查詢、分析系統;
好比微博的傳播,我發了一個微博,個人粉絲延遲幾秒纔看到,這是能夠接受的,由於他並不會注意到晚了幾秒;
好比搜索引擎的索引,我發了一篇博客,可能幾分鐘以後,纔會被搜索引擎索引到;
好比支付寶轉賬、提現,也並不是這邊轉出以後,對方當即收到;
。。。
這類例子不少。這種「非實時也能夠接受「的場景,就爲架構的設計贏得了充分的迴旋餘地。
由於非實時,咱們就能夠作異步,好比使用消息隊列,好比使用後臺的Job,週期性處理某類任務;
也由於非實時,咱們能夠作讀寫分離,讀和寫不是徹底同步,好比Mysql的Master-Slave。
計算的分拆有2種思路:
數據分拆:一個大的數據集,拆分紅多個小的數據集,並行計算。
好比大規模數據歸併排序
任務分拆:把一個長的任務,拆分紅幾個環節,各個環節並行計算。
Java中多線程的Fork/Join框架,Hadoop中的Map/Reduce,都是計算分拆的典型框架。其思路都是類似的,先分拆計算,再合併結果。
再好比分佈式的搜索引擎中,數據分拆,分別建索引,查詢結果再合併。
適配器模式:將一個類的接口轉換成客戶但願的另一個接口。適配器模式使得本來因爲接口不兼容而不能一塊兒工做的那些類能夠一塊兒工做。
裝飾者模式:動態給類加功能。
觀察者模式:有時被稱做發佈/訂閱模式,觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態發生變化時,會通知全部觀察者對象,使它們可以自動更新本身。
簡單的例子就是一個天氣系統,當天氣變化時必須在展現給公衆的視圖中進行反映。這個視圖對象是一個主體,而不一樣的視圖是觀察者。
策略模式:定義一系列的算法,把它們一個個封裝起來, 而且使它們可相互替換。
外觀模式:爲子系統中的一組接口提供一個一致的界面,外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用
命令模式:將一個請求封裝成一個對象,從而使您能夠用不一樣的請求對客戶進行參數化。
建立者模式:將一個複雜的構建與其表示相分離,使得一樣的構建過程能夠建立不一樣的表示。
抽象工廠模式:提供一個建立一系列相關或相互依賴對象的接口,而無需指定它們具體的類。
分佈式的、開源的分佈式應用程序協調服務,本來是Hadoop、HBase的一個重要組件。它爲分佈式應用提供一致性服務的軟件,包括:配置維護、域名服務、分佈式同步、組服務等
羣首(leader),追隨者(follower),觀察者(observer)。
Leader做爲整個ZooKeeper集羣的主節點,負責響應全部對ZooKeeper狀態變動的請求。它會將每一個狀態更新請求進行排序和編號,以便保證整個集羣內部消息處理的FIFO。
事物請求的惟一調度和處理者,保證集羣事務處理的順序性。
集羣內部各個服務器的調度者。
Follower
處理客戶端非事物請求,轉發事物請求給Leader服務器。
參與事務請求Proposal的投票。
參與Leader選舉投票。
Observer
和Follower惟一的區別在於,Observer不參與任何形式的投票,包括事物請求Proposal的投票和Leader選舉投票。簡單地講,Observer服務器只提供非事物服務,一般用於在不影響集羣事務處理能力的前提下提高集羣的非事物處理能力
對於zk系統的數據都是保存在內存裏面的,一樣也會備份一份在磁盤上。對於每一個zk節點而言,能夠看作每一個zk節點的命名空間是同樣的,也就是有一樣的數據(下面的樹結構)若是Leader掛了,zk集羣會從新選舉,在毫秒級別就會從新選舉出一個Leaer集羣中除非有一半以上的zk節點掛了,zk service纔不可用。
其默認選舉算法爲FastLeaderElection。FastLeaderElection的本質是類fast Paxos的算法。Google Chubby的做者Mike Burrows說過這個世界上只有一種一致性算法,那就是Paxos,其它的算法都是殘次品。
如下給出FastLeaderElection算法的分析過程:
首先給出幾個名詞解釋:
Serverid:在配置server時,給定的服務器的標示id。
Zxid:服務器在運行時產生的數據id,zxid越大,表示數據越新。
Epoch:選舉的輪數,即邏輯時鐘。
Server狀態:LOOKING,FOLLOWING,OBSERVING,LEADING
總結:
1、首先開始選舉階段,每一個Server讀取自身的zxid。
2、發送投票信息
a、首先,每一個Server第一輪都會投票給本身。
b、投票信息包含 :所選舉leader的Serverid,Zxid,Epoch。Epoch會隨着選舉輪數的增長而遞增。
3、接收投票信息
1、若是所接收數據中服務器的狀態是否處於選舉階段(LOOKING 狀態)。
首先,判斷邏輯時鐘值:
a)若是發送過來的邏輯時鐘Epoch大於目前的邏輯時鐘。首先,更新本邏輯時鐘Epoch,同時清空本輪邏輯時鐘收集到的來自其餘server的選舉數據。而後,判斷是否須要更新當前本身的選舉leader Serverid。判斷規則rules judging:保存的zxid最大值和leader Serverid來進行判斷的。先看數據zxid,數據zxid大者勝出;其次再判斷leader Serverid,leader Serverid大者勝出;而後再將自身最新的選舉結果(也就是上面提到的三種數據(leader Serverid,Zxid,Epoch)廣播給其餘server)
b)若是發送過來的邏輯時鐘Epoch小於目前的邏輯時鐘。說明對方server在一個相對較早的Epoch中,這裏只須要將本機的三種數據(leader Serverid,Zxid,Epoch)發送過去就行。
c)若是發送過來的邏輯時鐘Epoch等於目前的邏輯時鐘。再根據上述判斷規則rules judging來選舉leader ,而後再將自身最新的選舉結果(也就是上面提到的三種數據(leader Serverid,Zxid,Epoch)廣播給其餘server)。其次,判斷服務器是否是已經收集到了全部服務器的選舉狀態:如果,根據選舉結果設置本身的角色(FOLLOWING仍是LEADER),退出選舉過程就是了。
最後,若沒有收到沒有收集到全部服務器的選舉狀態:也能夠判斷一下根據以上過程以後最新的選舉leader是否是獲得了超過半數以上服務器的支持,若是是,那麼嘗試在200ms內接收一下數據,若是沒有新的數據到來,說明你們都已經默認了這個結果,一樣也設置角色退出選舉過程。
2、 若是所接收服務器不在選舉狀態,也就是在FOLLOWING或者LEADING狀態。
a)邏輯時鐘Epoch等於目前的邏輯時鐘,將該數據保存到recvset。此時Server已經處於LEADING狀態,說明此時這個server已經投票選出結果。若此時這個接收服務器宣稱本身是leader, 那麼將判斷是否是有半數以上的服務器選舉它,若是是則設置選舉狀態退出選舉過程。
b) 不然這是一條與當前邏輯時鐘不符合的消息,那麼說明在另外一個選舉過程當中已經有了選舉結果,因而將該選舉結果加入到outofelection集合中,再根據outofelection來判斷是否能夠結束選舉,若是能夠也是保存邏輯時鐘,設置選舉狀態,退出選舉過程。
HTTP:超文本傳輸協議。使用的是可靠的數據傳輸協議,在傳輸的過程當中不會被損壞或產生混亂。HTTP能夠從遍及全世界的Web服務器商將各類信息塊迅速、便捷、可靠地搬移到人們桌面上的Web瀏覽器上去。
URI:統一資源標識符,在世界範圍內惟一標識並定位信息資源。
URI有兩種形式:URL和URN。
200 成功。請求的全部數據都在響應主體中。
206 成功執行了一個部分或Range(範圍)請求。206響應中必須包含Content-Range、Date以及ETag或Content-Location首部。斷點續傳必考題。
302 重定向。到其餘地方去獲取資源。客戶端應該是用使用Location首部給出的URL來臨時定位資源。未來的請求仍應使用老的URL。
304 若是客戶端發起了一個GET請求,而資源最近未被修改,則用304說明資源未被修改。帶有這個狀態嗎的響應不該該包含實體的主體部分。緩存必考題。
305 用來講明必須經過一個代理來訪問資源;代理的位置由Locatin首部給出。
403 請求被服務器拒絕了
404 沒法找到所請求的URL
500 服務器遇到一個妨礙它爲請求提供服務的錯誤。
503 服務器如今沒法爲請求提供服務,但未來能夠。
HTTP報文是由一行一行的簡單的字符串組成的。HTTP報文都是純文本,不是二進制代碼。
請求報文:從Web客戶端發往Web服務器的HTTP報文稱爲請求報文。
響應報文:從Web服務器發往客戶端的報文稱爲響應報文。
HTTP報文包含如下三個部分:
起始行:報文的第一行就是起始行,在請求報文中用來講明要作些什麼,在響應報文中說明出現了什麼狀況。如:GET /jackson0714/p/algorithm_1.html HTTP/1.1
首部字段:起始行後面由零個或多個首部字段。以鍵值對的形式表示首部字段。鍵和值之間用冒號分隔。首部以一個空行結束。如Content-Type:text/html:charset=utf-8
主體:首部字段空行以後就是可選的報文主體了,其中包含了全部類型的數據。請求主體中包括了要發送Web服務器的數據,響應主體中裝載了要返回給客戶端的數據。
域名解析服務。將主機名轉換爲IP地址。如將http://www.cnblogs.com/主機名轉換爲IP地址:211.137.51.78。
(1)瀏覽器從URL中解析出服務器的主機名;
(2)瀏覽器將服務器的主機名轉換成服務器的IP地址;
(3)瀏覽器將端口號(若是有的話),從URL中解析出來;
(4)瀏覽器創建一條與Web服務器的TCP鏈接;
(5)瀏覽器向服務器發送一條HTTP請求報文;
(6)服務器向瀏覽器回送一條HTTP響應報文;
(7)關閉鏈接,瀏覽器顯示文檔。
HTTP是應用層協議。它把聯網的細節都交給了通用、可靠的因特網傳輸協議TCP\IP協議。
HTTP網絡協議棧:
HTTP 應用層、 TCP 傳輸層、 IP 網絡層、 網絡特有的鏈路接口 數據鏈路層、 物理網絡硬件 物理層
1. HTTP 的URL 以http:// 開頭,而HTTPS 的URL 以https:// 開頭
2. HTTP 是不安全的,而 HTTPS 是安全的
3. HTTP 標準端口是80 ,而 HTTPS 的標準端口是443
4. 在OSI 網絡模型中,HTTP工做於應用層,而HTTPS 的安全傳輸機制工做在傳輸層
5. HTTP 沒法加密,而HTTPS 對傳輸的數據進行加密
6. HTTP無需證書,而HTTPS 須要CA機構wosign的頒發的SSL證書
1、首先HTTP請求服務端生成證書,客戶端對證書的有效期、合法性、域名是否與請求的域名一致、證書的公鑰(RSA加密)等進行校驗;
2、客戶端若是校驗經過後,就根據證書的公鑰的有效, 生成隨機數,隨機數使用公鑰進行加密(RSA加密);
3、消息體產生的後,對它的摘要進行MD5(或者SHA1)算法加密,此時就獲得了RSA簽名;
4、發送給服務端,此時只有服務端(RSA私鑰)能解密。
5、解密獲得的隨機數,再用AES加密,做爲密鑰(此時的密鑰只有客戶端和服務端知道)。
在說HTTPS以前先說說什麼是HTTP,HTTP就是咱們平時瀏覽網頁時候使用的一種協議。HTTP協議傳輸的數據都是未加密的,也就是明文的,所以使用HTTP協議傳輸隱私信息很是不安全。爲了保證這些隱私數據能加密傳輸,因而網景公司設計了SSL(Secure Sockets Layer)協議用於對HTTP協議傳輸的數據進行加密,從而就誕生了HTTPS。SSL目前的版本是3.0,被IETF(Internet Engineering Task Force)定義在RFC 6101中,以後IETF對SSL 3.0進行了升級,因而出現了TLS(Transport Layer Security) 1.0,定義在RFC 2246。實際上咱們如今的HTTPS都是用的TLS協議,可是因爲SSL出現的時間比較早,而且依舊被如今瀏覽器所支持,所以SSL依然是HTTPS的代名詞,但不管是TLS仍是SSL都是上個世紀的事情,SSL最後一個版本是3.0,從此TLS將會繼承SSL優良血統繼續爲咱們進行加密服務。目前TLS的版本是1.2,定義在RFC 5246中,暫時尚未被普遍的使用。
同步:函數調用在沒獲得結果以前,沒有調用結果,不返回任何結果。
異步:函數調用在沒獲得結果以前,沒有調用結果,返回狀態信息。
阻塞:函數調用在沒獲得結果以前,當前線程掛起。獲得結果後才返回。
非阻塞:函數調用在沒獲得結果以前,當前線程不會掛起,當即返回結果。
當你想把的內存中的對象狀態保存到一個文件中或者數據庫中時候;
· 當你想用套接字在網絡上傳送對象的時候;
· 當你想經過RMI傳輸對象的時候;
序列化注意事項:
1、若是子類實現Serializable接口而父類未實現時,父類不會被序列化,但此時父類必須有個無參構造方法,不然會拋InvalidClassException異常。
2、靜態變量不會被序列化,那是類的「菜」,不是對象的。串行化保存的是對象的狀態,即非靜態的屬性,即實例變量。不能保存類變量。
3、transient關鍵字修飾變量能夠限制序列化。對於不須要或不該該保存的屬性,應加上transient修飾符。要串行化的對象的類必須是公開的(public)。
4、虛擬機是否容許反序列化,不只取決於類路徑和功能代碼是否一致,一個很是重要的一點是兩個類的序列化 ID是否一致,就是 private static final long serialVersionUID = 1L。
5、Java 序列化機制爲了節省磁盤空間,具備特定的存儲規則,當寫入文件的爲同一對象時,並不會再將對象的內容進行存儲,而只是再次存儲一份引用。反序列化時,恢復引用關係。
6、序列化到同一個文件時,如第二次修改了相同對象屬性值再次保存時候,虛擬機根據引用關係知道已經有一個相同對象已經寫入文件,所以只保存第二次寫的引用,因此讀取時,都是第一次保存的對象。
JDK提供的流繼承了四大類:
InputStream(字節輸入流),OutputStream(字節輸出流),Reader(字符輸入流),Writer(字符輸出流)。
按流向分類:
輸入流: 程序能夠從中讀取數據的流。
輸出流: 程序能向其中寫入數據的流。
按數據傳輸單位分類:
字節流:以字節(8位二進制)爲單位進行處理。主要用於讀寫諸如圖像或聲音的二進制數據。
字符流:以字符(16位二進制)爲單位進行處理。
都是經過字節流的方式實現的。字符流是對字節流進行了封裝,方便操做。在最底層,全部的輸入輸出都是字節形式的。
後綴是Stream是字節流,然後綴是Reader,Writer是字符流。
按功能分類:
節點流:從特定的地方讀寫的流類,如磁盤或者一塊內存區域。
過濾流:使用節點流做爲輸入或輸出。過濾流是使用一個已經存在的輸入流或者輸出流鏈接建立的。
Client
Server
所謂三次握手(Three-Way Handshake)即創建TCP鏈接,就是指創建一個TCP鏈接時,須要客戶端和服務端總共發送3個包以確認鏈接的創建。在socket編程中,這一過程由客戶端執行connect來觸發,整個流程以下圖所示:
(1)第一次握手:
Client將標誌位SYN置爲1,隨機產生一個值seq=J,並將該數據包發送給Server,Client進入SYN_SENT狀態,等待Server確認。
(2)第二次握手:
Server收到數據包後由標誌位SYN=1知道Client請求創建鏈接,Server將標誌位SYN和ACK都置爲1,ack=J+1,隨機產生一個值seq=K,並將該數據包發送給Client以確認鏈接請求,Server進入SYN_RCVD狀態。
(3)第三次握手:
Client收到確認後,檢查ack是否爲J+1,ACK是否爲1,若是正確則將標誌位ACK置爲1,ack=K+1,並將該數據包發送給Server,Server檢查ack是否爲K+1,ACK是否爲1,若是正確則鏈接創建成功,Client和Server進入ESTABLISHED狀態,完成三次握手,隨後Client與Server之間能夠開始傳輸數據了。
SYN攻擊:
在三次握手過程當中,Server發送SYN-ACK以後,收到Client的ACK以前的TCP鏈接稱爲半鏈接(half-open connect),此時Server處於SYN_RCVD狀態,當收到ACK後,Server轉入ESTABLISHED狀態。SYN攻擊就是Client在短期內僞造大量不存在的IP地址,並向Server不斷地發送SYN包,Server回覆確認包,並等待Client的確認,因爲源地址是不存在的,所以,Server須要不斷重發直至超時,這些僞造的SYN包將產時間佔用未鏈接隊列,致使正常的SYN請求由於隊列滿而被丟棄,從而引發網絡堵塞甚至系統癱瘓。SYN攻擊時一種典型的DDOS攻擊,檢測SYN攻擊的方式很是簡單,即當Server上有大量半鏈接狀態且源IP地址是隨機的,則能夠判定遭到SYN攻擊了,使用以下命令可讓之現行:
#netstat -nap | grep SYN_RECV
三次握手耳熟能詳,四次揮手估計就少有人知道了。所謂四次揮手(Four-Way Wavehand)即終止TCP鏈接,就是指斷開一個TCP鏈接時,須要客戶端和服務端總共發送4個包以確認鏈接的斷開。在socket編程中,這一過程由客戶端或服務端任一方執行close來觸發,整個流程以下圖所示:
Client
Server
因爲TCP鏈接時全雙工的,所以,每一個方向都必需要單獨進行關閉,這一原則是當一方完成數據發送任務後,發送一個FIN來終止這一方向的鏈接,收到一個FIN只是意味着這一方向上沒有數據流動了,即不會再收到數據了,可是在這個TCP鏈接上仍然可以發送數據,直到這一方向也發送了FIN。首先進行關閉的一方將執行主動關閉,而另外一方則執行被動關閉,上圖描述的便是如此。
(1) TCP客戶端發送一個FIN,用來關閉客戶到服務器的數據傳送(報文段4)。
(2) 服務器收到這個FIN,它發回一個ACK,確認序號爲收到的序號加1(報文段5)。和SYN同樣,一個FIN將佔用一個序號。
(3) 服務器關閉客戶端的鏈接,發送一個FIN給客戶端(報文段6)。
(4) 客戶段發回ACK報文確認,並將確認序號設置爲收到序號加1(報文段7)。
(1)序號:Seq序號,佔32位,用來標識從TCP源端向目的端發送的字節流,發起方發送數據時對此進行標記。
(2)確認序號:Ack序號,佔32位,只有ACK標誌位爲1時,確認序號字段纔有效,Ack=Seq+1。
(3)標誌位:共6個,即URG、ACK、PSH、RST、SYN、FIN等,具體含義以下:
(A)URG:緊急指針(urgent pointer)有效。
(B)ACK:確認序號有效。
(C)PSH:接收方應該儘快將這個報文交給應用層。
(D)RST:重置鏈接。
(E)SYN:發起一個新鏈接。
(F)FIN:釋放一個鏈接。
須要注意的是:
(A)不要將確認序號Ack與標誌位中的ACK搞混了。
(B)確認方Ack=發起方Req+1,兩端配對。