面試官,Java多線程併發我能講3個小時,你確定要聽?

關於Java核心面試知識點,已經講到了Java多線程併發,由於Java多線程併發內容較多,這章我將分爲三篇文章來講,各位記得關注霸哥,上車不跟丟!

故事小情節:

一個程序員去公司面試,本想穿着整齊,給面試官一個好印象,但沒想到被面試官瞧不起了…

面試官:這位同志,我們這裏招Java高級工程師,你是不是走錯了?

程序員:沒有啊,我接到面試通知來的…

面試官:好吧,咱們這就開始吧!

程序員:好的

面試官:你之前做過哪些項目?

程序員:我一共主持開發過5個大型項目,若干個小項目,其中使我記憶最深刻的是…,當時我們運用了…技術,併發量提升了…

面試官:那你來講講Java中的多線程併發問題?

程序員:這個…

面試官:怎麼了?講不出來?我就知道…

程序員摘下帽子

程序員:Java多線程併發我能講3個小時,你確定要聽?

面試官看着面前程序員鋥光發亮的腦門,眼中閃爍着無窮的光芒,聽到程序員這句胸有成竹的話,忙說:

目錄

  • JAVA 併發知識庫
  • JAVA 線程實現/創建方式
  • 4 種線程池
  • 線程生命週期(狀態)
  • 終止線程 4 種方式
  • sleep 與 wait 區別
  • start 與 run 區別
  • JAVA 後臺線程
  • 線程基本方法

正文

評論區獲取高清完整大圖


JAVA 併發知識庫


JAVA 線程實現/創建方式

1、繼承 Thread 類

Thread 類本質上是實現了 Runnable 接口的一個實例,代表一個線程的實例。啓動線程的唯一方法就是通過 Thread 類的 start()實例方法。start()方法是一個 native 方法,它將啓動一個新線程,並執行 run()方法。

public class MyThread extends Thread { 
       public void run() { 
         System.out.println("MyThread.run()"); 
       } 
} 
MyThread myThread1 = new MyThread(); 
myThread1.start();

2、實現 Runnable 接口

如果自己的類已經 extends 另一個類,就無法直接 extends Thread,此時,可以實現一個Runnable 接口。

public class MyThread extends OtherClass implements Runnable { 
       public void run() { 
         System.out.println("MyThread.run()"); 
       } 
}
//啓動 MyThread,需要首先實例化一個 Thread,並傳入自己的 MyThread 實例:
MyThread myThread = new MyThread(); 
Thread thread = new Thread(myThread); 
thread.start(); 
//事實上,當傳入一個 Runnable target 參數給 Thread 後,Thread 的 run()方法就會調用
target.run()
public void run() { 
       if (target != null) { 
        target.run(); 
       } 
}

3、ExecutorService、Callable、Future 有返回值線程

有返回值的任務必須實現 Callable 接口,類似的,無返回值的任務必須 Runnable 接口。執行Callable 任務後,可以獲取一個 Future 的對象,在該對象上調用 get 就可以獲取到 Callable 任務返回的 Object 了,再結合線程池接口 ExecutorService 就可以實現傳說中有返回結果的多線程了。

//創建一個線程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 創建多個有返回值的任務
List<Future> list = new ArrayList<Future>(); 
for (int i = 0; i < taskSize; i++) { 
Callable c = new MyCallable(i + " "); 
// 執行任務並獲取 Future 對象
Future f = pool.submit(c); 
list.add(f); 
} 
// 關閉線程池
pool.shutdown(); 
// 獲取所有併發任務的運行結果
for (Future f : list) { 
// 從 Future 對象上獲取任務的返回值,並輸出到控制檯
System.out.println("res:" + f.get().toString()); 
}

4、基於線程池的方式

線程和數據庫連接這些資源都是非常寶貴的資源。那麼每次需要的時候創建,不需要的時候銷燬,是非常浪費資源的。那麼我們就可以使用緩存的策略,也就是使用線程池。

// 創建線程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while(true)
{
    threadPool.execute(new Runnable()
    { //提交多個線程任務,並執行
        @Override
        public void run()
        {
            System.out.println(Thread.currentThread().getName() + " is running ..");
            try
            {
                Thread.sleep(3000);
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    });
}

4 種線程池

Java 裏面線程池的頂級接口是 Executor,但是嚴格意義上講 Executor 並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是 ExecutorService

1、newCachedThreadPool

創建一個可根據需要創建新線程的線程池,但是在以前構造的線程可用時將重用它們。對於執行很多短期異步任務的程序而言,這些線程池通常可提高程序性能。調用 execute 將重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則創建一個新線程並添加到池中。終止並從緩存中移除那些已有 60 秒鐘未被使用的線程。因此,長時間保持空閒的線程池不會使用任何資源。

2、newFixedThreadPool

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

3、newScheduledThreadPool

創建一個線程池,它可安排在給定延遲後運行命令或者定期地執行。

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
scheduledThreadPool.schedule(newRunnable()
{
    @Override
    public void run()
    {
        System.out.println("延遲三秒");
    }
}, 3, TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(newRunnable()
{
    @Override
    public void run()
    {
        System.out.println("延遲 1 秒後每三秒執行一次");
    }
}, 1, 3, TimeUnit.SECONDS);

4、newSingleThreadExecutor

Executors.newSingleThreadExecutor()返回一個線程池(這個線程池只有一個線程),這個線程池可以在線程死後(或發生異常時)重新啓動一個線程來替代原來的線程繼續執行下去!


爲了避免文章篇幅過長,下面的章節就簡單介紹,不一一將小節內容發出來了!需要的話可以後臺私信【Java】免費領取PDF文檔!

線程生命週期(狀態)

當線程被創建並啓動以後,它既不是一啓動就進入了執行狀態,也不是一直處於執行狀態。在線程的生命週期中,它要經過新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)5 種狀態。尤其是當線程啓動以後,它不可能一直"霸佔"着 CPU 獨自運行,所以 CPU 需要在多條線程之間切換,於是線程狀態也會多次在運行、阻塞之間切換。

終止線程 4 種方式

sleep 與 wait 區別

1.對於 sleep()方法,我們首先要知道該方法是屬於 Thread 類中的。而 wait()方法,則是屬於Object 類中的。

2.sleep()方法導致了程序暫停執行指定的時間,讓出 cpu 該其他線程,但是他的監控狀態依然保持着,當指定的時間到了又會自動恢復運行狀態。

3.在調用 sleep()方法的過程中,線程不會釋放對象鎖。

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

start 與 run 區別

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

2.通過調用 Thread 類的 start()方法來啓動一個線程, 這時此線程是處於就緒狀態, 並沒有運行。

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

JAVA 後臺線程

守護線程–也稱“服務線程”,他是後臺線程,它有一個特性,即爲用戶線程提供公共服務,在沒有用戶線程可服務時會自動離開。

線程基本方法

線程相關的基本方法有 wait,notify,notifyAll,sleep,join,yield 等。


針對於Java程序員,我這邊還準備免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)

學習視頻和麪試PDF都是可以免費分享的,希望能夠幫助到有需要的朋友,同時也節省了大家再去網上找資料的時間。

評論區獲取領取方式

下面霸哥將繼續分享Java多線程編髮:Java鎖、上下文切換、線程池原理等,關注我上車別跟丟!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章