Java 進階

Java

Java 的一些進階知識,以及一些經常使用示例。java

Jvm

Jvm組成

Jvm 是個虛擬機,主要由:類裝載系統、執行引擎、垃圾回收器組成。c++

執行引擎,是Java跨平臺的核心,負責虛擬機與OS的指令交互工做。算法

類裝載系統

  • 加載器工做過程

類加載的過程主要包括:加載、驗證、準備、解析、初始化等階段。sql

  1. 加載:將字節碼文件讀入虛擬機,並堆中生成一個class引用。
  2. 驗證:利用加載完成的class驗證文件正確性,以及父類子類是否衝突等。
  3. 準備:給類變量,以及存在默認值的成員分配空間。
  4. 解析:生成全部成員的引用,就是把指針連起來。
  5. 初始化:初始化全部靜態變量,或存在默認值的變量,過一次靜態代碼塊。

1~4 都是 c++ 程序在執行,5爲java執行程序。編程

  • 加載器分類
  1. 啓動類加載器:引導類裝載器進入虛擬機,相似Linux內核文件,c++。
  2. 擴展類加載器:加載指定Jre目錄文件,造成 java基本運行環境,java。
  3. 系統類加載器: 加載指定路徑的字節碼文件,虛擬機開機自啓程序。
  4. 自定義加載器:這裏須要加載的是未定義的,動態class文件。
  • 雙親委派原則

Java 除頂層加載器以外,類加載器是一種子->父的單向循環過程。
主要是爲了保證類不會被加載第二次,相似於一種層級的環境變量。數組

類加載器的主要過程就是重複以下操做:
先到當前class查詢緩存是否存在此類,存在則返回,不存在將加載請求拋給父類。緩存

當頂層類依然不存在此類的時候:安全

// 自定義類加載器--> 遵循雙親委派原則 通常都用這個
public class ExpLoder extends ClassLoader{
    // 頂層找不到類的時候,會來這裏
    @Override
    protected Class<?> findClass(String name){
        return null;
    }
    // 將字節流轉化爲,jvm可識別的文件
    @Override
    protected final Class<?> defineClass(byte[] b,int off,int len){
        return defineClass(null, b, off, len, null);
    }
}

// 自定義類加載器 --> 不遵循雙親委派原則
public class ExpLoder extends ClassLoder{

    // 重寫這個方法會直接破壞雙親原則
    @Override
    protected Class<?> loadClass(String name,boolean resolve){
        //...
    }
}
  • 線程上下文類加載

自己是併發帶來的問題,多個線程各自走雙親委派原則,致使類加載不一致。
因此線程上下文類加載,並不遵循雙親委派原則,而是直接給主線程去加載了。數據結構

垃圾回收器

Java 對象執行完畢以後,清除無用空間的操做,這裏指堆空間。多線程

  • 通常性內存空間整理算法
  1. 標記清除:標記無用空間,統一清除,易產生碎片空間。
  2. 複製算法:將使用空間複製到一個新的區域,以後清除整片舊空間,太浪費了。
  3. 標記整理:標記無用空間,前移全部使用空間,覆蓋無用空間,太耗時了。
  • 分代算法
  1. 新生代:每一個週期都有大量空間失效,因此用複製算法。

    Eden 區:存儲 new 的對象,不少對象會當場去世。
    from 區:存儲從 Eden 區存活下的對象。
    to 區:存儲上 from 區活蝦的對象。

    • 當 Eden區滿了,則開啓 GC,執行一次週期。
    • 再將 to 區存活對象,移動至老年代。
    • 再將 from 區存活對象,移動至 to 區域。
    • 先將 Eden 區存對象,移動至 from 區,再清除 Eden 區。
  2. 老年代:相對而言,每一個週期少許空間失效,通常用 標記-整理,或標記-清楚。

    空間滿了,則開啓GC,根據算法不一樣選擇不一樣的策略。

  3. 元空間,之前叫永久代,受限於本地內存。

    string 常量區就在這裏,相似於方法區這個東西。

  • 垃圾收集器
  1. 串行收集器 serial

    串行收集器會中止全部線程,簡單且高效,適用於單線程、C/S的client端。
    新生代-複製,老年代-標記整理。

  2. ParNew

    自己就是串行收集器的多線程版本,沒啥差異,中止全部線程,簡單高效。

  3. CMS

    CMS 是一種基本的以空間換時間的收集器,追求最短響應時間。

    • 初始標記:中止全部線程,標記失效空間。
    • 併發標記:不會中止任何線程,而是開新線程去獲取失效空間地址。
    • 從新標記:中止全部線程,將併發標記結果寫入標記集合。
    • 併發清除:不會中止任何線程,開新線程清除失效的空間。

    CMS 會致使吞吐量降低,部分浮動垃圾沒法清除,且碎片空間太多了。

  4. G1

    G1 是對 CMS 的一種改進版本。

    • 初始標記:短期中止全部線程,標記失效空間。
    • 併發標記:不會中止任何線程,開新線程獲取失效空間地址。
    • 最終標記:停不停線程都行,將併發標記結果寫入標記集合。
    • 篩選回收:停不停線程均可以,開新線程標記-總體失效空間。

    G1 相對於 CMS主要是,線程停頓時間可控,且採用標記-整理。
    G1 垃圾回收的性能由,線程停頓時間控制。

Jvm 內存

Jvm 內存結構,內存模型。

內存結構

Jvm 內存結構主要分爲兩大部分。

  • 進程區

進程區,亦稱爲線程共享區,全部線程均可訪問。

  1. 堆區:存儲對象用的,被垃圾收集器所管理。
  2. 方法區:主要存儲類一級別的變量,包括類信息,靜態變量,方法等,class對象引用在堆區,其指向的就是這裏。
  3. 元空間:存儲一些常量,String常量就是在這一個區域。
  • 線程區

線程區,亦稱線程私有區,僅線程自身能夠訪問。

  1. 程序計數器:切換線程上下文所用的標識位,記錄行號。
  2. 本地方法棧:一些OS方法,或其它語言的方法。
  3. 虛擬機棧:線程執行時用的棧,變量,對象引用,出口,函數連接等等。

內存模型

自己就是一個緩存模型,由主內存,工做內存,棧內存構成。

  • 變量類型
  1. 可被內存模型處理的變量僅包括了實例字段、靜態字段和構成數組對象的元素。
  2. 不包括局部變量與方法參數,這些是線程私有的。
  • 線程通訊
  1. 任何一個線程都有本身的緩存,裏面存放着經常使用變量,其餘變量在主內存裏。
  2. 線程之間的通訊,是其中一個線程將變量寫入主內存裏,另外一個去主內存拿。
  3. 線程之間的通訊必須在主內存裏完成,線程之間是沒法直接執行通訊的。

JMM通訊模型:files/jmm.png。

  • 操做說明
  1. lock:將一個變量標識爲一條線程獨佔狀態。
  2. unlock:將一個處於鎖定狀態的變量釋放出來。
  3. read:將一個變量從主內存傳輸到線程的工做內存中,以便隨後的 load 使用。
  4. load:將 read 操做從主內存中獲得的變量值放入工做內存的變量副本中。
  5. use:將工做內存中的一個變量值傳遞給執行引擎,每當虛擬機遇到一個須要使用變量的值的字節碼指令時將會執行這個操做。
  6. assign:把一個從執行引擎接收到的值賦值給工做內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操做。
  7. store:把工做內存中的一個變量的值傳送到主內存中,以便隨後的write的操做。
  8. write:把 store 操做從工做內存中一個變量的值傳送到主內存的變量中。
  • 模型性質

被 volatile 的變量的全部讀寫都是在主存內執行的,且會全部操做會加鎖。

  1. 原子性:全部內存模型操做都是原子性的,即不會幹到一半不幹了。
  2. 可見性:被 volatile修飾的變量,任何更改對多有線程都是可見的。
  3. 順序性:被 volatile修飾的變量,禁止做內存優化,全部操做都是順序的。

內存屏障

  • 問題所在

    1. 當下 cpu 通常都存在多級緩存,其查找變量的時候,先在一級緩存中尋找,繼續在二級緩存之中尋找,如此類推。雖然緩存有不少,但內存終究就只有一個。多級緩存會帶來一個數據不一致的問題。

    2. 在不一樣 cpu 執行的不一樣線程對同一個變量的緩存值不一樣,爲了解決這個問題。

  • 硬件協議

    通常爲解決,多級緩存,以及多線程緩存不一致的問題,從 cpu 自己出發的 mesi 協議,可解決緩存不一致的問題。

    讀屏障:在指令前插入讀屏障,可讓高速緩存中的數據失效,強制從主內存取。

  • Jvm內存屏障

Jvm是在代碼指令中添加彙編指令,使用cpu的內存屏障協議保證數據的一致性。

1.阻止屏障兩側指令重排序,(Jvm有內存優化的)。
2.強制把寫緩衝區/高速緩存中的髒數據等寫回主內存,讓緩存中相應的數據失效。

併發

併發的主要依靠多線程來解決,在這個過程當中須要鎖來維持數據一致性。

鎖主要的做用是保證線程安全,但會影響性能。

分類

  • 獨佔鎖與共享鎖

獨佔鎖:即爲寫鎖,亦稱互斥鎖,最多隻能有一個線程獲取此類鎖。
共享鎖:即爲讀鎖,存在寫操做時,只能有一個線程可獲取鎖;僅存在讀操做時,可存在多個線程同時獲取鎖。

  • 悲觀鎖與樂觀鎖

悲觀鎖:默認會發生衝突,因此任何操做都加鎖。
樂觀鎖:默認發生錯誤較少,因此僅在寫操做時加鎖。

  • 公平鎖與非公平鎖

公平鎖:內部順序獲取鎖,不會產生飢餓,但效率較低。
非公平鎖:內部會根據規則搶鎖,會產生飢餓,但效率高。

  • 重入鎖

亦稱遞歸鎖,鎖是以現稱爲單位獲取獲釋放的。
當線程中某一部分獲取鎖後,至關於整個線程獲取到了鎖。
主要是保證了同一個鎖,不會由於相同線程的爭搶而致使發生死鎖。

  • 自選鎖

當線程獲取鎖被阻塞時,輪詢獲取鎖。
優勢是避免程序切換上下文,缺點是消耗cpu。

  • 引用鎖,非引用鎖

全部被 synchronized 鎖住的都是引用類型。
全部被 lock 鎖住的都是代碼塊。

synchronized & ReentrantLock

  • synchronized

悲觀鎖,非公平鎖,重入鎖。
只有類鎖,對象鎖,成員鎖這三種引用鎖,且是虛擬機級別的。

任何一個類,對象,成員都有一個內置鎖,亦稱監聽器 monitor。
是一個互斥鎖,即同時刻只能有一個線程得到某一級別的鎖,其餘線程阻塞。
但不一樣線程或相同線程可得到不一樣級別的鎖,即爲重入鎖。

被synchronized修飾的代碼都會根據條件獲取指定鎖,其餘線程只能等鎖釋放

public class Example{
    static int k = 10;
    public int m = 10;
    public void show(){
        //...
    }
    synchronized static public void sh(){
        //...
    }
    synchronized public void show(){
        //...
    }
}
// 測試對象
Example exp = new Example();

// 同步 類,靜態變量 或調用靜態同步方法時,會加類鎖。
synchronized(Example.class){};
synchronized(exp.k){};
sh();

// 同步對象,則加對象鎖。
synchronized(exp){}

// 同步成員變量,或調用同步成員函數時,加成員鎖。
synchronized(exp.m){}
show();
  • ReentrantLock

悲觀鎖,默認非公平鎖,可設定爲公平鎖,可重入鎖,。
核心爲 AbstractQueueSynchronzer,AQS基於FIFO雙向隊列實現。

// 此時爲,非公平鎖
ReentrantLock lock = new ReentrantLock();
// 此時爲,公平鎖
ReentrantLock lock = new ReentrantLock(true);
  • 二者異同

相同之處:都是悲觀鎖,且都是可重入鎖,都是阻塞式鎖。

類型 加鎖範圍 公平鎖 級別 鎖問題
synchronized 類,對象,成員 非公平鎖 虛擬機級別 寫鎖
ReentrantLock 代碼區 公平鎖,非公平所均可以 線程級別 可設定讀寫鎖
  • volatile

volatile 亦是一種線程同步的方式,主要是線程通訊。

  • 其餘

synchronized 由一對 monitorenter/moniterexit 字節碼指令實現。
java 鎖競爭的基本原則,會根據金證程度來使用權限。
偏向鎖->輕量級鎖->重量級鎖。

線程

多線程是提升併發量、吞吐量的主要手段。

線程同步

  • 線程同步

線程同步的方式,可根據消耗按照以下三種方式排序。

// 利用 volatile 同步線程
public class Example implements Runnable{

    // 被 volatile 修飾的變量,寫操做後必須刷新變量的值
    static volatile int k = 20;
    @Override
    public void run(){
        while(true){
            System.out.println(Thread.currentThread().getName()+":"+k--);
            try{
                Thread.currentThread().sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

// 利用 synchronized 同步數據
public class Example implements Runnable{

    static int k = 20;
    @Override
    public void run(){
        while(true){
            // 成員鎖,僅限定這個成員的訪問
            show();
            try{
                Thread.currentThread().sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    // 同步方法
    synchronized private void show(){
        System.out.println(Thread.currentThread().getName()+":"+k--);
    }

}

import java.util.concurrent.locks.ReentrantLock;

// 併發量很大的時候,用 ReentrantLock 鎖同步代碼區
public class Example implements Runnable{

    // 同步測試
    static int k = 20;
    // 同一時間,被 lock 的代碼區,只能由一個線程來執行
    Lock lock = new ReentrantLock();

    @Override
    public void run(){
        while(true){
            try{
                // 同步鎖開始
                lock.lock();
                // 輸當前線程名稱,k的值,以後再 k--
                System.out.println(Thread.currentThread().getName()+":"+k--);
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                // 同步鎖結尾
                // 放在這裏是爲了防止發生異常後直接鎖死整個線程組
                lock.unlock();
                try{
                    // try catch 塊 真他媽是個讓人頭疼的東西。
                    Thread.currentThread().sleep(1000);
                }catch(InterruptedException d){
                    d.printStackTrace();
                }
            }
        }
    }
}
Example exp = new Example();

// 併發執行多線程,同步數據。
new Thread(exp,"Thread-A").start();
new Thread(exp,"Thread-B").start();
new Thread(exp,"Thread-C").start();
new Thread(exp,"Thread-D").start();

線程池

  • 多線程弊端

多線程併發除了可能會影響數據一致性以外,亦會因線程自己而引起異常。

  1. 實時新建、銷燬、切換線程,自己就很是消耗Cpu。
  2. 可能存在須要短期內new大量線程,易發生OO,引起宕機。
  3. 線程缺少管理,好比定時執行,按期執行,定時中斷等等。
  • 概念

線程池就是首先建立一些線程,它們的集合稱爲線程池。
線程池能夠很好地提升性能,線程池在系統啓動時即建立大量空閒的線程。
程序將任務傳給線程池,線程池就會啓動一條線程來執行這個任務。
執行結束之後,該線程並不會死亡,而是再次返回線程池中成爲空閒狀態,等待執行下一個任務。

  • 工做機制

在線程池的編程模式下,任務是提交給整個線程池,而不是直接提交給某個線程。
線程池在拿到任務後,就在內部尋找是否有空閒的線程,若是有,則將任務交給某個空閒的線程。

一個線程同時只能執行一個任務,但能夠同時向一個線程池提交多個任務。

線程池示例

線程池管理接口。

  • 緩存線程池

一般用於執行生存週期較短的異步任務。

當任務來的時候,會檢測是否有被緩存的線程,有則用緩存的線程,無則新增線程。
全部正在執行的線程都是核心線程,全部被緩存的線程都是非核心線程。
全部被緩存的線程都有超時機制,超過 60秒沒有被引用,線程會被回收。

// 初始化線程池,此時線程池內無線程,以後新增的線程都會被緩存,緩存後可複用
ExecutorService cache = Executors.newCachedThreadPool();

for(int i=0; i<10; i++){
    Thread.sleep(1000);
    // 新增線程-->至線程池中
    cache.execute(new Runnable(){
        @Override
        public void run(){
            System.out.println(Thread.currentThread.getName()+"-running");
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    });
}
// 雖然循環 10 次,但實際只有 2 個線程。
  • 固定大小的線程池

內部擁有固定數量的可重用線程。

全部線程都是核心線程,不會被回收,任務超過線程上限則進入隊列等待。

// 獲取系統核數,利用這個建立固定大小的線程池
int corenum = Runtime.getRuntime().availableProcessors();
// 建立 固定數量線程的線程池
ExecutorService fix = Executors.newFixedThreadPool(corenum);

for(int i=0; i<100; i++){
    fix.execute(new Runnable(){
        @Override
        public void run(){
            System.out.println(Thread.currentThread.getName()+"-running");
            try{
                Thread.sleep(1000);
            }catch(TnterruptedException e){
                e.printStackTrace();
            }
        }
    });
}
  • 延遲的週期性線程池

通常爲固定數量的可重用線程,外加額外線程,且線程都是週期性執行任務。

其核心線程數量是固定的,空閒亦不會被回收,而非核心線程執行完畢則當即回收。

// 建立固定數量的線程池
ScheduledExecutorService sched = Executors.newScheduledThreadPool(5);
for(int i = 0; i<5; i++) {
    sched.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            System.out.println("延遲執行" + Thread.currentThread().getName());
        }
    }, 5, 1, TimeUnit.SECONDS);
}
// 此處含義爲,建立任務後 5 秒以後開始週期性執行,每一個週期間隔 1 秒
  • 單線程式線程池

內部只有一個線程,且全部任務會按照 FIFO 順序執行。

內部只有一個核心線程,空閒亦不會被回收。

// 內部只有一個線程
ExecutorService single = Executors.newSingleThreadExecutor();
for(int i = 0;i < 10;i++){
    // 任務執行順序不會改變
    single.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"-running");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
}
自定義線程池

其上所展現的四種線程池都來自這個類,只不過參數被肯定了。

類型 建立 核心線程
緩存線程池 newCachedThreadPool() 核心線程與非核心線程動態變化
固定線程池 newFixedThreadPool(int n) 全部線程都是核心線程
單線程線程池 newSingleThreadExecutor() 只有一個核心線程
週期性線程池 newScheduledThreadPool(int n) 固定的核心線程,動態變化的非核心線程
  • 線程池核心類
// 線程池原始定義方法
public ThreadPoolExecutor(
    // 核心線程數量
    int corePoolSize,
    // 最大線程數量,當核心線程滿了以後,會查詢這個值來肯定是否新增線程
    int maximumPoolSize,
    // 線程總數大於核心線程數量時,多餘空閒線程最多空閒時間,超時則釋放資源
    long keepAliveTime,
    // keepAliveTime 時間單位
    TimeUnit unit,
    // 超出核心線程時,多餘任務的存儲位置
    BlockingQueue<Runnable> workQueue,
    // 建立新線程時使用的工廠
    ThreadFactory threadFactory,
    // 當線程池達到上限之時,所要執行的策略
    // 當任務隊列達到最大值被阻塞,且總線程數量達到最大值時,即爲上限
    RejectedExecutionHandler handler
    ) {
    //...
}
  • 線程池通常策略

線程總數 < 核心線程:新開核心線程,任務進入線程。

線程總數 = 核心線程:若存在空閒的核心線程,任務進入線程,不然進入隊列。

隊列到達上限,線程總數 < 線程最大值:新增非核心線程,任務進入隊列。

隊列到達上限,線程總數 = 線程最大值:執行飽和策略。

// 此處主要是來存儲,超出執行的任務
BlockingQueue<Runnable> task = null;
// 內部是數組,數組必有界
task = new ArrayBlockingQueue<>(100);
// 內部是鏈表,鏈表內存是動態化的,但也可設置上限,即有界、無界皆可
task = new LinkedBlockingQueue<>();
// 無緩衝的隊列,無界
task = new SynchronousQueue<>();

// 任務到達上限時所要執行的 飽和策略
RejectedExecutionHandler rejection = null;

// 忽略新增任務,新增則拋異常,亦爲默認值
rejection = new ThreadPoolExecutor.AbortPolicy();
// 忽略新增任務,新增不會拋異常
rejection = new ThreadPoolExecutor.DiscardPolicy();
// FIFO,刪除隊列頭部任務,以後繼續新增
rejection = new ThreadPoolExecutor.DiscardOldestPolicy();
// 新增任務失敗後,主線程本身去執行,不要用這玩意兒
// 由於新增任務都須要主線程,纔可進入隊列或直接進入線程
// 且隊列內的任務,都須要主線程分配給各個線程纔可執行
rejection = new ThreadPoolExecutor.CallerRunsPolicy();
  • 自定義示例
BlockingQueue<Runnable> task = new SynchronousQueue<Runnable>();
RejectedExecutionHandler rej = new ThreadPoolExecutor.AbortPolicy();
ThreadFactory th = Executors.defaultThreadFactory();
// 核心線程池
ExecutorService core = new ThreadPoolExecutor(6,18,60,TimeUnit.SECONDS,task,th,rej);

/*
 * 線程池
 * execute 實現 Runnable接口建立任務
 */
core.execute(new Runnable(){
    @Override
    public void run(){
        System.out.println("Runnable--execute");
    }
});


Future<?> ex = core.submit(new Runnable(){
    @Override
    public void run(){
        System.out.println("running");
        System.out.println(0/0);
    }
});


/*
 * 線程池
 * submit 實現 Callable<Object> 接口建立任務,存在返回值
 * Future 類能夠獲取,submit 任務的返回值,但會阻塞至任務結束。
 * Future submit Ruuuable 能獲取到的只有異常,或null
 * Future submit Callable 能夠獲取到異常以及程序返回值
 */
Future<?> res = core.submit(new Callable<Integer>(){
    @Override
    public Integer call(){
        System.out.println("Runnable-submit");
        return 1;
    }
});

// 此處會阻塞至,res執行完畢,纔會有結果
System.out.println(res.get());
  • 線程池異常

Java 全部線程池中的任務發生異常後,整個線程會被回收。

// 新建線程池
ExecutorService core = Executors.newFixedThreadPool(3);

// execute Runnable 系列沒法捕獲異常,線程會被回收
core.execute(new Runnable{
    @Override
    public void run(){
        System.out.println("runnable -- Exception");
        // 此處會發生異常
        System.out.println(10/0);
    }
});

// submit Runnnable 可捕獲異常,線程依然會被回收
Future exp = core.submit(new Runnable(){
    @Override
    public void run(){
        System.out.println("runnable -- Exception");
        // 此處會發生異常
        System.out.println(10/0);
    }
});

// 此處能夠捕獲線程執行發生的異常
try{
    System.out.println("exception"+exp.get());
}catch(Exception e){
    e.printStackTrace();
}
特殊操做

Future 是面向任務的一種類,可控制當前任務。
submit 通常配合 Future 使用。
Callable<?>存在返回值,可拋出異常,僅在線程池中使用。

// 新建線程池
ExecutorService core = Executors.newFixedThreadPool(3);

/*
 * Future submit callable 系列可控制一個完成的線程生命
 */
Future res = core.submit(new Callable<Integer>{
    // callable 通常僅在線程池中使用
    @Override
    public Integer call(){
        System.out.println("submit future");
        return 1;
    }
})

// Future 可獲取當前任務的全部信息
System.out.println(res);

/*
 * Future 類亦能夠中途取消執行任務
 * true 可強行中斷,false 不可強行中斷
 * 返回值,取消結果
 */
Boolean ok = res.cancel(true);

/*
 * Future 可獲取線程執行結果
 * Future 可捕獲,任務線程執行時發生的異常
 */
try{
    // res.get() 便可獲取線程執行結果,阻塞式獲取
    System.out.println(res.get());
}catch(Exception e){
    e.printStackTrace();
}
線程變量
  • 局部變量

ThreadLocal,線程級別的HashMap,存儲線程局部變量。

  1. 默認新開線程時,此線程即會被建立。
  2. 可用在多線程共享變量。
  • 數據結構

主要是一些線程安全的數據結構。

Synchronized:全部被修飾的變量都是線程安全的。
StringBuffer:線程安全的字符串結構。
BlockQueue:線程安全的隊列。
Vector:線程安全的數據結構。
Collections.synchronizedList:將列表轉化爲線程安全的,但內部鎖粒度過大。
ConcurrentHashMap:內部是分段鎖,不會把整個表鎖起來。
SynchronizedMap/Hashtable:線程安全,但鎖粒度太大。

IO

主要介紹常見的IO知識點。

IO流

  • 通常IO

主要分爲字節流、字符流。

字節流:即爲二進制數據流。
字符流:即爲char[]流,通常須要字符編碼格式,默認utf-8。

  • 核心概念

同步:一種可靠的時序操做,執行操做後,後續任務須要等待。
異步:執行操做後,後續任務無需等待,通常經過事件、回調等方式實現。

阻塞:這是線程的狀態,僅當條件容許時,線程會進入就緒狀態等待執行。
非阻塞:無論 IO是否結束,該返回直接返回。

  • IO模型

IO,亦稱BIO,是一種同步且阻塞的IO方式,默認IO都是這個模型。
NIO 是一種同步非阻塞的IO方式。
AIO 是一種異步非組賽的IO方式。

BIO:讀寫時,會阻塞線程,只有操做執行完畢時纔會返回結果。
NIO:讀寫時,不會阻塞線程,只有操做執行完畢時纔會返回結果。
AIO:讀寫時,不會阻塞線程,執行結果會分段返回。

經常使用類

主要是一些類的經常使用示例。

Java8 新特性

主要介紹下 Java-8的一些特色。

1.語法糖

// 語法糖,本質上源代碼沒變,只是減小了代碼量
// lambda 系列
new Thread(()->{System.out.println("lambda");}).start();
// lambda 若一行能夠寫完,則無需;
new Thread(()->System.out.println("lambda")).start();

2.接口默認方法

// 接口內部能夠存在默認方法,且能夠被重寫
public interface Exp{
    default void show(){
        System.out.println("interface-default-function");
    }
}

3.函數式接口

函數式接口,就是一個接口默認一個方法,能夠即時定義使用。
調用形式有一種,面向切面編程的思想。

// 函數式接口是一種特殊的接口

// 必須被 FunctionalInterface 註解標註,且內部只能存在一個方法
@FunctionalInterface
public interface Exp{
    public void show();
}

// 定義 接口內的 show 方法
Exp exp = () -> System.out.println("Function interface");
exp.show();

@FunctionalInterface
public interface Example{
    public int sh(int k);
}

// 不一樣的函數接口,就是不一樣的切面,且此處 return 可省略
Example example = (x) -> x++ ;
Example exp = (x) -> x*10;

System.out.println(example.sh(10)+"-"+exp.sh(10));

// java8 自帶一些函數式接口
Consumer<String> ex = (x) -> System.out.println(x);
ex.accept("hello");

Suppiler<String> ex = () -> "hello";
System.out.println(ex.get());

// 這裏就是動態代理
public void proxyFun(Consumer<String> consumer,String msg){
    consumer.accept(msg);
}
// 函數式編程模式
proxyFun((x)->System.out.println(x),"123465");

4.時間API

// 新增一組時間API
Clock clock = Clock.systemDefaultZone();
System.out.println(clock.millis());

//...

Date date = Date.from(clock.instance());

5.Stream

增長了部分相似,sql語法的方法。

6.多重註解

容許對註解單位加多重註解,面向多切面編程。

常見運算

  • Number

此下的方法適用於Number的全部子類。

// 將 string 解析爲 Number
Integer.parseInt("123");
// 將 Number 轉化爲 String
Integer.toString(123);
// 比較兩個 Number 的大小,相等 0 ,小於 -1,大於 1
Integer.compare(1,2);
  • Math

此下主要包括一些經常使用的計算方法。

// Number 求絕對值
Math.abs(-1000);
// Number 四捨五入
Math.round();
// Number 四捨五入 保留一位小數
Math.ceil();

// Math.max(); Math.min();
// ...

String

String 是最爲通用的類型。

  • Character

char 類型能夠看做是 int 類型。

// char 類型能夠看做特殊的類型
char a = 100;
// 將 char 轉化爲長度爲一的string
Character.toString(a);
  • String

String 是一個特殊的類型,實際是個常量,只要改變至關於一次新建。

// 將 Number 類型轉化爲 String
String p = String.valueOf(123);

String p = "abc";
// p 轉大寫
p = p.toUpperCase();
String p = "ABC";
// p 轉小寫
p = p.toLowerCase();

// 相等爲 0,adc-abd -1,abc-abb 1
p.compareTo("ABD");

// 比較 String 是否相同
"sd".equals("sd");

// 比較 String 是否相同,忽略大小寫
"SD".equalsIgnoreCase("sd");

// ...
  • String 常量池

常量池中的變量都是 final,即不可更改的變量,自己是一個char數組。

  1. String p = "hellow"; 此種方式建立string的時候,先到常量池中搜索,存在相同的string則直接返回引用,不然在常量池之中建立相應的 string,並返回引用。

  2. String p = new String("hellow"); 此種方式建立string的時候,先在常量池中搜索,若存在則直接返回引用,不然在堆區建立相應的對象。

// 此種方式建立的string即是在常量池中間的變量
String p = "hellow";
// p q 都指向常量池中的一個變量
String q = "hellow";
  • StringBuilder & StringBuffer

不一樣於 String 類型,其自己是對象,任何操做都是針對對象自己,不會出現瘋狂召喚GC的狀況。

  1. StringBuilder:線程不安全。
  2. StringBuffer:線程安全,其全部方法都被 synchronized 所修飾。

時間類型

時間類型是最爲基本的經常使用類型。

// 獲取時間對象
Date date = new Date();

// 這裏獲取的是一個完整的時間對象,通常用不到
Calendar calendar = Calendar.getInstance();

// 這裏獲取的就只是時間對象
Date cal = Calendar.getInstance().getTime();

// 時間戳 13 位
date.getTime();
Calendar.getInstance().getTimeInMillis();
System.currentTimeMillis();

// 時間格式化類型
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

// 格式化時間,Date對象便可
sdf.format(date);
sdf.format(cal);

流類型

Java 自己分爲字符流、字節流,以下爲基本的抽象類。

流類型 in out
字節流 InputStream OutputStream
字符流 Reader Writer
// 常見流
FileInputStream input;
FileOutputStream output;
FileReader reader;
FileWriter writer;

//序列化的流
FileOutputStream out = new FileOutputStream("./exp.txt");
ObjectOutputStream obj = new ObjectOutputStream(out);
obj.write(new Object());
obj.close();
out.close();

// 反序列化的流
FileInputStream in = new FileInputStream("./exp.txt");
ObjectInputStream obj = new ObjectInputStream(in);
Object exp = obj.readObject();
obj.close();
in.close();

// IOException

集合類型

集合類型是一種經常使用數據結構類型,動態存儲空間,且面向對象設計。

Collection

Collection接口採用線性列表的方式存儲數據。

  • List

List接口用於存儲有序,可重複的數據。

/*
 * ArrayList<?> 實現類
 * 優勢:實際結構是動態數組,查詢快,增刪慢。
 * 缺點:線程不安全。
 */
ArrayList<String> list = new ArrayList<String>();

/*
 * Vector<?> 實現類
 * 優勢:實際結構是動態數組,查詢快,增刪慢。
 * 缺點:線程安全。
 */
Vector<String> vector = new Vector<String>();

/*
 * LinkedList<?> 實現類
 * 優勢:實際結構是鏈表,查詢慢,增刪快。
 * 缺點:線程不安全。
 */
LinkedList<String> linklist = new LinkedList<String>();

/*
 * Collections.synchronizedList(),靜態化方法
 * 此方法的做用是將,任何基於List接口實現的類轉化爲線程安全的類。
 */
List safelist = Collections.synchronizedList(list);

// 新增 移除 索引 ...
  • Set

Set接口用於不可重複的數據。

/*
 * HashSet<?> 實體類
 * 內部就是個 hash表,依賴 hashCode(),equals()方法保證惟一性。
 * 主要存儲 無序,不重複的數據。
 */
HashSet<String> set = new HashSet<String>();

/*
 * LinkedHashSet<?> 實體類
 * 內部是一個由 hash表,和鏈表結構組成。
 * 主要存儲 有序,不可重複的數據。
 */
LinkedHashSet<String> linkset = new LinkedHashSet<String>();

/*
 * TreeSet<?> 實體類
 * 內部就是大名鼎鼎的紅黑樹。
 * 主要存儲 有序,不可重複的數據。
 */
TreeSet<String> tree = new TreeSet<String>();
  • Queue

Queue接口提供阻塞式,以及非阻塞式隊列。

/*
 * BlockingQueue<?> 接口
 * 這個接口下的隊列都是阻塞式的。
 */
BlockingQueue blockqueue;

/*
 * ConcurrentLinkedQueue<?> 實體類
 * 這個類提供非阻塞式的隊列。
 */
ConcurrentLinkedQueue<String> q = new ConcurrentLinkedQueue<String>();

Map

Map 接口採用鍵值對映射的方式存儲數據。

/*
 * HashMap<?,?> 實體類
 * 內部就是 hash表。
 */
HashMap<String,String> map = HashMap<String,String>();

/*
 * LinkedHashMap<?,?> 實體類
 * 內部鍵值映射爲 hash表,外部鍵排序去重爲鏈表。
 * 是一個鍵有序的,hash表。
 */
LinkedHashMap<String,String> m = new LinkedHashMap<String,String>();

Thread

java 線程,其優勢在於併發,缺點在於相較於協程其更復雜且佔用資源較大。

  • Runnable 接口

Runnable 接口核心在於,執行線程資源申請,並在新資源中間執行線程。
Runnable 接口內僅定義 run方法,線程管理須要手動定義。

// Runnable 接口實現過程
public class Example implements Runnable{
    // 線程核心方法
    @Override
    public void run(){
        while(true){
            //...
        }
    }
}

// 實例化對象,內部沒有定義線程管理
Example exp = new Example();
// 沒有線程管理策略,僅能直接打開線程,其餘操做作不了
exp.run();

// Runnable 接口,管理
public class Demo implements Runnable{

    private boolean flag = false;

    public Demo(){
        //...
    }

    // 線程核心方法
    @Override
    public void run(){
        while(true){
            //...
            if(this.flag){
                break;
            }
        }
    }

    // 此時打開線程
    public void start(){
        this.run();
    }

    // 此時會直接退出線程而並不是掛起線程
    public void stop(){
        this.flag = true;
    }
}
// 實例化對象,Demo內部實現了部分線程管理
Demo demo = new Demo();
// 開線程
demo.start();
// 關閉線程,線程處於死亡狀態
demo.stop();
  • Thread

Thread 類通常須要重寫 run()方法來使用。

public class Example extends Thread{
    @Override
    public void run(){
        //...
    }
}

// Thread 類自帶線程管理方法
new Example().start();

public class Demo extends Runnable{
    @Override
    public void run(){
        //...
        try{
            // Thread.currentThread(),靜態方法獲取當前線程引用
            Thread.currentThread().sleep(1000);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
}

Demo demo = new Demo();

// 利用 Thread類以及實現Runnable接口的類的對象建立線程
// 線程能夠命名,也不命名
Thread th = new Thread(demo,"thread-1");
// 建立線程,命名線程,開啓線程
new Thread(demo).start();

// 簡寫開線程,name字段能夠不加
new Thread(
    new Runnable(){
        @Override
        public void run(){
            //...
        }
    }
).start();

// lambda 簡寫建立線程
new Thread(() -> { while(true){System.out.println(123);} }).start();

特殊

一些特殊的類。

特殊數據結構

Hash 亦稱哈希,是一種將任意字符串轉化爲定長字符串,本質是一種壓縮映射。
Hash 亦屬於數字簽名,或稱信息摘要算法中的一個。

Object obj = new Object();

// java 中任何一個對象都有其相應的 hash 值。
obj.hashCode();
  • HashMap

Map 是一種 key:value 鍵值對形式的對象。

1.底層原理

默認是一個數組,且數組內部可嵌套其餘數據結構。

// 1. 獲取 hashCode()
int hash = obj.hashCode();

// 2. 用數組長度對 hash 取模運算,主要是獲取數組下標
int i = hash%len;
//    或執行與運算,數組長度要減 1,現版是這個,但效果和取模同樣
int i = hash&(len-1);

// 3. 根據 i 存入數組內部,此處是鏈表
//    當後續新的對象已被存到這裏時,加入鏈表便可
//    當鏈表長度超 8,則變爲紅黑樹,否則查詢太慢
//    當紅黑樹實際元素少於 6,則退化爲鏈表
// 4. 獲取對象到這裏時,利用  equals 方法便可

2.哈希碰撞與擴容

哈希碰撞即爲兩個對象哈希值相同而產生的異常。 HashMap 數組默認 16,鍵值對 64。 超過最大閾值 75%,則擴容,空間大一倍。

相關文章
相關標籤/搜索