Java多線程入門(mén)不完全指南

Java多線程入門(mén)不完全指南

序言

最近在讀《把時(shí)間當(dāng)作朋友》,序言就教導(dǎo)我“無(wú)論是誰(shuí),都是在某一刻意識(shí)到時(shí)間的珍貴,并且注定會(huì)因懂事太晚而多少有些后悔”。想想我,快走出奔三的泥沼,踏入奔四的深淵,越來(lái)越意識(shí)到時(shí)間的彌足珍貴。于是就想著,趁著還有些精力的時(shí)候,記錄一下所聞、所學(xué)、所感。思考良久,記錄些什么呢?先記錄一下我這拙劣而又不放棄的Java學(xué)習(xí)之路吧,挖坑一篇"粗而廣"的Java多線程介紹壓壓驚。

基礎(chǔ)知識(shí)

線程與進(jìn)程

  • 線程:是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位,是進(jìn)程中的實(shí)際運(yùn)作單位。一條線程是進(jìn)程中一個(gè)單一順序的控制流。
  • 進(jìn)程:是計(jì)算機(jī)中已運(yùn)行程序的實(shí)體,是線程的容器。

同步與異步

  • 同步:在發(fā)出一個(gè)調(diào)用時(shí),在沒(méi)有得到結(jié)果之前,該調(diào)用就不返回。一旦返回,必然會(huì)得到返回值。
  • 異步:在調(diào)用發(fā)出之后,這個(gè)調(diào)用就直接返回。隨后,被調(diào)用者通過(guò)狀態(tài)、通知來(lái)通知調(diào)用者,或者通過(guò)回調(diào)函數(shù)來(lái)處理調(diào)用。

同步與異步關(guān)注的是消息通信機(jī)制。

阻塞與非阻塞

  • 阻塞:調(diào)用結(jié)果返回之前,當(dāng)前線程會(huì)被掛起。調(diào)用線程只有在得到結(jié)果之后才會(huì)返回。
  • 非阻塞:調(diào)用在不能立刻得到結(jié)果之前,該調(diào)用不會(huì)阻塞當(dāng)前線程,當(dāng)前線程仍會(huì)處理其他調(diào)用。

阻塞與非阻塞關(guān)注的是程序在等待調(diào)用結(jié)果(消息,返回值)時(shí)的狀態(tài)。

并行與并發(fā)

  • 并發(fā):在同一個(gè)處理器上“同時(shí)”處理多個(gè)任務(wù)。通過(guò)cpu調(diào)度算法,讓用戶看上去是同時(shí)處理。
  • 并行:在多臺(tái)機(jī)器上同時(shí)處理多個(gè)任務(wù),真正的同時(shí)。

多線程與線程安全

  • 多線程:一個(gè)程序?qū)崿F(xiàn)多個(gè)線程并發(fā)執(zhí)行。
  • 線程安全:多個(gè)線程同時(shí)執(zhí)行同一段代碼,線程的調(diào)度順序不會(huì)影響該段代碼的任何結(jié)果。線程安全主要通過(guò)線程同步實(shí)現(xiàn)。

線程的狀態(tài)

5種狀態(tài)

根據(jù)線程的生命周期,可以將線程分為以下5種狀態(tài):

  1. NEW(新建):創(chuàng)建了一個(gè)線程,尚未啟動(dòng)
  2. RUNNABLE(可運(yùn)行):線程創(chuàng)建后,其他線程調(diào)用了該線程的start()方法,該線程便等待被CPU調(diào)度執(zhí)行。
  3. RUNNING(運(yùn)行):RUNNABLE狀態(tài)的線程被CPU調(diào)度,獲取了CPU時(shí)間片,運(yùn)行run()方法。
  4. BLOCKERD(阻塞):線程無(wú)法獲取CPU時(shí)間片,暫時(shí)停止運(yùn)行的狀態(tài)。這個(gè)狀態(tài)的線程只有狀態(tài)轉(zhuǎn)為RUNNABLE時(shí),才有機(jī)會(huì)被CPU調(diào)度執(zhí)行。阻塞有三種情況:
    • 等待阻塞:如RUNNING狀態(tài)的線程執(zhí)行wait()方法。
    • 同步阻塞:如RUNNING狀態(tài)的線程在獲取同步鎖時(shí),同步鎖被其他線程占用。
    • 其他阻塞:如RUNNING狀態(tài)的線程執(zhí)行sleep()方法或其他線程調(diào)用join()方法。
  5. DEAD(死亡):線程run()方法執(zhí)行結(jié)束或異常退出,該線程死亡,結(jié)束生命周期。

狀態(tài)轉(zhuǎn)換

線程狀態(tài)轉(zhuǎn)換圖

等待隊(duì)列和鎖池是如何工作的?

Thread類(lèi)定義的線程狀態(tài)

  1. NEW(新建):線程創(chuàng)建后未啟動(dòng)。

     /**
      * Thread state for a thread which has not yet started.
      */
    
  2. RUNNABLE(可運(yùn)行):等待系統(tǒng)資源運(yùn)行的線程。

     /**
      * Thread state for a runnable thread.  A thread in the runnable
      * state is executing in the Java virtual machine but it may
      * be waiting for other resources from the operating system
      * such as processor.
      */
    
  3. BLOCKED(阻塞):當(dāng)一個(gè)線程要進(jìn)入synchronized語(yǔ)句塊/方法時(shí),如果沒(méi)有獲取到鎖,會(huì)變成BLOCKED?;蛘咴谡{(diào)用Object.wait()后,被notify()喚醒,再次進(jìn)入synchronized語(yǔ)句塊/方法時(shí),如果沒(méi)有獲取到鎖,會(huì)變成BLOCKED。進(jìn)入阻塞狀態(tài)是被動(dòng)的。

     /**
      * Thread state for a thread blocked waiting for a monitor lock.
      * A thread in the blocked state is waiting for a monitor lock
      * to enter a synchronized block/method or
      * reenter a synchronized block/method after calling
      * {@link Object#wait() Object.wait}.
      */
    
  4. WAITING(無(wú)限等待):等待其它線程執(zhí)行后,顯示喚醒。調(diào)用鎖對(duì)象的wait()方法并未設(shè)置時(shí)間、其它線程調(diào)用join()方法并未設(shè)置時(shí)間、調(diào)用LockSupport.park()方法都會(huì)使當(dāng)前線程進(jìn)入此狀態(tài)。進(jìn)入等待狀態(tài)是主動(dòng)的。

     /**
      * Thread state for a waiting thread.
      * A thread is in the waiting state due to calling one of the
      * following methods:
      * <ul>
      *   <li>{@link Object#wait() Object.wait} with no timeout</li>
      *   <li>{@link #join() Thread.join} with no timeout</li>
      *   <li>{@link LockSupport#park() LockSupport.park}</li>
      * </ul>
      *
      * <p>A thread in the waiting state is waiting for another thread to
      * perform a particular action.
      *
      * For example, a thread that has called <tt>Object.wait()</tt>
      * on an object is waiting for another thread to call
      * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
      * that object. A thread that has called <tt>Thread.join()</tt>
      * is waiting for a specified thread to terminate.
      */
    
  5. TIMED_WAITING(限期等待):等待一段時(shí)間后被系統(tǒng)喚醒,不需要顯示被喚醒。調(diào)用Thread.sleep()方法、調(diào)用鎖對(duì)象的wait()方法并設(shè)置時(shí)間、其它線程調(diào)用join()方法并設(shè)置時(shí)間、調(diào)用LockSupport.parkNanos()方法、調(diào)用LockSupport.parkUntil()方法都會(huì)使線程進(jìn)入此狀態(tài)。

     /**
      * Thread state for a waiting thread with a specified waiting time.
      * A thread is in the timed waiting state due to calling one of
      * the following methods with a specified positive waiting time:
      * <ul>
      *   <li>{@link #sleep Thread.sleep}</li>
      *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
      *   <li>{@link #join(long) Thread.join} with timeout</li>
      *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
      *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
      * </ul>
      */
    
  6. TERMINATED(結(jié)束):線程run()方法執(zhí)行結(jié)束或異常退出后的線程狀態(tài)。

     /**
      * Thread state for a terminated thread.
      * The thread has completed execution.
      */
    

VisualVM線程監(jiān)控

通過(guò)JDK自帶的VisualVM工具可以監(jiān)控線程的運(yùn)行狀態(tài),按照下圖操作可以抓取線程Dump,監(jiān)控線程的狀態(tài)。


visualVM界面.png

VisualVM將線程的狀態(tài)分為五種:運(yùn)行、休眠、等待、駐留、監(jiān)視,與Thread類(lèi)中的線程狀態(tài)對(duì)應(yīng)如下:

Thread類(lèi) VisualVM
RUNNABLE 運(yùn)行
TIMED_WAITING (sleeping) 休眠
TIMED_WAITING (on object monitor) WAITING (on object monitor) 等待
TIMED_WAITING (parking) WAITING (parking) 駐留
BLOCKED (on object monitor) 監(jiān)視

內(nèi)存原子性、可見(jiàn)性和有序性

Java內(nèi)存模型

概念

  1. 線程之間通信由Java內(nèi)存模型控制,內(nèi)存模型決定了一個(gè)線程對(duì)共享變量的寫(xiě)入何時(shí)對(duì)另一個(gè)線程可見(jiàn)。
  2. 每個(gè)線程都被抽象出一個(gè)保存共享變量副本的工作內(nèi)存,線程對(duì)共享變量的操作都在此工作內(nèi)存中進(jìn)行。
  3. 某個(gè)線程無(wú)法直接訪問(wèn)其他線程中的變量,線程之間的通信需要通過(guò)主內(nèi)存來(lái)實(shí)現(xiàn)。


    內(nèi)存模型

線程通信過(guò)程

  1. 線程A從主內(nèi)存中拷貝共享變量1到工作內(nèi)存中的副本,對(duì)副本的值進(jìn)行修改。
  2. 線程A刷新修改后的值到主內(nèi)存中。
  3. 線程B將主內(nèi)存中共享變量1拷貝到工作內(nèi)存中。

并發(fā)編程三個(gè)特征

  • 原子性:類(lèi)似于數(shù)據(jù)庫(kù)事務(wù),要么全部執(zhí)行,要么不執(zhí)行。
    如:num++不具有原子性,先取出num的值,再進(jìn)行加1。
    a=1,return a則都具有原子性。
  • 可見(jiàn)性:某個(gè)線程對(duì)共享變量做了修改后,其他線程可以立馬感知到該變量的修改。
  • 有序性:若在本線程內(nèi)觀察,所有操作都是有序的;若在一個(gè)線程中觀察其他線程,所有的操作都是無(wú)序的。前半句指“線程內(nèi)表現(xiàn)為串行語(yǔ)義”,后半句指“指令重排序”現(xiàn)象和“工作內(nèi)存中主內(nèi)存同步延遲”現(xiàn)象。

Sychronized與Volatile

  • Sychronized:可以保證原子性、可見(jiàn)性和有序性。
    Sychronized關(guān)鍵字能保證在同一時(shí)刻,只有一個(gè)線程可以獲取鎖執(zhí)行同步代碼,執(zhí)行完之后釋放鎖之前,會(huì)將修改后變量的值刷新的主內(nèi)存中。
  • Volatile:可以保證可見(jiàn)性和有序性,不能保證操作的原子性。
    被Volatile關(guān)鍵字修飾的變量,在寫(xiě)操作后會(huì)加入一條store指令,強(qiáng)行將共享變量最新的值刷新到主內(nèi)存中。在讀操作前,會(huì)加入一條load指令,強(qiáng)行從主內(nèi)存中讀取共享變量最新的值。

Java鎖機(jī)制

Java中的鎖

  • Sychronized
  • ReentrantLock
  • ReentrantReadWriteLock

這三種鎖是怎么實(shí)現(xiàn)的?什么是AQS?什么是CAS?CAS的ABA問(wèn)題怎么解決?集群環(huán)境下如何實(shí)現(xiàn)同步?有待后續(xù)分曉

Java多線程實(shí)現(xiàn)

創(chuàng)建線程的方式

  • 繼承Thread類(lèi):重寫(xiě)run()方法,創(chuàng)建線程后調(diào)用start()方法啟動(dòng)。

      public class ThreadOne extends Thread {
      private static Integer num = 100;
    
      @Override
      public void run() {
          synchronized (num) {
              while (num > 0) {
                  try {
                      Thread.sleep((long) (Math.random() * 100));
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.print("線程:" + this.getName() + "執(zhí)行num--操作后,");
                  num--;
                  System.out.println("num的值為:" + num);
              }
          }
    
      }
    
      public static void main(String[] args) {
          Thread t1 = new ThreadOne();
          Thread t2 = new ThreadOne();
          Thread t3 = new ThreadOne();
          t1.start();
          t2.start();
          t3.start();
          }
      }
    
  • 實(shí)現(xiàn)Runnable接口:實(shí)現(xiàn)run()方法,創(chuàng)建線程后調(diào)用start()方法啟動(dòng)。

      public class ThreadTwo implements Runnable{
      private static Integer num = 100;
    
      @Override
      public void run() {
          synchronized (num) {
              while (num > 0) {
                  try {
                      Thread.sleep((long) (Math.random() * 100));
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.print("線程:" + Thread.currentThread().getName() + "執(zhí)行num--操作后,");
                  num--;
                  System.out.println("num的值為:" + num);
              }
          }
    
      }
    
      public static void main(String[] args) {
          Thread t1 = new Thread(new ThreadTwo());
          Thread t2 = new Thread(new ThreadTwo());
          Thread t3 = new Thread(new ThreadTwo());
          t1.start();
          t2.start();
          t3.start();
          }
      }
    
  • 實(shí)現(xiàn)Callable接口:實(shí)現(xiàn)call()方法,可以創(chuàng)建有返回值的線程。

      public class ThreadThree implements Callable<Integer> {
          private static Integer num = 100;
      
          @Override
          public Integer call() throws Exception {
              synchronized (num) {
                  while (num > 0) {
                      try {
                          Thread.sleep((long) (Math.random() * 100));
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      num--;
                  }
              }
              return num;
          }
    
      public static void main(String[] args) throws InterruptedException, ExecutionException {
          FutureTask<Integer> task1 = new FutureTask<>(new ThreadThree());
          FutureTask<Integer> task2 = new FutureTask<>(new ThreadThree());
          FutureTask<Integer> task3 = new FutureTask<>(new ThreadThree());
    
          Thread t1 = new Thread(task1);
          Thread t2 = new Thread(task2);
          Thread t3 = new Thread(task3);
    
          t1.start();
          t2.start();
          t3.start();
    
          System.out.println("線程1的返回值:" + task1.get());
          System.out.println("線程2的返回值:" + task2.get());
          System.out.println("線程3的返回值:" + task3.get());
          }
      }
    

Executor線程池框架

為什么要用線程池?

  • 通過(guò)復(fù)用“池”中的已有線程,減少線程創(chuàng)建和銷(xiāo)毀的開(kāi)銷(xiāo),提高性能。
  • 可以設(shè)置最大并發(fā)線程數(shù),避免過(guò)多線程競(jìng)爭(zhēng)資源。

Executors創(chuàng)建線程池

  • newFixedThreadPool創(chuàng)建固定線程數(shù)的線程池。若所有線程都處于活動(dòng)狀態(tài),新提交的任務(wù)會(huì)在隊(duì)列中等待。若某個(gè)線程異常結(jié)束,則線程池會(huì)重新補(bǔ)充一個(gè)新線程。

      public static ExecutorService newFixedThreadPool(int nThreads) {
          return new ThreadPoolExecutor(nThreads, nThreads,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>());
      }
    
  • newCachedThreadPool創(chuàng)建可緩存的線程池,若線程池中的線程超過(guò)60s未被使用會(huì)被移除。

      public static ExecutorService newCachedThreadPool() {
          return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                        60L, TimeUnit.SECONDS,
                                        new SynchronousQueue<Runnable>());
      }
    
  • newScheduledThreadPool創(chuàng)建定時(shí)線程,可定時(shí)執(zhí)行任務(wù)。

      public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
          return new ScheduledThreadPoolExecutor(corePoolSize);
      }
    
  • newSingleThreadExecutor創(chuàng)建一個(gè)單線程來(lái)執(zhí)行任務(wù)。

      public static ExecutorService newSingleThreadExecutor() {
          return new FinalizableDelegatedExecutorService
              (new ThreadPoolExecutor(1, 1,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>()));
      }
    

線程池執(zhí)行任務(wù)

  • 使用execute()方法執(zhí)行Runnable任務(wù)

      public class RunnableThreadPool {
          public static void main(String[] args) {
              ExecutorService eService = Executors.newFixedThreadPool(5);
              for (int i = 0; i < 10; i++) {
                  eService.execute(new Runnable() {
                      
                      @Override
                      public void run() {
                          System.out.println(Thread.currentThread().getName() + "正在執(zhí)行");
                      }
                  });
              }
              eService.shutdown();
      
          }
      }
    
  • 使用submit()方法執(zhí)行Callable任務(wù)

          public class CallableThreadPool {
              public static void main(String[] args) {
                  ExecutorService eService = Executors.newFixedThreadPool(5);
                  List<Future<String>> resultList = new ArrayList<Future<String>>();
                  for (int i = 0; i < 10; i++) {
                      Future<String> future = eService.submit(new Callable<String>() {
          
                          @Override
                          public String call() throws Exception {
                              return Thread.currentThread().getName() + "已執(zhí)行call方法并成功返回";
                          }
                      });
                      resultList.add(future);
                  }
                  eService.shutdown();
                  
                  for (Future<String> future : resultList) {
                      //等待future返回結(jié)果
                      while(!future.isDone());
                      try {
                          System.out.println(future.get());
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      } catch (ExecutionException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
    

那么線程池的實(shí)現(xiàn)原理是什么?有待后續(xù)見(jiàn)分曉

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容