1. 程式人生 > >Java高併發程式設計——多執行緒

Java高併發程式設計——多執行緒

執行緒簡介

  • 程序
    • 正在執行的程式。
    • 程序是系統進行資源分配和呼叫的獨立單位。每一個程序都有他自己的記憶體空間和系統資源。
  • 執行緒
    • 在同一個程序內又可以執行多個任務。而這每一個任務就可以看成是一個執行緒。
    • 執行緒是執行在程序中的一個獨立實體,是CPU排程和分派的基本單位。
    • 單執行緒:程式只有一條執行路勁。
    • 多執行緒:程式有多條執行路勁。
      • 多個執行緒會共享程序所擁有的全部資源。
      • 多執行緒:為了提高應用程式的使用率。(程式的執行其實都是在搶CPU資源,CPU的執行權。多個程序在搶這個資源,而其中的某一個程序如果執行路徑比較多,就會有更高的機率搶到CPU的執行權。)通過多個執行緒併發執行,從而提高任務處理的速度。
    • 執行緒的開銷
      • 關於時間:建立執行緒使用是直接向系統申請資源的,對作業系統來說,建立一個執行緒的代價是十分昂貴的, 需要給它分配記憶體、列入排程,同時線上程切換的時候還要執行記憶體換頁,CPU 的快取被 清空,切換回來的時候還要重新從記憶體中讀取資訊,破壞了資料的區域性性。
      • 關於資源:Java執行緒的執行緒棧所佔用的記憶體是在Java堆外的,所以是不受java程式控制的,只受系統資源限制,預設一個執行緒的執行緒棧大小是1M(當讓這個可以通過設定-Xss屬性設定,但是要注意棧溢位問題),但是,如果每個使用者請求都新建執行緒的話,1024個使用者光執行緒就佔用了1個G的記憶體,如果系統比較大的話,一下子系統資源就不夠用了,最後程式就崩潰了。
    • 並行和併發
      • 並行:邏輯上同時發生,指在某一個時間內同時執行多個程式。
      • 併發:物理上同時發生,指在某一個時間點同時執行多個程式。
    • Q:JVM虛擬機器的啟動是單執行緒還是多執行緒?
      • 多執行緒。
      • 原因是垃圾回收執行緒也要先啟動,否則很容易出現記憶體溢位。
      • 垃圾回收執行緒+主執行緒,最低啟動了兩個執行緒,所以jvm的啟動其實是多執行緒的。

多執行緒實現方式

  • 實現原理
    • 由於執行緒是依賴程序而存在的
      ,所以我們應該先建立一個程序出來。 而程序是由系統建立的,所以我們應該去呼叫系統功能建立一個執行緒。
    • java是不能直接呼叫系統功能的,所以,我們沒辦法直接實現多執行緒程式。但是,Java可以呼叫C/C++寫好的程式來實現多執行緒程式。由C/C++去呼叫系統功能建立程序,然後由java去呼叫即可實現多執行緒程式。
  • 實現方式
    • 方式一:繼承Thread類
    • 方式二:實現Runnable介面
    • 方式三:執行緒池Executors類
      • 建立固定數目執行緒的執行緒池
      • 建立一個可快取的執行緒池(可大可小,隨著任務量)
      • 定時及週期性的執行任務的執行緒池
        • scheduleAtFixedRate 這個方法是不管你有沒有執行完,反正我每隔幾秒來執行一次,以相同的頻率執行
        • scheduleWithFixedDelay 這個是等你方法執行完後,我再隔幾秒來執行,也就是相對延遲後,以固定的頻率去執行
# 有返回值的執行緒。通過Callable和Future建立執行緒
public static void main(String[] args) throws ExecutionException {
    //Callable的返回值就要使用Future物件,Callable負責計算結果,Future負責拿到結果
    //1、實現Callable介面
    Callable<Integer> callable = new Callable<Integer>() {
        public Integer call() throws Exception {
            int i=999;
            //do something
            // eg request http server and process
            return i;
        }
    };
    //2、使用FutureTask啟動執行緒
    FutureTask<Integer> future = new FutureTask<Integer>(callable);
    new Thread(future).start();
    //3、獲取執行緒的結果
    try {
        Thread.sleep(5000);// 可能做一些事情
        System.out.println(future.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}
# 執行緒池
public static  void testFixedThreadPool() {
    //建立固定的執行緒池,使用3個執行緒來併發執行提交的任務。底層是個無界佇列
    ExecutorService executorService = Executors.newFixedThreadPool(6);
    executorService.execute(new MyThread());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyThread());
    executorService.execute(new MyThread());
}

public static void testSingleThreadPool() {
    //建立單執行緒,在任務執行時,會依次執行任務。底層是個無界佇列。
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    executorService.execute(new MyThread());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyThread());
}

public static void testCacheThreadPool() {
    //建立非固定數量,可快取的執行緒池。當提交的任務數量起起伏伏時,會自動建立或者減少執行執行緒的數量。
    //當然,重用執行緒是執行緒池的基本特徵。
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(new MyThread());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyThread());
}

public static void testScheduledThreadPool(){
    //建立一個定時執行執行緒池
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(30);
    //1、配置任務的執行週期
    //scheduleAtFixedRate 固定週期執行完畢
    executorService.scheduleAtFixedRate(new MyRunnable(),0,1000,TimeUnit.MILLISECONDS);
    //scheduleWithFixedDelay 上一次執行完畢之後下一次開始執行
    executorService.scheduleWithFixedDelay(new MyRunnable(),0,1000,TimeUnit.MILLISECONDS);
}

public static void testSingleCacheThreadPool(){
    //建立一個單個執行緒執行的定時器
    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    //scheduleAtFixedRate 固定週期執行完畢
    executorService.scheduleAtFixedRate(new MyRunnable(),0,1000,TimeUnit.MILLISECONDS);
    //scheduleWithFixedDelay 上一次執行完畢之後下一次開始執行
    executorService.scheduleWithFixedDelay(new MyRunnable(),0,1000,TimeUnit.MILLISECONDS);
}

public static void testMyThreadPool(){
    //自定義連線池稍微麻煩些,不過通過建立的ThreadPoolExecutor執行緒池物件,可以獲取到當前執行緒池的尺寸、正在執行任務的執行緒數、工作佇列等等。
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,100,10,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(100));
    threadPoolExecutor.execute(new MyThread());
    threadPoolExecutor.execute(new MyRunnable());
    threadPoolExecutor.execute(new MyRunnable());
}
  • Q:多執行緒中run()和start()的區別?
    • run():僅僅是封裝被執行緒執行的程式碼,直接呼叫是普通方法
    • start():首先啟動了執行緒,然後再由JVM去呼叫該執行緒的run()方法
  • 執行緒排程
    • 分時排程模型:所有執行緒輪流使用 CPU 的使用權,平均分配每個執行緒佔用 CPU 的時間片
    • 搶佔式排程模型:優先讓優先順序高的執行緒使用 CPU,如果執行緒的優先順序相同,那麼會隨機選擇一個,優先順序高的執行緒獲取的 CPU 時間片相對多一些。
    • Java使用的是搶佔式排程模型。
  • 執行緒的生命週期
    • 新建:建立執行緒物件
    • 就緒:有執行資格,沒有執行權
    • 執行:有執行資格,有執行權
      • 阻塞:由於一些操作讓執行緒處於了該狀態。沒有執行資格,沒有執行權。而另一些操作卻可以把它啟用,啟用後處於就緒狀態。
    • 死亡:執行緒物件變成垃圾,等待回收。
      執行緒的生命週期
  • 多執行緒安全問題
    • 原因:是否是多執行緒環境;是否有共享資料 ;是否有多條語句操作共享資料。
    • 解決方式:加鎖
      • 把多條語句操作共享資料的程式碼包成一個整體,讓某個執行緒在執行的時候,別人不能來執行。
      • 同步程式碼塊:synchronized(物件){ code }
        • 物件:可以是任意物件
        • private Object obj = new Object();(成員變數,共用一把鎖)
      • 同步方法:鎖物件為this
      • JDK5之後提供lock鎖
        • lock():獲取鎖 unlock():釋放鎖。
    • Q:如何把一個執行緒不安全的集合類變成一個執行緒安全的集合類?
      • Collections工具類的帶synchronized的方法
    • Q:死鎖問題
      • 同步的弊端:效率低;容易產生死鎖
      • 死鎖:兩個或兩個以上的執行緒在爭奪資源過程中,發生的一種相互等待的現象。