前言
java的一些特性、集合框架概要、線程安全的可用性說明、NIO網絡編程
博客地址:芒果橙的個人博客 【http://mangocheng.com】
一、特性
1.抽象類和接口的區別
- 定義
- 抽象類:用abstract修飾的類,一個類沒有包含足夠多的信息來描述一個具體的對象
- 有構造器
- 接口方法可以有public、protectd、default修飾
- 單個繼承
- 接口:抽象類型,抽象方法的集合。
- 無構造器
- 接口方法只能由public修飾
- 多重實現
抽象類可以有實例變量,而接口不能擁有實例變量,接口中的變量都是靜態(static)的常量(final)
- 默認的方法實現
- 抽象類:可以有方法實現
- 接口: 沒有(java8後可以使用default關鍵字添加非抽象方法)
- 實現
- 抽象類:使用extends繼承,並且如果不是抽象類,則需要實現所有方法
- 接口:使用implements實現,並且需要實現所有方法
- 速度
- 抽象方法比接口快
2.反射
反射的操作都是在編譯之後,即運行時
動態的獲取類的信息和調用其方法
-
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();
- 動態加載類
Class.forName()代表動態加載類
編譯時加載類是靜態加載類,運行時是動態加載類
- 獲取方法信息
基本的數據類型、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();
- 獲取屬性構造方法信息
屬性也是對象
-
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();
- 方法反射的操作
方法的名稱和方法的參數列表才能唯一決定某個方法
反射的操作: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.創建兩個集合 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中將變量存儲到內存和從內存中讀取出變量這樣的底層細節
- 所有變量都存儲在主內存中
- 每個線程都有自己獨立的工作內存,裏面保存該線程使用到的變量的副本(主內存的拷貝)
- 規定
- 線程對共享內存的操作只能在自己的工作內存中
- 不同線程之間無法直接訪問其他線程工作內存中的變量,線程間變量值的傳遞需要通過主內存來完成
-
實現原理
一個線程修改數據後,讓其他線程能夠看到
- 把該線程中的變量刷新(同步)到主內存中
- 將主內存中的最新變量值更新到其他線程的工作內存中
- 實現方式:synchronized、volatile、final
-
synchronized實現原理
- 規定
- 線程解鎖前,必須把共享變量的最新值刷新到主內存中
- 線程加鎖時,將清空工作內存中共享變量的值,從而使用需要從主內存中沖洗讀取最新的值
-
線程執行互斥代碼的過程
- 獲得互斥鎖
- 清空工作內存
- 從主內存拷貝變量的最新副本到工作內存
- 執行代碼
- 將更改後的共享變量刷新到主內存
- 釋放互斥鎖
重排序:代碼書寫順序與實際執行的順序不同,指令重排序是編譯器或處理器爲了提高程序性能而做的優化
- 編譯器優化的重排序(編譯器優化)
- 指令級並行重排序(處理器優化)
- 內存系統的重排序(處理器優化)
as-if-serial:無論如何重排序,程序執行的結果都應該與代碼順序執行的結果一致(java編譯器、運行時和處理器都會保證Java在單線程下遵循as-if-serial語義)
-
作用
- 原子性==>線程不會交叉執行
- 原子性==>重排序是在單個線程內
- 可見性==>規定加鎖解鎖數據的同步機制
- 規定
-
volatile實現原理
-
作用
- 保證volatile變量的可見性
- 不能保證volatile變量複合操作的原子性
-
原理:加入內存屏障和禁止重排序優化
- 對volatile變量執行寫操作時,會在寫操作後加入一條store屏障指令
- 對volatile變量執行讀操作時,會在讀操作前加入一條load屏障指令
-
過程
- 寫操作
- 改變線程工作內存中volatile變量副本的值
- 變量值刷新到主內存
- 讀操作
- 從主內存中讀取volatile變量值
- 變量值同步到工作內存中
- 寫操作
-
解決非原子性問題
-
使用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只能保證可見性
- 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網絡模型
- 流程
- 服務端註冊建立連接事件
- 服務端有Selector組件,用於循環檢測註冊事件就緒情況
- 當客戶端發起建立連接請求
- 服務端會啓動建立連接事件處理器
- Acceptor Handler處理器
- 處理器會創建與客戶端連接
- 處理器響應客戶端建立連接請求
- 處理器向Selector註冊連續可讀事件
- 可多個
- 客戶端發送請求到Selector
- Selector啓動連接讀寫處理器
- Read&Write Handler讀寫處理器
- 讀寫處理器處理客戶端讀寫業務
- 讀寫處理器響應客戶端請求
- 讀寫處理器向Selector註冊連續可讀事件
- 服務端註冊建立連接事件
[(G:/note/圖/NIO網絡模型.png)]
- 改進(相比BIO)
- 非阻塞I/O模型
- 彈性伸縮能力強
- 單線程節省資源
- 弊端
- 麻煩:NIO類庫和API繁雜
- 心累:可靠性能力補齊,工作量和難度大
- 有坑:Selector空輪詢,導致CPU 100%
- 流程
2.NIO核心
-
Channel:通道
-
簡介:雙向性、非阻塞性、操作唯一性
-
實現
- 文件類:FileChannel
- UDP類:DatagramChannel
- TCP類:ServerSocketChannel/SocketChannel
-
使用
// 服務器通過服務端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:緩存區
- 簡介
- 作用:讀寫Channel中數據
- 本質:一塊內存區域
- 屬性
- Capacity:容量
- Position:位置
- Limit:上限
- Mark:標記
- 簡介
-
Selector:選擇器/多路複用器
-
簡介
- 作用:I/O就緒選擇
- 地位:基礎
-
使用
// 創建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();
-
SelectionKey簡介
- 四種就緒狀態常量
- 有價值的屬性
-
3.NIO實現步驟
- 創建Selector
- 創建ServerSocketChannel,並綁定監聽端口
- 將Channel設置爲非阻塞模式
- 將Channel註冊到Selector上,監聽連接事件
- 循環調用Selector的select,檢測就緒情況
- 調用selectedKeys方法獲取就緒channel集合
- 判斷就緒狀態種類,調用業務處理方法
- 根據業務需要決定是否再次註冊監聽事件,重複執行第三步操作