日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

面试必会系列 - 1.3 Java 多线程

發(fā)布時(shí)間:2024/2/28 java 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 面试必会系列 - 1.3 Java 多线程 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文已收錄至 github,完整圖文:https://github.com/HanquanHq/MD-Notes

多線程

線程有多少種狀態(tài)?

指定時(shí)刻,線程只可能處于下面 6 種不同狀態(tài) 的其中一個(gè)狀態(tài)(圖源《Java 并發(fā)編程藝術(shù)》4.1.4 節(jié))

線程在生命周期中,而是隨著代碼的執(zhí)行在不同狀態(tài)之間切換。Java 線程狀態(tài)變遷如下圖所示(圖源《Java 并發(fā)編程藝術(shù)》4.1.4 節(jié)):

原圖中 wait 到 runnable 狀態(tài)的轉(zhuǎn)換中,join實(shí)際上是Thread類的方法,但這里寫成了Object。

在代碼中,可以使用 getState 獲取線程狀態(tài)

Thread t = new MyThread(); System.out.println(t.getState());
面試題:既然調(diào)用 start() 方法時(shí)會(huì)執(zhí)行 run() 方法,為什么不能直接調(diào)用 run() 方法?

new 一個(gè) Thread,線程進(jìn)入了新建狀態(tài);調(diào)用 start() 方法,會(huì)啟動(dòng)一個(gè)線程并使線程進(jìn)入了就緒狀態(tài),當(dāng)分配到時(shí)間片后就可以開始運(yùn)行了。 start() 會(huì)執(zhí)行線程的相應(yīng)準(zhǔn)備工作,然后自動(dòng)執(zhí)行 run() 方法的內(nèi)容,這是真正的多線程工作。

而直接執(zhí)行 run() 方法,會(huì)把 run 方法當(dāng)成一個(gè) main 線程下的普通方法去執(zhí)行,并不會(huì)在某個(gè)線程中執(zhí)行它,所以這并不是多線程工作。

總結(jié): 調(diào)用 start 方法方可啟動(dòng)線程并使線程進(jìn)入就緒狀態(tài),而 run 方法只是 thread 的一個(gè)普通方法調(diào)用,還是在主線程里執(zhí)行。

面試題:說(shuō)說(shuō) sleep() 方法和 wait() 方法區(qū)別和共同點(diǎn)?
  • 兩者最主要的區(qū)別在于:sleep 方法沒有釋放鎖,而 wait 方法釋放了鎖

  • 兩者都可以暫停線程的執(zhí)行。

  • wait 通常被用于線程間交互/通信,sleep 通常被用于暫停執(zhí)行。

  • wait() 方法被調(diào)用后,線程不會(huì)自動(dòng)蘇醒,需要?jiǎng)e的線程調(diào)用同一個(gè)對(duì)象上的 notify() 或者 notifyAll() 方法。或者可以使用 wait(long timeout)超時(shí)后線程會(huì)自動(dòng)蘇醒。

    sleep() 方法執(zhí)行完成后,線程會(huì)自動(dòng)蘇醒。

多線程的使用

啟動(dòng)線程的三種方式

  • 繼承 Thread 類,重寫 run 方法
  • 實(shí)現(xiàn) Runnable 接口,重寫 run 方法(或Lambda表達(dá)式)
  • 通過(guò)線程池來(lái)啟動(dòng) Executors.newCachedThread(實(shí)際上是以上兩種之一)
package com.mashibing.juc.c_000;public class T02_HowToCreateThread {static class MyThread extends Thread {@Overridepublic void run() {System.out.println("Hello MyThread!");}}static class MyRun implements Runnable {@Overridepublic void run() {System.out.println("Hello MyRun!");}}public static void main(String[] args) {new MyThread().start(); // 第一種new Thread(new MyRun()).start(); // 第二種(1)new Thread(()->{ // 第二種(2)System.out.println("Hello Lambda!");}).start();} }

sleep yield join 的含義

  • sleep:當(dāng)前線程暫停一段時(shí)間,讓別的線程去執(zhí)行。不釋放鎖。睡眠時(shí)間到,自動(dòng)復(fù)活
  • yield:當(dāng)前線程執(zhí)行時(shí),停下來(lái)進(jìn)入等待隊(duì)列。系統(tǒng)調(diào)度算法決定哪個(gè)線程繼續(xù)運(yùn)行(有可能還是自己)
  • join:在當(dāng)前線程加入調(diào)用的 join 線程,等調(diào)用的線程運(yùn)行完了,自己再繼續(xù)執(zhí)行

wait notify notifyAll 的含義

Object 對(duì)象中有三個(gè)方法 wait()、notify()、notifyAll(),它們的用途都是用來(lái)控制線程的狀態(tài)。

  • wait:必須在同步方法/同步代碼塊中被調(diào)用;釋放鎖,調(diào)用該線程的方法進(jìn)入等待隊(duì)列,直到被喚醒
  • notify:隨機(jī)喚醒等待隊(duì)列中等待的一個(gè)線程,使得該線程由 等待狀態(tài) 進(jìn)入 可運(yùn)行狀態(tài)
  • notifyAll:喚醒在此對(duì)象監(jiān)視器上等待的所有線程,被喚醒的線程將以常規(guī)方式與在該對(duì)象上主動(dòng)同步的其他所有線程進(jìn)行競(jìng)爭(zhēng)

如何關(guān)閉線程?interrupt 的含義?

Java沒有提供任何機(jī)制來(lái)安全地終止線程。它提供了 interrupt,這僅僅是會(huì)通知到被終止的線程“你該停止運(yùn)行了”,由編寫者決定如何處理 InterruptedException

@Override public void run() {while (true && !Thread.currentThread().isInterrupted()) { // 判斷是否處于中斷狀態(tài)System.out.println("go");try {throwInMethod();} catch (InterruptedException e) {// 恢復(fù)設(shè)置中斷狀態(tài),便于在下一個(gè)循環(huán)的時(shí)候檢測(cè)到中斷狀態(tài),正常退出Thread.currentThread().interrupt();e.printStackTrace();// 可以記錄一下日志}} } // JDK鎖、框架源碼的一些實(shí)現(xiàn)使用過(guò)interrupt,實(shí)際工程中,很少有人用它控制業(yè)務(wù)邏輯

不要使用 stop() 關(guān)閉線程,你應(yīng)該讓線程正常結(jié)束。

sleep阻塞時(shí)候會(huì)自動(dòng)檢測(cè)中斷:拋出異常 java.lang.InterruptedException: sleep interrupted

ThreadLocal

ThreadLocal 是線程引用對(duì)象,線程之間不共享。

  • 為什么要有 ThreadLocal?

    • Spring的聲明式事務(wù)會(huì)用到。(Spring 的聲明式事務(wù)在一個(gè)線程里)

    • connection 在連接池里,不同的 connection 之間怎么形成完整的事務(wù)?

      把 connection 放在當(dāng)前線程的 ThreadLocal 里面,以后拿的時(shí)候從 ThreadLocal 直接拿,不去線池里面拿。

  • ThreadLocal是怎么做到線程獨(dú)有的?

  • ThreadLocalMap是當(dāng)前 Thread 的一個(gè)成員變量,其 Key 是 ThreadLocal 對(duì)象,值是 Entry 對(duì)象,Entry 中只有一個(gè) Object 類的 vaule 值。
  • 使用 虛引用,讓 Key 指向 ThreadLocal

強(qiáng)軟弱虛四種引用

  • 強(qiáng)引用 StrongReference

    • Object o = new Object()
    • 只要有引用指向它,就算是OOM了,也不會(huì)被回收
  • 軟引用 SoftReference

    SoftReference<byte[]> m = new SoftReference<>(new byte[1024 * 1024 * 10]); System.out.println(m.get());
    • 內(nèi)存空間不夠時(shí),弱引用會(huì)被回收
    • 用來(lái)做緩存
  • 弱引用 WeakReference

    WeakReference<M> m = new WeakReference<>(new M()); System.out.println(m.get());
    • 只要發(fā)生GC,弱引用就會(huì)被回收
    • ThreadLocal 中會(huì)使用弱引用
  • 虛引用 PhantomReference

    虛引用,顧名思義,就是形同虛設(shè),與其他幾種引用都不同,虛引用并不會(huì)決定對(duì)象的生命周期。如果一個(gè)對(duì)象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時(shí)候都可能被垃圾回收。

    private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>(); public static void main(String[] args) {PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);
    • 虛引用主要用來(lái)跟蹤對(duì)象被垃圾回收的活動(dòng),必須和 引用隊(duì)列 ReferenceQueue 聯(lián)合使用
    • 虛引用對(duì)象被回收前,會(huì)被加入到隊(duì)列中,我們需要另一線程不斷檢測(cè)隊(duì)列,并以在所引用的對(duì)象的內(nèi)存被回收之前采取必要的行動(dòng)。
    • 用來(lái)管理 堆外內(nèi)存,堆外內(nèi)存包括:
      • 方法區(qū)
      • NIO 的 DirectByteBuffer

線程池

線程池前值知識(shí)

Executor 接口關(guān)系
Callable

? 類似于Runnable,但是可以有返回值

Future

? 存儲(chǔ)將來(lái)執(zhí)行的結(jié)果。Callable被執(zhí)行完之后的結(jié)果,被封裝到Future里面。

FutureTask

? 更加靈活,是Runnable和Future的結(jié)合,既是一個(gè)Runnable,又可以存結(jié)果。

CompletableFuture

? 可以用來(lái)管理多個(gè)Future的結(jié)果,對(duì)各種各樣的結(jié)果進(jìn)行組合處理。提供了非常好用的接口,十分友好。

場(chǎng)景:假設(shè)你需要提供一個(gè)服務(wù),這個(gè)服務(wù)查詢 京東、淘寶、天貓 對(duì)于同一類產(chǎn)品的價(jià)格并匯總展示,你用CompletableFuture開啟三個(gè)線程,來(lái)完成這個(gè)任務(wù),三個(gè)任務(wù)全部完成之后,才能繼續(xù)向下運(yùn)行。

線程池

ThreadPoolExecutor:是我們通常所說(shuō)的線程池。多個(gè)線程共享同一個(gè)任務(wù)隊(duì)列。

  • SingleThreadPool

    為什么會(huì)有單線程的線程池?單線程的線程池是有任務(wù)隊(duì)列的;線程池能幫你提供線程生命周期的管理。

    • 線程池里面只有一個(gè)線程
    • 保證我們?nèi)舆M(jìn)去的任務(wù)是被順序執(zhí)行的
  • CachedThreadPool

    當(dāng)任務(wù)到來(lái)時(shí),如果有線程空閑,我就用現(xiàn)有的線程;如果所有線程忙,就啟動(dòng)一個(gè)新線程。

    • 核心線程數(shù)為0
    • 最大線程數(shù)是Integer.MAX_VALUE
    • 保證任務(wù)不會(huì)堆積
    • SynchronousQueue 是容量為0的阻塞隊(duì)列,每個(gè)插入操作必須等待另一個(gè)線程執(zhí)行相應(yīng)的刪除操作
  • FixedThreadPool

    • 固定線程數(shù)的線程池
    • 適合做一些并行的計(jì)算,比如你要找1-200000之內(nèi)所有的質(zhì)數(shù),你將這個(gè)大任務(wù)拆成4個(gè)小線程,共同去運(yùn)行,肯定比串行計(jì)算要更快。
  • ScheduledPool

    • 專門用來(lái)執(zhí)行定時(shí)任務(wù)的一個(gè)線程池

ForkJoinPoll:先將任務(wù)分解,最后再匯總,可以有返回值或無(wú)返回值。每個(gè)線程有自己的任務(wù)隊(duì)列。

  • WorkStealingPool
    • 普通的線程池是有一個(gè)線程的集合,所有線程去同一個(gè)任務(wù)隊(duì)列里面取任務(wù),取出任務(wù)之后執(zhí)行,而 WorkStealingPool 是每一個(gè)線程都有自己獨(dú)立的任務(wù)隊(duì)列,如果某一個(gè)線程執(zhí)行完自己的任務(wù)之后,要去別的線程那里偷任務(wù),分擔(dān)別的線程的任務(wù)。
    • WorkStealingPool 本質(zhì)上還是一個(gè) ForkJoinPool

自定義一個(gè)線程池

public class TestThreadPool {static class Task implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " is running task");}}public static void main(String[] args) {// ThreadPoolExecutor 7個(gè)參數(shù)ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4,60, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(4),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy()); // 調(diào)用者處理服務(wù),這里是main調(diào)用for (int i = 0; i < 8; i++) { // 開啟8個(gè)任務(wù),放進(jìn)線程池執(zhí)行tpe.execute(new Task());}tpe.shutdown();} }
new ThreadPoolExecutor() 7個(gè)參數(shù)
  • corePoolSize:核心線程數(shù)

  • maximumPoolSize:最大線程數(shù)

  • keepAliveTime:空閑線程生存時(shí)間

  • TimeUnit:生存時(shí)間的單位

  • BlockingQueue:各種各樣的任務(wù)隊(duì)列

  • ThreadFactory:線程工廠

    可以使用Executors.defaultThreadFactory(),也可以自定義工廠,指定線程名稱

  • RejectedExecutionHandler:線程池忙且任務(wù)隊(duì)列滿時(shí)的 拒絕策略

    • CallerRunsPolicy,讓調(diào)用者線程去處理任務(wù)
    • AbortPolicy,拋異常
    • DiscardPolicy,扔掉,不拋異常
    • DiscardOldestPolicy,扔掉排隊(duì)時(shí)間最久的

總結(jié)

以上是生活随笔為你收集整理的面试必会系列 - 1.3 Java 多线程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。