目錄
- 💖前言
- 🎄Java基礎知識
- 💟基礎知識導圖
- 🎄IO流
- 🎈Lambda
- 🎃&和&&的區別?
- 🎋在java中如何跳出當前的多重回圈?
- 🎨面向物件的三大特征
- 🎍"=="和equals方法究竟有什么區別?
- 🎎三個與取整有關的方法
- 🎏Java中運算子
- ?多載和重寫的區別?
- 🎢String和StringBuffuer、StringBuilder的區別?
- 🎨java中有幾種方法實作一個執行緒?用什么關鍵字修飾同步方法?stop()和suspend()方法為何不推薦使用?
- 🎗sleep()和wait()有什么區別?
- 🎃基本陳述句(for、if else、switch、while、break和continue)
- 🎉關鍵字
- 🥫作用域public、private、protected 以及不寫時的區別?
- 🥚forward和redirect兩種跳轉方式的區別?
- 🍧HashMap和Hashtable的區別?
- 🍨List、Set和Map的區別?
- 🍷hashCode與equals的區別與聯系?
- 🧉模式
- 🥯Java常用類
- 🎄Java集合框架
- 🚀常用的三種集合介面:
- 🎉List(有序,可以重復的集合)
- 🎋Set(典型實作 HashSet()是一個無序,不可重復的集合)
- 🎡Map(key-value 的鍵值對,key 不允許重復,value可以)
- 🎄Map常用方法及實作類
- 🧨Map與Set關系
- 🎄Java多執行緒
- 🎗創建執行緒和啟動
- 🎀執行緒生命周期
- 🎭執行緒管理
- 🎗執行緒睡眠——sleep
- 🎗執行緒讓步——yield
- 🎗設定執行緒的優先級
- 🎗后臺(守護)執行緒
- 🎗正確結束執行緒
- 🎁執行緒同步
- 🎗同步方法
- 🎗同步代碼塊
- 🎗使用特殊域變數(volatile)實作執行緒同步
- 🎗使用重入鎖(Lock)實作執行緒同步
- 🧶執行緒通信
- 🎗借助于Object類的wait()、notify()和notifyAll()實作通信
- 🎗使用Condition控制執行緒通信
- 👕執行緒池
- 🎗newSingleThreadExecutor()
- 🎗newCachedThreadPool的使用
- 🎗newFixedThreadPool的使用
- 🧵執行緒五種狀態
- 🎄Java虛擬機
- 🎄MySQL
- 🎄Spring相關知識點
- 🎨Bean生命周期
- 🎪Spring應用
- 🎗常用注解
- 🎠Spring優點
- 🎀Spring中IOC理解
- 🎇Spring中AOP理解
- 🎭Spring配置方式
- 🎢Spring中的設計模式
- 🧨SpringMVC執行流程
- 🎄計算機網路
- 🎍OSI,TCP/IP,五層協議的體系結構,以及各層協議
- 🥽IP地址的分類
- 🥼各種協議
- 👓TCP三次握手和四次揮手的全程序
- 🎄MQ訊息佇列
- 🧵MQ應用(異步解耦削峰)
- 🎗異步提速
- 🎗解耦
- 🎗削峰填谷
- 🎊MQ選型
- 🎁MQ訊息佇列相關資料分享
- 🎄Redis
- 👟快取擊穿、快取穿透、快取雪崩
- 🏀使用Redis做快取的優點
- ?redis 常見資料結構以及使用場景分析
- 🥅Redis主從復制與哨兵機制
- 🎄Nginx
- 🎁Nginx基本配置詳解
- 🪁Nginx常用命令
- 🎨Nginx正向代理
- 🥏Nginx反向代理
- 🔮Nginx負載均衡
💖前言
國內的互聯網面試,恐怕是現存的、最接近科舉考試的制度,而且,我國的八股文(基礎知識、集合框架、多執行緒、執行緒的五種狀態、虛擬機、MySQL、Spring相關、計算機網路、MQ系列等)確實是獨樹一幟,以美國為例,北美工程師面試比較重視演算法(Coding),近幾年也會加入Design輪(系統設計和面向物件設計OOD)和BQ輪(Behavioral question,行為面試問題),今天博主為大家熬斷半頭青絲捋一捋這現代八股文
🎄Java基礎知識
💟基礎知識導圖

需要完整圖的小伙伴可聯系博主
🎄IO流
詳情見博主此文:IO流知識體系詳解
按照流的方向:輸入流(inputStream)和輸出流(outputStream).
按照實作功能分:節點流(可以從或向一個特定的地方(節點)讀寫資料,如 FileReader)和處理流(是對一個已存在的流的連接和封裝,通過所封裝的流的功能呼叫實作資料讀寫,如 BufferedReader,處理流的構造方法總是要帶一個其他的流物件做引數,一個流物件經過其他流的多次包裝,稱為流的鏈接)
按照處理資料的單位: 位元組流和字符流,位元組流繼承于 InputStream 和 OutputStream,字符流繼承于InputStreamReader 和 OutputStreamWriter,
位元組流如何轉為字符流
位元組輸入流轉字符輸入流通過 InputStreamReader 實作,該類的建構式可以傳入 InputStream 物件,
位元組輸出流轉字符輸出流通過 OutputStreamWriter 實作,該類的建構式可以傳入 OutputStream 物件
如何將一個 java 物件序列化到檔案里
在 java 中能夠被序列化的類必須先實作 Serializable 介面,該介面沒有任何抽象方法只是起到一個標記作用
🎈Lambda
詳情見博主此文:Lambda運算式詳細講解
🎃&和&&的區別?
&:邏輯與(and),運算子兩邊的運算式均為true時,整個結果才為true
&&:短路與,如果第一個運算式為false時,第二個運算式就不會計算了
🎋在java中如何跳出當前的多重回圈?
在回圈陳述句外前面定義一個標號,然后在里層回圈體的代碼中使用帶有標號的break陳述句,即可跳出回圈,
比如:
ok:
for (int j = 0; j < 10; j++) {
break ok;
}
🎨面向物件的三大特征
1.封裝
作用:提高代碼的安全性
1、將屬性私有化,并提供對外界的介面(get/set方法),
2、用private修飾的屬性和方法,只能在本類中使用,
2.繼承
作用:提高代碼的復用性,減少重復代碼
1、子類可以繼承父類非私有的屬性和方法,不能繼承構造方法和私有的屬性和方法,
2、可以綜合子類的共同特征來去提煉父親的類,
3、子類在繼承父類的各種屬性和方法時,也可以有自己的屬性和方法,
4、一個子類只能有一個父類,java只能單繼承,不能多繼承,因為多個類中的方法名相同,方法體不同,不知使用哪個,
5、一個類繼承最頂端叫做基類或者超類,所有的超類叫做object,
6、在繼承關系中,如果父類沒有無引數的構造方法,如何解決?
1.子類中添加一個和父類構造方法引數串列相同的構造方法,通過super引數傳遞給父類的構造方法
2.如果父類允許修改的時候,可以在父類中創建一個無參的構造方法
7、在繼承關系中,代碼塊的執行順序:父靜>子靜>父構造代碼塊>父構造方法>子構造代碼塊>子構造方法
3.多型
1.分類
編譯時多型:在編譯程序中察覺的多型,多載,向上轉型,
運行時多型:在運行程序中察覺的多型,向下轉型,
2.向上轉型、向下轉型是在繼承關系中,向下轉型必須在向上轉型的基之上,
3.在繼承關系中,父類的物件可以指向子類的實體,父類參考物體方法的時候,是呼叫子類重寫以后的方法,
4.向上轉型
父類的參考指向子類的物體
父類類名物件名=new 子類類();
優點:減少重復代碼,提高代碼的復用性
缺點:父類的參考無法呼叫子類特有的屬性和方法
解決方案:向下轉型
5.向下轉型:
子類物件的父類參考賦給子類
子類類名物件名=(子類類名)父類物件;
6. instanceof 判斷左邊的物件是否屬于右邊的類 物件名instanceof類名(子類類名)
7.匿名物件
new類名()只有堆空間,沒有堆疊空間,只能屬于一次,為了節省代碼,
🎍"=="和equals方法究竟有什么區別?
==:表示兩個變數的值是否相等,比較兩個基本資料型別的資料或者參考變數,用 ==
equals:用于比較兩個獨立物件的內容是否相同,字串的比較也用equals
🎎三個與取整有關的方法
Math.ceil():表示向上取整;Math.ceil(11.3)=12;Math.ceil(-11.3)=-12,
Math.floor():表示向下取整;Math.floor(11.6)=11;Math.floor(-11.6)=-12,
Math.round():表示四舍五入;Math.round(11.5)=12;Math.round(-11.5)=-11;
Math.round(11.3)=11;Math.round(-11.3)=-11;
🎏Java中運算子
算術運算子:+ 、 - 、 * 、 / 、 % 、 ++ 、 --
賦值運算子:= 、 += 、 -= 、 *= 、 /= 、 %=
關系運算子:> 、 < 、 >= 、 <= 、 == 、 !=
邏輯運算子:! 、 & (只要有一個false 最終結果就是false) 、
| (但凡有一個true 最終結果就是true) 、
^ (如果兩邊一樣 最終結果為false 如果兩邊不同 最終結果為true)、
&&(如果第一個是false 那第二個不執行 最終結果是false)、
||(如果第一個運算式的結果是true 那第二個運算式 就不去計算了 ,最終結果是true)
位運算子: ~ 、 >> 、 << 、 >>>
字串連接運算子:+
三目運算子:X ? Y : Z
X為boolean型別運算式,先計算x的值,若為true,整個三目運算的結果為運算式Y的值,否則整個運算結果為運算式Z的值,
?多載和重寫的區別?
多載(
Overload):函式名相同,引數不同,可以改變回傳值型別,引數的個數和型別,
重寫(Override):和父類的的方法名稱、引數完全相同
🎢String和StringBuffuer、StringBuilder的區別?
String:字串數值不可變;
StringBuffer:字串可修改,可以動態構造字符資料,StringBuffer類是可以通過Append()來修改值,執行緒安全,
StringBuilder:執行緒不安全,
三者在執行速度方面的比較:StringBuilder > StringBuffer > String
對于三者使用的總結:
1.如果要操作少量的資料用 =String
2.單執行緒操作字串緩沖區下操作大量資料 =StringBuilder
3.多執行緒操作字串緩沖區下操作大量資料 =StringBuffer
🎨java中有幾種方法實作一個執行緒?用什么關鍵字修飾同步方法?stop()和suspend()方法為何不推薦使用?
第一種:繼承Thread類,New Thread(){}.start():表示呼叫子類物件的run方法
第二種:實作Runable介面
第三種:執行緒池創建多執行緒
第四種:實作Callable介面,重寫call函式
繼承Thread類實作多執行緒,重寫run方法時沒有回傳值也不能拋出例外,使用Callable介面就可以解決這個問題
Callable介面和Runnable介面的不同之處:
1.Callable規定的方法是call,而Runnable是run
2.call方法可以拋出例外,但是run方法不行
3.Callable物件執行后可以有回傳值,運行Callable任務可以得到一個Future物件,通過Future物件可以了解任務執行情況,可以取消任務的執行,而Runnable不可有回傳值
用synchronized關鍵字修飾同步方法,
反對使用stop(),是因為它不安全,
suspend()方法容易發生死鎖,呼叫suspend()的時候,目標執行緒會停下來,但卻仍然持有在這之前獲得的鎖定,此時,其他任何執行緒都不能訪問鎖定的資源,除非被" 掛起"的執行緒恢復運行,對任何執行緒來說,如果它們想恢復目標執行緒,同時又試圖使用任何一個鎖定的資源,就會造成死鎖,所以不應該使用suspend(),而應在自己的Thread類中置入一個標志,指出執行緒應該活動還是掛起,若標志指出執行緒應該掛起,便用wait()命其進入等待狀態,若標志指出執行緒應當恢復,則用一個notify()重新啟動執行緒,
🎗sleep()和wait()有什么區別?
sleep是執行緒被呼叫時,占著cpu休眠,其他執行緒不能占用cpu,os認為該執行緒正在作業,不會讓出系統資源,wait是進入等待池等待,讓出系統資源,其他執行緒可以占用cpu
sleep()和wait()方法的區別可從兩個角度闡述:
1.cpu的搶占權;2.鎖旗標是否釋放
兩者都會釋放cpu的搶占權;
wait()方法執行完即可釋放鎖旗標,進入執行緒的等待佇列;
sleep()執行完,不會釋放,進入等待佇列;
🎃基本陳述句(for、if else、switch、while、break和continue)
1.for回圈陳述句
for ([回圈變數初始值設定]; [回圈條件判斷]; [改變回圈變數的值]){
回圈體
}
注意:1、運算式2一般不可以省略,否則死回圈
2、運算式3可以省略,但是在回圈體中必須有陳述句修改變數,以使運算式2在某一時刻為false結束回圈,
3、若同時省略運算式1,表運算式3,則相當于while(運算式2)陳述句
4、三個運算式均省略 即for(;;)陳述句,此時相當于while(true)陳述句
5、運算式1、運算式3可以是逗號運算式,以使回圈變數值在修改時可以對其它變數賦值
2.if…else… 判斷陳述句
1、if(){}
2、if(){}else{}
3、if(){}else if(){}
4、if(){if(){}else()}
5、if()執行陳述句 esle 執行陳述句 注意:執行陳述句只有一條陳述句的時候.可以將if esle 的大括號省略
注意:()內是boolean型別運算式,{}是陳述句塊
比較字串用equals,比較內容,比較數值用==,比較地址,
基本資料型別:變數名、變數值在堆疊中,
參考資料型別:變數名在堆疊中,變數值在常量池中,
3.while 回圈陳述句
while( 條件運算式陳述句){
回圈體陳述句;
}
//初始條件
do{
//回圈體;
//迭代
}while( 回圈條件判斷);
注意:1、當第一次執行時,若運算式=false時,則while陳述句不執行,而do/while陳述句執行一次后面的陳述句
2、一定要切記在switch回圈中,如果沒有break跳出陳述句,每一個case都要執行一遍,在計算最終結果,
4.switch 陳述句
switch(運算式expr){
case const1:
statement1;
break;
… …
case constN:
statementN;
break;
[default:
statement_dafault;
break;]
}
注意:1、運算式必須是int、byte、char、short、enmu、String型別
2、constN必須是常量或者finall變數,不能是范圍
3、所有的case陳述句的值不能相同,否則編譯會報錯
4、default可要可不要
5、break用來執行完一個分支后使程式跳出switch陳述句塊,否則會一直會執行下去,
5.if和switch的區別是什么?
1、
if可以判斷范圍,也可以判斷一個值
switch只能判斷指定的值
2、若只判斷指定的值,則使用switch陳述句,效率快
if判斷范圍,對資料判斷靈活,自身的格式也比較靈活
6.break和continue區別是什么?
break跳出某個回圈
continue跳過某個回圈
注意:if外有回圈可以用break、continue,單純if不可以用,
🎉關鍵字
1、static呼叫格式:
1、同一個類中:
靜態的:
方法名 屬性名
類名.方法名 類名.屬性名
物件名.方法名 物件名.屬性名
非靜態的:
物件名.屬性名 物件名.方法名
2、不同類中:
靜態的:
物件名.方法名 物件名.屬性名
類名.方法名 類名.屬性名
非靜態的:
物件名.屬性名 類名.方法名
注意:1、static可以修飾屬性、方法、代碼塊,不可以修飾類和構造方法,
2、靜態方法隨著類的加載而加載,
3、在靜態方法區內的東西只有一份,所有的物件共享這一份空間,只要有一個物件對屬性進行修改,所有的物件呼叫都是修改后的資料,
4、代碼塊的執行順序:靜態代碼塊(只被呼叫一次)>構造代碼塊{}>構造方法>普通方法(需呼叫)
2、this關鍵字
1、可以呼叫屬性和方法,
this.屬性名(全域變數)
this.方法名();
2、在構造方法中:
1、this();括號內的引數個數、順序、型別根據呼叫的方法來決定,
2、必須放在第一行,只能呼叫一次,
3、呼叫構造方法時只能在構造方法中呼叫,呼叫屬性和方法時可以在構造方法中可以在普通方法中,
4、當全域變數和區域變數有重名字的時候,用this來區分,
3、super關鍵字
1、super指代父類物件,
2、super可以呼叫屬性、方法、構造方法,
3、super呼叫父類的構造方法,
4、super呼叫構造方法時候必須放在第一行,
4、final最終的
1、可以修飾全域變數,宣告的時候必須賦值,只能賦值一次,
2、可以修飾區域變數,宣告時候可以不賦值,但也只能賦值一次,
3、可以修飾方法,可以正常使用,不能被重寫,
4、可以修飾類,可以正常使用,不能被繼承,
5、用final修飾的屬性通常叫常量,
6、static final 全域變數,每個字母都要大寫,
5、this和super的區別
1、this指的是本類創建的物件, super指代的是父類的物件
2、this可以呼叫屬性、方法、構造方法, super也可以呼叫屬性、方法、構造方法,
3、this呼叫屬性和方法的時候,呼叫自己本類的屬性和方法, 如果本類沒有,那就用super去父類中找
4、this呼叫構造方法呼叫,呼叫本類的其他構造方法, super呼叫父類的構造方法,
5、this和super在呼叫構造方法的時候必須放在第一行,
6、this和super不能同時存在
6、最小作用域最強原則:
局域變數在此方法中,比全域變數在此方法中的作用強,
🥫作用域public、private、protected 以及不寫時的區別?
private修飾的成員變數和函式只能在類本身和內部類中被訪問
protected修飾的成員變數和函式能被類本身、子類及同一個包中的類訪問
public修飾的成員變數和函式可以被類、子類、同一個包中的類以及任意其他類訪問
默認情況(不寫)下,屬于一種包訪問,即能被類本身以及同一個包中的類訪問
| 作用域 | 當前類 | 統一package | 子孫類 | 其他package |
|---|---|---|---|---|
| public | √ | √ | √ | √ |
| protected | √ | √ | √ | × |
| friendly | √ | √ | × | × |
| private | √ | × | × | × |
🥚forward和redirect兩種跳轉方式的區別?
1.從地址欄顯示來說
forward是服務器請求資源,服務器直接訪問目標地址的URL,把那個URL的回應內容讀取過來,然后把這些內容再發給瀏覽器.瀏覽器根本不知道服務器發送的內容從哪里來的,所以它的地址欄還是原來的地址.
redirect是服務端根據邏輯,發送一個狀態碼,告訴瀏覽器重新去請求那個地址.所以地址欄顯示的是新的URL
2.從資料共享來說
forward:轉發頁面和轉發到的頁面可以共享request里面的資料
redirect:不能共享資料.
3.從運用地方來說
forward:一般用于用戶登陸的時候,根據角色轉發到相應的模塊
redirect:一般用于用戶注銷登陸時回傳主頁面和跳轉到其它的網站等
4.從效率來說
forward:高
redirect:低
🍧HashMap和Hashtable的區別?
HashMap:實作了Map介面,允許空(null)鍵值(key),由于非執行緒安全,在只有一個執行緒訪問的情況下,效率高于Hashtable,
Hashtable:不能將null作為key或者value,方法是同步的,執行緒安全,
🍨List、Set和Map的區別?
List:是存盤單列資料的集合,存盤有順序,允許重復,繼承Collection介面,
Set: 是存盤單列資料的集合,繼承Collection介面,不允許重復,
Map:存盤鍵和值這樣的雙列資料的集合,存盤資料無順序,鍵(key)不能重復,值(value),可以重復,
🍷hashCode與equals的區別與聯系?
一、equals方法的作用
1、默認情況(沒有覆寫equals方法)下equals方法都是呼叫Object類的equals方法,而Object的equals方法主要用于判斷物件的記憶體地址參考是不是同一個地址(是不是同一個物件),
2 、要是類中覆寫了equals方法,那么就要根據具體的代碼來確定equals方法的作用了,覆寫后一般都是通過物件的內容是否相等來判斷物件是否相等,
二、Hashcode()方法:
1、我們并沒有覆寫equals方法只覆寫了hashCode方法,兩個物件雖然hashCode一樣,但在將stu1和stu2放入set集合時由于equals方法比較的兩個物件是false,所以就沒有在比較兩個物件的hashcode值,
2、覆寫一下equals方法和hashCode方法,stu1和stu2通過equals方法比較相等,而且回傳的hashCode值一樣,所以放入set集合中時只放入了一個物件,
3、我們讓兩個物件equals方法比較相等,但hashCode值不相等試試,雖然stu1和stu2通過equals方法比較相等,但兩個物件的hashcode的值并不相等,所以在將stu1和stu2放入set集合中時認為是兩個不同的物件,
總結:
1、equals方法用于比較物件的內容是否相等(覆寫以后)
2、hashcode方法只有在集合中用到
3、當覆寫了equals方法時,比較物件是否相等將通過覆寫后的equals方法進行比較(判斷物件的內容是否相等),
4、將物件放入到集合中時,首先判斷要放入物件的hashcode值與集合中的任意一個元素的hashcode值是否相等,如果不相等直接將該物件放入集合中,如果hashcode值相等,然后再通過equals方法判斷要放入物件與集合中的任意一個物件是否相等,如果equals判斷不相等,直接將該元素放入到集合中,否則不放入,
🧉模式
1、單例模式
分類:懶漢式、餓漢式
1、構造方法私有化
2、在本類中創建本類物件
3、保證物件的唯一性final
4、給外界提供得到物件的方法 static
5、在多執行緒中,餓漢式安全,懶漢式不安全
2、簡單工廠模式
批量創建物件
1 創建工廠類 : 創建物件的方法
2 果汁類 是所有種類果汁的父類
3 在工廠類的方法中回傳果汁類
4 根據測驗類中傳遞的字串判斷到底回傳哪種果汁
5 測驗類通過工廠類回傳果汁物件
3、建造者模式
內部類使用場景
目的:靜態內部類創建外部類物件
1、 創建外部類,在其中創建一個靜態內部類
2、靜態內部類中寫屬性,構造方法和set get方法
3、靜態內部類中寫一個方法,必須回傳外部類物件
4、 給外部類創建物件,傳遞引數,
4、裝飾者模式
1、在處理流中使用
2、子類重寫父類的方法,提高父類方法的功能及效率
3、為了盡可能減少重復代碼,在重寫的方法中用父類的物件呼叫父類原來的方法
4、得到父類物件可以通過將父類物件作為子類屬性,通過子類構造方法傳遞父類物件
🥯Java常用類
1.裝箱拆箱
1、裝箱:把基本資料型別轉成包裝型別別
2、拆箱:把包裝型別別轉成基本資料型別
3、為什么要包裝類
八種基本資料型別不滿足面向物件的思想,不包括屬性和方法,如果給基本資料型別添加功能,只能創建其包裝類,將方法和屬性封裝進去(jdk5.0以后出現了自動拆箱,裝箱)
4、Integer支持字串,但字串必須是數字,Integer integer3=new Integer("2");
compareTo(); 比較大小,大回傳整數,小于回傳負數,相等回傳0
toBinaryString(); 將十進制數轉成二進制,回傳String字串的表現形式
toHexString(); 將十進制轉成十六進制
toOctalString(); 將十進制轉成八進制
toString(); 將int型別資料轉成String字串
Integer.valueOf(); 將int轉成integer型別物件
new Integer(); 將int轉成integer型別物件
parseInt(); 將Integer轉成int
2.String字串
== 比較地址
.equals() 比較內容
.equalsIgnoreCase() 忽略大小寫比較是否相同
.charAt(); 字串截取出指定的下標開始
.compareTo() 比較大小
.compareToIgnore() 忽略大小比較
.concat() 將引數字串連接到指定字串后面
.contains() 是否包含引數字串
.startsWith() 以指定前綴開頭
.endsWith() 以指定后綴結尾
.indexOf("/") 第一次出現
.indexOf("/", 3) 指定位置開始索引
.lastIndexOf("/") 最后一次出現
.substring(string11.lastIndexOf("/")+1);截取指定位置
.substring(string11.lastIndexOf("/")+1, string11.lastIndexOf("."));//截取字串,指定開始位置和結束位置
.replace('a', 'b') 替換指定字串,替換所有的
.toUpperCase() 全部轉為大寫
.toLowerCase() 全部轉成小寫
.trim() 去掉字串前后的空格,中間的去不掉
3.Boolean
Boolean boolean=new Boolean("false");
System.out.println(boolean);
4.正則運算式
字符類
[abc] a、b、c其中任意一個
[^abc] 除了a、b、c中的任意一個
[a-zA-Z] a-z或A-Z范圍中的任意一個
[a-zA-Z0-9] a-z A-Z 0-9 其中任意一個
[……] 可以自己定義范圍
預定字符類
\d 數字0-9
\D 非數字0-9
\s 空白字符:[ \t\n\x0B\f\r]
\S 非空白字符:\s
\w 單詞字符:[a-zA-Z_0-9]
\W 非單詞字符\w
數量詞
? 一次或者一次也沒有
* 0次到多次
+ 一次或者多次
{n} 恰好n次
{n,} 至少n次
{n,m} 至少n次但不超過m次
.matches(); 匹配是否適合
.spil(); 拆分
5.時間相關類
1、Date類
.getTime();計算毫秒
2、SimpleDateFormat類 格式化時間
.format();回傳的是String字串
3、Calendar介面 日歷欄位之間的轉換提供了一些方法
.get(Calendar.YEAR);
.get(Calendar.MONTH);// 默認是當前月份減一 從0開始的
.get(Calendar.DAY_OF_MONTH);
.get(Calendar.DAY_OF_WEEK);
Calendar calendar = Calendar.getInstance();
Date date = calendar.getTime();
4、Runtime運行時時間
.freeMemory(); 當前的系統剩余空間
5、System.exit(0);退出程式,引數是0 是正常退出
System.gc();呼叫垃圾回收器 ,不一定能夠起來 ,只是起到一個促進的作用
🎄Java集合框架

🚀常用的三種集合介面:
1.List:繼承自Collection,可以存在相同的物件,有序的,具體實作類有ArrayList,LinkedList,Vector等(已經被拋棄,很少使用)
2.Set:繼承自Collection,不能存在相同的物件,無序的,就是數學意義上的集合,具體實作類有HashSet,LinkedHashSet,TreeSet等,
3.Map:以鍵值對的形式存放物件,key-value,一般是key為String型別,value為Object的型別,具體實作類有HashMap,LinkedHashMap,TreeMap等,
🎉List(有序,可以重復的集合)
public interface List<E> extends Collection<E> {}
由于 List 介面是繼承于 Collection 介面,所以基本的方法如上所示,
1、List 介面的三個典型實作:
①、List list1 = new ArrayList();
底層資料結構是陣列,查詢快,增刪慢;執行緒不安全,效率高
②、List list2 = new Vector();
底層資料結構是陣列,查詢快,增刪慢;執行緒安全,效率低,幾乎已經淘汰了這個集合
③、List list3 = new LinkedList();
底層資料結構是鏈表,查詢慢,增刪快;執行緒不安全,效率高
代碼示例:
//產生一個 List 集合,典型實作為 ArrayList
List list = new ArrayList();
//添加三個元素
list.add("Tom");
list.add("Bob");
list.add("Marry");
//構造 List 的迭代器
Iterator it = list.iterator();
//通過迭代器遍歷元素
while(it.hasNext()){
Object obj = it.next();
//System.out.println(obj);
}
//在指定地方添加元素
list.add(2, 0);
//在指定地方替換元素
list.set(2, 1);
//獲得指定物件的索引
int i=list.indexOf(1);
System.out.println("索引為:"+i);
//遍歷:普通for回圈
for(int j=0;j<list.size();j++){
System.out.println(list.get(j));
}
🎋Set(典型實作 HashSet()是一個無序,不可重復的集合)
HashSet 基于 HashMap 實作,使用了 HashMap 的 K 作為元素存盤,V 為 new Object() ,在 add() 方法中如果兩個元素的 Hash 值相同,則通過 equals 方法比較是否相等,
LinkedHashSet LinkedHashSet 繼承于 HashSet,并且其內部是通過 LinkedHashMap 來實作的,
TreeSet 紅黑樹實作有序唯一,
1、Set hashSet = new HashSet();
①、HashSet:不能保證元素的順序;不可重復;不是執行緒安全的;集合元素可以為 NULL;
②、其底層其實是一個陣列,存在的意義是加快查詢速度,我們知道在一般的陣列中,元素在陣列中的索引位置是隨機的,元素的取值和元素的位置之間不存在確定的關系,因此,在陣列中查找特定的值時,需要把查找值和一系列的元素進行比較,此時的查詢效率依賴于查找程序中比較的次數,而 HashSet 集合底層陣列的索引和值有一個確定的關系:index=hash(value),那么只需要呼叫這個公式,就能快速的找到元素或者索引,
③、對于 HashSet: 如果兩個物件通過 equals() 方法回傳 true,這兩個物件的 hashCode 值也應該相同,
1、當向HashSet集合中存入一個元素時,HashSet會先呼叫該物件的hashCode()方法來得到該物件的hashCode值,然后根據hashCode值決定該物件在HashSet中的存盤位置
1.1、如果 hashCode 值不同,直接把該元素存盤到hashCode()指定的位置
1.2、如果 hashCode 值相同,那么會繼續判斷該元素和集合物件的 equals() 作比較
1.2.1、hashCode相同,equals 為 true,則視為同一個物件,不保存在 hashSet()中
1.2.2、hashCode相同,equals 為 false,則存盤在之前物件同槽位的鏈表上,這非常麻煩,我們應該約束這種情況,即保證:如果兩個物件通過equals()方法回傳 true,這兩個物件的 hashCode 值也應該相同,
注意:每一個存盤到 哈希 表中的物件,都得提供hashCode()和equals()方法的實作,用來判斷是否是同一個物件
對于HashSet集合,我們要保證如果兩個物件通過equals()方法回傳 true,這兩個物件的 hashCode 值也應該相同,
常見的 hashCode()演算法:

2、Set linkedHashSet = new LinkedHashSet();
不可以重復,有序,因為底層采用 鏈表 和 哈希表的演算法,鏈表保證元素的添加順序,哈希表保證元素的唯一性
3、Set treeSet = new TreeSet();
TreeSet:有序;不可重復,底層使用 紅黑樹演算法,擅長于范圍查詢,
如果使用TreeSet()無引數的構造器創建一個TreeSet物件, 則要求放入其中的元素的類必須實作Comparable介面所以, 在其中不能放入null元素
必須放入同樣類的物件(默認會進行排序) 否則可能會發生型別轉換例外.我們可以使用泛型來進行限制
🎡Map(key-value 的鍵值對,key 不允許重復,value可以)
1、嚴格來說
Map并不是一個集合,而是兩個集合之間 的映射關系,
2、這兩個集合沒每一條資料通過映射關系,我們可以看成是一條資料,即Entry(key,value),Map 可以看成是由多個 Entry 組成,
3、因為 Map 集合即沒有實作于Collection介面,也沒有實作Iterable介面,所以不能對Map集合進行for-each遍歷,
代碼示例:
Map<String,Object> hashMap = new HashMap<>();
//添加元素到 Map 中
hashMap.put("key1", "value1");
hashMap.put("key2", "value2");
hashMap.put("key3", "value3");
hashMap.put("key4", "value4");
hashMap.put("key5", "value5");
//洗掉 Map 中的元素,通過 key 的值
hashMap.remove("key1");
//通過 get(key) 得到 Map 中的value
Object str1 = hashMap.get("key1");
//可以通過 添加 方法來修改 Map 中的元素
hashMap.put("key2", "修改 key2 的 Value");
//通過 map.values() 方法得到 Map 中的 value 集合
Collection<Object> value = hashMap.values();
for(Object obj : value){
//System.out.println(obj);
}
//通過 map.keySet() 得到 Map 的key 的集合,然后 通過 get(key) 得到 Value
Set<String> set = hashMap.keySet();
for(String str : set){
Object obj = hashMap.get(str);
//System.out.println(str+"="+obj);
}
//通過 Map.entrySet() 得到 Map 的 Entry集合,然后遍歷
Set<Map.Entry<String, Object>> entrys = hashMap.entrySet();
for(Map.Entry<String, Object> entry: entrys){
String key = entry.getKey();
Object value2 = entry.getValue();
System.out.println(key+"="+value2);
}
System.out.println(hashMap);
🎄Map常用方法及實作類
1.添加:
map.put(key,value) //在key位置上存盤value值,key存在則覆寫原有值;
map.putAll(Map m);//將Map集合m放在map中
2.洗掉:
map.clear(); //清空map中的資料
map.remvoe(key); //洗掉key及其位置上的元素,回傳其值,
3.判斷:
map.containsValue(value); //判斷集合是否包含value值
map.containsKey(key); //判斷集合是否包含key鍵
4.獲取:
map.get(key); //獲取key鍵上的value值
map.size(); //獲取map集合的大小
Collection c = map.values();
// 回傳map集合中的value值 的Collection集合;
Set< K > set = keySet(); //取出key的所有值的Set集合
Set< Map.Entry< K , V > >set =entrySet();
實作類:
1.
Hashtable: 底層用哈希表實作,不允許存在null鍵和值,集合執行緒安全(執行緒同步) jdk1.0以前常用
2.HashMap: 底層用哈希表實作,運行存在null的鍵和值,集合執行緒不同步,用法與Hastable相同
3.TreeMap: 底層用二叉樹實作,用于需要排序的Map集合中
4.Properties:繼承Hastbale,主要用于流中檔案固化
5.ConcurrentHashMap:執行緒安全的 HashMap,
1.7 采用分段鎖的形式加鎖;1.8 使用 Synchronized 和 CAS 實作同步,若陣列的 Node 為空,則通過 CAS 的方式設定值,不為空則加在鏈表的第一個節點,獲取第一個元素是否為空使用 Unsafe 類提供的 getObjectVolatile 保證可見性,
對于讀操作,陣列由 volatile 修飾,同時陣列的元素為 Node,Node 的 K 使用 final 修飾,V 使用 volatile 修飾,下一個節點也用 volatile 修飾,保證多執行緒的可見性,
6.LinkedHashMap:繼承自 HashMap,所以它的底層仍然是基于拉鏈式散列結構即由陣列和鏈表或紅黑樹組成,另外,LinkedHashMap 在上面結構的基礎上,增加了一條雙向鏈表,使得上面的結構可以保持鍵值對的插入順序,
🧨Map與Set關系
1、都有幾個型別的集合,HashMap 和 HashSet ,都采 哈希表演算法;TreeMap 和 TreeSet 都采用 紅-黑樹演算法;LinkedHashMap 和 LinkedHashSet 都采用 哈希表演算法和紅-黑樹演算法,
2、分析 Set 的底層原始碼,我們可以看到,Set 集合 就是 由 Map 集合的 Key 組成
🎄Java多執行緒
🎗創建執行緒和啟動
(1)繼承
Thread類創建執行緒類
通過繼承Thread類創建執行緒類的具體步驟和具體代碼如下:
? 定義一個繼承Thread類的子類,并重寫該類的run()方法;
? 創建Thread子類的實體,即創建了執行緒物件;
? 呼叫該執行緒物件的start()方法啟動執行緒,
class SomeThead extends Thraad {
public void run() {
//do something here
}
}
public static void main(String[] args){
SomeThread oneThread = new SomeThread();
步驟3:啟動執行緒:
oneThread.start();
}
(2)實作
Runnable介面創建執行緒類
通過實作Runnable介面創建執行緒類的具體步驟和具體代碼如下:
? 定義Runnable介面的實作類,并重寫該介面的run()方法;
? 創建Runnable實作類的實體,并以此實體作為Thread的target物件,即該Thread物件才是真正的執行緒物件,
class SomeRunnable implements Runnable { `在這里插入代碼片`
public void run() {
//do something here
}
}
Runnable oneRunnable = new SomeRunnable();
Thread oneThread = new Thread(oneRunnable);
oneThread.start();
(3)通過
Callable和Future創建執行緒
通過Callable和Future創建執行緒的具體步驟和具體代碼如下:
? 創建Callable介面的實作類,并實作call()方法,該call()方法將作為執行緒執行體,并且有回傳值,
? 創建Callable實作類的實體,使用FutureTask類來包裝Callable物件,該FutureTask物件封裝了該Callable物件的call()方法的回傳值,
? 使用FutureTask物件作為Thread物件的target創建并啟動新執行緒,
? 呼叫FutureTask物件的get()方法來獲得子執行緒執行結束后的返回值其中,Callable介面(也只有一個方法)定義如下:
public interface Callable {
V call() throws Exception;
}
步驟1:創建實作Callable介面的類SomeCallable(略);
步驟2:創建一個類物件:
Callable oneCallable = new SomeCallable();
步驟3:由Callable創建一個FutureTask物件:
FutureTask oneTask = new FutureTask(oneCallable);
注釋: FutureTask是一個包裝器,它通過接受Callable來創建,它同時實作了 Future和Runnable介面,
步驟4:由FutureTask創建一個Thread物件:
Thread oneThread = new Thread(oneTask);
步驟5:啟動執行緒:
oneThread.start();
🎀執行緒生命周期

1、新建狀態
用new關鍵字和Thread類或其子類建立一個執行緒物件后,該執行緒物件就處于新生狀態,處于新生狀態的執行緒有自己的記憶體空間,通過呼叫start方法進入就緒狀態(runnable),
注意:不能對已經啟動的執行緒再次呼叫start()方法,否則會出現Java.lang.IllegalThreadStateException例外,
2、就緒狀態
處于就緒狀態的執行緒已經具備了運行條件,但還沒有分配到CPU,處于執行緒就緒佇列(盡管是采用佇列形式,事實上,把它稱為可運行池而不是可運行佇列,因為cpu的調度不一定是按照先進先出的順序來調度的),等待系統為其分配CPU,等待狀態并不是執行狀態,當系統選定一個等待執行的Thread物件后,它就會從等待執行狀態進入執行狀態,系統挑選的動作稱之為“cpu調度”,一旦獲得CPU,執行緒就進入運行狀態并自動呼叫自己的run方法,
提示:如果希望子執行緒呼叫start()方法后立即執行,可以使用Thread.sleep()方式使主執行緒睡眠一伙兒,轉去執行子執行緒,
3、運行狀態
處于運行狀態的執行緒最為復雜,它可以變為阻塞狀態、就緒狀態和死亡狀態,
處于就緒狀態的執行緒,如果獲得了cpu的調度,就會從就緒狀態變為運行狀態,執行run()方法中的任務,如果該執行緒失去了cpu資源,就會又從運行狀態變為就緒狀態,重新等待系統分配資源,也可以對在運行狀態的執行緒呼叫yield()方法,它就會讓出cpu資源,再次變為就緒狀態,
注: 當發生如下情況是,執行緒會從運行狀態變為阻塞狀態:
①、執行緒呼叫sleep方法主動放棄所占用的系統資源
②、執行緒呼叫一個阻塞式IO方法,在該方法回傳之前,該執行緒被阻塞
③、執行緒試圖獲得一個同步監視器,但更改同步監視器正被其他執行緒所持有
④、執行緒在等待某個通知(notify)
⑤、程式呼叫了執行緒的suspend方法將執行緒掛起,不過該方法容易導致死鎖,所以程式應該盡量避免使用該方法,
當執行緒的run()方法執行完,或者被強制性地終止,例如出現例外,或者呼叫了stop()、desyory()方法等等,就會從運行狀態轉變為死亡狀態,
4、阻塞狀態
處于運行狀態的執行緒在某些情況下,如執行了sleep(睡眠)方法,或等待I/O設備等資源,將讓出CPU并暫時停止自己的運行,進入阻塞狀態,
在阻塞狀態的執行緒不能進入就緒佇列,只有當引起阻塞的原因消除時,如睡眠時間已到,或等待的I/O設備空閑下來,執行緒便轉入就緒狀態,重新到就緒佇列中排隊等待,被系統選中后從原來停止的位置開始繼續運行,
5、死亡狀態
當執行緒的run()方法執行完,或者被強制性地終止,就認為它死去,這個執行緒物件也許是活的,但是,它已經不是一個單獨執行的執行緒,執行緒一旦死亡,就不能復生, 如果在一個死去的執行緒上呼叫start()方法,會拋出java.lang.IllegalThreadStateException例外,
🎭執行緒管理
🎗執行緒睡眠——sleep
Java提供了一些便捷的方法用于會執行緒狀態的控制,具體如下:
1、執行緒睡眠——sleep
如果我們需要讓當前正在執行的執行緒暫停一段時間,并進入阻塞狀態,則可以通過呼叫Thread的sleep方法,
注:
(1)sleep是靜態方法,最好不要用Thread的實體物件呼叫它,因為它睡眠的始終是當前正在運行的執行緒,而不是呼叫它的執行緒物件,它只對正在運行狀態的執行緒物件有效,如下面的例子:
public class Test1 {
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().getName());
MyThread myThread=new MyThread();
myThread.start();
myThread.sleep(1000);//這里sleep的就是main執行緒,而非myThread執行緒
Thread.sleep(10);
for(int i=0;i<100;i++){
System.out.println("main"+i);
}
}
}
(2)Java執行緒調度是Java多執行緒的核心,只有良好的調度,才能充分發揮系統的性能,提高程式的執行效率,但是不管程式員怎么撰寫調度,只能最大限度的影響執行緒執行的次序,而不能做到精準控制,因為使用sleep方法之后,執行緒是進入阻塞狀態的,只有當睡眠的時間結束,才會重新進入到就緒狀態,而就緒狀態進入到運行狀態,是由系統控制的,我們不可能精準的去干涉它,所以如果呼叫Thread.sleep(1000)使得執行緒睡眠1秒,可能結果會大于1秒,
🎗執行緒讓步——yield
yield()方法和sleep()方法有點相似,它也是Thread類提供的一個靜態的方法,它也可以讓當前正在執行的執行緒暫停,讓出cpu資源給其他的執行緒,但是和sleep()方法不同的是,它不會進入到阻塞狀態,而是進入到就緒狀態,yield()方法只是讓當前執行緒暫停一下,重新進入就緒的執行緒池中,讓系統的執行緒調度器重新調度器重新調度一次,完全可能出現這樣的情況:當某個執行緒呼叫yield()方法之后,執行緒調度器又將其調度出來重新進入到運行狀態執行,
實際上,當某個執行緒呼叫了yield()方法暫停之后,優先級與當前執行緒相同,或者優先級比當前執行緒更高的就緒狀態的執行緒更有可能獲得執行的機會,當然,只是有可能,因為我們不可能精確的干涉cpu調度執行緒,用法如下:
public class Test1 {
public static void main(String[] args) throws InterruptedException {
new MyThread("低級", 1).start();
new MyThread("中級", 5).start();
new MyThread("高級", 10).start();
}
}
class MyThread extends Thread {
public MyThread(String name, int pro) {
super(name);// 設定執行緒的名稱
this.setPriority(pro);// 設定優先級
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println(this.getName() + "執行緒第" + i + "次執行!");
if (i % 5 == 0)
Thread.yield();
}
}
}
注:關于sleep()方法和yield()方的區別如下:
①、sleep方法暫停當前執行緒后,會進入阻塞狀態,只有當睡眠時間到了,才會轉入就緒狀態,而yield方法呼叫后 ,是直接進入就緒狀態,所以有可能剛進入就緒狀態,又被調度到運行狀態,
②、sleep方法宣告拋出了InterruptedException,所以呼叫sleep方法的時候要捕獲該例外,或者顯示宣告拋出該例外,而yield方法則沒有宣告拋出任務例外,
③、sleep方法比yield方法有更好的可移植性,通常不要依靠yield方法來控制并發執行緒的執行,
🎗設定執行緒的優先級
每個執行緒執行時都有一個優先級的屬性,優先級高的執行緒可以獲得較多的執行機會,而優先級低的執行緒則獲得較少的執行機會,與執行緒休眠類似,執行緒的優先級仍然無法保障執行緒的執行次序,只不過,優先級高的執行緒獲取CPU資源的概率較大,優先級低的也并非沒機會執行,
每個執行緒默認的優先級都與創建它的父執行緒具有相同的優先級,在默認情況下,main執行緒具有普通優先級,
注:Thread類提供了setPriority(int newPriority)和getPriority()方法來設定和回傳一個指定執行緒的優先級,其中setPriority方法的引數是一個整數,范圍是1~·0之間,也可以使用Thread類提供的三個靜態常量:
MAX_PRIORITY =10
MIN_PRIORITY =1
NORM_PRIORITY =5
public class Test1 {
public static void main(String[] args) throws InterruptedException {
new MyThread("高級", 10).start();
new MyThread("低級", 1).start();
}
}
class MyThread extends Thread {
public MyThread(String name,int pro) {
super(name);//設定執行緒的名稱
setPriority(pro);//設定執行緒的優先級
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + "執行緒第" + i + "次執行!");
}
}
}
注:雖然Java提供了10個優先級別,但這些優先級別需要作業系統的支持,不同的作業系統的優先級并不相同,而且也不能很好的和Java的10個優先級別對應,所以我們應該使用MAX_PRIORITY、MIN_PRIORITY和NORM_PRIORITY三個靜態常量來設定優先級,這樣才能保證程式最好的可移植性,
🎗后臺(守護)執行緒
守護執行緒使用的情況較少,但并非無用,舉例來說,JVM的垃圾回收、記憶體管理等執行緒都是守護執行緒,還有就是在做資料庫應用時候,使用的資料庫連接池,連接池本身也包含著很多后臺執行緒,監控連接個數、超時時間、狀態等等,呼叫執行緒物件的方法setDaemon(true),則可以將其設定為守護執行緒,守護執行緒的用途為:
? 守護執行緒通常用于執行一些后臺作業,例如在你的應用程式運行時播放背景音樂,在文字編輯器里做自動語法檢查、自動保存等功能,
? Java的垃圾回收也是一個守護執行緒,守護線的好處就是你不需要關心它的結束問題,例如你在你的應用程式運行的時候希望播放背景音樂,如果將這個播放背景音樂的執行緒設定為非守護執行緒,那么在用戶請求退出的時候,不僅要退出主執行緒,還要通知播放背景音樂的執行緒退出;如果設定為守護執行緒則不需要了,
setDaemon方法的詳細說明:
public final void setDaemon(boolean on) 將該執行緒標記為守護執行緒或用戶執行緒,當正在運行的執行緒都是守護執行緒時,Java 虛擬機退出,
該方法必須在啟動執行緒前呼叫, 該方法首先呼叫該執行緒的 checkAccess 方法,且不帶任何引數,這可能拋出 SecurityException(在當前執行緒中),
引數:
on - 如果為 true,則將該執行緒標記為守護執行緒,
拋出:
IllegalThreadStateException - 如果該執行緒處于活動狀態,
SecurityException - 如果當前執行緒無法修改該執行緒,
注:JRE判斷程式是否執行結束的標準是所有的前臺執執行緒行完畢了,而不管后臺執行緒的狀態,因此,在使用后臺縣城時候一定要注意這個問題,
🎗正確結束執行緒
Thread.stop()、Thread.suspend、Thread.resume、Runtime.runFinalizersOnExit這些終止執行緒運行的方法已經被廢棄了,使用它們是極端不安全的!想要安全有效的結束一個執行緒,可以使用下面的方法:
? 正常執行完run方法,然后結束掉;
? 控制回圈條件和判斷條件的識別符號來結束掉執行緒,
class MyThread extends Thread {
int i=0;
boolean next=true;
@Override
public void run() {
while (next) {
if(i==10)
next=false;
i++;
System.out.println(i);
}
}
}
🎁執行緒同步
java允許多執行緒并發控制,當多個執行緒同時操作一個可共享的資源變數時(如資料的增刪改查),將會導致資料不準確,相互之間產生沖突,因此加入同步鎖以避免在該執行緒沒有完成操作之前,被其他執行緒的呼叫,從而保證了該變數的唯一性和準確性,
🎗同步方法
即有synchronized關鍵字修飾的方法,由于java的每個物件都有一個內置鎖,當用此關鍵字修飾方法時,內置鎖會保護整個方法,在呼叫該方法前,需要獲得內置鎖,否則就處于阻塞狀態,
public synchronized void save(){}
注:
synchronized關鍵字也可以修飾靜態方法,此時如果呼叫該靜態方法,將會鎖住整個類
🎗同步代碼塊
即有
synchronized關鍵字修飾的陳述句塊,被該關鍵字修飾的陳述句塊會自動被加上內置鎖,從而實作同步,
public class Bank {
private int count =0;//賬戶余額
//存錢
public void addMoney(int money){
synchronized (this) {
count +=money;
}
System.out.println(System.currentTimeMillis()+"存進:"+money);
}
//取錢
public void subMoney(int money){
synchronized (this) {
if(count-money < 0){
System.out.println("余額不足");
return;
}
count -=money;
}
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}
//查詢
public void lookMoney(){
System.out.println("賬戶余額:"+count);
}
}
注:同步是一種高開銷的操作,因此應該盡量減少同步的內容,通常沒有必要同步整個方法,使用synchronized代碼塊同步關鍵代碼即可,
🎗使用特殊域變數(volatile)實作執行緒同步
? volatile關鍵字為域變數的訪問提供了一種免鎖機制;
? 使用volatile修飾域相當于告訴虛擬機該域可能會被其他執行緒更新;
? 因此每次使用該域就要重新計算,而不是使用暫存器中的值;
? volatile不會提供任何原子操作,它也不能用來修飾final型別的變數,
public class SynchronizedThread {
class Bank {
private volatile int account = 100;
public int getAccount() {
return account;
}
/**
* 用同步方法實作
*
* @param money
*/
public synchronized void save(int money) {
account += money;
}
/**
* 用同步代碼塊實作
*
* @param money
*/
public void save1(int money) {
synchronized (this) {
account += money;
}
}
}
class NewThread implements Runnable {
private Bank bank;
public NewThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// bank.save1(10);
bank.save(10);
System.out.println(i + "賬戶余額為:" +bank.getAccount());
}
}
}
/**
* 建立執行緒,呼叫內部類
*/
public void useThread() {
Bank bank = new Bank();
NewThread new_thread = new NewThread(bank);
System.out.println("執行緒1");
Thread thread1 = new Thread(new_thread);
thread1.start();
System.out.println("執行緒2");
Thread thread2 = new Thread(new_thread);
thread2.start();
}
public static void main(String[] args) {
SynchronizedThread st = new SynchronizedThread();
st.useThread();
}
注:多執行緒中的非同步問題主要出現在對域的讀寫上,如果讓域自身避免這個問題,則就不需要修改操作該域的方法,用final域,有鎖保護的域和volatile域可以避免非同步的問題,
🎗使用重入鎖(Lock)實作執行緒同步
在JavaSE5.0中新增了一個java.util.concurrent包來支持同步,ReentrantLock類是可重入、互斥、實作了Lock介面的鎖,它與使用synchronized方法和快具有相同的基本行為和語意,并且擴展了其能力,ReenreantLock類的常用方法有:
ReentrantLock() : 創建一個ReentrantLock實體
lock() : 獲得鎖
unlock() : 釋放鎖
注:
ReentrantLock()還有一個可以創建公平鎖的構造方法,但由于能大幅度降低程式運行效率,不推薦使用
//只給出要修改的代碼,其余代碼與上同
class Bank {
private int account = 100;
//需要宣告這個鎖
private Lock lock = new ReentrantLock();
public int getAccount() {
return account;
}
//這里不再需要synchronized
public void save(int money) {
lock.lock();
try{
account += money;
}finally{
lock.unlock();
}
}
}
🧶執行緒通信
🎗借助于Object類的wait()、notify()和notifyAll()實作通信
執行緒執行wait()后,就放棄了運行資格,處于凍結狀態;
執行緒運行時,記憶體中會建立一個執行緒池,凍結狀態的執行緒都存在于執行緒池中,notify()執行時喚醒的也是執行緒池中的執行緒,執行緒池中有多個執行緒時喚醒第一個被凍結的執行緒,
notifyall(), 喚醒執行緒池中所有執行緒,
注:
(1)
wait(), notify(),notifyall()都用在同步里面,因為這3個函式是對持有鎖的執行緒進行操作,而只有同步才有鎖,所以要使用在同步中;
(2)wait(),notify(),notifyall(), 在使用時必須標識它們所操作的執行緒持有的鎖,因為等待和喚醒必須是同一鎖下的執行緒;而鎖可以是任意物件,所以這3個方法都是Object類中的方法,
單個消費者生產者例子如下:
class Resource{ //生產者和消費者都要操作的資源
private String name;
private int count=1;
private boolean flag=false;
public synchronized void set(String name){
if(flag)
try{wait();}catch(Exception e){}
this.name=name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
flag=true;
this.notify();
}
public synchronized void out(){
if(!flag)
try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...消費者..."+this.name);
flag=false;
this.notify();
}
}
class Producer implements Runnable{
private Resource res;
Producer(Resource res){
this.res=res;
}
public void run(){
while(true){
res.set("商品");
}
}
}
class Consumer implements Runnable{
private Resource res;
Consumer(Resource res){
this.res=res;
}
public void run(){
while(true){
res.out();
}
}
}
public class ProducerConsumerDemo{
public static void main(String[] args){
Resource r=new Resource();
Producer pro=new Producer(r);
Consumer con=new Consumer(r);
Thread t1=new Thread(pro);
Thread t2=new Thread(con);
t1.start();
t2.start();
}
}//運行結果正常,生產者生產一個商品,緊接著消費者消費一個商品,
多個消費者生產者例子如下:
class Resource{
private String name;
private int count=1;
private boolean flag=false;
public synchronized void set(String name){
while(flag) /*原先是if,現在改成while,這樣生產者執行緒從凍結狀態醒來時,還會再判斷flag.*/
try{wait();}catch(Exception e){}
this.name=name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
flag=true;
this.notifyAll();/*原先是notity(), 現在改成notifyAll(),這樣生產者執行緒生產完一個商品后可以將等待中的消費者執行緒喚醒,否則只將上面改成while后,可能出現所有生產者和消費者都在wait()的情況,*/
}
public synchronized void out(){
while(!flag) /*原先是if,現在改成while,這樣消費者執行緒從凍結狀態醒來時,還會再判斷flag.*/
try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...消費者..."+this.name);
flag=false;
this.notifyAll(); /*原先是notity(), 現在改成notifyAll(),這樣消費者執行緒消費完一個商品后可以將等待中的生產者執行緒喚醒,否則只將上面改成while后,可能出現所有生產者和消費者都在wait()的情況,*/
}
}
public class ProducerConsumerDemo{
public static void main(String[] args){
Resource r=new Resource();
Producer pro=new Producer(r);
Consumer con=new Consumer(r);
Thread t1=new Thread(pro);
Thread t2=new Thread(con);
Thread t3=new Thread(pro);
Thread t4=new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
🎗使用Condition控制執行緒通信
jdk1.5中,提供了多執行緒的升級解決方案為:
(1)將同步synchronized替換為顯式的Lock操作;
(2)將Object類中的wait(), notify(),notifyAll()替換成了Condition物件,該物件可以通過Lock鎖物件獲取;
(3)一個Lock物件上可以系結多個Condition物件,這樣實作了本方執行緒只喚醒對方執行緒,而jdk1.5之前,一個同步只能有一個鎖,不同的同步只能用鎖來區分,且鎖嵌套時容易死鎖,
class Resource{
private String name;
private int count=1;
private boolean flag=false;
private Lock lock = new ReentrantLock();/*Lock是一個介面,ReentrantLock是該介面的一個直接子類,*/
private Condition condition_pro=lock.newCondition(); /*創建代表生產者方面的Condition物件*/
private Condition condition_con=lock.newCondition(); /*使用同一個鎖,創建代表消費者方面的Condition物件*/
public void set(String name){
lock.lock();//鎖住此陳述句與lock.unlock()之間的代碼
try{
while(flag)
condition_pro.await(); //生產者執行緒在conndition_pro物件上等待
this.name=name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
flag=true;
condition_con.signalAll();
}
finally{
lock.unlock(); //unlock()要放在finally塊中,
}
}
public void out(){
lock.lock(); //鎖住此陳述句與lock.unlock()之間的代碼
try{
while(!flag)
condition_con.await(); //消費者執行緒在conndition_con物件上等待
System.out.println(Thread.currentThread().getName()+"...消費者..."+this.name);
flag=false;
condition_pro.signqlAll(); /*喚醒所有在condition_pro物件下等待的執行緒,也就是喚醒所有生產者執行緒*/
}
finally{
lock.unlock();
}
}
}
👕執行緒池
優點:
(1). 降低資源消耗,通過重復利用已創建的執行緒,降低執行緒創建和銷毀造成的消耗,
(2). 提供回應速度,當任務到達時,任務可以不需要等待執行緒創建就能立即執行,
(3).提供執行緒的可管理性,執行緒是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一分配、調優和監控,但是要做到合理地利用執行緒,必須對其原理了如指掌,
(4).防止服務器過載,形成記憶體溢位,或者CPU耗盡,
創建執行緒池常見的三種方法:
1、newSingleThreadExecutor:創建一個單執行緒的執行緒池,
2、newFixedThreadPool:創建固定大小的執行緒池,
3、newCachedThreadPool:創建一個可快取的執行緒池,
🎗newSingleThreadExecutor()
創建一個單執行緒的執行緒池,這個執行緒只有一個執行緒在作業,也就是相當于單執行緒串行執行所有任務,如果這個唯一的執行緒因為例外而結束,那么會有一個新的執行緒來代替它,此執行緒保證所有的任務的執行順序按照任務的提交順序執行,
public class SingleThreadExecutorDemo {
public static void main(String[] args) {
ExecutorService executorService=Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int no=i;
Runnable task=new Runnable() {
@Override
public void run() {
try{
System.out.println("into "+no);
Thread.sleep(1000L);
System.out.println("end "+no);
}catch(InterruptedException e){
e.printStackTrace();
}
}
};
//交由執行緒池處理任務
executorService.execute(task);
}
executorService.shutdown();
System.out.println("main thread have terminate");
}
}
🎗newCachedThreadPool的使用
創建一個緩沖池大小可根據需要伸縮的執行緒池,但是在以前構造的執行緒可用時將重用它們,對于執行很多短期異步任務而言,這些執行緒池通常可提供程式性能,呼叫execute將重用以前構造的執行緒(如果執行緒可用),如果現有執行緒沒有可用的,則創建一個新執行緒并添加到池中,終止并從快取中移除那些已有60s未被使用的執行緒,因此,長時間保持空閑的執行緒池不會使用任何資源,
public class CachedThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService=Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
final int no=i;
Runnable task=new Runnable() {
@Override
public void run() {
try{
System.out.println("into "+no);
Thread.sleep(10001L);
System.out.println("end "+no);
}catch(InterruptedException e){
e.printStackTrace();
}
}
};
executorService.execute(task);
}
System.out.println("main thread have terminate");
executorService.shutdown();
}
}
🎗newFixedThreadPool的使用
創建一個可重用固定執行緒數的執行緒池,以共享的無界佇列方式來運行這些執行緒,在任意點,在大多數nThreads執行緒會處于處理任務的活動狀態,如果在所有執行緒處于活動狀態時提交附加任務,則在有可用執行緒之前,附加任務將在佇列中等待,如果在關閉前的執行期間由于失敗而導致任何執行緒終止,那么一個新的執行緒將代替它執行后續任務(如果需要),在某個執行緒被顯示關閉之前,池中的執行緒將一直存在,
public class newFixedThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService=Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
final int no=i;
Runnable task=new Runnable() {
@Override
public void run() {
try{
System.out.println("into "+no);
Thread.sleep(1000L);
System.out.println("end "+no);
}catch(InterruptedException e){
e.printStackTrace();
}
}
};
executorService.execute(task);
}
System.out.println("main thread have terminate");
executorService.shutdown();
}
}
🧵執行緒五種狀態

1.New (新創建)
當用new運算子創建一個執行緒時,如new Thread?,該執行緒還沒有開始運行,這意外這它的狀態是new,此時程式還沒有開始運行執行緒中的代碼,在執行緒運行之前還有一些基礎作業要做,
2.Runnable (可運行/就緒)
一旦處于新狀態的執行緒呼叫start方法(如圖中的1所示),執行緒就處于Runnbale狀態,
處于Runnable狀態的執行緒還未運行run()方法的代碼,只有在獲得CPU時間片才開始運行,
3.Running (運行中)
當執行緒獲得CPU時間片,執行緒就進入Running狀態(如圖中的2所示),
處于Running狀態的執行緒有可能在運行中CPU時間片用完,而run方法沒運行完,執行緒就又進入Runnable狀態,
通常情況下,運行中的執行緒一直處于Running與Runnable交替轉換的程序中,
4.Blocked (等待/阻塞/睡眠)
當執行緒在Running狀態中,遇到阻塞等待鎖、等待用戶輸入、呼叫sleep()方法、呼叫join等待其他執行緒情況,會導致執行緒進入阻塞狀態(Blocked),
處于阻塞狀態的執行緒,在阻塞等待結束之后,會進入Runnable狀態,等等獲得CPU時間片繼續運行程式,
5.Dead (死亡)
當執行緒運行完run方法,直接進入死亡狀態Dead ,
🎄Java虛擬機
Jvm相關資料:
鏈接:Jvm面試資料
提取碼:u5i9
廢話不說 直接看大佬操作:jvm
🎄MySQL
MySQL相關資料:
鏈接:MySQL
提取碼:1jbm
詳情見博主之前文章:MySQL入門到畢業
🎄Spring相關知識點
🎨Bean生命周期

面試中經常問到的bean的生命周期,先看綠色的部分,bean的創建程序:
第1步:呼叫bean的構造方法創建bean;
第2步:通過反射呼叫setter方法進行屬性的依賴注入;
第3步:如果實作BeanNameAware介面的話,會設定bean的name;
第4步:如果實作了BeanFactoryAware,會把bean factory設定給bean;
第5步:如果實作了ApplicationContextAware,會給bean設定ApplictionContext;
第6步:如果實作了BeanPostProcessor介面,則執行前置處理方法;
第7步:實作了InitializingBean介面的話,執行afterPropertiesSet方法;
第8步:執行自定義的init方法;
第9步:執行BeanPostProcessor介面的后置處理方法,
這時,就完成了bean的創建程序,
在使用完bean需要銷毀時,會先執行DisposableBean介面的destroy方法,然后在執行自定義的destroy方法,
🎪Spring應用
🎗常用注解
a.型別類注釋:
型別類注釋包括
controller、service等,需要重點了解
其中component和bean注解的區別如下:
@Component注解在類上使用表明這個類是個組件類,需要Spring為這個類創建bean,
@Bean注解使用在方法上,告訴Spring這個方法將會回傳一個Bean物件,需要把回傳的物件注冊到Spring的應用背景關系中,
b.設定類注解
重點了解
@Autowire和@Qualifier以及bytype、byname等不同的自動裝配機制,
c.web類注解
主要以了解為主,關注
@RequestMapping、@GetMapping、@PostMapping等路徑匹配注解,以及@PathVariable、@RequestParam等引數獲取注解,
d.功能類注解
包括
@ImportResource參考配置、@ComponentScan注解自動掃描、@Transactional事務注解等等,這里不一一介紹了,
🎠Spring優點
(1)spring屬于低侵入式設計,代碼的污染極低;
(2)spring的DI機制將物件之間的依賴關系交由框架處理,減低組件的耦合性;
(3)Spring提供了AOP技術,支持將一些通用任務,如安全、事務、日志、權限等進行集中式管理,從而提供更好的復用,
(4)spring對于主流的應用框架提供了集成支持,
🎀Spring中IOC理解
(1)IOC就是控制反轉,指創建物件的控制權轉移給Spring框架進行管理,并由Spring根據組態檔去創建實體和管理各個實體之間的依賴關系,物件與物件之間松散耦合,也利于功能的復用,DI依賴注入,和控制反轉是同一個概念的不同角度的描述,即 應用程式在運行時依賴IoC容器來動態注入物件需要的外部依賴,
(2)最直觀的表達就是,以前創建物件的主動權和時機都是由自己把控的,IOC讓物件的創建不用去new了,可以由spring自動生產,使用java的反射機制,根據組態檔在運行時動態的去創建物件以及管理物件,并呼叫物件的方法的,
(3)Spring的IOC有三種注入方式 :構造器注入、setter方法注入、根據注解注入,
🎇Spring中AOP理解
AOP,一般稱為面向切面,作為面向物件的一種補充,用于將那些與業務無關,但卻對多個物件產生影響的公共行為和邏輯,抽取并封裝為一個可重用的模塊,這個模塊被命名為“切面”(Aspect),減少系統中的重復代碼,降低了模塊間的耦合度,提高系統的可維護性,可用于權限認證、日志、事務處理,
AOP實作的關鍵在于 代理模式,AOP代理主要分為靜態代理和動態代理,靜態代理的代表為AspectJ;動態代理則以Spring AOP為代表,
(1)AspectJ是靜態代理,也稱為編譯時增強,AOP框架會在編譯階段生成AOP代理類,并將AspectJ(切面)織入到Java位元組碼中,運行的時候就是增強之后的AOP物件,
(2)Spring AOP使用的動態代理,所謂的動態代理就是說AOP框架不會去修改位元組碼,而是每次運行時在記憶體中臨時為方法生成一個AOP物件,這個AOP物件包含了目標物件的全部方法,并且在特定的切點做了增強處理,并回呼原物件的方法,
Spring AOP中的動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理:
① JDK動態代理只提供介面的代理,不支持類的代理,要求被代理類實作介面,JDK動態代理的核心是InvocationHandler介面和Proxy類,在獲取代理物件時,使用Proxy類來動態創建目標類的代理類(即最終真正的代理類,這個類繼承自Proxy并實作了我們定義的介面),當代理物件呼叫真實物件的方法時, InvocationHandler 通過
invoke()方法反射來呼叫目標類中的代碼,動態地將橫切邏輯和業務編織在一起;
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最終生成的代理物件; method 是被代理目標實體的某個具體方法; args 是被代理目標實體某個方法的具體入參, 在方法反射呼叫時使用,
② 如果被代理類沒有實作介面,那么Spring AOP會選擇使用CGLIB來動態代理目標類,CGLIB(Code Generation Library),是一個代碼生成的類別庫,可以在運行時動態的生成指定類的一個子類物件,并覆寫其中特定方法并添加增強代碼,從而實作AOP,CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記為final,那么它是無法使用CGLIB做動態代理的,
(3)靜態代理與動態代理區別在于生成AOP代理物件的時機不同,相對來說AspectJ的靜態代理方式具有更好的性能,但是AspectJ需要特定的編譯器進行處理,而Spring AOP則無需特定的編譯器處理,
IoC讓相互協作的組件保持松散的耦合,而AOP編程允許你把遍布于應用各層的功能分離出來形成可重用的功能組件,
🎭Spring配置方式
基于XML的配置:基于注解的配置: Spring在2.5版本以后開始支持用注解的方式來配置依賴注入,可以用注解的方式來替代XML方式的bean描述,可以將bean描述轉移到組件類的內部,只需要在相關類上、方法上或者欄位宣告上使用注解即可,注解注入將會被容器在XML注入之前被處理,所以后者會覆寫掉前者對于同一個屬性的處理結果
基于Java的配置: Spring對Java配置的支持是由
@Configuration注解和@Bean注解來實作的,由@Bean注解的方法將會實體化、配置和初始化一個新物件,這個物件將由Spring的IoC容器來管理,@Bean宣告所起到的作用與元素類似,被@Configuration所注解的類則表示這個類的主要目的是作為bean定義的資源,被@Configuration宣告的類可以通過在同一個類的內部呼叫@bean方法來設定嵌入bean的依賴關系,
🎢Spring中的設計模式
1.代理模式—在AOP和remoting中被用的比較多,
2.單例模式—在spring組態檔中定義的bean默認為單例模式,
模板方法—用來解決代碼重復的問題 比如. RestTemplate, JmsTemplate, JpaTemplate, 前端控制器—Srping提供了DispatcherServlet來對請求進行分發, 視圖幫助(View Helper )—Spring提供了一系列的JSP標簽,高效宏來輔助將分散的代碼整合在視圖里, 依賴注入—貫穿于BeanFactory / ApplicationContext介面的核心理念,
3.工廠模式—BeanFactory用來創建物件的實體,
4.Builder模式- 自定義組態檔的決議bean是時采用builder模式,一步一步地構建一個beanDefinition
5.策略模式:Spring 中策略模式使用有多個地方,如 Bean 定義物件的創建以及代理物件的創建等,這里主要看一下代理物件創建的策略模式的實作, 前面已經了解 Spring 的代理方式有兩個 Jdk 動態代理和 CGLIB 代理,這兩個代理方式的使用正是使用了策略模式,
🧨SpringMVC執行流程

1.DispatcherServlet接收到一個URL請求
2.根據URL到HandlerMapping中查找處理器
3.回傳給DispatcherServlet一個Handler執行鏈
4.DispatcherServlet請求HandlerAdapter配接器,找到對應的Handler
5.執行Handler即Controller的相關業務邏輯
6.Handler回傳ModelAndView到HandlerAdapter
7.HandlerAdapter回傳ModelAndView到DispatcherServlet
8.DispatcherServlet請求ViewRessolver視圖決議器,找到對應的View根據ModelAndView生成視圖物件
9.DispatcherServlet回傳View
深入學習可查看此文:Spring常見面試題總結(超詳細回答)
🎄計算機網路
計算機網路相關資料:
鏈接:計算機網路
提取碼:1wq1
🎍OSI,TCP/IP,五層協議的體系結構,以及各層協議
OSI分層 (7層):物理層、資料鏈路層、網路層、傳輸層、會話層、表示層、應用層,
TCP/IP分層(4層):網路介面層、 網際層、運輸層、 應用層,
五層協議 (5層):物理層、資料鏈路層、網路層、運輸層、 應用層,
每一層的協議如下:
物理層:RJ45、CLOCK、IEEE802.3 (中繼器,集線器,網關)
資料鏈路:PPP、FR、HDLC、VLAN、MAC (網橋,交換機)
網路層:IP、ICMP、ARP、RARP、OSPF、IPX、RIP、IGRP、 (路由器)
傳輸層:TCP、UDP、SPX
會話層:NFS、SQL、NETBIOS、RPC
表示層:JPEG、MPEG、ASII
應用層:FTP、DNS、Telnet、SMTP、HTTP、WWW、NFS
每層作用:
物理層:通過媒介傳輸位元,確定機械及電氣規范(位元Bit)
資料鏈路層:將位元組裝成幀和點到點的傳遞(幀Frame)
網路層:負責資料包從源到宿的傳遞和網際互連(包PackeT)
傳輸層:提供端到端的可靠報文傳遞和錯誤恢復(段Segment)
會話層:建立、管理和終止會話(會話協議資料單元SPDU)
表示層:對資料進行翻譯、加密和壓縮(表示協議資料單元PPDU)
應用層:允許訪問OSI環境的手段(應用協議資料單元APDU)
🥽IP地址的分類
A類地址:以0開頭, 第一個位元組范圍:0~127(1.0.0.0 - 126.255.255.255);
B類地址:以10開頭, 第一個位元組范圍:128~191(128.0.0.0 - 191.255.255.255);
C類地址:以110開頭, 第一個位元組范圍:192~223(192.0.0.0 - 223.255.255.255);
10.0.0.0—10.255.255.255, 172.16.0.0—172.31.255.255, 192.168.0.0—192.168.255.255,(Internet上保留地址用于內部)
🥼各種協議
ICMP協議:因特網控制報文協議,它是TCP/IP協議族的一個子協議,用于在IP主機、路由器之間傳遞控制訊息,
TFTP協議:是TCP/IP協議族中的一個用來在客戶機與服務器之間進行簡單檔案傳輸的協議,提供不復雜、開銷不大的檔案傳輸服務,
HTTP協議:超文本傳輸協議,是一個屬于應用層的面向物件的協議,由于其簡捷、快速的方式,適用于分布式超媒體資訊系統,
DHCP協議:動態主機配置協議,是一種讓系統得以連接到網路上,并獲取所需要的配置引數手段,
NAT協議:網路地址轉換屬接入廣域網(WAN)技術,是一種將私有(保留)地址轉化為合法IP地址的轉換技術,
DHCP協議:一個局域網的網路協議,使用UDP協議作業,用途:給內部網路或網路服務供應商自動分配IP地址,給用戶或者內部網路管理員作為對所有計算機作中央管理的手段,
RARP協議 :RARP是逆地址決議協議,作用是完成硬體地址到IP地址的映射,主要用于無盤作業站,因為給無盤作業站配置的IP地址不能保存,作業流程:在網路中配置一臺RARP服務器,里面保存著IP地址和MAC地址的映射關系,當無盤作業站啟動后,就封裝一個RARP資料包,里面有其MAC地址,然后廣播到網路上去,當服務器收到請求包后,就查找對應的MAC地址的IP地址裝入回應報文中發回給請求者,因為需要廣播請求報文,因此RARP只能用于具有廣播能力的網路,
👓TCP三次握手和四次揮手的全程序
三次握手:
第一次握手:客戶端發送syn包(syn=x)到服務器,并進入SYN_SEND狀態,等待服務器確認;
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=x+1),同時自己也發送一個SYN包(syn=y),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=y+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手,
握手程序中傳送的包里不包含資料,三次握手完畢后,客戶端與服務器才正式開始傳送資料,理想狀態下,TCP連接一旦建立,在通信雙方中的任何一方主動關閉連接之前,TCP 連接都將被一直保持下去,
四次揮手:
與建立連接的“三次握手”類似,斷開一個TCP連接則需要“四次揮手”,
第一次揮手:主動關閉方發送一個FIN,用來關閉主動方到被動關閉方的資料傳送,也就是主動關閉方告訴被動關閉方:我已經不 會再給你發資料了(當然,在fin包之前發送出去的資料,如果沒有收到對應的ack確認報文,主動關閉方依然會重發這些資料),但是,此時主動關閉方還可 以接受資料,
第二次揮手:被動關閉方收到FIN包后,發送一個ACK給對方,確認序號為收到序號+1(與SYN相同,一個FIN占用一個序號),
第三次揮手:被動關閉方發送一個FIN,用來關閉被動關閉方到主動關閉方的資料傳送,也就是告訴主動關閉方,我的資料也發送完了,不會再給你發資料了,
第四次揮手:主動關閉方收到FIN后,發送一個ACK給被動關閉方,確認序號為收到序號+1,至此,完成四次揮手,
🎄MQ訊息佇列
博主用的較多的是RabbitMq 可見此文:訊息佇列—RabbitMQ深入研究(含Springboot+RabbitMQ整合)
🧵MQ應用(異步解耦削峰)
🎗異步提速

🎗解耦

🎗削峰填谷

🎊MQ選型

🎁MQ訊息佇列相關資料分享
鏈接:MQ相關資料
提取碼:u11g
🎄Redis
👟快取擊穿、快取穿透、快取雪崩
三張圖帶你吃透快取擊穿、雪崩、穿透(超詳細)
🏀使用Redis做快取的優點
高性能:
假如用戶第一次訪問資料庫中的某些資料,這個程序會比較慢,因為是從硬碟上讀取的,將該用戶訪問的資料存在數快取中,這樣下一次再訪問這些資料的時候就可以直接從快取中獲取了,操作快取就是直接操作記憶體,所以速度相當快,如果資料庫中的對應資料改變的之后,同步改變快取中相應的資料即可

高并發:
直接操作快取能夠承受的請求是遠遠大于直接訪問資料庫的,所以我們可以考慮把資料庫中的部分資料轉移到快取中去,這樣用戶的一部分請求會直接到快取這里而不用經過資料庫

?redis 常見資料結構以及使用場景分析
- String
常用命令:
set,get,decr,incr,mget等,
String資料結構是簡單的key-value型別,value其實不僅可以是String,也可以是數字, 常規key-value快取應用;
2.Hash
常用命令:
hget,hset,hgetall等,
Hash 是一個 string 型別的 field 和 value 的映射表,hash 特別適合用于存盤物件,后續操作的時候,你可以直接僅僅修改這個物件中的某個欄位的值, 比如我們可以Hash資料結構來存盤用戶資訊,商品資訊等等,比如下面我就用 hash 型別存放了我本人的一些資訊:
key=JavaUser293847
value={
"id": 1,
"name": "SnailClimb",
"age": 22,
"location": "Wuhan, Hubei"
}
3.List
常用命令:
lpush,rpush,lpop,rpop,lrange等
list 就是鏈表,Redis list 的應用場景非常多,也是Redis最重要的資料結構之一,比如微博的關注串列,粉絲串列,訊息串列等功能都可以用Redis的 list 結構來實作,
Redis list 的實作為一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的記憶體開銷,
另外可以通過 lrange 命令,就是從某個元素開始讀取多少個元素,可以基于 list 實作分頁查詢,這個很棒的一個功能,基于 redis 實作簡單的高性能分頁,可以做類似微博那種下拉不斷分頁的東西(一頁一頁的往下走),性能高,
4.Set
常用命令:
sadd,spop,smembers,sunion等
set 對外提供的功能與list類似是一個串列的功能,特殊之處在于 set 是可以自動排重的,
當你需要存盤一個串列資料,又不希望出現重復資料時,set是一個很好的選擇,并且set提供了判斷某個成員是否在一個set集合內的重要介面,這個也是list所不能提供的,可以基于 set 輕易實作交集、并集、差集的操作,
5.Sorted Set
常用命令:
zadd,zrange,zrem,zcard等
和set相比,sorted set增加了一個權重引數score,使得集合中的元素能夠按score進行有序排列,
舉例: 在直播系統中,實時排行資訊包含直播間在線用戶串列,各種禮物排行榜,彈幕訊息(可以理解為按訊息維度的訊息排行榜)等資訊,適合使用 Redis 中的 SortedSet 結構進行存盤,
🥅Redis主從復制與哨兵機制
Redis|主從復制與哨兵機制
🎄Nginx
🎁Nginx基本配置詳解
nginx基本配置與引數說明
##
# 全域配置
##
user www-data; ## 配置 worker 行程的用戶和組
worker_processes auto; ## 配置 worker 行程啟動的數量,建議配置為 CPU 核心數
error_log logs/error.log; ## 全域錯誤日志
pid /run/nginx.pid; ## 設定記錄主行程 ID 的檔案
worker_rlimit_nofile 8192; ## 配置一個作業行程能夠接受并發連接的最大數
##
# 作業模式及連接數上限
##
events {
# epoll 是多路復用 IO(I/O Multiplexing)中的一種方式,
# 僅用于 Linux 2.6 以上內核,可以大大提高 Nginx 性能
use epoll
# 單個后臺 worker process 行程的最大并發鏈接數
# 并發總數 max_clients = worker_professes * worker_connections
worker_connections 4096; ## Defaule: 1024
# multi_accept on; ## 指明 worker 行程立刻接受新的連接
}
##
# http 模塊
##
http {
##
# Basic Settings
##
#sendfile 指令指定 nginx 是否呼叫 sendfile 函式(zero copy 方式)來輸出檔案,
#對于普通應用,必須設為 on,
#如果用來進行下載等應用磁盤 IO 重負載應用,可設定為 off,
#以平衡磁盤與網路 I/O 處理速度,降低系統的 uptime.
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65; ## 連接超時時間
types_hash_max_size 2048; ## 指定散列型別表的最大大小
# server_tokens off;
# server_names_hash_bucket_size 64; # this seems to be required for some vhosts
# server_name_in_redirect off;
include /etc/nginx/mime.types; ## 設定 mine 型別
default_type application/octet-stream;
# 設定請求緩沖
client_header_buffer_size 128k; # 指定客戶端請求頭快取大小,當請求頭大于 1KB 時會用到該項
large_client_header_buffers 4 128k; # 最大數量和最大客戶端請求頭的大小
##
# SSL Settings
##
# 啟用所有協議,禁用已廢棄的不安全的SSL 2 和SSL 3
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
# 讓服務器選擇要使用的演算法套件
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log; ## 訪問日志
error_log /var/log/nginx/error.log; ## 錯誤日志
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf; # 這個檔案夾默認是空的
include /etc/nginx/sites-enabled/*; # 開啟的 Server 服務配置
}
##
# mail 模塊
##
mail {
# See sample authentication script at:
# http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
# auth_http localhost/auth.php;
# pop3_capabilities "TOP" "USER";
# imap_capabilities "IMAP4rev1" "UIDPLUS";
server {
listen localhost:110;
protocol pop3;
proxy on;
}
server {
listen localhost:143;
protocol imap;
proxy on;
}
}
🪁Nginx常用命令
## 查看 Nginx 程式檔案目錄:/usr/sbin/nginx
$ ps -ef | grep nginx
## 查看 nginx.conf 組態檔目錄:/etc/nginx/nginx.conf
$ nginx -t
$ vim /etc/nginx/nginx.conf
## 組態檔目錄:/etc/nginx
## 虛擬主機組態檔目錄:/etc/nginx/sites-available/
## 虛擬主機檔案夾目錄:/var/www/,詳情可在 /etc/nginx/sites-available/ 中配置
## 默認網頁檔案目錄:/usr/share/nginx/html
## 測驗組態檔,只檢查組態檔是否存在語法錯誤
$ nginx -t -c <path-to-nginx.conf>
$ sudo nginx -t -c /etc/nginx/nginx.conf
## 啟動 Nginx 服務
$ nginx 安裝目錄 -c <path-to-nginx.conf>
$ sudo /etc/init.d/nginx start
## 停止 Nginx 服務
$ sudo /usr/sbin/nginx -s stop
## 重啟 Nginx
$ sudo /usr/sbin/nginx -s reload # 0.8 版本之后的方法
$ kill -HUP pid # 向 master 行程發送信號從容地重啟 Nginx,即服務不中斷
$ sudo service nginx start
$ sudo service nginx stop
$ sudo service nginx restart
🎨Nginx正向代理
正向代理:內網服務器主動去請求外網的服務的一種行為
光看概念,可能有讀者還是搞不明白:什么叫做“正向”,什么叫做“代理”,我們分別來理解一下這兩個名詞,
正向:相同的或一致的方向
代理:自己做不了的事情或者自己不打算做的事情,委托或依靠別人來完成,
借助解釋,回歸到nginx的概念,正向代理其實就是說客戶端無法主動或者不打算完成主動去向某服務器發起請求,而是委托了nginx代理服務器去向服務器發起請求,并且獲得處理結果,回傳給客戶端,
從下圖可以看出:客戶端向目標服務器發起的請求,是由代理服務器代替它向目標主機發起,得到結果之后,通過代理服務器回傳給客戶端,

正向代理的配置:
server {
#指定DNS服務器IP地址
resolver 114.114.114.114;
#指定代理埠
listen 8080;
location / {
#設定代理服務器的協議和地址(固定不變)
proxy_pass http://$http_host$request_uri;
}
}
這樣就可以做到內網中埠為8080的服務器主動請求到1.2.13.4的主機上,如在Linux下可以:
curl --proxy proxy_server:8080 http://www.taobao.com/
正向代理的關鍵配置:
1.resolver:DNS服務器IP地址
2.listen:主動發起請求的內網服務器埠
3.proxy_pass:代理服務器的協議和地址
🥏Nginx反向代理
反向代理:reverse proxy,是指用代理服務器來接受客戶端發來的請求,然后將請求轉發給內網中的上游服務器,上游服務器處理完之后,把結果通過nginx回傳給客戶端,
舉個栗子:一個服務器的80埠只有一個,而服務器中可能有多個專案,如果A專案是埠是8081,B專案是8082,C專案是8083,假設指向該服務器的域名為www.xxx.com,此時訪問B專案是www.xxx.com:8082,以此類推其它專案的URL也是要加上一個埠號,這樣就很不美觀了,這時我們把80埠給nginx服務器,給每個專案分配一個獨立的子域名,如A專案是a.xxx.com,并且在nginx中設定每個專案的轉發配置,然后對所有專案的訪問都由nginx服務器接受,然后根據配置轉發給不同的服務器處理,具體流程如下:

反向代理配置:
server {
#監聽埠
listen 80;
#服務器名稱,也就是客戶端訪問的域名地址
server_name a.xxx.com;
#nginx日志輸出檔案
access_log logs/nginx.access.log main;
#nginx錯誤日志輸出檔案
error_log logs/nginx.error.log;
root html;
index index.html index.htm index.php;
location / {
#被代理服務器的地址
proxy_pass http://localhost:8081;
#對發送給客戶端的URL進行修改的操作
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_max_temp_file_size 0;
}
}
這樣就可以通過a.xxx.com來訪問a專案對應的網站了,而不需要帶上難看的埠號,
反向代理的配置關鍵點是:
1.server_name:代表客戶端向服務器發起請求時輸入的域名
2.proxy_pass:代表源服務器的訪問地址,也就是真正處理請求的服務器(localhost+埠號),
🔮Nginx負載均衡
負載均衡:將服務器接收到的請求按照規則分發的程序,稱為負載均衡,負載均衡是反向代理的一種體現,
可能絕大部分人接觸到的web專案,剛開始時都是一臺服務器就搞定了,但當網站訪問量越來越大時,單臺服務器就扛不住了,這時候需要增加服務器做成集群來分擔流量壓力,而在架設這些服務器時,nginx就充當了接受流量和分流的作用了,當請求到nginx服務器時,nginx就可以根據設定好的負載資訊,把請求分配到不同的服務器,服務器處理完畢后,nginx獲取處理結果回傳給客戶端,這樣,用nginx的反向代理,即可實作了負載均衡,

nginx實作負載均衡有幾種模式:
1.輪詢:每個請求按時間順序逐一分配到不同的后端服務器,也是nginx的默認模式,輪詢模式的配置很簡單,只需要把服務器串列加入到upstream模塊中即可,
下面的配置是指:負載中有三臺服務器,當請求到達時,nginx按照時間順序把請求分配給三臺服務器處理,
2.ip_hash:每個請求按訪問IP的hash結果分配,同一個IP客戶端固定訪問一個后端服務器,可以保證來自同一ip的請求被打到固定的機器上,可以解決session問題,
下面的配置是指:負載中有三臺服務器,當請求到達時,nginx優先按照ip_hash的結果進行分配,也就是同一個IP的請求固定在某一臺服務器上,其它則按時間順序把請求分配給三臺服務器處理,
3.url_hash:按訪問url的hash結果來分配請求,相同的url固定轉發到同一個后端服務器處理,
而在每一種模式中,每一臺服務器后面的可以攜帶的引數有:
1.down: 當前服務器暫不參與負載
2.weight: 權重,值越大,服務器的負載量越大,
3.max_fails:允許請求失敗的次數,默認為1,
4.fail_timeout:max_fails次失敗后暫停的時間,
5.backup:備份機, 只有其它所有的非backup機器down或者忙時才會請求backup機器,
最后給大家分享一些資料 需要可聯系博主:

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/298358.html
標籤:java

