線程專題 -- 線程的創建,狀態,工作過程,常見方法


線程(Thread)是並發編程的基礎,一般會作為並發編程的起始問題,用於引出更多的關於並發編程的面試問題。對於線程的掌握程度也決定了你對並發編程的掌握程度。

什么是進程?線程?區別?

1)進程是一個獨立的運行環境,它可以被看作是一個程序或者一個應用。而線程是在進程中執行的一個任務。eg:打開360安全衛士,它本身是一個程序,也是一個進程,它里面有殺毒,清理垃圾,電腦加速等功能,當你點擊殺毒的時候,殺毒任務就相當於一個線程。

 2)進程是操作系統進行資源分配的基本單位,而線程是操作系統進行調度的基本單位。

 3)進程讓操作系統的並發性成為可能,而線程讓進程的內部並發成為可能。

補充理解:

進程與線程的區別

--https://jingyan.baidu.com/article/624e74598efcc834e9ba5a66.html

5個步驟,教你瞬間明白線程和線程安全

-- https://baijiahao.baidu.com/s?id=1610396903519844310&wfr=spider&for=pc

線程安全?

定義:一個類或者程序所提供的接口對於線程來說是原子操作或者多個線程之間的切換不會導致該接口的執行結果存在二義性,也就是說我們不用考慮同步的問題。

出現線程不安全的原因是什么?

如果我們創建的多個線程,存在着共享數據,那么就有可能出現線程的安全問題:當其中一個線程操作共享數據時,還未操作完成,另外的線程就參與進來,導致對共享數據的操作出現問題。

線程不安全解決辦法?

要求一個線程操作共享數據時,只有當其完成操作共享數據,其它線程才有機會執行共享數據。java提供了兩種方式來實現同步互斥訪問:synchronized和Lock。

創建線程的方式?區別?

1)繼承Thread類;

2)實現Runnable接口;

3)實現Callable接口;

4)  通過線程池創建線程;

前三種方式的區別?

Java中,類僅支持單繼承,如果一個類繼承了Thread類,就無法再繼承其它類,因此,如果一個類既要繼承其它的類,又必須創建為一個線程,就可以使用實現Runable接口的方式。

使用實現Runable接口的方式創建的線程可以處理同一資源,實現資源的共享。

使用實現Callable接口的方式創建的線程,可以獲取到線程執行的返回值、是否執行完成等信息。

Java中Runnable和Callable有什么不同?

Runnable和Callable都是創建線程的方式。Runnable從JDK1.0開始就有了,Callable是在JDK1.5增加的。

1)實現Callable接口的任務線程能返回執行結果;而實現Runnable接口的任務線程不能返回結果;

2)Callable接口的call()方法允許拋出異常;而Runnable接口的run()方法的異常只能在內部消化,不能繼續上拋;

線程的狀態有哪些?

線程的狀態在 JDK 1.5 之后以枚舉的方式被定義在 Thread 的源碼中,它總共包含以下 6 個狀態:

線程狀態的源代碼如下:

(1)NEW,新建狀態,線程被創建出來,但尚未啟動時的線程狀態;

(2)RUNNABLE,就緒狀態,表示可以運行的線程狀態,它可能正在運行,或者是在排隊等待操作系統給它分配 CPU 資源;

(3)BLOCKED,阻塞等待鎖的線程狀態,表示處於阻塞狀態的線程正在等待監視器鎖,比如等待執行 synchronized 代碼塊或者使用 synchronized 標記的方法;

(4) WAITING,等待狀態,一個處於等待狀態的線程正在等待另一個線程執行某個特定的動作,比如,一個線程調用了Object.wait()方法,那它就在等待另一個線程調用Object.notify()或 Object.notifyAll() 方法;

(5)TIMED_WAITING,計時等待狀態,和等待狀態(WAITING)類似,它只是多了超時時間,比如調用了有超時時間設置的方法Object.wait(longtimeout)和Thread.join(long timeout) 等這些方法時,它才會進入此狀態;

(6)TERMINATED,終止狀態,表示線程已經執行完成。

如果想要確定線程當前的狀態,可以通過 getState() 方法,並且線程在任何時刻只可能處於 1 種狀態。

線程是如何工作的?(工作過程?)

  首先先要創建線程並指定線程需要執行的業務方法,然后再調用線程的start()方法,此時線程就從NEW(新建)狀態變成了RUNNABLE(就緒)狀態,然后線程會判斷要執行的方法中有沒有 synchronized 同步代碼塊,如果有並且其他線程也在使用此鎖,那么線程就會變為 BLOCKED(阻塞等待鎖的線程)狀態,當其他線程使用完此鎖之后,線程會繼續執行剩余的方法。當遇到Object.wait()或Thread.join()方法時,線程會變為WAITING(等待狀態)狀態,如果是帶了超時時間的等待方法,那么線程會進入TIMED_WAITING(計時等待)狀態,當有其他線程執行了notify()或notifyAll()方法之后,線程被喚醒繼續執行剩余的業務方法,直到方法執行完成為止,此時整個線程的流程就執行完了。

start() 和run() 方法有什么區別?

源碼分析:

(1) start() 方法屬於 Thread 自身的方法,並且使用了 synchronized 來保證線程安全。它的作用是啟動一個新線程。通過start()方法來啟動的新線程,從NEW(新建)狀態變成了處於RUNNABLE(就緒)狀態,並沒有運行,一旦得到cpu時間片,就開始執行相應線程的run()方法,這里方法run()稱為線程體,它包含了線程要執行的業務方法,run方法運行結束,此線程隨即終止。

源碼如下:

 (2)run() 方法為 Runnable 的抽象方法,必須由調用類重寫此方法,重寫的 run() 方法其實就是此線程要執行的業務方法;run() 就和普通的成員方法一樣,可以被重復調用,如果直接調用run方法,並不會啟動新線程!程序中依然只有主線程這一個線程,其程序執行路徑還是只有一條,還是要順序執行。

源碼如下:

 Thred實現Runnable接口,重寫run方法,源碼如下

 

區別

 (1) start() 可以啟動一個新線程,讓線程從NEW狀態轉換成RUNNABLE狀態,而run()不能,只是一個普通的方法。

 (2) start()不能被重復調用(否則會拋出 java.lang.IllegalStateException),而 run() 方法可以進行多次調用,因為它只是一個普通的方法而已。

 (3)start()中的run代碼可以不執行完就繼續執行下面的代碼,即進行了線程切換。直接調用run方法必須等待其代碼全部執行完才能繼續執行下面的代碼。

線程的優先級有什么用?該如何設置?

線程的優先級可以理解為線程搶占 CPU 時間片的概率,優先級越高的線程優先執行的概率就越大,但並不能保證優先級高的線程一定先執行。

在程序中我們可以通過 Thread.setPriority() 來設置優先級,setPriority() 源碼如下:

線程的常用方法?

 join()

在一個線程中調用other.join(),這時候當前線程會讓出執行權給other線程,直到other線程執行完或者過了超時時間之后再繼續執行當前線程,join() 源碼如下:

從源碼中可以看出 join() 方法底層還是通過 wait() 方法來實現的。

yield()

看 Thread 的源碼可以知道 yield() 為本地方法,也就是說 yield() 是由 C 或 C++ 實現的,源碼如下:

yield() 方法表示向線程調度器提示當前線程願意出讓 CPU 使用權,但是線程調度器可能會忽略這個暗示。

Java多線程中調用wait() 和 sleep()方法有什么不同?

sleep()和wait()都是使線程暫停執行一段時間的方法。二者區別為:

1)原理不同(面試時可不答,偏重2,3,4)

sleep()方法是Thread類的靜態方法,是線程用來控制自身流程的

wait()方法是Object類的方法,用於線程間的通信。

2)對鎖的處理機制不同

調用wait()的時候方法會釋放當前持有的鎖,而sleep方法不會釋放鎖。

3)使用地方不同

sleep方法則可以放在任何地方使用,而wait()方法必須放在同步方法或者同步代碼塊中使用。

4)異常處理

sleep()方法必須捕獲異常,而wait()、notify()、notifyAll()不需要捕獲異常。

補充說明:由於sleep不會釋放鎖標志,容易導致死鎖問題的發生,一般情況下,不推薦使用sleep()方法,而推薦使用wait()方法。

Java中notify 和 notifyAll有什么區別?

 調用notify時,只有一個等待線程會被喚醒而且它不能保證哪個線程會被喚醒,這取決於線程調度器。如果你調用notifyAll方法,那么等待該鎖的所有線程都會被喚醒。

 Java中interrupted 和 isInterruptedd方法的區別?

  interrupted() 和 isInterrupted()的主要區別是前者會將中斷狀態清除而后者不會。

  Java多線程的中斷機制是用內部標識來實現的,調用Thread.interrupt()來中斷一個線程就會設置中斷標識為true。當中斷線程調用靜態方法Thread.interrupted()來檢查中斷狀態時,中斷狀態會被清零。而非靜態方法isInterrupted()用來查詢其它線程的中斷狀態且不會改變中斷狀態標識。簡單的說就是任何拋出InterruptedException異常的方法都會將中斷狀態清零。無論如何,一個線程的中斷狀態有有可能被其它線程調用中斷來改變。

什么是ThreadLocal?

  ThreadLocal用於創建線程的本地變量,我們知道一個對象的所有線程共享它的全局變量,所以這些變量是非線程安全的,我們可以使用同步技術。但是當我們不想使用同步的時候,我們可以選擇ThreadLocal變量。每個線程都會擁有他們自己的Thread變量,它們可以使用get()/set()方法去獲取他們的默認值或者在線程內部改變他們的值。

  有三個線程T1,T2,T3,怎么確保它們按順序執行?

在多線程中有多種方法讓線程按特定順序執行,你可以用線程類的join()方法在一個線程中啟動另一個線程,另外一個線程完成該線程繼續執行。為了確保三個線程的順序你應該先啟動最后一個(T3調用T2,T2調用T1),這樣T1就會先完成而T3最后完成。

  如何創建守護線程?

  使用Thread類的setDaemon(true)方法可以將線程設置為守護線程,需要注意的是,需要在調用start()方法前調用這個方法,否則會拋出IllegalThreadStateException異常。

/**
 * @author liao.wenhui
 * @date 2019/7/15 15:13
 */
public class DaemonThread {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(new Runnable() {
            @Override
            public void run() {

            }
        });

        //設置守護線程
        daemonThread.setDaemon(true);
        daemonThread.start();
    }
}

前提知識:

  守護進程(Daemon)是運行在后台的一種特殊進程。它獨立於控制終端並且周期性地執行某種任務或等待處理某些發生的事件(百度百科)。

  Java線程分為兩類分別為daemon線程(守護線程)和User線程(用戶線程),在JVM啟動時候會調用main函數,main函數所在的線程是一個用戶線程,這個是我們可以看到的線程,其實JVM內部同時還啟動了好多守護線程,比如垃圾回收線程。那么守護線程和用戶線程有什么區別那?區別之一是當最后一個非守護線程結束時候,JVM會正常退出,而不管當前是否有守護線程,也就是說守護線程是否結束並不影響JVM的退出。言外之意是只要有一個用戶線程還沒結束正常情況下JVM就不會退出。

什么是線程調度器(Thread Scheduler)和時間分片(Time Slicing)?

  線程調度器是一個操作系統服務,它負責為Runnable狀態的線程分配CPU時間。一旦我們創建一個線程並啟動它,它的執行便依賴於線程調度器的實現。

  時間分片是指將可用的CPU時間分配給可用的Runnable狀態的線程的過程。分配CPU時間可以基於線程優先級或者線程等待的時間。線程調度並不受到Java虛擬機控制,所以由應用程序來控制它是更好的選擇(即最好不要讓你的程序依賴於線程的優先級)。

什么是FutureTask?

  在Java並發程序中FutureTask表示一個可以取消的異步運算。它有啟動和取消運算、查詢運算是否完成和取回運算結果等方法。只有當運算完成的時候結果才能取回,如果運算尚未完成get方法將會阻塞。一個FutureTask對象可以對調用了Callable和Runnable的對象進行包裝,由於FutureTask也是調用了Runnable接口所以它可以提交給Executor來執行。

在多線程中,什么是上下文切換?

上下文切換是存儲和恢復CPU狀態的過程,它使得線程執行能夠從中斷點恢復執行。是多任務操作系統和多線程環境的基本特征。 

並發編程三要素

(1) 原子性:程序中的所有操作是不可中斷的,要么全部執行成功要么全部執行失敗。

(2) 有序性:程序執行的順序按照代碼的先后順序執行。(處理器可能會對指令進行重排序)

(3) 可見性:當多個線程訪問同一個變量時,如果其中一個線程對其作了修改,其他線程能立即獲取到最新的值。

參考/好文:

Java 面試真題及源碼 34 講

-- https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM