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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

JavaEE-多线程(基础篇一)

發布時間:2024/3/13 java 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JavaEE-多线程(基础篇一) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 線程是啥?!(Thread)
  • Java的線程與操作系統線程的關系
    • 操作系統線程模型
      • 線程-在用戶空間下實現
      • 線程實現在操作系統內核中
      • 使用用戶線程更加輕量級進程混合實現
    • Java線程
      • Java線程在操作系統上本質
      • Java中的線程
        • 操作系統中的進程(線程)狀態(區分和JVM中的線程狀態)
        • 操作系統中線程和Java線程狀態的關系
  • 為哈要有線程?
    • 線程的優點
    • 進程和線程的區別
  • 創建一個多線程程序
  • 創建線程
    • 法一-創建Thread子類
    • 法二-實現Runnable接口
    • 法三-匿名內部類創建Thread子類對象
    • 法四-匿名內部類創建Runnable子類對象
    • 法五-lambda表達式創建Runnable子類對象
    • Thread的run和start的區別
  • 多線程的使用場景


上一篇博客,為大家詳細得講解了進程,那么本篇,主要就開始研究線程,以及在Java中是如何進行多線程并發編程的.
本篇博客目標在于帶大家理解和認識多線程,然后進一步掌握多線程程序的編寫和狀態,認識什么是線程不安全,以及其對應的解決思路,災后掌握一系列的關鍵字

線程是啥?!(Thread)

OK,為了保持本篇博客的嚴謹性,我們先來非常offensive一下,去查一查官方的解釋
給大家總結一下.

線程是程序運行的基本執行單元。當操作系統(不包括單線程的操作系統,如微軟早期的DOS)在執行一個程序時,會在系統中建立一個進程,而在這個進程中,必須至少建立一個線程(這個線程被稱為主線程)來作為這個程序運行的入口點。因此,在操作系統中運行的任何程序都至少有一個主線程

進程和線程是現代操作系統中兩個必不可少的運行模型。在操作系統中可以有多個進程,這些進程包括系統進程(由操作系統內部建立的進程)和用戶進程(由用戶程序建立的進程);一個進程中可以有一個或多個線程。進程和進程之間不共享內存,也就是說系統中的進程是在各自獨立的內存空間中運行的。而一個進程中的線可以共享系統分派給這個進程的內存空間。

線程不僅可以共享進程的內存,而且還擁有一個屬于自己的內存空間,這段內存空間也叫做線程棧, 是在建立線程時由系統分配的,主要用來保存線程內部所使用的數據,如線程執行函數中所定義的變量

注意:任何一個線程在建立時都會執行一個函數,這個函數叫做線程執行函數。也可以將這個函數看做線程的入口點(類似于程序中的main函數)。無論使用什么語言或技術來建立線程,都必須執行這個函數(這個函數的表現形式可能不一樣,但都會有一個這樣的函數)。如在Windows中用于建立線程的API函數CreateThread的第三個參數就是這個執行函數的指針。

在操作系統將進程分成多個線程后,這些線程可以在操作系統的管理下并發執行,從而大大提高了程序的運行效率。雖然線程的執行從宏觀上看是多個線程同時執行,但實際上這只是操作系統的障眼法。由于一塊CPU同時只能執行一條指令,因此,在擁有一塊CPU的計算機上不可能同時執行兩個任務。而操作系統為了能提高程序的運行效率,在一個線程空閑時會撤下這個線程,并且會讓其他的線程來執行,這種方式叫做線程調度。我們之所以從表面上看是多個線程同時執行,是因為不同線程之間切換的時間非常短,而且在一般情況下切換非常頻繁。假設我們有線程A和B。在運行時,可能是A執行了1毫秒后,切換到B后,B又執行了1毫秒,然后又切換到了A,A又執行1毫秒。由于1毫秒的時間對于普通人來說是很難感知的,因此,從表面看上去就象A和B同時執行一樣,但實際上A和B是交替執行的。

以上便是整理出的線程概念,初學的小白可能很難看得明白
那么現在我們就對這些非常官方的東西做一些比較人性化的解釋

首先,線程和進程之間有一定的練習,那么為什么要有多個進程呢?
答:是為了并發編程!但是CPU單個核心已經發展到了極致,如果想要提升算力,就得使用多個核心,那么我們引入并發編程,最大的目的就是為了能夠充分的利用好CPU的多核資源.
使用多進程這種編程模型,是完全可以做到并發編程的,并且也能夠使CPU多核被充分利用.但是在有些場景下,又會產生一些問題:如果需要頻繁地創建/銷毀進程,這個時候就會比較低效.
那么創建和銷毀進程有什么應用背景呢?比如,你寫了一個服務器程序,服務器要同一時刻給很多客戶端提供服務,這個時候就需要并發編程了,典型地做法,就是每個客戶端分配一個進程,提供一對一的服務
創建/銷毀進程,本身就是一個比較低效的操作:

  • 創建PCB
  • 分配系統資源(尤其是內存資源)<–這一步比較消耗時間
  • 把PCB加入到內核的雙向鏈表中
  • 為了提高這個場景下的效率,我們就引入了"線程",線程其實也叫做"輕量級進程"
    一個線程其實是包含在進程中的,一個進程里面可以有很多個線程.每個線程也有自己的PCB(一個進程里面可能就對應多個PCB)同一個進程里面的多個線程之間,共用一份系統資源,這就意味著,新創建的線程,可以不必重新給他分配系統資源,只需要復用之前的就可以了.
    因此創建線程只需要:

  • 創建PCB
  • 把PCB加入到內核的鏈表當中
  • 這就是線程相對于進程做出的重大改進,也就是線程更加輕量的原因
    進程是系統分配資源的最小單位,線程是系統調度的最小單位.

    Java的線程與操作系統線程的關系

    操作系統線程模型

    線程-在用戶空間下實現

    當線程在用戶空間下實現的時候,操作系統對線程的存在并不知曉,操作系統只能看到進程,而不能看到進程.所有的線程都是在用戶控件實現的.在操作系統看來,每一個進程只有一個線程,過去的操作系統大部分是這種實現方式,這種方式的好處之一是即使操作系統不支持線程,也可以通過庫函數來支持線程.
    那么我們用更通俗的方式來講解這段話.在這種模型下,程序員需要自己實現線程的數據結構,創建銷毀和調度維護.也就相當于需要實現一個自己的線程調度內核,而同時這些線程運行在操作系統的一個進程內,最后操作系用直接對進程進行調度

    這樣做是由一些有點的,首先就是確實在操作系統種實現了真實的多線程,其次線程的調度只是在用戶態,減少了操作系統從內核態到用戶態的切換開銷
    當然,缺點也是很明顯的.
    這種模式最致命的缺點也是由于操作系統不知道線程的存在,因此當一個進程中的某一個線程進行系統調用時,比如缺頁中斷而導致線程阻塞,此時操作系統會阻塞整個進程即使這個進程中其它線程還在工作。還有一個問題是假如進程中一個線程長時間不釋放CPU,因為用戶空間并沒有時鐘中斷機制,會導致此進程中的其它線程得不到CPU持續等待

    線程實現在操作系統內核中

    內核線程就是直接由操作系統內核支持的線程,這種線程由內核來完成內核切換,內核通過操縱調度器隊線程進行調度,并負責將線程的任務映射在各個處理器上.每個內核線程可以視為內核的一個分身,這樣操作系統就有能力同時處理多件事情,支持多線程的內核就叫做多線程內核

    通俗的講,程序員直接使用操作系統中已經實現的線程,而線程的創建,銷毀,調度,和維護,都是靠操作系統(準確的說是內核)來實現,程序員只需要使用系統調用,而不需要自己設計線程的調度算法和線程隊CPU資源的搶占使用

    輕量級進程(LWP)是建立在內核之上并由內核支持的用戶線程,它是內核線程的高度抽象,每一個輕量級進程都與一個特定的內核線程關聯.內核線程只能由內核管理并像普通進程一樣被調度


    這種輕量級進程與內核線程之間1:1的 關系就稱為一對一的線程模型

    使用用戶線程更加輕量級進程混合實現

    在這種混合實現下,即存在用戶線程,也存在輕量級線程.用戶線程還是可以完全建立在用戶空間中,因此用戶線程的創建,切換,析構等操作依然十分廉價,并且可以支持大規模的用戶線程并發.而操作系統提供支持的輕量級進程則作為用戶線程和內核線程之間的橋梁,這樣可以使用內核提供的線程線程調度共嗯那個及處理器映射,并且用戶線程的系統調用要通過輕量級進程來完成,大大降低了整個進程被完全阻塞的風險.在這種混合模式中,用戶線程與輕量級進程的數量比是不定的


    明白了前面兩種模型,就很好理解這種線程模型了,但實際上現在主流的操作系統已經不太常用這種線程模型了

    Java線程

    Java線程在操作系統上本質

    Java線程在JDK1.2之前,是基于稱為綠色線程的用戶線程實現的,而在JDK1.2中,線程模型替換為基于操作系統原生線程模型來實現.因此,在目前的我JDK版本中,操作系統支持怎樣的線程模型,在很大程度上決定了Java虛擬機的線程是怎樣映射的,這單在不同的平臺沒有辦法達成一致,虛擬機規范中也并未限定Java線程需要使用哪種線程模型來實現.線程模型支隊線程的并發規模和操作成本產生影響,隊Java程序的編碼和運行過程來說,這些差異都是透明的.

    也就是說程序員們為JVM開發了自己的一個線程調度內核,而到操作系統層面就是用戶空間內的線程實現.而到了JDK1.2以后,JVM選擇了更加穩健且方便使用的操作系統原生的線程模型,通過系統調用,將程序的線程交給了操作系統內核進行調度
    也就是說,現在的Java中線程的本質,也就是操作系統中的線程,Linux下是基于pthread庫實現的輕量級進程,Windows下是原生的系統win32API提供系統調用從而實現多線程

    Java中的線程


    特別注意:這些線程的狀態時JVM中的線程狀態!不是操作系統中的線程狀態。

    操作系統中的進程(線程)狀態(區分和JVM中的線程狀態)


    這里需要著重解釋一點,再現在的操作系統中,因為線程依舊被視為輕量級進程,所以操作系統中線程的狀態實際上和進程狀態時一致的模型

    操作系統中線程和Java線程狀態的關系

    從實際意義上來講,操作系統中的線程除去new和terminated狀態,一個和線程真實存在的狀態,只有

    • ready:表示線程已經被創建,正在等待系統調度分配CPU使用權
    • running:表示線程獲得了CPU使用權,正在運行
    • waiting:表示線程等待(或者說掛起),讓出CPU資源給其他線程使用

    那么為什么除去new和terminated狀態呢?是因為這兩種狀態實際上并不存在于線程運行中,所以也沒什么實際討論的意義
    對于Java中的線程狀態:
    無論是Timed Waiting ,Waiting還是Blocked,對應的都是操作系統線程的waiting(等待)狀態
    而Runnable狀態,則對應了操作系統中的ready和running狀態
    而對不同的操作系統,由于本身設計思路不一樣,對于線程的設計也存在差異,所以JVM在設計的時候已經聲明:虛擬機中的線程狀態,不反應任何操作系統線程狀態.

    為哈要有線程?

    進程屬于在CPU和系統資源等方面提供的抽象,能夠有效提高CPU的利用率。
    線程是在進程這個層次上提供的一層并發的抽象:

  • 能夠使系統在同一時間能夠做多件事情;
  • 當進程遇到阻塞時,例如等待輸入,線程能夠使不依賴輸入數據的工作繼續執行
  • 可以有效地利用多處理器和多核計算機,在沒有線程之前,多核并不能讓一個進程的執行速度提高
  • 進程有很多優點,它提供了多道編程,讓我們感覺我們每個人都擁有自己的CPU和其他資源,可以提高計算機的利用率。很多人就不理解了,既然進程這么優秀,為什么還要線程呢?其實,仔細觀察就會發現進程還是有很多缺陷的,主要體現在兩點上:
    進程只能在一個時間干一件事,如果想同時干兩件事或多件事,進程就無能為力了。
    進程在執行的過程中如果阻塞,例如等待輸入,整個進程就會掛起,即使進程中有些工作不依賴于輸入的數據,也將無法執行。

    如果這兩個缺點理解比較困難的話,舉個現實的例子也許你就清楚了:如果把我們上課的過程看成一個進程的話,那么我們要做的是耳朵聽老師講課,手上還 要記筆記,腦子還要思考問題,這樣才能高效的完成聽課的任務。而如果只提供進程這個機制的話,上面這三件事將不能同時執行,同一時間只能做一件事,聽的時 候就不能記筆記,也不能用腦子思考,這是其一;如果老師在黑板上寫演算過程,我們開始記筆記,而老師突然有一步推不下去了,阻塞住了,他在那邊思考著,而 我們呢,也不能干其他事,即使你想趁此時思考一下剛才沒聽懂的一個問題都不行,這是其二。
    現在你應該明白了進程的缺陷了,而解決的辦法很簡單,我們完全可以讓聽、寫、思三個獨立的過程,并行起來,這樣很明顯可以提高聽課的效率。而實際的操作系統中,也同樣引入了這種類似的機制——線程。

    線程的優點

    因為要并發,我們發明了進程,又進一步發明了線程。只不過進程和線程的并發層次不同:進程屬于在處理器這一層上提供的抽象;線程則屬于在進程這個層 次上再提供了一層并發的抽象。如果我們進入計算機體系結構里,就會發現,流水線提供的也是一種并發,不過是指令級的并發。這樣,流水線、線程、進程就從低 到高在三個層次上提供我們所迫切需要的并發!
    除了提高進程的并發度,線程還有個好處,就是可以有效地利用多處理器多核計算機。現在的處理器有個趨勢就是朝著多核方向發展,在沒有線程之前多核并不能讓一個進程的執行速度提高,原因還是上面所有的兩點限制。但如果講一個進程分解為若干個線程,則可以讓不同的線程運行在不同的核上,從而提高了進 程的執行速度。

    進程和線程的區別

    • 進程是具有一定獨立功能的而程序關于某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位
    • 線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位,線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源
    • 一個線程可以創建和撤銷另一個線程,同一個進程中的多個線程之間可以并發執行

    進程和線程的主要差別在于他們是不同的操作系統資源管理方式.進程有獨立的地址空間,一個進程崩潰后,在保護模式下不會對其他進程產生影響,而線程只是一個進程中的不同執行路徑.線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等于整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些.但對于一些要求同時進行并且又要共享某些變量的并發操作,只能用線程,不能用進程.

    創建一個多線程程序

    package thread;/** * @author Gremmie102 * @date 2022/7/21 9:15 * @purpose : 關于多線程的一些代碼,創建線程的第一種方法 */ class MyThread extends Thread{public void run(){//這個run方法重寫的目的,是為了明確,咱們新創建出來的線程要干啥活while(true){System.out.println("hello thread!");try{Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}} } public class Demo1 {public static void main(String[] args) {//創建一個線程//Java中創建線程,離不開一個關鍵的類.Thread//其中一種比較樸素的創建方式,是寫一個子類,繼承Thread,重寫其中的run方法//光創建了這個類,還不算創建線程,還得創建實例Thread t = new MyThread();//向上轉型的寫法,可寫可不寫t.start();//這才是真正開始創建線程//(在操作系統內核中,創建出對應線程的PCB,然后讓這個PCB加入到系統鏈表中,參與調度)//在這個代碼中,雖然先啟動的線程,后打印的hello main//但是實際執行的時候,看到的確是,先打印了hello main ,后打印了hello thread!//這是因為:// 1.每個線程是獨立的執行流,//main對應的線程是一個執行流,MyThread是另一個執行流.//這兩個執行流之間是并發的執行關系(并發+并行)//2.此時兩個線程執行的先后順序,取決于操作系統調度器具體實現//(程序員可以把這里的調度規則,簡單得視為"隨機調度")//System.out.println("hello main");//雖然反復運行了多次,好像結果都是一樣的,但我們的順序仍然是不可確定的//當前看到的先打印main,大概率是受到創建線程自身的開銷影響的.//哪怕連續運行1000次main在前,也不能保證1001次的時候不出現thread在前!//*編寫多線程代碼的時候,一定要注意到!//默認情況下,多個線程的執行順序,是"無序",是"隨機調度"的//進程的退出碼為:exit code 0//操作系統中用進程的退出碼來表示"進程的運行結果"//使用0表示進程執行完畢.結果正確//使用非0表示進程執行完畢,結果不正確//還有個別情況main還沒返回呢,進程就崩潰,此時返回的值很可能是一個隨機值//我們可以想辦法讓這個進程別結束得那么快,好看清楚這個進程//我們可以搞兩個si循環while(true){System.out.println("hello main");try{Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}//運行之后我們就可以發現,main和thread交替打印//每一波打印機個,切換到下一波是什么時候,都是不確定的,都是由調度器控制的//在JDK里提供了一個jconsole這樣的工具,可以看到Java進程里的線程詳情.//在jdk/bin/jconsole.exe//啟動之后先選擇我們要看哪個Java進程//少數情況打開jconsle時,可能不顯示這里的進程列表.//這個時候退出,然后右鍵管理員運行.//在標簽頁中選擇線程,往下翻,在左下角部分可以查詢到當前Java進程中的線程信息了//剛才的死循環代碼,打印的太多太快//有的時候不希望它們打印的這么快(不方便來觀察)//我們可以用sleep()來讓線程適當的"休息一下"-->指定讓線程摸一會魚,不要上cpu干活//使用Thread.sleep();的方法進行休眠//sleep時Thread的靜態成員方法//sleep的參數是一個時間,單位是ms//計算機算的快,常用的就是ms,us,ns這結果單位//sleep(1000)就是要休眠1000ms,除非遇到一些異常打斷休眠,//所以為了防止這樣的情況發生,我們要套上try catch的殼//這里還有一個經典面試題:談談Thread的run和start的區別//使用run,可以看到只是在打印thread,沒有打印main//直接調用run,并沒有創建新的線程,而是在之前的線程中,執行了run里的內容//使用start,則是創建新的線程,新的線程里面會調用run,新線程和舊線程之間是并發執行的關系} }

    普通程序一般都是按照代碼執行的順序,所以一般會卡在第一個死循環的地方,然后一直在那個地方運行
    我們寫一個簡單的main方法的時候

    class MyThread extends Thread{public static void main(){System.out.println("我叫葛玉禮");} }

    這里雖然我們并沒有手動創建其他線程,但是Java進程在運行的時候,內部也會創建出多個線程.
    運行這個程序,操作系統就會創建一個Java進程,在這個Java進程里就會有一個線程調用main方法,這個這個線程我們就稱為主線程
    談到多進程的時候,我們經常會談到"父進程"“子進程”
    進程A里面創建了進程B
    A是B的進程,B是A的子進程
    但是,在多線程里面,沒有"父線程""子線程"這種說法
    但是仍然認為線程之間的地位是對等的

    創建線程

    法一-創建Thread子類

  • 繼承Thread類
  • class MyThread extends Thread {public void run(){System.out.println("此處為線程執行的語句");} }

    首先我們要繼承Thread類,并且重寫其中的run方法,在這個run方法中,就描述著我們的線程要執行的內容.

  • 創建該線程的實例
  • Thread t = new MyThread();//向上轉型

    我們創建一個線程實例出來,但此時,該線程還沒有塞入任務鏈表中參與系統調度,我們還差最后一步

  • 調用Thread中的start方法啟動線程
  • t.start();//線程此時開始運行

    法二-實現Runnable接口

  • 實現Runnable接口
  • class MyRunnable implements Runnable {public void run(){System.out.println("這里是線程運行的代碼")} }
  • 創建Thread的實例,并且在調用Thread的構造方法時將實現Runnable接口的MyRunnable對象作為target的參數.
  • Thread t = new Thread(new MyRunnable());
  • 調用start方法
  • t.start//線程開始運行.

    我們繼承Thread類,可以直接使用this來表示當前線程對象的引用.
    但是實現Runnable接口的話,this表示的就是MyRunnable的引用.需要使用Thread.currentThread()來表示當前線程對象的引用.

    法三-匿名內部類創建Thread子類對象

    //使用匿名內部類創建Thread子類對象 Thread t1 = new Thread() {public void run(){System.out.println("使用匿名內部類創建Thread子類對象");} };

    法四-匿名內部類創建Runnable子類對象

    Thread t2 = new Thread(new Runnbale() {public void run(){System.out.println("使用匿名內部類創建Runnbale子類對象");} });

    法五-lambda表達式創建Runnable子類對象

    Thread t2 = new Thread(() -> {System.out.println("使用匿名內部類創建Thread子類對象"); });

    Thread的run和start的區別

    package thread;/*** @author Gremmie102* @date 2022/7/21 9:15* @purpose : 關于多線程的一些代碼,創建線程的第一種方法*/ class MyThread extends Thread{public void run(){//這個run方法重寫的目的,是為了明確,咱們新創建出來的線程要干啥活while(true){System.out.println("hello thread!");try{Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}} } public class Demo1 {public static void main(String[] args) {//創建一個線程//Java中創建線程,離不開一個關鍵的類.Thread//其中一種比較樸素的創建方式,是寫一個子類,繼承Thread,重寫其中的run方法//光創建了這個類,還不算創建線程,還得創建實例Thread t = new MyThread();//向上轉型的寫法,可寫可不寫t.start();//這才是真正開始創建線程//(在操作系統內核中,創建出對應線程的PCB,然后讓這個PCB加入到系統鏈表中,參與調度)//在這個代碼中,雖然先啟動的線程,后打印的hello main//但是實際執行的時候,看到的確是,先打印了hello main ,后打印了hello thread!//這是因為:// 1.每個線程是獨立的執行流,//main對應的線程是一個執行流,MyThread是另一個執行流.//這兩個執行流之間是并發的執行關系(并發+并行)//2.此時兩個線程執行的先后順序,取決于操作系統調度器具體實現//(程序員可以把這里的調度規則,簡單得視為"隨機調度")//System.out.println("hello main");//雖然反復運行了多次,好像結果都是一樣的,但我們的順序仍然是不可確定的//當前看到的先打印main,大概率是受到創建線程自身的開銷影響的.//哪怕連續運行1000次main在前,也不能保證1001次的時候不出現thread在前!//*編寫多線程代碼的時候,一定要注意到!//默認情況下,多個線程的執行順序,是"無序",是"隨機調度"的//進程的退出碼為:exit code 0//操作系統中用進程的退出碼來表示"進程的運行結果"//使用0表示進程執行完畢.結果正確//使用非0表示進程執行完畢,結果不正確//還有個別情況main還沒返回呢,進程就崩潰,此時返回的值很可能是一個隨機值//我們可以想辦法讓這個進程別結束得那么快,好看清楚這個進程//我們可以搞兩個si循環while(true){System.out.println("hello main");try{Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}//運行之后我們就可以發現,main和thread交替打印//每一波打印機個,切換到下一波是什么時候,都是不確定的,都是由調度器控制的//在JDK里提供了一個jconsole這樣的工具,可以看到Java進程里的線程詳情.//在jdk/bin/jconsole.exe//啟動之后先選擇我們要看哪個Java進程//少數情況打開jconsle時,可能不顯示這里的進程列表.//這個時候退出,然后右鍵管理員運行.//在標簽頁中選擇線程,往下翻,在左下角部分可以查詢到當前Java進程中的線程信息了//剛才的死循環代碼,打印的太多太快//有的時候不希望它們打印的這么快(不方便來觀察)//我們可以用sleep()來讓線程適當的"休息一下"-->指定讓線程摸一會魚,不要上cpu干活//使用Thread.sleep();的方法進行休眠//sleep時Thread的靜態成員方法//sleep的參數是一個時間,單位是ms//計算機算的快,常用的就是ms,us,ns這結果單位//sleep(1000)就是要休眠1000ms,除非遇到一些異常打斷休眠,//所以為了防止這樣的情況發生,我們要套上try catch的殼//這里還有一個經典面試題:談談Thread的run和start的區別//使用run,可以看到只是在打印thread,沒有打印main//直接調用run,并沒有創建新的線程,而是在之前的線程中,執行了run里的內容//使用start,則是創建新的線程,新的線程里面會調用run,新線程和舊線程之間是并發執行的關系} }

    我們再來觀察這段代碼,我們在start之后,主線程和thread(我們自己定義的線程)一起被系統調度執行.那么這里兩個線程都是有個無限循環執行的任務.這里就可以很容易看出系統調度線程的過程
    start可以看到兩個線程并發的執行,兩組打印時交替出現的
    如果我們直接調用run,此時并沒有創建新的線程,而只是在之前的線程中,執行了run中的內容
    我們使用start,則是創建新的線程,新的線程里面會調用run,新線程和舊線程之間時并發執行的關系.


    那么使用多線程帶來的好處是啥呢?
    hi用多線程,能夠更加充分得利用CPU多和資源
    同一項任務,我們利用多線程就可以更快的執行完

    package thread;/*** @author Gremmie102* @date 2022/7/21 11:41* @purpose :*/ public class Demo6 {//1.單個線程,串行的,完成10億次自增.//2.兩個線程,并發的,完成10億次自增private static final long COUNT = 20_0000_0000;private static void serial(){//需要把方法執行的時間給記錄下來//記錄當前的毫秒級時間戳long beg = System.currentTimeMillis();int a = 0;for (long i=0;i<COUNT;i++){a++;}a = 0;for (long i = 0;i < COUNT;i++){a++;}long end = System.currentTimeMillis();System.out.println("單線程消耗時間:"+(end-beg)+"ms");}private static void concurrency(){//并發long beg = System.currentTimeMillis();Thread t1 = new Thread(()->{int a = 0;for (long i =0;i<COUNT;i++){a++;}});Thread t2 = new Thread(()->{int a = 0;for (long i =0;i<COUNT;i++){a++;}});t1.start();t2.start();try{t1.join();//這里的join是等待線程結束,等待線程把自己的run方法執行完//在主線程中調用t1.join,意思就是讓main線程等t1執行完t2.join();//t1 t2是會開始執行,同時不等t1 t2執行玩,main線程就往下走了,于是就結束計時//此處的即使,是為了衡量t1和t2的執行時間,正確的做法應該是等到t1和t2都執行完再計時} catch (InterruptedException e) {e.printStackTrace();}long end = System.currentTimeMillis();System.out.println("單線程消耗時間:"+(end-beg)+"ms");}public static void main(String[] args) { // serial();concurrency();} }

    如上代碼,我們可以通過單線程和多線程的對比發現,運行時間有很大的改善
    那么我們注意到了這個join()的方法,這里我們在前一篇博客中有講到,join就是等待線程結束(等待線程把自己的run方法執行完),在主線程中調用t1.join,意思就是讓main線程等待t1執行完.這兩個join操作誰先誰后其實并不影響.
    針對這里的先后順序并不影響的原因,我做一下解釋,因為如果是先t1先join,t2后join.
    那么主線程就要先等待t1線程執行完,這時main線程是在阻塞狀態的,t2此時和t1并發執行,那么當t1執行完之后,主線程又可以繼續執行了,這時又運行到了下一條t2的join,主線程又要停下來等待了,等到t2線程執行完之后,主線程再繼續.
    那我們把t1和t2的順序反過來之后,也是如此,都是main線程等待t1t2都運行結束之后再運行,在這之前t1t2都是并發執行的.
    那么又有問題了,t2可以等待t1執行完再去執行嗎,可以的話代碼怎么實現呢?
    很簡單,只要在t2的run方法中寫上t1.join就可以了.
    如果我們沒有加上t1.join和t2.join,這時雖然某種意義上是并發的,但消耗的時間其實并不是單個線程的一半,比如單個線程串行執行,消耗的時間是1300ms,那么兩個線程并發執行確是800ms,因為這里

  • 創建線程本身也是有開銷的
  • 兩個線程在CPU上不一定是純并行,也可能是并發,一部分時間里面是并行了,一部分時間里是并發了.
  • 線程的調度,也是有開銷的(當前場景中,開銷應該是非常小的)
  • 多線程的使用場景

  • 在CPU密集型場景
  • 代碼中大部分工作,都是在使用CPU進行運算
    使用多線程,就可以更好的利用CPU多核計算資源,從而提高效率

  • 在I/O密集型場景
  • I input 輸入
    O output 輸出
    讀寫硬盤,讀寫網卡…這些操作都算I/O
    在這些場景里,就需要花很大時間來等待
    像這些IO操作,都是幾乎不消耗CPU就能完成快速讀寫數據的操作
    既然CPU在摸魚,就可以給他找點活干,也可以使用多線程,避免COU過于閑置

    希望能夠幫助到你

    總結

    以上是生活随笔為你收集整理的JavaEE-多线程(基础篇一)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。