久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

快速掌握并發(fā)編程---基礎(chǔ)篇

 田維常 2020-10-06

回復(fù)“面試”獲取全套面試資料

進程與線程

進程

進程的本質(zhì)是一個正在執(zhí)行的程序,,程序運行時系統(tǒng)會創(chuàng)建一個進程,并且給每個進程分配獨立的內(nèi)存地址空間保證每個進程地址不會相互干擾,。同時,,在 CPU 對進程做時間片的切換時,保證進程切換過程中仍然要從進程切換之前運行的位置出開始執(zhí)行,。所以進程通常還會包括程序計數(shù)器,、堆棧指針。

相對好理解點的解釋:電腦上開啟QQ就是開啟一個進程,、打開IDEA就是開啟一個進程,、打開瀏覽器也是開啟一個進程…..

當電腦開啟太多的應(yīng)用(QQ,微信,,瀏覽器,、PDF、word,、IDEA等)后,,很容易出現(xiàn)卡頓,甚至死機的情況,,最主要的原因是因為CPU在一直不停地切換,。下圖展示了單核CPU情況下,,多進程之間的切換過程。

有了進程以后,,可以讓操作系統(tǒng)從宏觀層面實現(xiàn)多應(yīng)用并發(fā),。而并發(fā)的實現(xiàn)是通過 CPU 時間片不斷切換執(zhí)行的。對于單核 CPU 來說,,在任意一個時刻只會有一個進程在被CPU 調(diào)度,。

線程

已經(jīng)有了進程,為什么還要搞個線程呢,?難道是為了為難我們程序員,?非也非也…下面來說說線程。

計算機中的程序關(guān)于某數(shù)據(jù)集合上的一次運行活動,,是系統(tǒng)進行資源分配和調(diào)度的基本單位,,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)。在早期面向進程設(shè)計的計算機結(jié)構(gòu)中,,進程是程序的基本執(zhí)行實體,;在當代面向線程設(shè)計的計算機結(jié)構(gòu)中,進程是線程的容器,。程序是指令,、數(shù)據(jù)及其組織形式的描述,進程是程序的實體,。

有時被稱為輕量級進程(Lightweight Process,,LWP),是程序執(zhí)行流的最小單元,。線程是程序中一個單一的順序控制流程,。進程內(nèi)一個相對獨立的、可調(diào)度的執(zhí)行單元,,是系統(tǒng)獨立調(diào)度和分派 CPU 的基本單位指運行中的程序的調(diào)度單位,。在單個程序中同時運行多個線程完成不同的工作,稱為多線程,。

既然有了進程,,那么線程出現(xiàn)有何意義呢?

  1. 在多核 CPU 中,,利用多線程可以實現(xiàn)真正意義上的并行執(zhí)行

  2. 在一個應(yīng)用進程中,,會存在多個同時執(zhí)行的任務(wù),如果其中一個任務(wù)被阻塞,,將會引起不依賴該任務(wù)的任務(wù)也被阻塞,。通過對不同任務(wù)創(chuàng)建不同的線程去處理,可以提升程序處理的實時性

  3. 線程可以認為是輕量級的進程,所以線程的創(chuàng)建,、銷毀比進程更快

進程VS線程

這也是面試中很容易被問到的,,請大家用心領(lǐng)會。下面就來說說進程與線程的區(qū)別

  1. 進程是資源分配的最小單位,,線程是程序執(zhí)行的最小單位,。

  2. 進程有自己的獨立地址空間,每啟動一個進程,,系統(tǒng)就會為它分配地址空間,,建立數(shù)據(jù)表來維護代碼段、堆棧段和數(shù)據(jù)段,,這種操作非常昂貴,。而線程是共享進程中的數(shù)據(jù)的,使用相同的地址空間,,因此 CPU 切換一個線程的花費遠比進程要小很多,,同時創(chuàng)建一個線程的開銷也比進程要小很多,線程的上下文切換的性能消耗要小于進程,。

  3. 線程之間的通信更方便,,同一進程下的線程共享全局變量、靜態(tài)變量等數(shù)據(jù),,而進程之間的通信需要以通信的方式(IPC)進行。不過如何處理好同步與互斥是編寫多線程程序的難點,。

  4. 但是多進程程序更健壯,,多線程程序只要有一個線程死掉,整個進程也死掉了,,而一個進程死掉并不會對另外一個進程造成影響,,因為進程有自己獨立的地址空間。

線程的應(yīng)用

線程的創(chuàng)建方式有哪些,?

在 Java 中,,有多種方式來實現(xiàn)多線程。和古代練習武功一樣,,萬變不離其宗,,所有的線程啟動方式都是基于Thread來做文章的。所以線程的根還是Thread這個類,。常用創(chuàng)建線程的方式有下幾種方式:

  • 繼承java.lang.Thread

  • 實現(xiàn) java.lang.Runnable

  • 使用java.util.concurrent.ExecutorService,、Callable、Future實現(xiàn)帶返回結(jié)果的多線程,。

繼承` Thread`類創(chuàng)建線程
public class ThreadDemo extends Thread{
 @Override
 public void run() {
  System.out.println("start thread");
 }

 public static void main(String[] args) {
  ThreadDemo threadDemo1=new ThreadDemo();
  ThreadDemo threadDemo2=new ThreadDemo();
  threadDemo1.start();
  threadDemo2.start();
 }
}

Thread 類本質(zhì)上是實現(xiàn)了 Runnable 接口的一個實例,,代表一個線程的實例。

1public class Thread implements Runnable {
2 public synchronized void start() 
3  if (threadStatus != 0)
4   throw new IllegalThreadStateException(); 
5  group.add(this); 
6  boolean started = false;
7  try {
8   //調(diào)用start0方法
9   start0();
10   started = true;
11  } finally {
12   try {
13 if (!started) {
14  group.threadStartFailed(this);
15 }
16   } catch (Throwable ignore) {
17 /* do nothing. If start0 threw a Throwable then
18   it will be passed up the call stack */

19   }
20  }
21 }
22 //native修飾的方法,只有觀看虛擬機源碼才知道這里面是怎么玩的,,
23 //個人認為看虛擬機源碼的必要性不是很強,,如果有精力的話也不妨去瞧瞧
24 private native void start0();
25}

啟動線程的唯一方法就是通過 Thread類的 start()實例方法。start()方法是一個 native 方法,,它會啟動一個新線程,,并執(zhí)行 run()方法。這種方式實現(xiàn)多線程很簡單,,通過自己的類直接 extend Thread,,并復(fù)寫 run()方法,就可以啟動新線程并執(zhí)行自己定義的 run()方法,。

面試的時候時長會被問start方法和run方法的區(qū)別,,筆試中遇到的概率是非常高的。start方法是啟動一個線程,,而run方法是普通的實例方法調(diào)用而已,。

實現(xiàn) Runnable 接口創(chuàng)建線程
  1. 定義一個類實現(xiàn)Runnable接口

  2. 創(chuàng)建該類的實例對象obj

  3. 將obj作為構(gòu)造器參數(shù)傳入Thread類實例對象,這個對象才是真正的線程對象

  4. 調(diào)用線程對象的start()方法啟動該線程

public class User {
 //....
}
public class Zhangsan extends User implements Runnable{
 @Override
 public void run() {
  System.out.println("實現(xiàn)Runnable接口創(chuàng)建線程");
 }

 public static void main(String[] args) 
  new Thread(new Zhangsan() ).start();
 }
}

如果需要繼承某個業(yè)務(wù)類,,那么此時就不能再使用繼承Thread的方式創(chuàng)建線程了,。本環(huán)境JDK版本為1.8+,所以可以看到Runnable接口使用注解@FunctionlInterface修飾,,也就是說Runnable接口是函數(shù)式接口,,可使用lambda表達式創(chuàng)建對象,使用lambda表達式就可以不像上述代碼一樣還要創(chuàng)建一個實現(xiàn)Runnable接口的類,,然后再創(chuàng)建類的實例,。

@FunctionalInterface
public interface Runnable {
 public abstract void run();
}

相比前面的繼承Thread,這種實現(xiàn)Runnable接口的方式更加靈活,,主要原因是Java中只支持單繼承,。

使用` ExecutorService、Callable,、Future `實現(xiàn)帶返回結(jié)果的多線程

從繼承Thread類和實現(xiàn)Runnable接口可以看出,,上述兩種方法都不能有返回值,且不能聲明拋出異常,。而Callable接口則實現(xiàn)了此兩點,,Callable接口如同Runable接口的升級版,其提供的call()方法將作為線程的執(zhí)行體,,同時允許有返回值,。

但是Callable對象不能直接作為Thread對象的target,因為Callable接口是 Java 5 新增的接口,,不是Runnable接口的子接口,。對于這個問題的解決方案,,就引入 Future接口,此接口可以接受call() 的返回值,,RunnableFuture接口是Future接口和Runnable接口的子接口,,可以作為Thread對象的target 。通過future.get()獲取返回值,。

public class CallableDemo  implements Callable<String{
 @Override
 public String call() throws Exception {
  //業(yè)務(wù)代碼...
  return "老田寫的demo";
 }

 public static void main(String[] args) throws ExecutionException, InterruptedException {
  ExecutorService executorService=Executors. newFixedThreadPool (1);
  CallableDemo callableDemo=new CallableDemo();
  Future<String> future=executorService.submit(callableDemo);
  System. out .println(future.get());
  executorService.shutdown();
 }
}

線程的生命周期

既然是生命周期,,那么就很有可能會有階段性的或者狀態(tài)的,比如人的一生一樣:

精子和卵子結(jié)合---> 嬰兒---> 小孩--> 成年--> 中年--> 老年-->去世

線程狀態(tài)

關(guān)于線程的生命周期網(wǎng)上有不一樣的答案,,有說五種也有說六種,。Java中線程確實有6種,這是有理有據(jù)的,,可以看看java.lang.Thread類中有個這么一個枚舉,。

public enum State {
  NEW,
  RUNNABLE,
  BLOCKED, 
  WAITING, 
  TIMED_WAITING, 
  TERMINATED;
}

這就是Java線程對應(yīng)的狀態(tài),組合起來就是Java中一個線程的生命周期,。下面是這個枚舉的注釋

每種狀態(tài)簡單說明:

  • NEW(初始):線程被創(chuàng)建后尚未啟動,。

  • RUNNABLE(運行):包括了操作系統(tǒng)線程狀態(tài)中的Running和Ready,也就是處于此狀態(tài)的線程可能正在運行,,也可能正在等待系統(tǒng)資源,,如等待CPU為它分配時間片。

  • BLOCKED(阻塞):線程阻塞于鎖,。

  • WAITING(等待):線程需要等待其他線程做出一些特定動作(通知或中斷),。

  • TIME_WAITING(超時等待):該狀態(tài)不同于WAITING,它可以在指定的時間內(nèi)自行返回,。

  • TERMINATED(終止):該線程已經(jīng)執(zhí)行完畢,。

線程生命周期


借用網(wǎng)上的這張圖,這張圖描述的很清楚了,,這里就不用在啰嗦了。

線程的啟動

前面的案列中,。我們演示了線程的啟動,,也就是調(diào)用start()方法去啟動一個線程,當 run 方法中的代碼執(zhí)行完畢以后,,線程的生命周期也將終止,。調(diào)用 start 方法的語義是當前線程告訴 JVM,啟動調(diào)用 start 方法的線程,。

線程啟動原理

我身邊很多小伙伴對于線程的啟動,,還是處于朦朧的狀態(tài),說不懂但是知道調(diào)用start方法,,就啟動了線程,。但是為什么不是調(diào)用run方法呢?這可能是很多人都不知道的。再回到Thread的源碼中

   /**
  * start方法將導(dǎo)致this thread開始執(zhí)行,。由JVM調(diào)用this thread的run方法,。
  * Causes this thread to begin execution; the Java Virtual Machine
  * calls the <code>run</code> method of this thread.
  * <p>
  * 結(jié)果是 調(diào)用start方法的當前線程 和 執(zhí)行run方法的另一個線程 同時運行。  
  * The result is that two threads are running concurrently: the
  * current thread (which returns from the call to the
  * <code>start</code> method) and the other thread (which executes its
  * <code>run</code> method).
  * <p>
  *  多次啟動線程永遠不合法,。 特別是,,線程一旦完成執(zhí)行就不會重新啟動。
  * It is never legal to start a thread more than once.
  * In particular, a thread may not be restarted once it has completed
  * execution.
  *
  * @exception  IllegalThreadStateException  if the thread was already
  *   started.
  * @see  #run()
  * @see  #stop()
  */

public class Thread implements Runnable {
 /* Make sure registerNatives is the first thing <clinit> does. */
 private static native void registerNatives();
 static {
  registerNatives();
 }
 //start方法用synchronized修飾,,為同步方法
 public synchronized void start() {
  /**
   * 對于由VM創(chuàng)建/設(shè)置的main方法線程或“system”組線程,,不會調(diào)用此方法。 
   * 未來添加到此方法的任何新功能可能也必須添加到VM中,。
   * This method is not invoked for the main method thread or "system"
   * group threads created/set up by the VM. Any new functionality added
   * to this method in the future may have to also be added to the VM.
   * status=0 代表是 status 是 "NEW",。
   * A zero status value corresponds to state "NEW".
   */

  if (threadStatus != 0)
   throw new IllegalThreadStateException();

  /* 
   * 通知組該線程即將啟動,以便將其添加到線程組的列表中,,并且減少線程組的未啟動線程數(shù)遞減,。
   * Notify the group that this thread is about to be started
   * so that it can be added to the group's list of threads
   * and the group's unstarted count can be decremented.
   */

  group.add(this);

  boolean started = false;
  try {
   //通知組該線程即將啟動,以便將其添加到線程組的列表中,,并且減少線程組的未啟動線程數(shù)遞減,。
   start0();
   started = true;
  } finally {
   try {
 if (!started) {
  group.threadStartFailed(this);
 }
   } catch (Throwable ignore) {
 /* do nothing. If start0 threw a Throwable then
   it will be passed up the call stack */

   }
  }
 }
 //native方法,JVM創(chuàng)建并啟動線程,,并調(diào)用run方法
 private native void start0();

}   

我們看到調(diào)用 start 方法實際上是調(diào)用一個 native 方法start0()來啟動一個線程,,首先 start0()這個方法是在Thread 的靜態(tài)塊中來注冊的。

對應(yīng)虛擬機源碼中 

其他部分JVM源碼這里就沒必要一一道來,。

線程的終止

上面說完線程的啟動原理,,下面來說說線程的終止方式和原理。

這也是3到5年工作經(jīng)驗的人面試被問頻率非常高的一個知識點,。

線程的終止,,并不是簡單的調(diào)用 stop 命令去。雖然 api 仍然可以調(diào)用,,但是和其他的線程控制方法如 suspend,、resume 一樣都是過期了的不建議使用,就拿 stop 來說,,top 方法在結(jié)束一個線程時并不會保證線程的資源正常釋放,,因此會導(dǎo)致程序可能出現(xiàn)一些不確定的狀態(tài)。

停止一個線程通常意味著在線程處理任務(wù)完成之前停掉正在做的操作,,也就是放棄當前的操作,。

在 Java 中有以下 3 種方法可以終止正在運行的線程:

  1. 使用退出標志,使線程正常退出,,也就是當 run() 方法完成后線程中止,。

  2. 使用 stop() 方法強行終止線程,,但是不推薦使用這個方法,該方法已被棄用,。

  3. 使用 interrupt 方法中斷線程,。

第一種方式就是定義一個變量,比如說boolean類型的,,通過不改變這個變量的值,,然后再run方法里判斷這個變量的值是否有變化,有變化則終止該線程,。

第二種已經(jīng)被棄用了,,為什么棄用stop:

  1. 調(diào)用 stop() 方法會立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 語句中的,,并拋出ThreadDeath異常(通常情況下此異常不需要顯示的捕獲),,因此可能會導(dǎo)致一些清理性的工作的得不到完成,如文件,,數(shù)據(jù)庫等的關(guān)閉,。

  2. 調(diào)用 stop() 方法會立即釋放該線程所持有的所有的鎖,導(dǎo)致數(shù)據(jù)得不到同步,,出現(xiàn)數(shù)據(jù)不一致的問題,。

第三種interrupt() 方法并不像第一種那樣在 for 循環(huán)語句中使用 break 語句那樣干脆,馬上就停止循環(huán),。調(diào)用 interrupt() 方法僅僅是在當前線程中打一個停止的標記,,并不是真的停止線程。也就是說,,線程中斷并不會立即終止線程,,而是通知目標線程,有人希望你終止,。至于目標線程收到通知后會如何處理,,則完全由目標線程自行決定。這一點很重要,,如果中斷后,,線程立即無條件退出,那么我們又會遇到 stop() 方法的老問題,。事實上,如果一個線程不能被 interrupt,,那么 stop 方法也不會起作用,。

這里面試最常被問

如何優(yōu)雅的終止一個線程

要優(yōu)雅的去中斷一個線程,在線程中提供了一個 interrupt方法,。當其他線程通過調(diào)用當前線程的 interrupt 方法,,表示向當前線程打個招呼,,告訴他可以中斷線程的執(zhí)行了,至于什么時候中斷,,取決于當前線程自己,。線程通過檢查自身是否被中斷來進行相應(yīng),可以通過isInterrupted()來判斷是否被中斷,。

下面一個案例

public class InterruptDemo {
 private static int  i ;
 public static void main(String[] args) throws InterruptedException {
  Thread thread=new Thread(()->{
   //默認情況下 isInterrupted 返回 false,、通過 thread.interrupt 變成了 true
   while(!Thread. currentThread ().isInterrupted()){
 i ++;
   }
   System. out .println("Num:"+ i );
  },"interruptDemo");
  thread.start();
  TimeUnit. SECONDS .sleep(1);
  //重點
  thread.interrupt();
 }
}

這種通過標識位或者中斷操作的方式能夠使線程在終止時有機會去清理資源,而不是武斷地將線程停止,,因此這種終止線程的做法顯得更加安全和優(yōu)雅,。

    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多