JAVA線程的那些事?

1.線程是什麼?

進程:每一個進程都有獨立的代碼和數據空間(進程上下文),進程間的切換會有較大的開銷,一個進程包含1–n個線程。java

線程:同一類線程共享代碼和數據空間,每一個線程有獨立的運行棧和程序計數器(PC),線程切換開銷小。編程

同一時刻運行多個程序的能力。每個任務稱爲一個線程。能夠同時運行一個以上線程的程序稱爲多線程程序。多線程

Java編寫程序都運行在在Java虛擬機(JVM)中,在JVM的內部,程序的多任務是經過線程來實現的。每用java命令啓動一個java應用程序,就會啓動一個JVM進程。在同一個JVM進程中,有且只有一個進程,就是它本身。在這個JVM環境中,全部程序代碼的運行都是以線程來運行。併發

通常常見的Java應用程序都是單線程的。好比,用java命令運行一個最簡單的HelloWorld的Java應用程序時,就啓動了一個JVM進程,JVM找到程序程序的入口點main(),而後運行main()方法,這樣就產生了一個線程,這個線程稱之爲主線程。當main方法結束後,主線程運行完成。JVM進程也隨即退出 。app

對於一個進程中的多個線程來講,多個線程共享進程的內存塊,當有新的線程產生的時候,操做系統不分配新的內存,而是讓新線程共享原有的進程塊的內存。所以,線程間的通訊很容易,速度也很快。不一樣的進程由於處於不一樣的內存塊,所以進程之間的通訊相對困難。異步

線程分爲兩類:用戶線程和守候線程。當全部用戶線程執行完畢後,JVM自動關閉。可是守候線程卻不獨立與JVM,守候線程通常是有操做系統或用戶本身建立的。ide

2.線程的生命週期

線程是一個動態執行的過程,它也有一個從產生到死亡的過程。測試

下圖顯示了一個線程完整的生命週期。this

 

  • 新建狀態:使用 new 關鍵字和 Thread 類或其子類創建一個線程對象後,該線程對象就處於新建狀態。它保持這個狀態直到程序 start() 這個線程。spa

  • 就緒狀態:當線程對象調用了start()方法以後,該線程就進入就緒狀態。就緒狀態的線程處於就緒隊列中,要等待JVM裏線程調度器的調度。

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

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

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

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

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

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

3.如何建立一個線程

Java 提供了三種建立線程的方法:

  • 經過實現 Runnable 接口;

  • 經過繼承 Thread 類自己;

  • 經過 Callable 和 Future 建立線程。

經過實現 Runnable 接口來建立線程

建立一個線程,最簡單的方法是建立一個實現 Runnable 接口的類。

爲了實現 Runnable,一個類只須要執行一個方法調用 run(),聲明以下:

 下面是一個建立線程並開始讓它執行的實例:

package org.java.base.thread;

public class RunnableDemo implements Runnable{
@Override
public void run() {
System.out.println(「我是線程」);
}
}

經過繼承Thread來建立線程

建立一個線程的第二種方法是建立一個新的類,該類繼承 Thread 類,而後建立一個該類的實例。

該方法儘管被列爲一種多線程實現方式,可是本質上也是實現了 Runnable 接口的一個實例。

package org.java.base.thread;

public class ThreadDemo extends Thread{
@Override
public void run() {
System.out.println(「我是線程」);
}
}

Thread 方法

下表列出了Thread類的一些重要方法:

序號 方法描述
1 public void start()
使該線程開始執行;Java 虛擬機調用該線程的 run 方法。
2 public void run()
若是該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;不然,該方法不執行任何操做並返回。
3 public final void setName(String name)
改變線程名稱,使之與參數 name 相同。
4 public final void setPriority(int priority)
更改線程的優先級。
5 public final void setDaemon(boolean on)
將該線程標記爲守護線程或用戶線程。
6 public final void join(long millisec)
等待該線程終止的時間最長爲 millis 毫秒。
7 public void interrupt()
中斷線程。
8 public final boolean isAlive()
測試線程是否處於活動狀態。

測試線程是否處於活動狀態。 上述方法是被Thread對象調用的。下面的方法是Thread類的靜態方法。

序號 方法描述
1 public static void yield()
暫停當前正在執行的線程對象,並執行其餘線程。
2 public static void sleep(long millisec)
在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操做受到系統計時器和調度程序精度和準確性的影響。
3 public static boolean holdsLock(Object x)
當且僅當當前線程在指定的對象上保持監視器鎖時,才返回 true。
4 public static Thread currentThread()
返回對當前正在執行的線程對象的引用。
5 public static void dumpStack()
將當前線程的堆棧跟蹤打印至標準錯誤流。

經過 Callable 和 Future 建立線程

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

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

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

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

 package org.java.base.thread;

import java.util.concurrent.Callable;

public class CallableThreadDemo implements Callable<Integer>{

@Override
public Integer call() throws Exception {
System.out.println(「我是一個Callable實現」);
return 1;
}

}

  • 建立線程的三種方式的對比

    • 1. 採用實現 Runnable、Callable 接口的方式建立多線程時,線程類只是實現了 Runnable 接口或 Callable 接口,還能夠繼承其餘類。

    • 2. 使用繼承 Thread 類的方式建立多線程時,編寫簡單,若是須要訪問當前線程,則無需使用 Thread.currentThread() 方法,直接使用 this 便可得到當前線程。

 

有效利用多線程的關鍵是理解程序是併發執行而不是串行執行的。例如:程序中有兩個子系統須要併發執行,這時候就須要利用多線程編程。

經過對多線程的使用,能夠編寫出很是高效的程序。不過請注意,若是你建立太多的線程,程序執行的效率其實是下降了,而不是提高了。

請記住,上下文的切換開銷也很重要,若是你建立了太多的線程,CPU 花費在上下文的切換的時間將多於執行程序的時間!

4.線程通訊

正常狀況下,每一個子線程完成各自的任務就能夠結束了。不過有的時候,咱們但願多個線程協同工做來完成某個任務,這時就涉及到了線程間通訊了。

線程之間通訊方式:

1.是經過共享變量,線程之間經過該變量進行協做通訊;

例如:多個線程共享同一個變量,要考慮併發的問題
2.經過隊列(本質上也是線程間共享同一塊內存)來實現消費者和生產者的模式來進行通訊;

例如:異步發送郵件或者短信

 

5.線程同步

java容許多線程併發控制,當多個線程同時操做一個可共享的資源變量時(如數據的增刪改查),

 將會致使數據不許確,相互之間產生衝突,所以加入同步鎖以免在該線程沒有完成操做以前,被其餘線程的調用,

 從而保證了該變量的惟一性和準確性。

即有synchronized關鍵字修飾的方法。因爲java的每一個對象都有一個內置鎖,當用此關鍵字修飾方法時,  內置鎖會保護整個方法。在調用該方法前,須要得到內置鎖,不然就處於阻塞狀態。

 代碼如:

    public synchronized void save(){}

注: synchronized關鍵字也能夠修飾靜態方法,此時若是調用該靜態方法,將會鎖住整個類

6.線程死鎖

死鎖就是兩個或兩個以上的線程被無限的阻塞線程之間相互等待所需的資源」>死鎖就是兩個或兩個以上的線程被無限的阻塞,線程之間相互等待所需的資源。這種狀況可能發生在當兩個線程嘗試獲取其餘資源的鎖,而每一個線程又陷入無線等待其餘資源鎖的釋放,除非一個用戶的進程被終止。
線程死鎖可能發生在如下的狀況:

  1. 當兩個線程相互調用Thread.join();

  2. 當兩個線程使用嵌套的同步塊時,一個線程佔用了另外一個線程的必需的鎖,互相等待時被阻塞,就有可能出現死鎖。

死鎖通常都是因爲對共享資源的競爭所引發的。但對共享資源的競爭又不必定就會發生死鎖。
死鎖的發生必需知足4個必要條件:

  1. 互斥

  2. 等待/持有

  3. 非搶佔

  4. 造成等待環

相關文章
相關標籤/搜索