自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面試中如何答好:線程

開發(fā) 后端
線程是cpu執(zhí)行的基本單位,可以理解為一個基本的執(zhí)行流,一個進程中至少有一個線程。

1.進程

是操作系統(tǒng)為應用程序分配資源的基本單位,比如操作系統(tǒng)會為一個應用程序分配獨立的工作空間,硬件資源,任務調度等。一個應用程序就是一個進程。

2.線程

是cpu執(zhí)行的基本單位,可以理解為一個基本的執(zhí)行流,一個進程中至少有一個線程。進程是線程的集合體或者載體。

3.線程模型

線程分為內核線程和用戶線程。

內核線程就是操作系統(tǒng)自己實現(xiàn)的一套線程機制,實現(xiàn)一套線程機制并不是像java一樣創(chuàng)建一個Thread對象即可,而是除了創(chuàng)建線程對象,還要基于操作系統(tǒng)及硬件的不同(比如cpu是多核單核等),制定何時凍結或喚醒線程、該給線程分配多少處理器執(zhí)行時間、該把線程安排給哪個處理器核心去執(zhí)行等事宜,而內核實現(xiàn)了這一套機制。

用戶線程就是用戶程序中創(chuàng)建的線程,和內核線程一樣,線程是交給處理器執(zhí)行,但是具體的執(zhí)行和分配機制需要用戶程序自己實現(xiàn),就是說也要根據(jù)機器的不同實現(xiàn)一套線程運行機制才行。但是實現(xiàn)這樣一套機制是極其復雜的,而且還要考慮不同的機器平臺,所以說有一定的困難度。

那我們開發(fā)的應用程序線程是怎么實現(xiàn)的呢,一般的應用程序實現(xiàn)的線程根據(jù)其特點可以分為三種:

  • 用戶線程和內核一一對應的1:1模型
  • 用戶線程和進程多對一的1:N模型
  • 用戶線程和內核線程多對多的M:N模型

(1) 1:1模型

應用程序創(chuàng)建一個線程實際上是通過內核提供的內核線程api調用與一個內核線程建立一比一映射關系,也就是說看起來應用程序創(chuàng)建了用戶線程,但其實應用程序只是創(chuàng)建線程對象,通過線程對象調用了內核api創(chuàng)建了內核線程,對于應用程序來說就比較省事了,因為具體的線程機制都是由內核完成。

這種模型免不了用戶態(tài)到內核態(tài)的切換,性能方面會被限制。

(2) 1:N模型

就是上面所說的,由用戶程序自己實現(xiàn)線程機制,包括線程創(chuàng)建,線程調度等。

這種模型基本能避免用戶態(tài)到內核態(tài)的切換,這就可以支持更多的線程并發(fā),不好的地方就是實現(xiàn)困難。

(3) M:N模型

就是用戶程序中的用戶線程和內核線程不是一比一映射,而是多條用戶線程對應一條內核線程或者多條內核線程。

這種模型既能夠保證并發(fā)量,提高性能,又能利用內核的調度機制,但是免不了自己實現(xiàn)一套線程機制,依然具備很高的復雜性。

java在實現(xiàn)線程機制這條路上可以說對于三種模型都曾有過實現(xiàn)。其實采用哪種線程模型依賴于所使用的操作系統(tǒng)是否支持,就拿Hotspot這款虛擬機來說,它在不同操作系統(tǒng)上的實現(xiàn)就不一樣,在Solaris平臺的HotSpot虛擬機,由于操作系統(tǒng)的線程特性本來就可以同時支持 1:1及N:M

但是主流的java虛擬機hotspot在主流的操作系統(tǒng)win和linux,采用的都是1:1模型。即線程調度交個內核來完成,這樣我們在創(chuàng)建線程的時候就受到了資源的很大限制,比如我們無法做到創(chuàng)建大批量的線程出來。

舉個例子:

內核是一個國有工廠負責加工產品,cpu是加工產品的機器,一個用戶程序就是一個私有企業(yè),也就是一個進程,而線程就是私企和國企里面的工作人員。每個私企員工都會將一定的原材料送到國企進行加工。

1:1模型中,每當一個私企員工攜帶原材料來加工,都會有一個國企的員工接待,負責加工流程中的所有事情,直到加工完成并將私企員工送走。因為有很多私企,一個私企里面有很多私企員工,而國企員工相對較少,這樣一對一的服務,如果私企的人來的太多,會把國企人員累死,整個生產線會癱瘓。

1:N模型中就是每個私企在國企里面租一臺機器的使用時間,就是某個時間段,由某個私企承包,這個時間段內機器只為這個私企工作,這種情況下只要在這個時間段內,私企想派多少人去加工就派多少人去加工。

M:N模型中,某個私企大概有100人攜帶原材料加工,私企感覺這樣效率有點低,就想了個辦法,和國企達成某種協(xié)議,讓國企出幾個人專門服務于私企的這100個人,這樣效率會提升了。

4.線程調度

多線程情況下,能夠讓每個線程都能有條不紊的得到運行就是線程調度。

我們知道cpu處理器是單線流水運行,線程是運行在cpu上的,一個時間點上只會有一條線程正在被cpu運行。

基于以上情況,線程要如何調度呢,一般情況下,操作系統(tǒng)有兩種調度方式:

(1) 協(xié)同式調度

正在執(zhí)行的線程自己控制自身的執(zhí)行時間,并且當前線程執(zhí)行完后由自身告知操作系統(tǒng)可以調度到其他線程上了。

這種方式相對來說實現(xiàn)簡單,不用考慮同步的問題。

但是缺點也很明顯,這種調度方式中有兩個點要注意,線程多長時間可以執(zhí)行完?線程一定能通知操作系統(tǒng)調度其他線程嗎?這兩點無法保證的話就會引發(fā)一些問題,比如當前線程本身運行時間很長,執(zhí)行邏輯中有一些io等待操作,這就造成cpu是空閑的,資源大大浪費;再比如如果代碼或者業(yè)務邏輯出了問題稍有不慎就會造成無法通知操作系統(tǒng)調度其他線程,這個是災難性的。

(2) 搶占式調度

由操作系統(tǒng)為每個線程分配操作時間,并且由操作系統(tǒng)自主負責調度其他線程。

cpu處理整個過程被分成若干個極小的時間片段,每個線程可以被分配多個時間片段。然后操作系統(tǒng)會按照分配情況進行調度。

這種方式可以讓所有的線程看起來是同時運行的,即便有一條線程出了問題,也不會影響其他線程。

主流的平臺linux的內核線程都是搶占式調度,這種方式的缺點就是接下來要說的上下文切換。

4.線程上下文切換

當處理器要運行線程時,除了運行代碼外還要有上下文數(shù)據(jù)的支撐。而這里說的“上下文”,以程序員的角度來看,是方法調用過程中的各種局部的變量與資源;以線程的角度來看,是方法的調用棧中存儲的各類信息;而以操作系統(tǒng)和硬件的角度來看,則是存儲在內存、緩存和寄存器中的一個個具體數(shù)值。

物理硬件的各種存儲設備和寄存器是被操作系統(tǒng)內所有線程共享的資源,比如線程A正在運行,線程B正在掛起等待運行,此時線程A的時間片段運行完畢了,線程B就會被調度。從線程A切換到線程B去執(zhí)行之前,操作系統(tǒng)首先要把線程A的上下文數(shù)據(jù)妥善保管好,然后把寄存器、內存分頁等恢復到線程B掛起時候的狀態(tài),這樣線程B被重新激活后才能仿佛從來沒有被掛起過。這種保護和恢復現(xiàn)場的工作,免不了涉及一系列數(shù)據(jù)在各種寄存器、緩存中的來回拷貝,這便是上下文切換,同時這個操作不是一個輕量級的操作,你想每個時間片段是很短的,也就意味著短時間內要做大量的上下文切換,這就一定會造成很大的時間消耗。

說到這里,java開發(fā)中的線程是java線程和內核線程一比一映射的1:1線程模型,一般java運行在linux系統(tǒng),所以java開發(fā)中常遇到的性能影響點是”用戶態(tài)轉換內核態(tài)和上下文切換“。

5.java線程

java中的線程類是Thread,它定義了java層面線程創(chuàng)建,線程操作的一些列方法。

java中線程實現(xiàn)有三種方式:

//1.繼承thread
Student extends Thread
Student xiaoming = new Student("小明",punishment);
xiaoming.start();

//2.實現(xiàn)Runnable
Student implements Runnable
Thread xiaoming = new Thread(new Student("小明",punishment),"小明");
xiaoming.start();

//3.任務FutureTask實現(xiàn)
public static class CallerTask implements Callable<String>{
@Override
public String call() throws Exception{
return "hello";
}
}
public static void main(String[] args) {
//創(chuàng)建異步任務
FutureTask<String> futureTask = new FutureTask<>(new CallerTask ());
new Thread(futureTask).start();
try{
String result=futureTask.get();
}catch(){
e.printStackTrace();
}
}
  • 第一種方式繼承Thread類,所以子類也是一個Thread類,這樣子類是一個攜帶任務的線程類,任務和線程牢牢綁在一起不可分割,且子類繼承Thread類,就再也不能繼承其他類。
  • 第二種方式,Runnable是任務類,子類實現(xiàn)Runnable后依然是一個任務類,而且還能實現(xiàn)其他的類,這種方式中任務和線程隔離,一個任務可以被多個線程執(zhí)行。符合java職責分離的設計原則和面向接口編程的原則。

以上兩種方式均沒有返回值,而第三種方式可以支持返回值,后面我們細講FutureTask。

我們來解析一下上面的前兩種方式:

Thread是java中的線程實體類,Runable是任務實體類,他們兩個的關系為,Runable代表任務,Thread代表任務的載體,以上面的例子來說,Thread就是企業(yè)內的工作人員,而Runable是具體的加工任務。

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

Runnable是一個接口,run是任務方法,自定一個類并實現(xiàn)Runnable類,然后重寫run方法定義具體的任務邏輯,這個類就是一個具體的任務類。

任務類自身不能運行,需要依賴載體。

public class Thread implements Runnable {

     private Runnable target;
     
     public Thread() {
     }
     
     public Thread(Runnable target) {
        this.target = target;
     }
    
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    
    
 public synchronized void start() {
       
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        
      group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
              
            }
        }
    }   
    
    
}

Thread線程作為任務的載體,通過實現(xiàn)Runnable而具備攜帶任務的能力,通過源碼可知,這個任務邏輯可以來自于自身,也可以來自外界傳進來的任務實體Runnable。

Thread線程除了可以攜帶任務,更重要的是能夠驅動任務運行,start方法就是啟動開關,其內部是本地方法,由jvm內部實現(xiàn),不難想象這個方法的底層一定是調用了內核線程api,與內核線程做映射,然后jvm回調java中的run方法。

6.java線程狀態(tài)

java中的線程具有六種狀態(tài)。

  • 新建(New):創(chuàng)建后尚未啟動的線程處于這種狀態(tài)。
  • 運行(Runnable):包括操作系統(tǒng)線程狀態(tài)中的Running和Ready,也就是處于此狀態(tài)的線程有可能正在執(zhí)行,也有可能正在等待著操作系統(tǒng)為它分配執(zhí)行時間。
  • 等待(Waiting):處于這種狀態(tài)的線程不會被分配處理器執(zhí)行時間,它們要等待被其他線喚醒。一般在調用wait方法和sleep方法的時候處于這種狀態(tài)。
  • 阻塞(Blocked):線程被阻塞了,“阻塞狀態(tài)”與“等待狀態(tài)”的區(qū)別是“阻塞狀態(tài)”在等待著獲取到一個排它鎖,這個事件將在另外一個線程放棄這個鎖的時候發(fā)生;而“等待狀態(tài)”則是在等待一段時間,或者喚醒動作的發(fā)生。在程序等待進入同步區(qū)域的時候,線程將進入這種狀態(tài)。
  • 結束(Terminated):已終止線程的線程狀態(tài),線程已經結束執(zhí)行。

7.java 線程 API

睡眠
public static native void sleep(long millis);
獲取當前線程對象
public static native Thread currentThread();
讓出cpu
public static native void yield();
設置優(yōu)先級
public final void setPriority(int newPriority);
阻塞等待執(zhí)行完成
public final void join();
設置為守護線程
public final void setDaemon(boolean on);
讓可中斷方法中斷
public void interrupt();
獲取中斷位狀態(tài)
public static boolean interrupted();
獲取中斷位狀態(tài),并重置
public boolean isInterrupted();

這些方法有一些不是很常用,所以了解即可,重點說一下join,interrupt,interrupted,isInterrupted。

join方法是一個成員方法,調用某個線程的join方法,會讓主線程一直處于wait狀態(tài)(join方法底層源碼中通過不斷調用wait方法實現(xiàn)),等待這個線程處理完成再繼續(xù)執(zhí)行下去。

interrupt是一個成員方法,這個方法會中斷靜止線程,而運行中的線程只能被設置為可中斷標志位為true,怎么理解呢?就是說如果線程處于sleep、wait狀態(tài),這個時候調用interrupt會把這個阻塞狀態(tài)喚醒并拋出異常,之后該線程繼續(xù)運行,如果線程正在運行中(非Waiting狀態(tài)),此時調用interrupt只會給線程的可中斷狀態(tài)設置為true,對線程運行不會有任何影響。

調用某個線程的interrupt方法,給線程設置可中斷狀態(tài)的意義是什么呢?因為線程運行過程中可能正在處理數(shù)據(jù),這個時候如果人為中斷線程是不安全的,所以要等到線程處于一個安全位置中斷才比較合理,那什么時候才是安全的位置呢,這個程序員自己決定,當處于安全位置的時候給一個可中斷標示,這樣中斷的時候判斷一下可中斷狀態(tài)再決定是否中斷即可。juc源碼中隨處可見該方法的應用。

上面說了中斷的時候判斷一下可中斷的狀態(tài),interrupted這個方法就是獲取線程的中斷狀態(tài),而isInterrupted方法也是獲取線程的可中斷狀態(tài),不同點在于isInterrupted方法在獲取中斷狀態(tài)后,會順便把中斷狀態(tài)重置為false。

責任編輯:趙寧寧 來源: 碼農本農
相關推薦

2023-10-11 08:22:33

線程AQScondition

2023-10-09 08:04:52

面試CASABA

2023-10-17 15:56:37

FutureTask線程

2023-10-12 08:19:04

Monitor線程

2023-10-10 08:55:12

AQS阻塞

2022-02-28 07:01:22

線程中斷interrupt

2024-06-12 08:10:04

虛擬線程JVMJava 虛擬機

2024-09-11 22:51:19

線程通訊Object

2024-05-16 17:58:30

線程任務線程通訊線程池

2024-09-09 15:09:30

2022-03-28 08:31:29

線程池定時任務

2020-12-01 07:16:05

重學設計模式

2010-03-18 14:36:46

Java線程同步

2023-05-12 14:14:00

Java線程中斷

2022-07-06 07:35:19

group byMySQL

2015-09-02 09:32:56

java線程面試

2024-05-08 00:00:00

核心線程數(shù)隊列

2021-11-29 10:55:11

線程池Java面試

2009-01-12 09:49:03

Java線程多線程thread

2009-03-11 11:10:15

JavaJava開發(fā)線程
點贊
收藏

51CTO技術棧公眾號