面試阿裡百度99%會被問到的java線程和線程池,看完這篇你就懂了

来源:https://www.cnblogs.com/java-xiatian/archive/2020/06/17/13155066.html
-Advertisement-
Play Games

最近也是在後臺收到很多小伙伴私信問我線程和線程池這一塊的問題,說自己在面試的時候老是被問到這一塊的問題,被問的很頭疼。前幾天看到後幫幾個小伙伴解決了問題,但是問的人有點多我一個個回答也回答不過來,乾脆花了一個上午時間寫了這篇文章分享給大家。話不多說,滿滿的乾貨都在下麵了! ...


前言:

最近也是在後臺收到很多小伙伴私信問我線程和線程池這一塊的問題,說自己在面試的時候老是被問到這一塊的問題,被問的很頭疼。前幾天看到後幫幾個小伙伴解決了問題,但是問的人有點多我一個個回答也回答不過來,乾脆花了一個上午時間寫了這篇文章分享給大家。話不多說,滿滿的乾貨都在下麵了!

併發與並行

併發:指兩個或多個事件在同一個時間段內發生。 在操作系統中,安裝了多個程式,併發指的是在一段時間內巨集觀上有多個程式同時運行,這在單 CPU 系統中,每 一時刻只能有一道程式執行,即微觀上這些程式是分時的交替運行,只不過是給人的感覺是同時運行,那是因為分 時交替運行的時間是非常短的。

並行:指兩個或多個事件在同一時刻發生(同時發生)。 在多個 CPU 系統中,這些可以併發執行的程式便可以分配到多個處理器上(CPU),實現多任務並行執行,即利用每個處理器來處理一個可以併發執行的程式,這樣多個程式便可以同時執行。目前電腦市場上說的多核 CPU,便是多核處理器,核越多,能夠並行處理的程式數量越多,這能大大的提高電腦運行的效率。

註意:單核處理器的電腦肯定是不能並行的處理多個任務的,只能是多個任務在單個CPU上併發運行。同理,線程也是一樣的,從巨集觀角度上理解線程是並行運行的,但是從微觀角度上分析卻是串列運行的,即一個線程一個線程的去運行,當系統只有一個CPU時,線程會以某種順序執行多個線程,我們把這種情況稱之為線程調度。

線程與進程

進程:是指一個記憶體中運行的應用程式,每個進程都有一個獨立的記憶體空間,一個應用程式可以同時運行多 個進程;進程也是程式的一次執行過程,是系統運行程式的基本單位;系統運行一個程式即是一個進程從創 建、運行到消亡的過程。 線程:線程是進程中的一個執行單元,負責當前進程中程式的執行,一個進程中至少有一個線程。一個進程 中是可以有多個線程的,這個應用程式也可以稱之為多線程程式

創建線程類

Java使用 java.lang.Thread 類代表線程,所有的線程對象都必須是Thread類或其子類的實例。每個線程的作用是完成一定的任務,實際上就是執行一段程式流即一段順序執行的代碼。Java使用線程執行體來代表這段程式流。 Java中通過繼承Thread類來創建並啟動多線程的步驟如下:

定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務,因此把 run()方法稱為線程執行體。 創建Thread子類的實例,即創建了線程對象。 調用線程對象的start()方法來啟動該線程。 首先自定義一個線程類

public class ThreadClass extends Thread {
    //重寫run方法
    @Override
    public void run()
    {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"正在執行"+i);
            try {
                //休眠500毫秒
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

主線程:

public class DemoTest {
    public static void main(String[] args) {
        //創建一個線程對象
        ThreadClass mythread = new ThreadClass();
        //開啟線程
        mythread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("主線程正在執行" + i);
            try {
                //休眠500毫秒
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

其實實際上我們一般不會去繼承線程類,由於java的單繼承特性,當我們繼承了線程類就無法繼承別的父類了,一般我們是通過重寫介面來開啟線程的。

重寫Runnable介面

步驟如下:

定義Runnable介面的實現類,並重寫該介面的run()方法,該run()方法的方法體同樣是該線程的線程執行體。 創建Runnable實現類的實例,並以此實例作為Thread的target來創建Thread對象,該Thread對象才是真正 的線程對象。 調用線程對象的start()方法來啟動線程。 首先重寫介面

public class Runnableimp implements Runnable {
    //重寫run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"正在執行"+i);
            try {
                //休眠500毫秒
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

 

主線程:

public class DemoTest {
    public static void main(String[] args) {
        //創建一個線程對象,傳入重寫了run方法的介面對象
        Thread mythread = new Thread(new Runnableimp());
        //開啟線程
        mythread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("主線程正在執行" + i);
            try {
                //休眠500毫秒
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

 

執行的結果和剛纔相同。

匿名內部類方式實現線程的創建

public class DemoTest {
    public static void main(String[] args) {
        //創建一個線程對象,使用匿名內部類重寫run方法
        Thread mythread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"正在執行"+i);
                    try {
                        //休眠500毫秒
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        //開啟線程
        mythread.start();
    }
}

 

 

使用lambda表達式

public class DemoTest {
    public static void main(String[] args) {
        //創建一個線程對象,使用lambda表達式重寫run方法
        Thread mythread = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"正在執行"+i);
                try {
                    //休眠500毫秒
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //開啟線程
        mythread.start();
    }
}

 

 

線程安全

線程安全問題都是由全局變數及靜態變數引起的。若每個線程中對全局變數、靜態變數只有讀操作,而無寫操作,一般來說,這個全局變數是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步, 否則的話就可能影響線程安全。

線程同步

當我們使用多個線程訪問同一資源的時候,且多個線程中對資源有寫的操作,就容易出現線程安全問題。 要解決上述多線程併發訪問一個資源的安全性問題,Java中提供了同步機制 (synchronized)來解決。

同步代碼塊

同步代碼塊: synchronized 關鍵字可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。 格式:

synchronized(同步鎖){
      需要同步操作的代碼 
      }

 

 

示例

private int num = 100;
private Object lock = new Object();
synchronized (lock)
{
      num--;
}

 

 

同步方法

同步方法:使用synchronized修飾的方法,就叫做同步方法,保證A線程執行該方法的時候,其他線程只能在方法外 等著。

 
public synchronized void method()
{     
    可能會產生線程安全問題的代碼   
}

 

 

Lock鎖


java.util.concurrent.locks.Lock 機制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作, 同步代碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現面向對象。 Lock鎖也稱同步鎖,加鎖與釋放鎖如下:

public void lock() :加同步鎖。 public void unlock() :釋放同步鎖。

Lock lock = new ReentrantLock();    
//加鎖
lock.lock();
可能會產生線程安全問題的代碼  
//釋放鎖
lock.unlock();

 

 

我們使用線程的時候就去創建一個線程,這樣實現起來非常簡便,但是就會有一個問題: 如果併發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因為頻繁創建線程和銷毀線程需要時間。 那麼有沒有一種辦法使得線程可以復用,就是執行完一個任務,並不被銷毀,而是可以繼續執行其他的任務? 在Java中可以通過線程池來達到這樣的效果。今天我們就來詳細講解一下Java的線程池。

線程池

線程池:其實就是一個容納多個線程的容器,其中的線程可以反覆使用,省去了頻繁創建線程對象的操作, 無需反覆創建線程而消耗過多資源。

Java裡面線程池的頂級介面是
java.util.concurrent.Executor ,但是嚴格意義上講 Executor 並不是一個線程 池,而只是一個執行線程的工具。真正的線程池介面是
java.util.concurrent.ExecutorService 。 要配置一個線程池是比較複雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優 的,因此在
java.util.concurrent.Executors線程工廠類裡面提供了一些靜態工廠,生成一些常用的線程池。官方建議使用Executors工程類來創建線程池對象。 newFixedThreadPool方法

public static ExecutorService newFixedThreadPool(int nThreads)

 

 

創建一個可重用固定線程數的線程池,以共用的無界隊列方式來運行這些線程。在任意點,在大多數 nThreads 線程會處於處理任務的活動狀態。如果在所有線程處於活動狀態時提交附加任務,則在有可用線程之前,附加任務將在隊列中等待。如果在關閉前的執行期間由於失敗而導致任何線程終止,那麼一個新線程將代替它執行後續的任務(如果需要)。在某個線程被顯式地關閉之前,池中的線程將一直存在。

參數:

nThreads - 池中的線程數

返回:

新創建的線程池

拋出:

IllegalArgumentException - 如果 nThreads <= 0 獲取到了一個線程池ExecutorService 對象,那麼怎麼使用呢,在這裡定義了一個使用線程池對象的方法如下: public Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,並執行。

下麵的代碼通過四種方式向線程池中提交任務執行

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo01 {
    public static void main(String[] args) throws InterruptedException {
        //創建線程池,線程數量為2
        ExecutorService es = Executors.newFixedThreadPool(2);
        //將任務扔到線程池的四種方式
        //使用匿名內部類,
        es.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"正在執行"+i);
                    try {
                        //休眠500毫秒
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        //使用lambda表達式
        es.submit(()->{
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"正在執行"+i);
                    try {
                        //休眠500毫秒
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        //使用重寫的介面
        es.submit(new Runnableimp());
        //使用重寫的線程類
        es.submit(new ThreadClass());
        //啟動一次順序關閉,執行以前提交的任務,但不接受新任務
        es.shutdown();
        //主線程等待所有線程將任務執行完畢
        while (!es.isTerminated());
        System.out.println("線程執行完畢!");
    }
}

 

除此之外java還提供了:

newScheduledThreadPool:創建一個線程池,它可安排在給定延遲後運行命令或者定期地執行。 newSingleThreadExecutor:創建一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。
newSingleThreadScheduledExecutor: 創建一個單線程執行程式,它可安排在給定延遲後運行命令或者定期 地執行。

小結:

最後小編整理了一份Java相關的資料,需要的小伙伴可以加我微信即可免費領取!

放一些大概截圖,感興趣的小伙伴可以收著。

面試阿裡百度99%會被問到的java線程和線程池,看完這篇你就懂了

java面試題及大廠真題

面試阿裡百度99%會被問到的java線程和線程池,看完這篇你就懂了

大量電子書

知識點相關面試題

面試阿裡百度99%會被問到的java線程和線程池,看完這篇你就懂了

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 登高遠眺 天高地迥,覺宇宙之無窮 基礎技術 Lighthouse 測試內幕 文章分享了網易雲音樂前端性能監控平臺使用 Lighthouse 的實踐經驗,介紹了 Lighthouse 的測試流程、內部模塊實現以及性能指標計算等。文章循循善誘,使用清晰明瞭的架構圖和簡單易懂的代碼例子,剖析了 Light ...
  • 伺服器需要將發送的多媒體數據的類型告訴瀏覽器,而告訴瀏覽器的手段就是告知多媒體的MIME類型。 form表單中的enctype屬性,可以告訴伺服器,我們提供給它的內容的MIME類型。 enctype屬性有三種狀態值: 1). application/x-www-form-urlencoded 數據發 ...
  • 優秀的大前端人才應該具備熟練編寫任何一個互聯網系統的前端頁面、交互代碼的能力,作者從事IT6年,目前是一名全棧開發工程師,根據這些年在職場的經驗,結合目前互聯網環境下對於前端工程師的招聘要求分析,總結出企業要求主要分為硬核技能、輔助要求兩部分。 一、硬核技能 第一階段:HTML+CSS:HTML、C ...
  • 本人一直推崇寫流暢、自然、可自解釋的代碼,讓優雅成為一種習慣。 溫故而知新,聊一聊現代編程幾大常見的編程原則 普世原則 KISS (Keep It Simple Stupid) 保持系統結構簡單可信賴 YAGNI (you aren't gonna need it) 當前確實需要,再去做 Do Th ...
  • 模板方法是結構最簡單的行為型設計模式,在抽象類中定義了一個稱為模板方法的方法,在這個方法中定義其他基本方法的執行步驟,而基本方法的實現可以放在抽象類,也可以放在其子類 模式動機 現實生活中很多事情的完成過程都包含幾個基本步驟,例如請客吃飯,無論吃什麼,一般都包含點單、吃東西、買單幾個步驟,到底吃什麼 ...
  • 結合一些文章閱讀源碼後整理的Java容器常見知識點。對於一些代碼細節,本文不展開來講,有興趣可以自行閱讀參考文獻。 ...
  • 一、不同輸出格式會有不同的結果 1.%ld 用於long類型的值,如果系統中int和long大小相同,使用%d就可以,這樣的程式被移植到其他系統(int和long類型的大小不同)。 2.同樣x和o​前面也可以使用l來修飾。 3.對於short類型,前面就是使用h來​當首碼。 4.h和l首碼都可以和u ...
  • 關於變數的命名,這又是一個容易引發程式員論戰的話題。如何命名才能更具有可讀性、易寫性與明義性呢?眾說紛紜。 本期“Python為什麼”欄目,我們將聚焦於變數命名中的連接方式,來切入這塊是非之地,想要回答的問題是——Python 為什麼要推薦蛇形命名法? 首先一點,對於單個字元或者單詞 (例如:a、A ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...