hashMap的底層是數組+鏈表的結構, 使用鍵值對存儲數據, 初始化的容量是 16個, 當數組已用容量超過實際容量超過3/4時, 會進行擴容, 每次擴容要 是2的倍數, 當數組上的鏈表深度大於8時, 鏈表會轉化爲紅黑樹(提升查詢 效率); 具體實現: hash算法和尋址算法: put(key, value)時, 對key進行hash((h = key.hashCode())^(h >>> 16)), key取hash值並右移16位, 和原hash值 取異或(不相同取1, 相同取0), 尋址的公式(n -1)&hash(這裏指從新計算 的hash), 之因此使用&運算(同爲1時爲1, 不然爲0), 而不是取模, 由於& 效率更高, 上面之因此使用高16位和低16位異或, 能夠有效減小hash碰撞 (有的hash值可能高16位不一樣, 低16位相同, 這樣若是和n-1進行&運算, 相 當於高16位沒有參與到運算中); hash碰撞解決: 主要是經過鏈表和紅黑樹的相互轉化; 擴容機制: 擴容就會涉及到rehash, 數據量大的時候比較消耗性能
hashmap存在線程安全問題, 因此會出現concurrentHashMap來解決; 兩種狀況, 1. 兩個線程同時向map中put元素, 因爲鏈表指向的轉化, 致使數據丟失; 2. 一個線程put操做, 一個線程get操做, 可能會get到null值; hashtable解決方式效率過低: 使用sychronized鎖住整個數組; concurrentHashMap解決的原理: 使用CAS, compare and swap;
private final Node<K,V>[] initTable() { Node<K,V>[] tab; int sc; while ((tab = table) == null || tab.length == 0) { if ((sc = sizeCtl) < 0) Thread.yield(); // lost initialization race; just spin //此處使用cas保證初識化時線程安全 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { if ((tab = table) == null || tab.length == 0) { int n = (sc > 0) ? sc : DEFAULT_CAPACITY; @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; table = tab = nt; sc = n - (n >>> 2); } } finally { sizeCtl = sc; } break; } } return tab; }
put方法的使用javascript
初始化數組完成, 可是沒有值時 final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode()); int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { //此處使用cas if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else { ... 省略代碼 } addCount(1L, binCount); return null; } 初始化數組完成, 而且有值時, 使用加鎖, 這個鎖其實只是加在一個鏈路上; synchronized (f) { 省略代碼... }
擴容時html
第一個線程來擴容的時候, 會將一個頭部節點修改成-1, 表示正在擴容, 每次從尾部開始分配給一個線程16個數組節點, 若是後續的線程發現map正在擴容, 則會幫助以前的線程進行rehash, 他會分配上一個線程前面的16個節點進行操做;
線程池的工做原理
線程池的拒絕策略
ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。 ThreadPoolExecutor.DiscardPolicy:丟棄任務,可是不拋出異常。 ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,而後從新提交被拒絕的任務 ThreadPoolExecutor.CallerRunsPolicy:由調用線程(提交任務的線程)處理該任務
線程池使用無界阻塞隊列會發生什麼?
一個場景, 當進行遠程調用發生阻塞的時候, 致使阻塞隊列中的任務 越積越多, 最後出現OOM的問題;
使用有界隊列, 當隊列和線程都滿了之後如何執行後續的任務?
能夠自定義拒絕策略, 將任務持久化到磁盤, 當隊列中的任務執行完以後, 到磁盤中獲取, 在執行;
機器宕機以後, 隊列中的任務怎麼辦?
此時能夠採用任務提交以前將他保存到數據庫, 並定義狀態(未提交), 當提交完成並操做完成以後, 修改狀態(已提交); 在程序啓動的時候, 定義一個後臺進程, 掃描數據庫, 將未提交的任 務進行提交;
有序性
程序的操做是按順序進行的, 這裏主要涉及到jvm和cpu的指令重排問題;
原子性
一個線程更新操做共享數據的時候, 不予許其餘線程進行更新操做;
可見性
一個線程對一個共享數據的操做, 其餘的線程可以看到, 內存層面就是, 一個線程更新完一個數據以後, 會將數據刷新進主內存, 另外一個線程讀數據的時候, 強制他從主內存中讀取;
volatile關鍵字的原理
主要的原理就是上面的可見性問題, volatile主要是將線程對數據的更新刷回主內存中, 並將其餘線程中使用這個數據的線程內存失效, 那麼當下次其餘線程讀取這個數據的時候, 從主內存讀取;
物理層
簡單來講就是無線信號, 網線和海底光纜之類的, 讓電腦之間能夠在物理上進行鏈接;
數據鏈路層
這一層是架構在物理層之上, 就是一臺電腦發送給另外一臺電腦的數據, 可是存在問題, 一臺電腦發送的數據是一連串的信號, 另外一臺電腦並 不知道哪些是發給本身的, 以及發給本身的這些數據要如何解析, 因 此誕生了一些協議, 這裏面好比以太網, 互聯網: 1. 以太網: 一組電信號就是一個數據包, 就是一幀, 每幀分爲兩個 部分, head和data, head中是些描述性信息, 數據從哪來到哪去, 以及數據的信息, data就是真實的數據, 這些數據包須要從一臺電腦 的網卡發出去, 由另外一臺電腦的網卡接收, 以太網規定, 每一個網卡都 要有一個惟一的標識, 就是mac地址, 並且網卡的數據包發送是以廣播 的方式發送給局域網內的全部電腦的, 接收方就是根據head裏面的消 息判斷這條消息是不是發給本身的;
網絡層
數據層解決了數據包發送和解析的問題, 可是, 不能區分網絡的分區, 就是那些電腦能夠互相聯通進行通訊, 網絡層裏面有IP協議, 定義了 電腦的ip地址, 每臺電腦的惟一標識, 而判斷電腦是否是一個子網的, 還須要一個子網掩碼, 將ip地址和子網掩碼進行二進制運算, 而後通 過這個二進制數來判斷哪些電腦是屬於一個子網的; 當兩個電腦不在一個局域網中時, 如何通訊鏈接, 這時候會用到路由 器, 路由器上能夠註冊電腦的IP地址和網卡地址;
傳輸層
上面解決了電腦和電腦之間的鏈接, 數據的傳輸和識別問題, 可是每一個電腦上有不一樣的應用程序, 而一臺電腦又只有一個網卡, 因此又引入端口號的概念, 每一個應用程序監聽不一樣的端口號, 進行消息接收, 這一層創建了TCP協議(如何創建鏈接, 如何發送和讀取消息, 就是tcp協議規定的);
應用層
拿到數據到底該怎麼辦, 怎麼解析, 就是這個層乾的事情, 具體的包括http協議, ftp協議...
1. 先走DNS解析器, 將地址解析成ip地址; 2. 走應用層的http協議, 這時候會根據http的協議對數據進行打包; 3. 包裝傳輸層, 根據tcp協議, 打包tcp協議的數據包, 包括那臺機器 的端口信息; 4. 包裝網絡層: 根據ip協議包裝數據包, 進行網絡傳輸; 5. 包裝數據鏈路層, 根據一臺網協議封裝數據包, 定義數據包接受者 ip和發送者ip等; 6. 經過路由, 交換機等對數據進行發送; 7. 接受者就根據各層的協議對數據進行層層解包;
三次握手
第一次: 客戶端給服務端發送請求,SYN=1, ACK=0, seq=x; 第二次: 服務端給客戶端響應, ack=x+1, SYN=1, ACK=1, seq=y; 第三次: 客戶端給服務端響應, ack=y+1, ACK=1, seq=x+1; 爲何是三次: 主要是爲了相互確認, 第一次的請求時客戶端要求服 務端創建鏈接, 第二次的請求是服務端給了客戶端響應, 客戶端確認 了本身能夠接收到服務端響應, 但此時服務端並不能肯定客戶端是否 要繼續鏈接或者可否接收到客戶端的響應, 因此須要第三次來作確認;
四次揮手
第一次揮手:客戶端向服務器發送一個FIN報文段,將設置seq爲100 和ack爲120,;此時,客戶端進入 FIN\_WAIT\_1狀態,這表示客戶端 沒有數據要發送服務器了,請求關閉鏈接; 第二次揮手:服務器收到了客戶端發送的FIN報文段,向客戶端回一個 ACK報文段,ack設置爲101,seq設置爲120;服務器進入了 CLOSE\_WAIT狀態,客戶端收到服務器返回的ACK報文後,進入 FIN\_WAIT\_2狀態; 第三次揮手:服務器會觀察本身是否還有數據沒有發送給客戶端,如 果有,先把數據發送給客戶端,再發送FIN報文;若是沒有,那麼服務 器直接發送FIN報文給客戶端。請求關閉鏈接,同時服務器進入 LAST\_ACK狀態; 第四次揮手:客戶端收到服務器發送的FIN報文段,向服務器發送ACK 報文段,將seq設置爲101,將ack設置爲121,而後客戶端進入 TIME\_WAIT狀態;服務器收到客戶端的ACK報文段之後,就關閉鏈接; 此時,客戶端等待2MSL後依然沒有收到回覆,則證實Server端已正常 關閉,客戶端也能夠關閉鏈接了;
http請求主要分爲請求頭和請求體, 請求頭中有狀態碼, 請求方式, 請求地址等信息, 請求體重主要是數據, 主要的過程是上面瀏覽器請求過程;
主要是經過#{}和${}的區別來作到的, #{}會將括號中的內容加上雙引號後加在sql語句中, 而${}則是將內容直接加在sql語句中, 前者會對sql語句進行預編譯, 後者不會, 所以使用#{}不會存在sql注入問題, ${}則會出現sql注入問題, 若是非要使用, 須要進行手動的參數校驗;
CSRF攻擊 跨站點請求僞造攻擊, 主要是獲取客戶端的jsessionid, 去模擬客戶端請求服務端; 解決方法: 返回cookie的時候, 將設置屬性爲HttpOnly; XSS攻擊 和上面比較相似, 只是xss攻擊使用的是將html+javascript腳本加載到客戶端電腦上執行, 一般是使用惡意連接或者在網站評論內容中書寫腳本; 解決方法: 返回的cookie, 設置HttpOnly屬性, 網站的由客戶決定並保存到數據庫的內容進行特殊字符處理;
即服務端可以承受的併發量不高, 黑客使用多臺或者高性能服務器, 發送大量請求給服務端, 直接讓服務癱瘓; 當服務端承受併發量很高時, 也會存在一種狀況, 黑客會空氣其餘人的電腦, 對服務端發起大量攻擊;