Java多線程入門不完全指南

Java多線程入門不完全指南

序言

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

基礎(chǔ)知識

線程與進程

  • 線程:是操作系統(tǒng)能夠進行運算調(diào)度的最小單位旦袋,是進程中的實際運作單位。一條線程是進程中一個單一順序的控制流它改。
  • 進程:是計算機中已運行程序的實體疤孕,是線程的容器。

同步與異步

  • 同步:在發(fā)出一個調(diào)用時央拖,在沒有得到結(jié)果之前祭阀,該調(diào)用就不返回。一旦返回鲜戒,必然會得到返回值柬讨。
  • 異步:在調(diào)用發(fā)出之后,這個調(diào)用就直接返回袍啡。隨后踩官,被調(diào)用者通過狀態(tài)、通知來通知調(diào)用者境输,或者通過回調(diào)函數(shù)來處理調(diào)用蔗牡。

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

阻塞與非阻塞

  • 阻塞:調(diào)用結(jié)果返回之前嗅剖,當前線程會被掛起辩越。調(diào)用線程只有在得到結(jié)果之后才會返回。
  • 非阻塞:調(diào)用在不能立刻得到結(jié)果之前信粮,該調(diào)用不會阻塞當前線程黔攒,當前線程仍會處理其他調(diào)用。

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

并行與并發(fā)

  • 并發(fā):在同一個處理器上“同時”處理多個任務(wù)。通過cpu調(diào)度算法旅掂,讓用戶看上去是同時處理赏胚。
  • 并行:在多臺機器上同時處理多個任務(wù),真正的同時商虐。

多線程與線程安全

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

線程的狀態(tài)

5種狀態(tài)

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

  1. NEW(新建):創(chuàng)建了一個線程,尚未啟動
  2. RUNNABLE(可運行):線程創(chuàng)建后割笙,其他線程調(diào)用了該線程的start()方法沦偎,該線程便等待被CPU調(diào)度執(zhí)行。
  3. RUNNING(運行):RUNNABLE狀態(tài)的線程被CPU調(diào)度咳蔚,獲取了CPU時間片豪嚎,運行run()方法。
  4. BLOCKERD(阻塞):線程無法獲取CPU時間片谈火,暫時停止運行的狀態(tài)侈询。這個狀態(tài)的線程只有狀態(tài)轉(zhuǎn)為RUNNABLE時,才有機會被CPU調(diào)度執(zhí)行糯耍。阻塞有三種情況:
    • 等待阻塞:如RUNNING狀態(tài)的線程執(zhí)行wait()方法扔字。
    • 同步阻塞:如RUNNING狀態(tài)的線程在獲取同步鎖時,同步鎖被其他線程占用温技。
    • 其他阻塞:如RUNNING狀態(tài)的線程執(zhí)行sleep()方法或其他線程調(diào)用join()方法革为。
  5. DEAD(死亡):線程run()方法執(zhí)行結(jié)束或異常退出,該線程死亡舵鳞,結(jié)束生命周期震檩。

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

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

等待隊列和鎖池是如何工作的?

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

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

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

     /**
      * 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(阻塞):當一個線程要進入synchronized語句塊/方法時,如果沒有獲取到鎖套才,會變成BLOCKED迂猴。或者在調(diào)用Object.wait()后背伴,被notify()喚醒沸毁,再次進入synchronized語句塊/方法時,如果沒有獲取到鎖傻寂,會變成BLOCKED息尺。進入阻塞狀態(tài)是被動的。

     /**
      * 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(無限等待):等待其它線程執(zhí)行后崎逃,顯示喚醒掷倔。調(diào)用鎖對象的wait()方法并未設(shè)置時間眉孩、其它線程調(diào)用join()方法并未設(shè)置時間个绍、調(diào)用LockSupport.park()方法都會使當前線程進入此狀態(tài)勒葱。進入等待狀態(tài)是主動的。

     /**
      * 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(限期等待):等待一段時間后被系統(tǒng)喚醒巴柿,不需要顯示被喚醒凛虽。調(diào)用Thread.sleep()方法、調(diào)用鎖對象的wait()方法并設(shè)置時間广恢、其它線程調(diào)用join()方法并設(shè)置時間凯旋、調(diào)用LockSupport.parkNanos()方法、調(diào)用LockSupport.parkUntil()方法都會使線程進入此狀態(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)控

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


visualVM界面.png

VisualVM將線程的狀態(tài)分為五種:運行、休眠舰蟆、等待趣惠、駐留、監(jiān)視身害,與Thread類中的線程狀態(tài)對應(yīng)如下:

Thread類 VisualVM
RUNNABLE 運行
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)存原子性味悄、可見性和有序性

Java內(nèi)存模型

概念

  1. 線程之間通信由Java內(nèi)存模型控制,內(nèi)存模型決定了一個線程對共享變量的寫入何時對另一個線程可見塌鸯。
  2. 每個線程都被抽象出一個保存共享變量副本的工作內(nèi)存侍瑟,線程對共享變量的操作都在此工作內(nèi)存中進行。
  3. 某個線程無法直接訪問其他線程中的變量丙猬,線程之間的通信需要通過主內(nèi)存來實現(xiàn)丢习。


    內(nèi)存模型

線程通信過程

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

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

  • 原子性:類似于數(shù)據(jù)庫事務(wù)袜腥,要么全部執(zhí)行见擦,要么不執(zhí)行。
    如:num++不具有原子性羹令,先取出num的值鲤屡,再進行加1。
    a=1,return a則都具有原子性福侈。
  • 可見性:某個線程對共享變量做了修改后酒来,其他線程可以立馬感知到該變量的修改。
  • 有序性:若在本線程內(nèi)觀察肪凛,所有操作都是有序的堰汉;若在一個線程中觀察其他線程辽社,所有的操作都是無序的。前半句指“線程內(nèi)表現(xiàn)為串行語義”翘鸭,后半句指“指令重排序”現(xiàn)象和“工作內(nèi)存中主內(nèi)存同步延遲”現(xiàn)象滴铅。

Sychronized與Volatile

  • Sychronized:可以保證原子性、可見性和有序性就乓。
    Sychronized關(guān)鍵字能保證在同一時刻汉匙,只有一個線程可以獲取鎖執(zhí)行同步代碼,執(zhí)行完之后釋放鎖之前生蚁,會將修改后變量的值刷新的主內(nèi)存中噩翠。
  • Volatile:可以保證可見性和有序性,不能保證操作的原子性邦投。
    被Volatile關(guān)鍵字修飾的變量绎秒,在寫操作后會加入一條store指令,強行將共享變量最新的值刷新到主內(nèi)存中尼摹。在讀操作前见芹,會加入一條load指令,強行從主內(nèi)存中讀取共享變量最新的值蠢涝。

Java鎖機制

Java中的鎖

  • Sychronized
  • ReentrantLock
  • ReentrantReadWriteLock

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

Java多線程實現(xiàn)

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

  • 繼承Thread類:重寫run()方法惯吕,創(chuàng)建線程后調(diào)用start()方法啟動惕它。

      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();
          }
      }
    
  • 實現(xiàn)Runnable接口:實現(xiàn)run()方法废登,創(chuàng)建線程后調(diào)用start()方法啟動淹魄。

      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();
          }
      }
    
  • 實現(xiàn)Callable接口:實現(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線程池框架

為什么要用線程池?

  • 通過復(fù)用“池”中的已有線程羽戒,減少線程創(chuàng)建和銷毀的開銷缤沦,提高性能。
  • 可以設(shè)置最大并發(fā)線程數(shù)易稠,避免過多線程競爭資源缸废。

Executors創(chuàng)建線程池

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

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

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

      public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
          return new ScheduledThreadPoolExecutor(corePoolSize);
      }
    
  • newSingleThreadExecutor創(chuàng)建一個單線程來執(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();
                      }
                  }
              }
          }
    

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拇勃,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子孝凌,更是在濱河造成了極大的恐慌方咆,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蟀架,死亡現(xiàn)場離奇詭異瓣赂,居然都是意外死亡,警方通過查閱死者的電腦和手機片拍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門煌集,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捌省,你說我怎么就攤上這事苫纤。” “怎么了纲缓?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵卷拘,是天一觀的道長。 經(jīng)常有香客問我祝高,道長栗弟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任工闺,我火速辦了婚禮横腿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘斤寂。我一直安慰自己耿焊,他們只是感情好,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布遍搞。 她就那樣靜靜地躺著罗侯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪溪猿。 梳的紋絲不亂的頭發(fā)上钩杰,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天纫塌,我揣著相機與錄音,去河邊找鬼讲弄。 笑死措左,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的避除。 我是一名探鬼主播怎披,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瓶摆!你這毒婦竟也來了凉逛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤群井,失蹤者是張志新(化名)和其女友劉穎状飞,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體书斜,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡诬辈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了荐吉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焙糟。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖稍坯,靈堂內(nèi)的尸體忽然破棺而出酬荞,到底是詐尸還是另有隱情,我是刑警寧澤瞧哟,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布混巧,位于F島的核電站,受9級特大地震影響勤揩,放射性物質(zhì)發(fā)生泄漏咧党。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一陨亡、第九天 我趴在偏房一處隱蔽的房頂上張望傍衡。 院中可真熱鬧,春花似錦负蠕、人聲如沸殖告。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至真仲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工芭概, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惩嘉。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓罢洲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親文黎。 傳聞我的和親對象是個殘疾皇子惹苗,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354