Java深入淺出之反射&集合&可見性&NIO

前言

java的一些特性、集合框架概要、線程安全的可用性說明、NIO網絡編程
博客地址:芒果橙的個人博客 【http://mangocheng.com】

一、特性

1.抽象類和接口的區別

  1. 定義
  • 抽象類:用abstract修飾的類,一個類沒有包含足夠多的信息來描述一個具體的對象
    • 有構造器
    • 接口方法可以有public、protectd、default修飾
    • 單個繼承
  • 接口:抽象類型,抽象方法的集合。
    • 無構造器
    • 接口方法只能由public修飾
    • 多重實現

抽象類可以有實例變量,而接口不能擁有實例變量,接口中的變量都是靜態(static)的常量(final)

  1. 默認的方法實現
  • 抽象類:可以有方法實現
  • 接口: 沒有(java8後可以使用default關鍵字添加非抽象方法)
  1. 實現
  • 抽象類:使用extends繼承,並且如果不是抽象類,則需要實現所有方法
  • 接口:使用implements實現,並且需要實現所有方法
  1. 速度
  • 抽象方法比接口快

2.反射

反射的操作都是在編譯之後,即運行時

動態的獲取類的信息和調用其方法

  1. Class類的使用

    類是對象,是java.lang.Class類的實例對象
    任何一個類都是Class的實例對象

  • 三種類實例對象(類類型)的表示方式

    // 1.任何一個類都有一個隱含的靜態成員變量
    Class c1 = Person.class;
    // 2.類的對象
    Class c2 = person.getClass();
    // 3.通過類的全路徑
    Class c3 = Class.forName("com.mango.Person");
    c1 == c2 == c3
    
  • 通過類的類類型創建實例對象

    Person person = c1.newInstance();
    
  1. 動態加載類

Class.forName()代表動態加載類
編譯時加載類是靜態加載類,運行時是動態加載類

  1. 獲取方法信息

基本的數據類型、void都存在類類型

  • Class

  • Method

    Class c1 = int.class;Class c2 = String.class;
    // 1.類的名稱:全稱、簡稱c1.getName(); 
    c2.getSimpleName();
    // 2.Method類,方法對象:一個類方法就是一個Method對象
    Method[] methods = c1.getMethods();
    // 2.1方法的返回值類型的類類型
    Class returnType = methods[0].getReturnType();
    // 2.2方法的名稱
    String methodName = methods[0].getName();
    // 2.3方法的參數列表類型的類類型
    Class[] paramTypes = methods[0].getParameterTypes();
    String paramTypeName = paramTypes[0].getName();
    
  1. 獲取屬性構造方法信息

屬性也是對象

  • Field

  • Constructor

    // cl.getFields() 獲取的public的屬性
    // 1.獲取所有該類屬性
    Field[] fields = c1.getDeclaredFields();
    // 1.1獲取屬性類型的類類型
    Class fieldType = fields[0].getType();
    // 1.2獲取屬性名稱
    String fieldName = fields[0].getName();
    
    // cl.getConstructors()獲取的public的構造方法
    // 2.獲取類的構造方法
    Constructor[] constructors = c1.getDeclaredConstructors();
    String constructorName = constructors[0].getName();
    // 2.1獲取構造方法參數列表的類類型
    Class[] paramTypes = constructors[0].getParameterTypes();
    String paramTypeName = paramTypes[0].getName();
    
  1. 方法反射的操作

方法的名稱和方法的參數列表才能唯一決定某個方法
反射的操作:method.invoke(對象,參數列表)

  • 獲取方法:c.getMethod(方法名,參數列表);

  • 方法調用:method.invoke(對象,參數列表)

    // 1.獲取類類型
    Class c = person.getClass();
    // 2.獲取具體方法
    Method method = c.getMethod("getAge",new Class[]{int.class});
    // 3.調用
    Object returnValue = method.invoke(person,23);
    
  1. 通過反射了解集合泛型的本質

編譯之後集合的泛型是去泛型化
集合的泛型,是防止錯誤輸入的,只在編譯階段有效

  • 反射的操作都是在編譯之後

    // 1.創建兩個集合
    ArrayList list = new ArrayList();ArrayList list2 = new ArrayList<String>();
    // 2.反射操作
    Class c1 = list.getClass();
    Class c2 = list2.getClass();
    c1 == c2
    

二、集合框架

單列集合根接口:Collection

雙列集合:Map

1.List

  • 特性
    • 元素是有序的(存入和取出的順序)、可重複的

1)ArrayList

  • 數據結構:數組
  • 線程不同步

2)LinkedList

  • 數據結構:鏈表(雙向)
  • 線程不同步

3)Vector-效率低,廢棄

  • 數據結構:數組
  • 線程同步

2.Set

  • 特性
    • 元素是無序的、不可重複的

1)HashSet

  • 數據結構:哈希表
  • 線程不同步

2)TreeSet

  • 數據結構:二叉樹
  • 線程不同步

3)LinkedHashSet

  • HashSet的子類
  • 元素是有序的(插入和輸出順序一致)

3.Map集合

  • 特性
    • 存儲形式是鍵值對,鍵是唯一的
  • 優缺點
    • 優點
      • 靈活性強,易擴展,耦合度低
      • 代碼簡單
    • 缺點
      • 存儲的類型和內容不可知

1)Hashtable-廢棄

  • 數據結構:哈希表
  • 線程同步

2)HashMap

  • 數據結構:哈希表
  • 線程不同步

3)TreeMap

  • 數據結構:二叉樹
  • 線程不同步

聲明集合線程安全,可以使用Collections工具類的方法Collections.synchronizedList、Collections.synchronizedSet、Collections.synchronizedMap

三、線程安全

1.可見性

  • 概述

    • 可見性:一個線程對共享變量值的修改,能夠及時地被其他線程看到
    • 共享變量:如果一個變量在多個線程的工作內存中都存在副本,那麼這個變量就是這幾個線程的共享變量
    • 不可見的原因
      • 線程交叉執行
      • 重排序結合線程交叉執行
      • 共享變量更新後沒有在工作內存與主內存同時刷新
  • Java內存模型:描述了java程序中各種變量(線程共享變量)的訪問規則,以及在JVM中將變量存儲到內存和從內存中讀取出變量這樣的底層細節

    • 所有變量都存儲在主內存中
    • 每個線程都有自己獨立的工作內存,裏面保存該線程使用到的變量的副本(主內存的拷貝)
    • 規定
      • 線程對共享內存的操作只能在自己的工作內存中
      • 不同線程之間無法直接訪問其他線程工作內存中的變量,線程間變量值的傳遞需要通過主內存來完成
  • 實現原理

    一個線程修改數據後,讓其他線程能夠看到

    1. 把該線程中的變量刷新(同步)到主內存中
    2. 將主內存中的最新變量值更新到其他線程的工作內存中
    3. 實現方式:synchronized、volatile、final
  • synchronized實現原理

    • 規定
      • 線程解鎖前,必須把共享變量的最新值刷新到主內存中
      • 線程加鎖時,將清空工作內存中共享變量的值,從而使用需要從主內存中沖洗讀取最新的值
    • 線程執行互斥代碼的過程

      1. 獲得互斥鎖
      2. 清空工作內存
      3. 從主內存拷貝變量的最新副本到工作內存
      4. 執行代碼
      5. 將更改後的共享變量刷新到主內存
      6. 釋放互斥鎖

      重排序:代碼書寫順序與實際執行的順序不同,指令重排序是編譯器或處理器爲了提高程序性能而做的優化

      1. 編譯器優化的重排序(編譯器優化)
      2. 指令級並行重排序(處理器優化)
      3. 內存系統的重排序(處理器優化)

      as-if-serial:無論如何重排序,程序執行的結果都應該與代碼順序執行的結果一致(java編譯器、運行時和處理器都會保證Java在單線程下遵循as-if-serial語義)

    • 作用

      • 原子性==>線程不會交叉執行
      • 原子性==>重排序是在單個線程內
      • 可見性==>規定加鎖解鎖數據的同步機制
  • volatile實現原理

    • 作用

      • 保證volatile變量的可見性
      • 不能保證volatile變量複合操作的原子性
    • 原理:加入內存屏障和禁止重排序優化

      • 對volatile變量執行寫操作時,會在寫操作後加入一條store屏障指令
      • 對volatile變量執行讀操作時,會在讀操作前加入一條load屏障指令
    • 過程

      • 寫操作
        1. 改變線程工作內存中volatile變量副本的值
        2. 變量值刷新到主內存
      • 讀操作
        1. 從主內存中讀取volatile變量值
        2. 變量值同步到工作內存中
    • 解決非原子性問題

      • 使用synchronized

      • 使用ReentrantLock

        private Lock lock = new ReentrantLock();
        // 加鎖開始
        lock.lock()
        // 操作(try)
            
        // 加鎖結束(只能在finally)
        lock.unlock();
        
      • 使用AtomicInteger

    • 場景(同時滿足)

      • 對變量的寫入操作不依賴當前值
        • 不滿足:number++;count*=5;
        • 滿足:Boolean變量
      • 該變量沒有包含在具有其他變量的不變式中
        • 不滿足:不變式 low<up
  • synchronized和volatile比較

    • volatile不需要加鎖,更輕量級,非阻塞
      • 從內存可見性角度,volatile讀相當於加鎖,寫相當於解鎖
    • synchronized可以保證可見性和原子性,volatile只能保證可見性
  • 其他

    • 對64位變量(long、double)的讀寫可能不是原子操作(java內存模型允許jvm將沒有被volatile修飾的64位數據類型的讀寫操作劃分爲兩次32位的讀寫操作來進行)

2.ThreadLocal

  • 基礎信息

    • 定義:提供線程局部變量;一個線程局部變量在多個線程中,分別有獨立的值
    • 特點:簡單(開箱即用)、快速(無額外開銷)、安全(線程安全)
    • 場景:多線程場景(資源持有、線程一致性、併發計算、線程安全等)
    • 實現原理:java 使用哈希表
    • 應用範圍:幾乎所有提供多線程特徵的語言
  • API

    • 構造:ThreadLocal<T>()
    • 初始化:initialVal()
    • 獲取/設值:get/set
    • 回收:remove

四、java網絡編程之NIO

簡介:Non-Blockiing I/O 或New I/O

起始於JDK1.4,主要用於高併發網絡服務

1.編程模型

  • 模型:對事物共性的抽象

  • 編程模型:對編程共性的抽象

  • 阻塞I/O:讀取時線程阻塞

  • BIO網絡模型

    • 流程

    1)服務器一直監聽建立連接請求

    2)當客戶端發起建立連接請求

    3)服務端啓動新線程

    4)該線程與於客戶端建立連接,響應客戶端

    5)該線程一直等待客戶端的請求,阻塞等待

    [(G:/note/圖/BIO網絡模型.png)]

    • 弊端

      • 阻塞式I/O模型
      • 彈性伸縮能力差
      • 多線程耗資源
      • 高併發時,服務器可能崩潰
    • 基本使用

      /**服務端 **/
      // 1.監聽端口
      ServerSocket ss = new ServerSocket(8000);
      while(true){
          // 2.接收請求,建立連接
          Socket socket = ss.accept();
          // 3.數據交換
          new Thread(new BIOServerHandler(socket)).start();
      }
      ss.close();
      /**客戶端 **/
      // 1.建立連接
      Socket s = new Socket("127.0.0.1",8000);
      // 2.獲取輸入輸出流
      InputStream is = s.getInputStream();
      OutputStream os = s.getOutputStream();
      
  • NIO網絡模型

    • 流程
      1. 服務端註冊建立連接事件
        • 服務端有Selector組件,用於循環檢測註冊事件就緒情況
      2. 當客戶端發起建立連接請求
      3. 服務端會啓動建立連接事件處理器
        • Acceptor Handler處理器
      4. 處理器會創建與客戶端連接
      5. 處理器響應客戶端建立連接請求
      6. 處理器向Selector註冊連續可讀事件
        • 可多個
      7. 客戶端發送請求到Selector
      8. Selector啓動連接讀寫處理器
        • Read&Write Handler讀寫處理器
      9. 讀寫處理器處理客戶端讀寫業務
      10. 讀寫處理器響應客戶端請求
      11. 讀寫處理器向Selector註冊連續可讀事件

    [(G:/note/圖/NIO網絡模型.png)]

    • 改進(相比BIO)
      • 非阻塞I/O模型
      • 彈性伸縮能力強
      • 單線程節省資源
    • 弊端
      • 麻煩:NIO類庫和API繁雜
      • 心累:可靠性能力補齊,工作量和難度大
      • 有坑:Selector空輪詢,導致CPU 100%

2.NIO核心

  • Channel:通道

    1. 簡介:雙向性、非阻塞性、操作唯一性

    2. 實現

      • 文件類:FileChannel
      • UDP類:DatagramChannel
      • TCP類:ServerSocketChannel/SocketChannel
    3. 使用

      // 服務器通過服務端socket創建channel
      ServerSocketChannel ssc = ServerSocketChannel.open();
      // 服務器綁定端口
      ssc.bind(new InetSocketAddress(8000));
      // 服務器監聽客戶端連接,建立socketChannel連接
      SocketChannel sc = ssc.accept();
      // 客戶端連接遠程主機及端口
      SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1",8000));
      
      
  • Buffer:緩存區

    1. 簡介
      • 作用:讀寫Channel中數據
      • 本質:一塊內存區域
    2. 屬性
      • Capacity:容量
      • Position:位置
      • Limit:上限
      • Mark:標記
  • Selector:選擇器/多路複用器

    1. 簡介

      • 作用:I/O就緒選擇
      • 地位:基礎
    2. 使用

      // 創建Selector
      Selector selector = Selector.open();
      // 將channel註冊到selector上,監聽讀就緒事件
      SectionKey sk = channel.register(selector,SectionKey.OP_READ);
      // 阻塞等待channel有就緒事件發生
      int selectNum = selector.select();
      // 獲取發生就緒事件的channel集合
      Set<SelectionKey> selectionKeySet = selector.selectedKeys();
      
      
    3. SelectionKey簡介

      • 四種就緒狀態常量
      • 有價值的屬性

3.NIO實現步驟

  1. 創建Selector
  2. 創建ServerSocketChannel,並綁定監聽端口
  3. 將Channel設置爲非阻塞模式
  4. 將Channel註冊到Selector上,監聽連接事件
  5. 循環調用Selector的select,檢測就緒情況
  6. 調用selectedKeys方法獲取就緒channel集合
  7. 判斷就緒狀態種類,調用業務處理方法
  8. 根據業務需要決定是否再次註冊監聽事件,重複執行第三步操作
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章