Java之線程與進程

1、線程與進程安全

  線程:一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務。多線程是多任務的一種特別形式,但多線程使用了更小的資源開銷。多線程

  進程:一個進程包括有操做系統分配的內存空間,包含一個或多個線程。一個線程不能獨立的存在。它必須是進程的一部分。一個進程一直運行直到全部的非守護線程都結束運行後才能結束。併發

  進程 線程
定義

進程是指處於運行中的程序,而且具備必定的獨立功能。進程是系統進行資源分配和調度的一個單位。異步

當程序進入內存時,即爲進程。socket

線程是進程的組成部分,一個進程能夠擁有多個線程,而一個線程必須擁有一個父進程。函數

線程能夠擁有本身的堆棧,本身的程序計數器和局部變量,但不能擁有系統資源。this

它與父進程的其餘線程共享該進城的全部資源。spa

特色

1)獨立性:進程是系統中獨立存在的實體,它能夠獨立擁有資源,每個進程都有本身獨立的地址空間,操作系統

沒有進程自己的運行,用戶進程不能夠直接訪問其餘進程的地址空間。線程

2)動態性:進程和程序的區別在於進程是動態的,進程中有時間的概念,進程具備本身的生命週期和各類

不一樣的狀態。

3)併發性:多個進程能夠在單個處理器上併發執行,互不影響。

1)線程能夠完成必定任務,能夠和其餘線程共享父進程的共享變量和部分環境,相互協做來完成任務。

2)線程是獨立運行的,其不知道進程中是否還有其餘線程存在。

3)線程的執行是搶佔式的,也就是說,當前執行的線程隨時可能被掛起,一邊運行另外一個線程。

4)一個線程能夠建立或撤銷另外一個線程,一個進程中的多個線程能夠併發執行。

2、線程的生命週期


  

新建狀態(New)

  使用new關鍵字和Thread類或七子類創建一個線程對象後,該線程對象就處於新建狀態。此時僅由JVM爲其分配內存,並初始化其成員變量的值。它保持這個狀態知道程序start()這個線程。

就緒狀態(runnable)

  當線程對象調用了start()方法以後,該線程就進入就緒狀態。就緒狀態的線程處於就緒隊列中,Java虛擬機會爲其建立方法調用棧和程序計數器,等待JVM裏線程調度器的調度。

運行狀態(running)

  若是就緒狀態的線程獲取CPU資源,就能夠執行run方法,此時線程便處於運行狀態。處於運行狀態的線程最爲複雜,它能夠變爲阻塞狀態、就緒狀態和死亡狀態。

阻塞狀態(blocked)

  線程由於某種緣由放棄了CPU使用權,暫時中止運行。直到線程進入可運行狀態,纔有機會再次得到CPU timeslice 轉到運行狀態。

  若是一個線程執行了sleep(睡眠),suspend(掛起)等方法,失去所佔用資源以後,該線程就從運行狀態進入阻塞狀態。在睡眠時間已到或得到設備資源後能夠從新進入就緒狀態。能夠分爲三種:

  1)等待阻塞:運行狀態中的線程執行wait方法,使線程進入到等待阻塞狀態;

  2)同步阻塞:線程在獲取synchronized同步鎖失敗(由於同步鎖被其餘線程佔用);

  3)其餘阻塞:經過調用線程的sleep()或join()發出了IO請求時,線程就會進入到阻塞狀態。當sleep()狀態超時,join()等待線程終止或超時,或IO請求處理完畢,線程從新轉入就緒狀態。

死亡狀態(dead)

  一個運行狀態的線程完成任務或其餘終止條件發生時,該線程就切換到終止狀態。

  正常結束:run()或call()方法執行完成,線程正常結束。

  異常結束:線程拋出一個未捕獲的Exception或Error。

  調用stop:直接調用該線程的stop()方法結束該線程(該方法容易致使死鎖,不推薦使用)。

 爲了肯定線程在當前是否存活着(就是要麼是可運行的,要麼是被阻塞了),須要使用isAlive方法。若是是可運行或被阻塞,這個方法返回true;若是線程仍舊是new狀態且不是可運行的,或線程死亡了,則返回false。

3、線程優先級

  每一個線程都有一個優先級,方便操做系統肯定線程的調度順序。Java線程的優先級是一個整數,其取值範圍是1(Thread.MIN_PRIORITY)-10(Thread.MAX_PRIORITY)。

  默認狀況下,每個線程都會分配一個優先級 NORM_PRIORITY(5)。具備較高優先級的線程在低優先級的線程以前分配處理器資源。但線程優先級不能保證線程執行的順序,並且很是依賴於平臺。

4、建立線程的方式

1)繼承Thread類:Thread類本質上是實現了Runnable接口的一個實例。啓動線程的方法就是經過Thread類的start()方法。他是一個native方法,它將啓動一個新線程,並執行其中的run()方法。

1 public class MyThread extends Thread { 2      public void run() { 3  System.out.println("MyThread.run()"); 4  } 5 } 6 MyThread myThread1 = new MyThread(); 7 myThread1.start(); 

2)實現Runnable接口

 1 public class MyThread extends OtherClass implements Runnable {  2 public void run() {  3      System.out.println("MyThread.run()");  4  }  5 }  6 //啓動 MyThread,須要首先實例化一個 Thread,並傳入本身的 MyThread 實例:
 7 MyThread myThread = new MyThread();  8 Thread thread = new Thread(myThread);  9 thread.start(); 10 target.run();    //當傳入一個 Runnable target 參數給 Thread 後,Thread 的 run()方法就會調用
11 public void run() { 12      if (target != null) { 13  target.run(); 14  } 15 } 

3)經過Callable和Future建立線程

  i. 建立Callable接口的實現類,並實現call()方法,該call()方法將做爲線程執行體,而且有返回值。

  ii. 建立Callable實現類的實例,使用FutureTask類包裝Callable對象,該FutureTask對象封裝了Callable對象的call()方法的返回值。

  iii. 使用FutureTask對象做爲Thread對象的target建立並啓動新線程。

  iv. 調用FutureTask對象的get()方法來得到子線程執行結束後的返回值。

 1 //建立一個線程池
 2 ExecutorService pool = Executors.newFixedThreadPool(taskSize);  3 // 建立多個有返回值的任務
 4 List<Future> list = new ArrayList<Future>();  5 for (int i = 0; i < taskSize; i++) {  6     Callable c = new MyCallable(i + " ");  7     // 執行任務並獲取 Future 對象
 8     Future f = pool.submit(c);  9  list.add(f); 10 } 11 // 關閉線程池
12 pool.shutdown(); 13 // 獲取全部併發任務的運行結果
14 for (Future f : list) { 15     // 從 Future 對象上獲取任務的返回值,並輸出到控制檯
16     System.out.println("res:" + f.get().toString()); 17 } 

4)基於線程池的方式

 1  ExecutorService threadPool = Executors.newFixedThreadPool(10);    // 建立線程池
 2  while(true) {  3      threadPool.execute(new Runnable() { // 提交多個線程任務,並執行
 4          public void run() {  5            System.out.println(Thread.currentThread().getName() + " is running ..");  6            try {  7                Thread.sleep(3000);  8            } catch (InterruptedException e) {  9    e.printStackTrace(); 10    } 11  } 12  }); 13  } 

5、終止線程的方式

1.正常運行結束:程序運行結束,線程自動結束。

2.使用退出標誌退出線程

  通常run()方法執行完,線程就會正常結束,然而,經常有些線程是伺服線程。它們須要長時間的運行,只有在外部某些條件知足的狀況下,才能關閉這些線程。使用一個變量來控制循環,例如:最直接的方法就是設一個boolean類型的標誌,並經過設置這個標誌爲true或false來控制while循環是否退出,代碼示例:

1 public class ThreadSafe extends Thread { 2  public volatile boolean exit = false; 3      public void run() { 4          while (!exit){ 5              //do something
6  } 7  } 8 }

  定義了一個退出標誌exit,當exit爲true時,while循環退出,exit的默認值爲false。在定義exit時,使用了一個Java關鍵字volatile,這個關鍵字的目的是使exit同步,也就是說在同一時刻只能由一個線程來修改exit值。

3.Interrupt方法結束線程

 使用interrupt()方法來中斷線程有兩種狀況:

  1)線程處於阻塞狀態:如使用了sleep,同步鎖的wait、socket中的receiver、accept等方法時,會使線程處於阻塞狀態。當調用線程的interrupt()方法時,會拋出InterruptException異常。阻塞中的那個方法拋出這個異常,經過代碼捕獲該異常,而後break跳出循環狀態,從而讓咱們有機會結束這個線程的執行。一般不少人認爲只要調用interrupt方法線程就會結束,其實是錯的,必定要先捕獲InterruptedException異常以後經過break來跳出循環,才能正常結束run方法。

  2)線程未處於阻塞狀態:使用isInterrupted()方法判斷線程的中斷標誌來退出循環。當使用interrupt方法時,中斷標誌就會置true,和使用自定義的標誌來控制循環是同樣的道理。

 1  public class ThreadSafe extends Thread {  2      public void run() {  3      while (!isInterrupted()){ //非阻塞過程當中經過判斷中斷標誌來退出
 4        try{  5              Thread.sleep(5*1000);//阻塞過程捕獲中斷異常來退出
 6             }catch(InterruptedException e){  7  e.printStackTrace();  8             break;//捕獲到異常以後,執行 break 跳出循環
 9  } 10  } 11  } 12   }

4.Stop方法終止線程(線程不安全)

  程序中能夠直接使用thread.stop()來強行終止線程,可是stop方法很危險,就像忽然關閉計算機電源,而不是按正常程序關機同樣,可能會產生不可預料的結果,不安全主要是:

    thread.stop()調用後,建立子線程的線程就會拋出ThreadDeatherror的錯誤,而且會釋放子線程所持有的全部鎖。通常任何進行加鎖的代碼塊,都是爲了保護數據的一致性,若是在調用thread.stop()後致使了該線程所持有的全部鎖被忽然釋放(不可控制),那麼被保護數據就有可能呈現不一致性,其餘線程在使用這些被破壞的數據時,有可能致使一些很奇怪的應用程序錯誤。所以,並不推薦使用stop方法來終止線程。

6、線程同步的方式

  臨界區:經過對多線程的串行化來訪問公共資源或一段代碼,速度快,適應控制數據訪問。

  互斥量:採用互斥對象機制,只有擁有互斥對象的線程纔有訪問公共資源的權限,由於互斥對象只有一個,因此能夠保證公共資源不會同時被多個線程訪問。

  信號量:它容許多個線程統一時刻訪問同一資源,可是須要限制同一時刻訪問此資源的最大線程數目。信號量對象對線程的同步方式與前面幾種方法不一樣,信號容許多個線程同時使用共享資源,這與操做系統中PV操做類似。

  事件(信號):經過通知操做的方式來保持對線程的同步,還能夠方便的實現多線程的優先級比較的操做。

7、進程同步與互斥的區別

  互斥:是指某一資源同時只容許一個訪問者對其進行訪問,具備惟一性和排他性。但互斥沒法限制訪問者對資源的訪問順序,即訪問是無序的。

  同步:是指在互斥的基礎上(大多數狀況),經過其餘機制實現訪問者對資源的有序訪問。在大多數狀況下,同步已經實現了互斥,特別是全部寫入資源的狀況一定是互斥的。少數狀況是指能夠容許多個訪問者同時訪問資源。

  同步體現的是一種協做性,護持體現的是一種排他性。

8、Java後臺線程

 守護線程(Daemon):也稱服務線程,是後臺線程。爲用戶線程提供公共服務,在沒有用戶線程可服務是會自動離開。

  1.優先級較低,用於爲系統中的其餘對象和線程提供服務。

  2.設置:經過setDaemon(true)來設置線程爲「守護線程」。

  3.垃圾回收線程就是守護線程,它始終在低級別的狀態運行,用於監視和管理系統中的可回收資源。

  4.生命週期:守護線程不依賴於終端但依賴於系統,與系統「同生共死」。

9、如何在兩個線程之間共享數據?

  Java裏面進行多線程痛心的主要方式就是共享內存的方式,共享內存主要的關注點有兩個:可見性和有序性。Java內存模型(JMM)解決了可見性和有序性的問題,而鎖解決了原子性的問題,理想狀況下咱們但願作到「同步」和「互斥」。有如下常規實現方法:

  1.將數據抽象成一個類,並將數據的操做做爲這個類的方法,這麼設計能夠很容易作到同步,只要在方法上加「synchronized」

 1 public class MyData {  2     private int j=0;  3     public synchronized void add(){  4         j++;  5         System.out.println("線程"+Thread.currentThread().getName()+"j 爲:"+j);  6  }  7     public synchronized void dec(){  8         j--;  9         System.out.println("線程"+Thread.currentThread().getName()+"j 爲:"+j); 10  } 11     public int getData(){ 12      return j; 13  } 14 } 15 public class AddRunnable implements Runnable{ 16  MyData data; 17     public AddRunnable(MyData data){ 18         this.data= data; 19  } 20     public void run() { 21  data.add(); 22  } 23 } 24 public class DecRunnable implements Runnable { 25  MyData data; 26     public DecRunnable(MyData data){ 27         this.data = data; 28  } 29     public void run() { 30  data.dec(); 31  } 32 } 33 public static void main(String[] args) { 34     MyData data = new MyData(); 35     Runnable add = new AddRunnable(data); 36     Runnable dec = new DecRunnable(data); 37     for(int i=0;i<2;i++){ 38         new Thread(add).start(); 39         new Thread(dec).start(); 40  } 41 }

 

  2.將Runnable對象做爲一個類的內部類,共享數據做爲這個類的成員變量,每一個線程對共享數據的操做方法也封裝在外部類,以便實現對數據的各個操做的同步和互斥,做爲內部類的各個Runnable對象調用外部類的這些方法。

 1 public class MyData {  2     private int j=0;  3     public synchronized void add(){  4         j++;  5         System.out.println("線程"+Thread.currentThread().getName()+"j 爲:"+j);  6  }  7     public synchronized void dec(){  8         j--;  9         System.out.println("線程"+Thread.currentThread().getName()+"j 爲:"+j); 10  } 11     public int getData(){ 12         return j; 13  } 14 } 15 public class TestThread { 16     public static void main(String[] args) { 17         final MyData data = new MyData(); 18         for(int i=0;i<2;i++){ 19             new Thread(new Runnable(){ 20                 public void run() { 21  data.add(); 22  } 23  }).start(); 24             new Thread(new Runnable(){ 25                 public void run() { 26  data.dec(); 27  } 28  }).start(); 29  } 30  } 31 }

10、多線程中的常見問題?

1. run()方法和start()方法之間的區別?

  只有調用了start方法,纔會表現出多線程的特性,不一樣線程的run方法裏面的代碼交替執行。若是隻是調用run方法,那麼代碼仍是同步執行的,必須等待一個線程的run方法裏面的代碼所有執行完畢以後,另一個線程才能夠執行其run方法裏面的代碼。

  i. start()方法來啓動線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,能夠直接繼續執行下面的代碼。

  ii. 經過調用Thread類的start方法來啓動一個線程,這時此線程是處於就緒狀態,並無運行。

  iii. 方法run稱爲線程體,它包含了要執行的這個線程的內容,線程就進入了運行狀態,開始運行run函數中的代碼。run方法運行結束,此線程終止,而後CPU再調度其餘線程。

2. Runnable接口和Callable接口的區別?

  Runnable接口中的run()方法的返回值爲void,它作的事情只是純粹地去執行run()方法中的代碼而已。Callable接口中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合能夠用來獲取異步執行的結果。

  Callable+Future/FutureTask能夠獲取多線程運行的結果,能夠在等待時間太長沒獲取須要的數據的狀況下取消該線程的任務。

3. Sleep()方法和Wait()方法的區別?

  sleep方法和wait方法均可以用來放棄CPU必定的時間,不一樣點在於若是線程持有某個對象的監視器(監視對象同步),sleep方法不會放棄這個對象的監視器, wait方法會放棄這個對象的監視器,而且wait只能在同步中使用。

  sleep方法屬於Thread類,sleep方法致使了程序暫停執行指定的時間,讓出CPU給其餘線程,可是它的監控狀態依然保持着,當指定的時間到了又會自動恢復運行狀態。在調用sleep方法過程當中,線程不會釋放對象鎖。

  wait方法屬於Object來,當調用方法時,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對對象調用notify方法後本線程才進入對象鎖定池,準備獲取對象鎖進入運行狀態。

4.線程讓步(yield)

  yield會使當前線程讓出CPU執行時間片,與其餘線程一塊兒從新競爭CPU時間片。通常狀況下,優先級高的線程有更大的可能性成功競爭獲得CPU時間片,但這又不是絕對的,有的操做系統對線程優先級並不敏感。

5. join 等待其餘線程終止

  join方法,等待其餘線程終止,在當前線程中調用一個線程的join方法,則當前線程轉爲阻塞狀態,當另外一個線程結束,當前線程再由阻塞狀態變爲就緒狀態,等待CPU。

  爲何要用join方法?

    不少狀況下,主線程生成並啓動子線程,須要用到子線程返回結果,也就是主線程須要在子線程結束後再結束,這時就要用到join方法。

1 System.out.println(Thread.currentThread().getName() + "線程運行開始!"); 2      Thread6 thread1 = new Thread6(); 3         thread1.setName("線程 B"); 4  thread1.join(); 5 System.out.println("這時 thread1 執行完畢以後才能執行主線程");
相關文章
相關標籤/搜索