java后端春招知識寶典

Java面試相關知識點

這篇筆記主要用來記錄一個大牛的java相關知識點

一、計算機網絡

網絡上的知識,基本上以理論為主

1. OSI七層協(xié)議

物理層:用于信號傳輸,進行數模轉換/模數轉換,基本數據是:比特,網卡在這一層工作

數據鏈路層:定義格式化數據進行傳輸和為物理介質的控制,提供數據校驗,基礎數據是:幀,交換機在這一層工作

網絡層:將網絡地址翻譯成物理地址并且決定傳輸的路徑,基本數據是:數據包,路由器在這里工作(TCP/IP協(xié)議就在此層

傳輸層:解決數據傳輸的穩(wěn)定性,必要時將數據分割(TCP/UDP協(xié)議就工作在此層

會話層:解決不同操作系統(tǒng)的通訊語法的問題

應用層:HTTP協(xié)議就工作在此層

OSI

2. TCP/IP協(xié)議族的四層協(xié)議

OSI七層協(xié)議知識一個概念性的框架,TCP/IP協(xié)議族是OSI七層模型的實現(xiàn),但是它并沒有完全按照OSI七層模型進行實現(xiàn)。

TCP/IP協(xié)議族

3. TCP和三次握手

IP協(xié)議是無鏈接的通訊協(xié)議,但是IP協(xié)議沒有控制包的順序和可靠性,因此需要上層的TCP協(xié)議進行控制。

TCP將上層的數據報文分割成適當大小的包交給網絡層進行發(fā)送,為了保證順序和可靠,TCP會對每個包進行編號,并且需要接收方發(fā)送ACK進行確認來確保不會丟包

3.1 TCP報文頭

TCP報文頭

3.2 三次握手

三次握手

相關的標識位如下

  • ACK:缺人需要表示1:確認有效 0:不含確認信息
  • SYN:同步需要 SYN=1且ACK=0表示沒有捎帶確認的信息 SYN=1且ACK=1表示會稍帶確認上一條數據
  • FIN:結束標志

握手過程

  • C端發(fā)出SYN包(seq = x)請求建立鏈接,進入SYN-SENT狀態(tài)
  • S端收到剛剛的SYN包,發(fā)出確認包(ack = x+1),并且自己也發(fā)出一個SYN包(seq = y),請求確認,進入SYN-REVD狀態(tài)
  • C端收到S端的確認包和SYN包,再發(fā)送一個確認包(ack = y+1),這是進入ESTAB-LISHED狀態(tài),S端收到后也進入ESTAB-LISHED狀態(tài),開始傳輸數據

注意:第二次握手時S端收到首次發(fā)送C端的SYN包,發(fā)送確認包和自己的SYN包時如果S端沒有收到ACK確認時,S端會不斷重試,linux默認情況下會等待63秒

4.四次揮手

四次揮手
  • 揮手過程
    • C端發(fā)送FIN包(seq = u)請求關閉鏈接,進入FIN-WAIT1狀態(tài)
    • S端收到FIN包,并且發(fā)送確認包(ack = u+1),進入CLOSE-WAIT狀態(tài),C端收到后進入FIN-WAIT2狀態(tài),等待S端發(fā)送最后的數據
    • S端完成最后數據的發(fā)送后,發(fā)送FIN包(seq = w),并且再次確認(ack = u+1),發(fā)送后S端進入LAST-ACL狀態(tài)
    • C端收到S端發(fā)送的FIN包后,發(fā)送確認包(ack = w+1),自己進入TIME-WAIT狀態(tài),經過兩個延遲時間后正式關閉鏈接,S端收到后也會直接關閉鏈接
  • 為啥需要有TIME-WAIT狀態(tài)
    • 保證S端收到最后發(fā)送的ACK包,并且保證不會和后面的鏈接混淆
  • 頻繁出現(xiàn)大量CLOST-WAIT的原因
    • 在客戶端應用層關閉Socket后,服務器忙于讀寫沒有進一步發(fā)送第二個FIN包導致大量CLOST-WAIT積壓

5.UDP和TCP的區(qū)別

5.1 UDP的特點

  • UDP時非鏈接的,不支持錯誤重傳,華東窗口等,只會盡可能快的傳入網絡中
  • 不用維護鏈接狀態(tài),可以同時想多個客戶端傳輸一樣的信息
  • UDP不保證可靠,保證速度
  • UDP時面向報文的,不具備有序性,所有的事情會交給上層應用處理

6. TCP的滑動窗口

TCP的滑動窗口是為了解決TCP的流浪控制和亂序重排的問題,未來研究這個問題,我們先知道下面這兩個概念

  • RTT:發(fā)出需要確認的包到收到確認包到時間

  • RTO:重傳時間間隔(確認包的超時時間)

6.1 滑動窗口過程

滑動窗口發(fā)送

每次收到接受放確認的連續(xù)包后,窗口會進行滑動,框入更多的未發(fā)送數據

7. HTTP協(xié)議

主要特點:

  • 支持客戶/服務器模式
  • 簡單快速,只要發(fā)送請求方法和路徑即可
  • 無鏈接的,傳輸完數據就會斷開鏈接
  • 無狀態(tài)的,兩次鏈接的狀體對方不可感知

7.1 HTTP請求/響應結構

  • 請求報文結構如下
HTTP請求報文
  • 響應報文結構如下
HTTP響應報文

7.2 Cookie和Session

  • Cookie:服務器要求客戶端保存的信息
  • Session:根據客戶端發(fā)送的session-id來在服務器中檢索session信息
    • 使用cookie保存session-id
    • 使用url參數保存session-id

7.3 HTTPS

加入ssl層,加入安全套接層,提供安全和數據完整性的協(xié)議,通過身份驗證和數據加加密的完整性

  • 加密的方式
    • 對稱加密:加密和解密使用同一個密鑰,性能較高
    • 非對稱加密:加密使用公鑰,解密使用私鑰
    • 哈希算法
    • 數字簽名:證明某個消息是某人發(fā)送的,沒有被篡改的
  • HTTPS流程
    • 瀏覽器發(fā)送支持的加密算法發(fā)送服務器
    • 服務器選擇一套瀏覽器支持的算法,發(fā)送相關證書給瀏覽器
    • 瀏覽器驗證證書合法性,并且使用證書中的公鑰加密傳輸用的對稱加密對稱加密的密碼,發(fā)給服務器
    • 服務器使用私鑰解密瀏覽器發(fā)送的對稱加密密碼

二、 數據庫

1. 數據庫設計和模塊劃分

如果我沒來設計數據庫,需要這樣幾部分

  • 存儲模塊:將數據存入磁盤
  • 存儲管理:將物理數據組織和表述
    • 優(yōu)化存儲效能?一次性讀取多行減少IO次數
  • 緩存機制
  • SQL接信息模塊
  • 日志管理:管理操作日志
  • 用戶權限模塊
  • 異常容災難機制
  • 索引模塊
  • 鎖模塊

2. 索引模塊

為什么要使用索引?

相當于字典的目錄,通過索引可以大幅提升查詢速度

2.1 索引的數據結構

  • 二叉查找樹

    二叉樹.png

    二叉查找樹:對于樹中的任意節(jié)點,比左子樹的值大,比右子樹的值小??

    平衡二叉樹:根結點的左右子樹高度相差不超過1

    查找效率:O(logn),但是最多兩個孩子的設定會大大增加樹的深度,增加查找次數

  • B

    3階段B樹

    B樹可以通過策略保證新增節(jié)點不會變成線性的(B樹節(jié)點的分裂,合并)

  • B+數(MySQL使用這種數據結構作為索引)

    B+樹

    B+樹的數據全部存儲在葉子結點上,非葉子結點只用于索引,葉子結點的鏈表結構也便于做數據統(tǒng)計和全盤掃描

  • Hash結構

    相當于Java的HashMap的索引方式,只需要O(1)的時間

    但是無法排序、范圍操作,可能同一個哈希插入太多值造成效率不如哈希

2.2. 密集索引和稀疏索引的區(qū)別

  • 密集索引:每一個搜索碼都對應一個搜索值(只能有一個,相當于組件,相當于葉子結點保存了所有的信息)

  • 稀疏索引:只為了索引某些相才建立的索引(葉子結點里只保存了數據的存儲地址)

    在MySQL的MyISAM引擎里所有的索引都是稀疏索引

    MySQL的InnoDB引擎的索引有且僅有一個密集索引(主鍵 ==> 第一個唯一非空索引 ==> 生成隱藏的索引 )

    因此在使用InnoDB的稀疏索引時會先找到對應的密集索引,再通過密集索引找到數據(兩次索引)

2.3 SQL衍生問題

  1. 如何優(yōu)化慢SQL

    • 根據慢日志定位問題:修改mysql中的變量來開啟慢日志(下面的操作在my.ini更改后就會永久保存)

      shwo variables like 'query'        #找到和查詢有關的全局變量
      set global show_query_log = no #開啟慢日志
      set global long_query_time = 1 #設置慢查詢時間為1秒
      show status like '%slow_queries' #查看慢sql個數
      
    • 利用explan來分析select查詢,用法就是在select前面加上explan就好了

      • 關注出現(xiàn)的type字段,如果是“index”或者"all"就表明查詢是通過全表掃描完成的,需要優(yōu)化

      • 關注extar字段

        [圖片上傳失敗...(image-52b86-1584189158882)]

    • 修改SQL讓其盡量使用索引或者添加索引

  2. 聯(lián)合索引最左原則

    • 最左匹配原則:A、B字段組成聯(lián)合索引,當where A = xxxwhere A = xxx and B = xxx時,會使用這個聯(lián)合索引,但是當where B = XXX時,就不會使用這個索引了

    • 原因:聯(lián)合索引建立時,會先對第一個字段進行排序,在這個順序的基礎上再對第二個字段進行陪許,因此第二個字段是局部有序的

      最左匹配
  1. 索引是越多越好嗎

    索引并不是越多越好。索引也需要維護并且占用空間。

3. 鎖模塊

3.1 MyISAM和InnoDB鎖的區(qū)別

  • MyISAM默認表級鎖,不支持行級鎖

  • InnoDB默認行級鎖:在使用索引時使用行級鎖,不使用索引時使用表鎖


    兩種引擎適合的場景:

    MyISAM:頻繁執(zhí)行全表count、查詢操作較多(只有表鎖,效率低),寫的操作較少、不需要事務

    InnoBD:增刪改查都很頻繁的系統(tǒng)、穩(wěn)定性要求較高

3.2 鎖的分類

  • 鎖的粒度劃分表級鎖,行級鎖,頁級鎖
  • 鎖的級別劃分讀鎖 = 共享鎖,可以反復加上 寫鎖=獨占鎖,只能加上一次,且不能和讀鎖共存
  • 加鎖的方式劃分自動鎖顯式鎖
  • 按操作劃分DML鎖:對數據進行加鎖 DDL鎖:對表結構枷鎖
  • 使用方式劃分悲觀鎖:先取鎖,再訪問,樂觀鎖:只有提交更新時才會檢測是否沖突

3.3 數據庫事務的四大特性

  • A:原子性:對于事務中的操作,要么全執(zhí)行,要么全不執(zhí)行(回滾)
  • C:一致性:數據庫中的數據在事務執(zhí)行前后應保存一致
  • I:隔離性:多個事務的并發(fā)執(zhí)行不會相互影響(隔離等級)
  • D:持久性:事務一旦提交,對系統(tǒng)的修改應該永久保存

3.4 事務并發(fā)訪問產生的問題和隔離級別

  • 事務并發(fā)訪問可能引起的問題

    • 丟失修改:一個事務的更新覆蓋了另一個事務的更新(在任何隔離級別(讀未提交)下都可以防止)

      丟失修改
    • 臟讀:讀取到還未提交的事務修改的數據(可以在讀已提交級別防止,該級別不允許讀未提交的數據)

    • 不可重復讀:事務A多次讀取同一個數據,事務B在A讀取時作出更新并提交導致A在多次讀取時內容不一致(可以在可重復讀級別防止,該級別確保別的事物的提交不會更改本次事務查詢的結果)

    • 幻讀:事務A多次讀取若干行,事務B進行插入或者刪除操作導致事務A多次讀取的數據的數量不一致(可在Serializable級別防止,這個級別會讓事務串行提交)

    事務隔離級別越高,事務的并發(fā)度越低,因此需要根據需求來確定隔離級別


    在MySQL的InnoDB中如何在可重復讀中避免了幻讀的?

    MySQL中存在兩種讀取數據的方式:當前讀和快照讀

    • 當前讀:selete ... lock in share mode、delete、update、insert操作都為當前讀,當前讀會獲取數據的最新版本
    • 快照讀:不加鎖的讀selete為快照讀,在“可重復讀”的隔離級別下,讀到的不是數據的最新版本。在“可重復讀”的級別下,快照讀讀出的都是第一次執(zhí)行這條語句的數據。

4. SQL關鍵語法

假設我們的表結構如下

表例子

4.1 Group By

根據查詢結果進行分組查詢,對每個組的數據進行聚合運算。

例子:查詢所有同學的學號,姓名,選課數,和每個同學成績的總和

selete s.student_id,stu.name,count(s.coures_id),sum(s.score)
    form score s,student stu
    where s.student_id = stu.student_id
    greop by student_id

4.2 Having

通常于Group By一起使用,給Group By 添加限定條件

例子: 查詢所有平均分大于60分的同學的學號,姓名,選課數,和每個同學的平均成績

selete s.student_id, stu.name, count(s.coures_id), avg(s.score)
    form score s,student stu
    where s.student_id = stu.student_id
    greop by student_id
    having avg(s.score) > 60

例子:查詢使用沒有學全所有課程的學號和姓名

selete s.student_id, stu.name
    from score s, student stu
    where s.student_id = stu.student_id
    group by s.student_id
    having count(*) < (
        select count(*) from course
    ) 

三、Redis

  • Redis快的原因
    • Redis是完全基于內存的
    • 數據結構簡單
    • 采用單線程和IO多路復用,不存在上下文切換問題

1 IO多路復用模型

  • Select系統(tǒng)調用:可以監(jiān)控多個IO的文件描述符,只用一個線程就可以完成多個IO的操作

2 Redis數據類型

  • String:最基礎的數據模型,是包括二進制的
  • Hash:String元素組成的字典,可以方便的存入POJO
  • List:String元素組成的隊列,有序可重復
  • Set:String元素組成的集合,無序不重復,集合可以交集并集的操作

3 相關問題

3.1 如何從redis中查詢固定前綴的簡直對

  • 使用keys指令:keys會一次性獲取所有參數,數據過大時可能會使服務器卡頓

  • 使用Scan命令,相當于給返回數據分頁,每次查詢可以從上次結束的地方繼續(xù)查詢,但是可能會獲取到重復的問題,而且給的的count并不會完全符合

    scan [開始行] match [匹配模式] count [個數]

3.2 實現(xiàn)分布式鎖

  • 使用 SETNT [key] [value]來設置一個值,設置成功返回1,失敗返回0且不會被更改,再使用EXPIRE [key] [seconds]來設置過期時間「這種模式并沒有原執(zhí)性,使用set [key] [value] ex [seconds]來保證原子性」

3.3 避免同一時間大量的key過期

過期時間需要添加隨機值

3.4 redis做一步隊列(消息隊列)

用list數據的pop操作和push的操作

  • 使用BLPOP [key] [超時時間]來達到監(jiān)聽的效果
  • 使用pub/sub主題訂閱者模式做消息訂閱的效果,用publish [topic] [msg]來發(fā)布消息,用subscribe [topic]來接受相關的消息,甚至不用創(chuàng)建頻道。但是這個狀態(tài)是無狀態(tài)的,不保證送達,如果有更高的需求,就要使用專業(yè)的消息隊列了

4 Redis持久化方式

4.1 RDB持久化

會在固定的間隔保存一次整個數據庫的全量數據。使用save指令在主線程中進行RDB備份,使用bgsave指令使用fork的子線程完成備份

4.2 AOF持久化

相當于備份操作日志,每一個和寫入有關的操作會被記錄到一個文件中

四、Java相關知識點

1. ClassLoader

1.1 類從編譯到執(zhí)行的過程

  • 編譯器把.java文件編譯成.class字節(jié)碼文件
  • ClassLoader將直接碼讀入內存中,轉換為JVM中的Class對象
  • JVM再使用Class對象將其實例化為對應的對象

1.2 ClassLoader

在JVM中,所有的類對象都是由ClassLoader進行加載的,主要功能是從class文件中的二進制數據流加載類對象,是Java的核心組件

  • ClassLoader的種類

    • BootStrapClassLoader:是java的核心庫,是C++編寫的

    • ExtClassLoader:Java編寫的,用于加載擴展庫

    • APPClassLoader:Java編寫,加載classpath下相關的文件

    • 自己定義的ClassLoader:用于定制化加載,只要傳入的二進制流,就可以進行類的加載(從壓縮包獲取,從網絡中獲取等等)

      package com.libi.classloader;
      
      /**
       * @author :Libi
       * @version :1.0
       * @date :2020-03-05 20:56
       */
      public class MyClassLoader extends ClassLoader {
          private String path;
          private String classLoaderName;
      
          public MyClassLoader(String path, String classLoaderName) {
              this.path = path;
              this.classLoaderName = classLoaderName;
          }
      
          @Override
          protected Class<?> findClass(String name) throws ClassNotFoundException {
              byte[] b = loadClassData(name);
              return defineClass(name, b, 0, b.length);
          }
      
          //從文件中讀出class數據流,我這邊不想寫了
          private byte[] loadClassData(String name) {
              return null;
          }
      
          //如何通過自定義的ClassLoader進行類的加載
          public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
              //創(chuàng)建ClassLoader
              MyClassLoader myClassLoader = new MyClassLoader("path", "name");
              //生成類對象
              Class c = myClassLoader.loadClass("name");
              //生成實例
              Object o = c.newInstance();
          }
      }
      
  • ClassLoader的雙親委派機制

    雙親委派機制

    ClassLoader中,加載類的部分源碼如下

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // 首先檢查這個類是否被加載
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                      //如果沒有被加載那么委托它的父加載器,父加載器為空則委托Bootstrap加載器
                    long t0 = System.nanoTime();
                    try {
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // ClassNotFoundException thrown if class not found
                        // from the non-null parent class loader
                    }
    
                    if (c == null) {
                        // 如果父加載器仍然沒有找到,就自己找
                        long t1 = System.nanoTime();
                        c = findClass(name);
    
                        // this is the defining class loader; record the stats
                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    
  • 為什么要使用雙親委派機制?

    避免一個類被加載多次,節(jié)約內存

1.3 類裝載方式

類裝改過程如下

類裝載過程
  • 隱試加載:new(順帶獲取實例)
  • 顯示加載:
    • ClassLoader.loadClass:還沒有完成鏈接,只完成加載
    • Class.forName:已經完成初始化

2. JVM內存模型

2.1 內存模型劃分的區(qū)域

從線程的角度分類

Q45Q2XM6V~13}$7H5_N2N@8.png
  • 程序計數器:相當于行號指示器,是線程獨立的
  • Java虛擬機棧:相當單個線程于虛擬機執(zhí)行的調用棧,每個棧幀包括局部變量表,關鍵數,方法出口等信息
  • 本地方法棧:調用本地方法是就會使用到本地方法棧

  • 元空間(類似之前的永久代,元空間使用本地內存,但是永久代使用的是java管理的內存)
    • 字符串常量池存(現(xiàn)在代隊內存中)在永久代中可能出現(xiàn)性能問題
    • 簡化GC的操作
    • 永久代的大小指定不再成為問題
  • Java堆

2.2 JVM常見問題

  • JVM性能調優(yōu)參數
    • -Xss:每個線程虛擬機棧的大小
    • -Xms:堆的初始大小
    • -Xmx:堆的最大大小
  • 內存分配策略
    • 靜態(tài)存儲:編譯時就可以確定的空間需求
    • 棧式存儲:編譯時未知,但是但是允許開始前夕可以確定
    • 堆式存儲:運行時都無法確定使用的內存大小,完全動態(tài)分配

3. Java垃圾回收機制

3.1 判定為垃圾的算法

當一個java對象沒有被任何對象引用時就會判定為垃圾

  • 引用計數法

    • 任何對象里有引用計數器,當引用計數為0時,就會判定為垃圾
    • 優(yōu)點:速度很快,不會影響程序、缺點:無法辨別循環(huán)引用的情況
  • 可達性分析

    • 通過對象的引用鏈來判斷是否可以從GC Root進行查找

      • GC Root:虛擬機棧、方法區(qū)的常量和靜態(tài)引用的對象
      • JNI引用的對象
      • 活躍的線程對象
      可讀性分析

3.2 垃圾回收算法

  • 標記清除算法

    • 標記:掃描整個GC root,對存活的對象進行標記。清除:對整個堆進行掃描,清除不可達對象
    標記清除
    • 缺點:可能產生大量碎片空間導致無法利用
  • 復制算法

    • 把整個堆空間分為對象面和空閑面,每次在創(chuàng)建對象在對象面,進行GC時把可用的對象復制到另一邊

      復制算法
    • 解決的空間的碎片化問題,且適用于存活率較低的情況。但是會浪費50的空間,若存活率較高,則會產生大量復制操作,浪費時間。

  • 標記整理算法

    • 標記:掃描整個GC root,對存活的對象進行標記。整理:移動存活的對象,移動到前端,再清除后面的所有的空間
      標記整理
    • 避免了空間的碎片,適用于存活率高的場景

3.3 分代收集算法

對內存進行分類,不同的空間使用不同的手機算法

空間劃分
  • 新生代的劃分

    • Eden區(qū):新創(chuàng)建的對象出生的區(qū)域
    • 兩個Survivor區(qū)

    GC會先去清理Eden區(qū)域,把存活的對象放入Survivor區(qū)域,Survivor區(qū)域使用復制算法進行手機。當Survivor區(qū)域的對象連續(xù)存活一定時間后,將會進度老年代。

    當較大的對象會直接進入老年代

  • 進入老年代的情況

    • 在Survivor區(qū)域存活次數超過一定數字時
    • Survivor區(qū)域裝不下時
    • 新生成的大對象
  • GC的分類

    Minor GC:用于新生代的GC

    Full GC: 用于老年代的GC,通常也會對新生代進行GC

    出發(fā)FullGC的條件

    • 老年代空間不足
    • 永久代空間不足
    • 預計下次MinorGC需要晉升到老年代的大小大于老年代的平均空間
    • 調用System.gc()

3.4 新生代的垃圾收集器

  • 相關概念

    • Stop-the-World:JVM在執(zhí)行GC時會停掉除了GC線程意外的所有線程
    • Safepoint:分析引用關系不會發(fā)生變化的時間點,只有程序執(zhí)行到safepoint,gc才會停掉工作線程。如:循環(huán)跳轉,方法調用等時間點
  • Serial收集器:單線程收集器,必須停掉所有線程

serial
  • ParNew收集器:多線程收集的Serial
parNew
  • Parallel Scavengel收集器:面向吞吐量的GC,會盡可能后臺完成任務
    • 吞吐量 = 運行時間/運行時間+GC停下來的時間

3.5 老年代收集器

  • Serial Old收集器:單線程標記整理
serial old
  • Parallel Old收集器:多線程的標記整理
parOld
  • CMS收集器:可以后臺運行一部分工作,使用標記清除算法

    CMS
CMS
  • G1收集器
    • 是并行和并發(fā)的收集器
    • 可以分代收集

3.6 相關問題

  • Java的finalize()方法有什么用

    這個方法會在將要被收集前調用,作用是讓其有機會再次和GC Root鏈接(給其重生的機會)

  • Java的強引用,弱引用,弱引用,虛引用

    • 強引用:寧可拋出OOM也不會釋放掉還在被強引用的對象
    • 軟引用:當內存不足時,即使對象還在被弱引用,這個對象也會被回收,用于高速緩存,可用于引用隊列
    • 弱引用:每次GC都會被回收,但是因為GC線程優(yōu)先級低,所以也不會被很快回收,可以配合引用隊列
    • 虛引用:對GC來說和沒有引用是一樣的,用于跟蹤對象的生命周期,必須配合引用隊列

4. Java并發(fā)編程

4.1 java線程相關

  • 進程和線程的區(qū)別

    進程是資源分配的最小單位,線程是cpu調度的最小單位。線程并沒有被看作獨立應用,只有獨立的堆棧和局部變量,沒有獨立的地址空間

    線程和進程
  • 如何給run()方法傳遞參數

    • 構造函數傳參
    • 成員變量傳參
    • 回調函數傳參
  • 如何處理線程的返回值

    • 主線程等待:使用Thread.join()的方法阻塞當前線程
    • 通過Callable接口實現(xiàn):
      • 通過FutureTask類,傳入Callable接口的實例,調用call()方法和get()方法,就可以獲取到返回值
      • 通過線程池的submit()方法傳入神經Callable接口的實例,通過返回的Future實例進行獲得返回值
  • 線程的狀態(tài)

    • 新建(new):創(chuàng)建后還沒有啟動的狀態(tài)
    • 運行(Runnable):線程正在運行或者等待cup時間
    • 等待(Waiting):無限期等待,這個線程正在等待被喚醒
    • 有限等待(Time Waiting):一定時間后會被系統(tǒng)自動喚醒
    • 阻塞(Blocked):等改獲取鎖或者等待io
    • 結束(Terminated):已經終止的線程,線程已經執(zhí)行完成,不能再次運行
    線程的狀態(tài)
  • sleep()wait()方法

    • wait方法用于讓出鎖(調用成為鎖的對象的wait方法),可以讓其他等待資源的線程獲得鎖
    • sleep方法只會讓出cpu,鎖的狀態(tài)不會改變
  • notify()notifyAll()

    • notifyAll會喚醒全部等待這個鎖的線程,而notify只會喚醒一個
    • 這兩個方法都不會釋放自己的鎖
  • Thread.yiele()方法

    當前線程愿意讓出cpu使用權,但是是否讓出還是取決于調度器

  • 如何中斷線程

    • 已經被廢棄的stop()方法,這個方法會暴力地停下線程,導致后續(xù)清理的問題
    • 調用interrupt()方法,希望線程結束。
      • 如果這時線程處在阻塞、等待的狀態(tài),那么就會馬上拋出InterruptedException異常
      • 如果線程處于正?;顒拥臓顟B(tài),也只會把它的中斷標志設置為true,線程仍會繼續(xù)運行,至于要如何中斷,需要自己實現(xiàn)

4.2 java中的鎖

  • synchronized的底層對鎖的優(yōu)化

    • 自適應自旋鎖

      • 自旋:大部分情況下,線程只需要很短的時間就可以獲取鎖,這時進行線程狀態(tài)切開銷較大。因此通過讓線程自行忙循環(huán)等待鎖是否
      • 自適應自旋:自旋嘗試的次數不再固定,由上一個獲取到鎖的自旋次數和現(xiàn)在擁有者的狀態(tài)來決定。如果上一次線程是通過自旋獲取到鎖的,而且線程正在運行中,jvm就會認為自旋獲取到鎖的幾率很大,就會增加自旋嘗試次數
    • 鎖消除:JIT編譯時,會對上下文掃描,消除沒有競爭的鎖,避免不必要的開銷

    • 鎖粗化:如果對一個對象頻繁進行加鎖,也會消耗不少的資源,這時就會進行鎖的粗化,擴大鎖的范圍


      鎖的狀態(tài):無鎖 ==> 偏向鎖 ==> 輕量級鎖 ===> (自旋鎖?) ===> 重量級鎖

    • 偏向鎖:大多數情況總是一個線程獲得了鎖,因此一個線程獲得鎖后,鎖就變成了偏向模式,該線程下一次獲取鎖事,不需要再做任何同步的操作就會獲得鎖。但是對于鎖競爭比較激烈的場合并不適用

    • 輕量級鎖:當第二個線程開始爭奪鎖后,鎖的模式就升級成的輕量級模式。如果線程是交替執(zhí)行同步快的時候,輕量級鎖就會啟動

    • 重量級鎖:當有鎖不再是兩個線程交替執(zhí)行而是開始等待時,鎖就會變成重量級鎖

  • synchronized和ReentrantLock(重入鎖)的區(qū)別

    • ReentrantLock是JUC包下的鎖類,實現(xiàn)了多種實用的方法,基于AQS實現(xiàn)的
    • ReentrantLock一定要顯示的釋放鎖,不然會導致該線程一直持有鎖
    • ReentrantLock有比synchronized實現(xiàn)了更多的功能
      • 實現(xiàn)公平鎖(給鎖的順序傾向于給等待時間最長的線程,比非公平鎖性能降低)
      • 可以設置獲取鎖的超時時間
      • 判斷是否有線程或特定線程是否在嘗試獲取該鎖
      • 判斷是否可以獲得該鎖
      • ReentrantLock的newCondition()方法獲取的對象有awit()singal()方法實現(xiàn)等待和通知操作
      • synchronized使用的是操作對象頭中的MarkWord,ReentrantLock使用的是Unsafe類下的park()方法

4.3 JMM如何解決可見性的問題

  • Java內存模型(JMM):只是一個規(guī)范,它規(guī)定了程序中的變量訪問內存的方式

    image.png
    • 所有的變量都存儲在主內存中,所有的內存都可以共享訪問。
    • 線程對變量的操作只能在工作內存中執(zhí)行,線程把要操作的數據拷貝到自己的工作內存中,運行完成后再寫回主內存,不會直接操作主內存
  • 多線程共享內存可能會導致數據一致性的問題,因此JMM會對一些操作進行優(yōu)化

    • 指令重排序:如果兩個操作無法通過happens-before原則推導出來,才可以進行重排序

    • happens-before原則:A操作如果需要對B操作可見,則A,B存在happens-before原則。happens-before原則具體如下圖

      image.png

      如果兩個操作不滿足上述任何一個規(guī)則,那么這兩個操作就沒有執(zhí)行順序上的保障,JVM會對這兩個操作進行重排序。反之,如果A happens-before B,那么A的操作在B上就是可見的

    • volatile關鍵字:

      • 保障被其修飾的共享變量在所有線程中是可見的,對這個變量的任何改動都會立即反應到共享內存中
      • 禁止指令的重排序
      • 并不能保障操作的原子性,因此不能完全保障并發(fā)安全
    • volatile如何保障立即可見

      • 被volatie修飾的變量被改變時,JMM會立即刷新到主內存中
      • 每讀一個volatile變量,JMM會對對應的工作內存置為無效,線程只能從主內存中讀取
    • volatile如何保障禁止重排序

      • 插入內存屏障

4.4 樂觀鎖和CAS操作

  • JUC包下提供了atomic包提供了很多原子性的數據類型和引用
  • 缺點:
    • 如果循環(huán)時間太長,開銷很大
    • 只能保障一個變量的原子性操作
    • ABA的問題

4.5 Java線程池

利用Executors提供的線程池可以滿足不同場景的需求

線程池
  • 使用線程池的原因

    • 減少創(chuàng)建線程的消耗,復用線程
    • 統(tǒng)一管理,調優(yōu)和監(jiān)控
  • JUC的有關線程池的接口

    • Executor:只有executor()的方法,只是用于和任務提交和執(zhí)行細節(jié)解耦

      public interface Executor {
          void execute(Runnable command);
      }
      
    • ExecutorService:擴展了Executer,提供了管理器和生命周期的方法

      public interface ExecutorService extends Executor {
      
          void shutdown();
      
          List<Runnable> shutdownNow();
      
          boolean isShutdown();
      
          boolean isTerminated();
      
          boolean awaitTermination(long timeout, TimeUnit unit)
              throws InterruptedException;
              //后面的代碼略過
      }
      
      
    • ScheduledExecutorService:擴展ExecutorService,增加了Future和定時任務

  • ThreadPoolExecutor線程池

    線程池
    • 線程池相關的參數
      • corePoolSize:核心線程數,長期駐留的線程數量
      • maximumPoolSize:線程可以創(chuàng)建的最大線程數量
      • woreQueue:當核心線程滿了之后任務等待的隊列。我們使用不同的隊列,就會有不同的排列機制
      • keepAliveTime:當核心線程以外的線程閑置以后可以存活的時間
      • ThreadFactory:用于創(chuàng)建線程
      • handler:任務的拒絕策略
        • AbortPolicy:直接拋出異常
        • CallerRunsPolicy:使用調用者的線程執(zhí)行任務
        • DiscardOldestPolicy:丟棄阻塞隊列中最前的任務并且執(zhí)行本任務
        • DiscardPOlicy:直接丟棄任務
        • 可也以自己實現(xiàn)RejectedExecutionHandler接口來自定義handler
    • 線程池中對任務的處理:
      • 如果當前線程數小于核心線程數,則創(chuàng)建一個線程執(zhí)行(即使其他線程空閑)
      • 如果當前任務大于核心線程數,則封裝后進入等待隊列
      • 如果等待隊列已滿,則創(chuàng)建臨時線程執(zhí)行任務
      • 若當前線程數大于最大線程數,且阻塞隊列已經滿了,則通過handler定義的策略處理任務
    • 線程池的運行狀態(tài)
      • Running:正在運行,線程池可以提交新的任務
      • Shutdown:正在停止,線程池不再接受新的任務,但是還會處理隊列中的任務,調用線程池的shutdown()方法會使線程池進入這個狀態(tài)
      • Stop:完全停止,線程池不再接受新的任務,也不處理隊列中的任務,線程池中的線程會被中斷,調用線程池的shutdownNow()方法或者shutdown狀態(tài)的線程池執(zhí)行完剩余任務會使線程池進入和這個狀態(tài)
      • Tydying:所有的任務都已經終止了
      • Terminated:
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Java基礎 類加載的時機和類初始化的時機(引出tomcat類加載器)JVM和絕大多數用戶自定義的類在JVM啟動的...
    fanyank閱讀 2,370評論 0 33
  • 一. Java基礎部分.................................................
    wy_sure閱讀 4,017評論 0 11
  • 第二部分 自動內存管理機制 第二章 java內存異常與內存溢出異常 運行數據區(qū)域 程序計數器:當前線程所執(zhí)行的字節(jié)...
    小明oh閱讀 1,284評論 0 2
  • 聽到這首《滴答》,女生版,葫蘆絲婉轉而傷感的曲調。 一天中腦子里都在回旋著這旋律,思緒一下子回到2012年的年初,...
    水水水立方閱讀 494評論 0 0
  • 有句話叫時尚是個圈 十幾年一個輪回 聽說現(xiàn)在又流行機械鍵盤了 很久以前的老電腦配的都是機械鍵盤 翻出個鍵盤里的老古...
    你才搞笑閱讀 121評論 0 0

友情鏈接更多精彩內容