Java並發編程(02):執行緒核心機制,基礎概念擴展

  • 2020 年 3 月 15 日
  • 筆記

本文源碼:GitHub·點這裡 || GitEE·點這裡

一、執行緒基本機制

1、概念描述

並發編程的特點是:可以將程式劃分為多個分離且獨立運行的任務,通過執行緒來驅動這些獨立的任務執行,從而提升整體的效率。下面提供一個基礎的演示案例。

2、應用案例

場景:假設有一個容器集合,需要拿出容器中的每個元素,進行加工處理,一般情況下直接遍歷就好,如果數據偏大,可以根據執行緒數量對集合切割,每個執行緒處理一部分數據,這樣處理時間就會減少很多。

public class ExtendThread01 {      public static void main(String[] args) {          List<Object> dataList = new ArrayList<>() ;          dataList.add("A");          dataList.add("B");          dataList.add("C");          // 把一個大的集合按照每個子集合的2個元素切割          List<List<Object>> splitList = splitList(dataList,2);          for (List<Object> list:splitList){              System.out.println(list);          }          // 多執行緒處理          for (List<Object> childList:splitList){              ListTask listTask = new ListTask(childList) ;              Thread runThread = new Thread(listTask);              runThread.start();          }      }      /**       * List 集合切割       */      private static List<List<Object>> splitList (List<Object> list, int childSize) {          if (list == null || list.size() == 0 || childSize < 1) {              return null;          }          List<List<Object>> result = new ArrayList<>();          int size = list.size();          int count = (size + childSize - 1) / childSize ;          for (int i = 0; i < count; i++) {              List<Object> subList = list.subList(i * childSize, ((i + 1) * childSize > size ? size : childSize * (i + 1)));              result.add(subList);          }          return result;      }  }  class ListTask implements Runnable {      private List<Object> list ;      public ListTask (List<Object> list){this.list=list;}      @Override      public void run() {          for (Object object:list){              System.out.println(Thread.currentThread().getName()+"=="+object);          }      }  }

注意:這裡案例只是對場景原理的實現,在開發中,是不允許這種操作的,需要使用執行緒池處理,後續會說。如果集合沒有控制好,導致大量創建Thread執行緒,導致記憶體溢出。

二、執行緒停止啟動

1、基礎流程

執行緒啟動後執行任務方法,在執行過程中可以被阻塞,休眠,喚醒,停止等一系列狀態操作。

執行緒休眠作用:當執行緒一部分任務執行完畢後進入休眠(阻塞)狀態,執行緒調度器可以切換到另外執行緒,這對分布任務的執行相對公平。

2、使用案例

public class ExtendThread02 {      public static void main(String[] args) {          StopThread stopThread = new StopThread() ;          stopThread.start();          // 標記當前執行緒停止訊號,且拋出中斷異常,但沒有停止          stopThread.interrupt();          // 判斷當前執行緒是否已經是終止狀態          System.out.println("1=="+stopThread.isInterrupted());          // 清除當前執行緒的終止訊號          System.out.println("2=="+stopThread.interrupted());          // 再次判斷當前執行緒狀態          System.out.println("3=="+stopThread.isInterrupted());          System.out.println("main end ...");      }  }  class StopThread extends Thread {      @Override      public void run() {          for (int i = 0 ; i < 10 ; i++){              try {                  System.out.println(Thread.currentThread().getId()+"="+i);                  // 執行緒阻塞1秒                  Thread.sleep(1000);              } catch (InterruptedException e){                  e.printStackTrace();              }          }      }  }

3、核心方法

sleep(long millis):執行緒休眠指定的時間,進入阻塞狀態;

interrupt():切換執行緒為中斷狀態,拋出中斷異常,不會停止執行緒,可以監視執行緒的中斷狀態並定義執行策略。

interrupted():清除調用該方法執行緒的中斷狀態,也不會影響執行緒的執行,且返回當前執行『stopThread.interrupted()』的執行緒是否中斷,這裡就是指main執行緒是否中斷。

isInterrupted():判斷調用該方法的執行緒是否已經是中斷狀態。

補刀一句:執行緒的這幾個方法極其容易混淆,需要斷點源碼追蹤一下看看,進入源碼方法,調用相關API查看一下狀態。(附斷點圖一張:)

三、執行緒優先順序

1、基礎概念

CPU執行和處理執行緒的順序是不確定的,但是執行緒調度器傾向執行執行緒優先順序高的執行緒,執行緒優先順序高說明獲取CPU資源的概率高,或者獲取的執行時間分片多,但不代表優先順序低的一定最後執行。

2、使用案例

public class ExtendThread03 {      public static void main(String[] args) {          Priority01 priority01 = new Priority01();          priority01.start();          System.out.println("priority01="+priority01.getPriority());          Priority02 priority02 = new Priority02();          priority02.start();          System.out.println("priority02="+priority02.getPriority());          priority01.setPriority(10);          priority02.setPriority(1);      }  }  class Priority01 extends Thread {      @Override      public void run() {          for (int i = 0 ; i < 100 ; i++){              System.out.println(Thread.currentThread().getName()+";i="+i);          }      }  }  class Priority02 extends Thread {      @Override      public void run() {          for (int a = 0 ; a < 100 ; a++){              System.out.println(Thread.currentThread().getName()+";a="+a);          }      }  }

注意:優先順序範圍[MAX_PRIORITY=10,MIN_PRIORITY=1],如果超出範圍會拋出IllegalArgumentException異常。

建議:通常實際開發中,是不允許輕易修改執行緒運行的參數,容易引發認知之外的異常。

四、執行緒加入

1、基本概念

如果在執行緒A中,執行執行緒B的加入方法,那麼A執行緒就會等待執行緒B執行完畢再返回繼續執行。

2、使用案例

public class ExtendThread04 {      public static void main(String[] args) {          JoinThreadA joinThreadA = new JoinThreadA() ;          joinThreadA.start();      }  }  class JoinThreadA extends Thread {      @Override      public void run() {          System.out.println("缺水中...");          JoinThreadB joinThreadB = new JoinThreadB() ;          joinThreadB.start();          try{              joinThreadB.join();          } catch (Exception e){              e.printStackTrace();          }          System.out.println("喝水中...");      }  }  class JoinThreadB extends Thread {      @Override      public void run() {          System.out.println("買水中...");          try{              TimeUnit.SECONDS.sleep(2);          } catch (Exception e){              e.printStackTrace();          }          System.out.println("買到水...");      }  }

注意:可以設置執行緒的加入時間join(long),畢竟等不到雪月風花,人生都是有時差,只能後會無期了。

五、本地執行緒

1、基本概念

本地的執行緒變數,底層維護ThreadLocalMap存儲值:

static class Entry extends WeakReference<ThreadLocal<?>> {      Object value;      Entry(ThreadLocal<?> k, Object v) {          super(k);          value = v;      }  }

即以Key-Value鍵值對的方式存儲數據。如果對集合容器的源碼熟悉的話,這個Entry就是似曾相識感覺。

2、使用案例

public class ExtendThread05 {      private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>() ;      private static void initBegin (){          threadLocal.set(System.currentTimeMillis());      }      private static Long overTime (){          return System.currentTimeMillis()-threadLocal.get();      }      public static void main(String[] args) throws Exception {          ExtendThread05.initBegin();          TimeUnit.SECONDS.sleep(3);          System.out.println(ExtendThread05.overTime());      }  }

ThreadLocal提供執行緒記憶體儲變數的能力,並且綁定到當前執行緒,通過get和set方法就可以得到和設置當前執行緒對應的值。這個在web開發中是常見的應用。

六、守護執行緒

1、基本概念

守護執行緒是支援輔助型執行緒,主要在程式中起到調度和支援性作用,當Jvm中非守護執行緒全部結束,守護執行緒也就會結束。

2、使用案例

public class ExtendThread06 {      public static void main(String[] args) throws Exception {          InputStreamReader is = new InputStreamReader(System.in);          BufferedReader br = new BufferedReader(is);          String value = br.readLine();          CheckThread checkThread = new CheckThread(value) ;          checkThread.setDaemon(true);          checkThread.start();          System.out.println("Main End ...");      }  }  class CheckThread extends Thread {      private String spell ;      public CheckThread (String spell){          this.spell = spell ;      }      @Override      public void run() {          if (spell.startsWith("cs")){              System.out.println(spell+":輸入正確");          } else {              System.out.println(spell+":輸入錯誤");          }          try {              TimeUnit.SECONDS.sleep(10);          } catch (InterruptedException e){              e.printStackTrace();          }      }  }

注意:守護執行緒需要顯式調用setDaemon(true);方法,這裡可以看到main執行緒結束,整合程式就結束了,絲毫理會休眠中的守護執行緒。如果打斷非守護執行緒的休眠,試試會不會拋你一臉異常?

七、執行緒異常處理

1、機制描述

按照Java中異常處理機制,拋異常逐級降低,執行緒的任務方法run沒有拋異常,那重寫或者實現的方法自然不能直接throws異常出去。多執行緒中推薦捕獲異常,可以針對性處理機制。

2、使用案例

public class ExtendThread07 {      public static void main(String[] args) {          TryThread tryThread = new TryThread();          tryThread.setName("try-name");          // 定義運行中異常處理策略          MyExe myExe = new MyExe() ;          tryThread.setUncaughtExceptionHandler(myExe);          tryThread.start();      }  }  class TryThread extends Thread {      @Override      public void run() {          try {              Thread.sleep(1000);          } catch (InterruptedException e){              e.printStackTrace();          }          // 如何處理這裡異常?          Integer.parseInt("cicada") ;      }  }  class MyExe implements Thread.UncaughtExceptionHandler {      @Override      public void uncaughtException(Thread t, Throwable e) {          System.out.println(t.getName()+";異常:"+e.getMessage());      }  }

通過實現UncaughtExceptionHandler介面,並且執行緒要指定自定義異常處理對象,也可以處理未檢查的異常。

八、源程式碼地址

GitHub·地址  https://github.com/cicadasmile/java-base-parent  GitEE·地址  https://gitee.com/cicadasmile/java-base-parent