線程與線程池
什么是線程,
提到線程就要說(shuō)一下進(jìn)程,
進(jìn)程: 進(jìn)程就是正在執(zhí)行的程序,(任務(wù)管理器)
線程: 是程序執(zhí)行的一條路徑,一個(gè)進(jìn)程中可以包含多條進(jìn)程,
舉個(gè)例子,你打開你的微信,這叫打開了一個(gè)進(jìn)程,你在微信里跟微信好友視頻聊天就是開啟了一個(gè)線程,
兩者之間的關(guān)系****一個(gè)進(jìn)程中至少有一個(gè)線程,當(dāng)然也可以有多條線程
一條線程一定會(huì)在進(jìn)程里面
一、說(shuō)說(shuō)概念
1、進(jìn)程(process)
狹義定義:進(jìn)程就是一段程序的執(zhí)行過(guò)程。
廣義定義:進(jìn)程是一個(gè)具有一定獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合的一次運(yùn)行活動(dòng)。
它是操作系統(tǒng)動(dòng)態(tài)執(zhí)行的基本單元,在傳統(tǒng)的操作系統(tǒng)中,進(jìn)程既是基本的分配單元,也是基本的執(zhí)行單元。
簡(jiǎn)單的來(lái)講進(jìn)程的概念主要有兩點(diǎn):第一,進(jìn)程是一個(gè)實(shí)體。每一個(gè)進(jìn)程都有它自己的地址空間,一般情況下,包括文本區(qū)域(text region)、數(shù)據(jù)區(qū)域(data region)和堆棧(stack region)。
文本區(qū)域存儲(chǔ)處理器執(zhí)行的代碼;數(shù)據(jù)區(qū)域存儲(chǔ)變量和進(jìn)程執(zhí)行期間使用的動(dòng)態(tài)分配的內(nèi)存;堆棧區(qū)域存儲(chǔ)著活動(dòng)過(guò)程調(diào)用的指令和本地變量。
第二,進(jìn)程是一個(gè)“執(zhí)行中的程序”。程序是一個(gè)沒(méi)有生命的實(shí)體,只有處理器賦予程序生命時(shí),它才能成為一個(gè)活動(dòng)的實(shí)體,我們稱其為進(jìn)程。
進(jìn)程狀態(tài):進(jìn)程有三個(gè)狀態(tài),就緒、運(yùn)行和阻塞。就緒狀態(tài)其實(shí)就是獲取了出cpu外的所有資源,只要處理器分配資源就可以馬上執(zhí)行。
就緒狀態(tài)有排隊(duì)序列什么的,排隊(duì)原則不再贅述。運(yùn)行態(tài)就是獲得了處理器分配的資源,程序開始執(zhí)行。
阻塞態(tài),當(dāng)程序條件不夠時(shí)候,需要等待條件滿足時(shí)候才能執(zhí)行,如等待i/o操作時(shí)候,此刻的狀態(tài)就叫阻塞態(tài)。
2、程序
說(shuō)起進(jìn)程,就不得不說(shuō)下程序。先看定義:程序是指令和數(shù)據(jù)的有序集合,其本身沒(méi)有任何運(yùn)行的含義,是一個(gè)靜態(tài)的概念。
而進(jìn)程則是在處理機(jī)上的一次執(zhí)行過(guò)程,它是一個(gè)動(dòng)態(tài)的概念。這個(gè)不難理解,其實(shí)進(jìn)程是包含程序的,進(jìn)程的執(zhí)行離不開程序,進(jìn)程中的文本區(qū)域就是代碼區(qū),也就是程序。
3、線程
通常在一個(gè)進(jìn)程中可以包含若干個(gè)線程,當(dāng)然一個(gè)進(jìn)程中至少有一個(gè)線程,不然沒(méi)有存在的意義。
線程可以利用進(jìn)程所擁有的資源,在引入線程的操作系統(tǒng)中,通常都是把進(jìn)程作為分配資源的基本單位,而把線程作為獨(dú)立運(yùn)行和獨(dú)立調(diào)度的基本單位,
由于線程比進(jìn)程更小,基本上不擁有系統(tǒng)資源,故對(duì)它的調(diào)度所付出的開銷就會(huì)小得多,能更高效的提高系統(tǒng)多個(gè)程序間并發(fā)執(zhí)行的程度。
4、多線程
在一個(gè)程序中,這些獨(dú)立運(yùn)行的程序片段叫作“線程”(Thread),利用它編程的概念就叫作“多線程處理”。
多線程是為了同步完成多項(xiàng)任務(wù),不是為了提高運(yùn)行效率,而是為了提高資源使用效率來(lái)提高系統(tǒng)的效率。線程是在同一時(shí)間需要完成多項(xiàng)任務(wù)的時(shí)候?qū)崿F(xiàn)的。
最簡(jiǎn)單的比喻多線程就像火車的每一節(jié)車廂,而進(jìn)程則是火車。車廂離開火車是無(wú)法跑動(dòng)的,同理火車也不可能只有一節(jié)車廂。多線程的出現(xiàn)就是為了提高效率。
二、說(shuō)說(shuō)區(qū)別
1、進(jìn)程與線程的區(qū)別:
進(jìn)程和線程的主要差別在于它們是不同的操作系統(tǒng)資源管理方式。進(jìn)程有獨(dú)立的地址空間,一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其它進(jìn)程產(chǎn)生影響,
而線程只是一個(gè)進(jìn)程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,但線程之間沒(méi)有單獨(dú)的地址空間,一個(gè)線程死掉就等于整個(gè)進(jìn)程死掉,
所以多進(jìn)程的程序要比多線程的程序健壯,但在進(jìn)程切換時(shí),耗費(fèi)資源較大,效率要差一些。但對(duì)于一些要求同時(shí)進(jìn)行并且又要共享某些變量的并發(fā)操作,只能用線程,不能用進(jìn)程。
1) 簡(jiǎn)而言之,一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程.
2) 線程的劃分尺度小于進(jìn)程,使得多線程程序的并發(fā)性高。
3) 另外,進(jìn)程在執(zhí)行過(guò)程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存,從而極大地提高了程序的運(yùn)行效率。
4) 線程在執(zhí)行過(guò)程中與進(jìn)程還是有區(qū)別的。每個(gè)獨(dú)立的線程有一個(gè)程序運(yùn)行的入口、順序執(zhí)行序列和程序的出口。
但是線程不能夠獨(dú)立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個(gè)線程執(zhí)行控制。
5) 從邏輯角度來(lái)看,多線程的意義在于一個(gè)應(yīng)用程序中,有多個(gè)執(zhí)行部分可以同時(shí)執(zhí)行。
但操作系統(tǒng)并沒(méi)有將多個(gè)線程看做多個(gè)獨(dú)立的應(yīng)用,來(lái)實(shí)現(xiàn)進(jìn)程的調(diào)度和管理以及資源分配。這就是進(jìn)程和線程的重要區(qū)別。
線程的5種狀態(tài):
新生狀態(tài)(New)****: 當(dāng)一個(gè)線程的實(shí)例被創(chuàng)建(new)后,此時(shí)該線程處于新生(new)狀態(tài),處于新生狀態(tài)的線程有自己的內(nèi)存空間,但該線程并沒(méi)有運(yùn)行,此時(shí)線程還不是活著的(not alive)。****就緒狀態(tài)(Runnable)****: 通過(guò)調(diào)用線程實(shí)例的start()方法來(lái)啟動(dòng)線程使線程進(jìn)入就緒狀態(tài)(runnable);處于就緒狀態(tài)的線程已經(jīng)具備了運(yùn)行條件,在系統(tǒng)為其分配CPU之后才會(huì)執(zhí)行;若線程未被分配到CPU的話不會(huì)執(zhí)行,就緒狀態(tài)并不是運(yùn)行狀態(tài);此時(shí)線程是活著的(alive)。****運(yùn)行狀態(tài)(Running)****: 一旦獲取CPU(被JVM選中),線程就進(jìn)入運(yùn)行(running)狀態(tài),線程的run()方法才開始被執(zhí)行;在運(yùn)行狀態(tài)的線程會(huì)執(zhí)行自己的run()方法中的操作,直到調(diào)用其他的方法而終止、或者等待某種資源而阻塞、或者完成任務(wù)而死亡;如果在給定的時(shí)間片內(nèi)沒(méi)有執(zhí)行結(jié)束,就會(huì)被系統(tǒng)給換下來(lái)回到線程的等待狀態(tài);此時(shí)線程是活著的(alive);****阻塞狀態(tài)(Blocked)****:通過(guò)調(diào)用join()、sleep()、wait()或者資源被占用使線程處于阻塞(blocked)狀態(tài);處于Blocking狀態(tài)的線程仍然是活著的(alive)****死亡狀態(tài)(Dead)****:當(dāng)一個(gè)線程的run()方法運(yùn)行完畢或被中斷或被異常退出,該線程到達(dá)死亡(dead)狀態(tài)。此時(shí)可能仍然存在一個(gè)該Thread的實(shí)例對(duì)象,但該Thread已經(jīng)不可能在被作為一個(gè)可被獨(dú)立執(zhí)行的線程對(duì)待了,線程的獨(dú)立的call stack已經(jīng)被dissolved。一旦某一線程進(jìn)入Dead狀態(tài),他就再也不能進(jìn)入一個(gè)獨(dú)立線程的生命周期了。對(duì)于一個(gè)處于Dead狀態(tài)的線程調(diào)用start()方法,會(huì)出現(xiàn)一個(gè)運(yùn)行期(runtime exception)的異常;處于Dead狀態(tài)的線程不是活著的(not alive)。
創(chuàng)建線程的三種方式
第一種:
1定義一個(gè)類繼承Thread,并重寫Run方法
2將要執(zhí)行的代碼寫到Run方法中,
3創(chuàng)建這個(gè)類的實(shí)例,得到對(duì)象調(diào)用start方法,開始這個(gè)條線程
[圖片上傳失敗...(image-10fc6a-1574826880818)]
第二種:
1、定義一個(gè)類MyRunnable實(shí)現(xiàn)Runnable接口,并重寫run方法。****2、將要執(zhí)行的代碼寫在run方法中。****3、創(chuàng)建Thread對(duì)象, 傳入MyRunnable的實(shí)例,并調(diào)用start()方法開啟線程。
[圖片上傳失敗...(image-be5943-1574826880818)]
第三種:
1、自定義一個(gè)類 MyCallable 實(shí)現(xiàn) Callable 接口,并重寫call()方法****2、將要執(zhí)行的代碼寫在call()方法中****3、創(chuàng)建線程池對(duì)象,調(diào)用submit()方法執(zhí)行MyCallable任務(wù),并返回Future對(duì)象****4、調(diào)用Future對(duì)象的get()方法獲取call()方法執(zhí)行完后的值
[圖片上傳失敗...(image-578d8b-1574826880818)]
創(chuàng)建線程的三種方式對(duì)比
一、繼承 Thread 類與實(shí)現(xiàn) Runnable 接口的區(qū)別
我們都知道****java****支持單繼承,多實(shí)現(xiàn)。實(shí)現(xiàn) Runnable 接口還可以繼承其他類,而使用繼承 Thread 就不能繼承其他類了。所以當(dāng)你想創(chuàng)建一個(gè)線程又希望繼承其他類的時(shí)候就該選擇實(shí)現(xiàn) Runnable 接口的方式。
二、實(shí)現(xiàn) Callable 接口與 Runnable 接口的區(qū)別
Callable 執(zhí)行的方法是 call() ,而 Runnable 執(zhí)行的方法是 run()。****call() 方法有返回值還能拋出異常, run() 方法則沒(méi)有沒(méi)有返回值,也不能拋出異常。
多線程
一個(gè)進(jìn)程中有N個(gè)線程,
多線程的優(yōu)點(diǎn),為什么要實(shí)現(xiàn)多線程
1、提高CPU的使用率****例如朋友圈發(fā)表圖片,當(dāng)你上傳9張圖片的時(shí)候,如果開啟一個(gè)線程用同步的方式一張張上傳圖片,假設(shè)每次上傳圖片的線程只占用了CPU 1%的資源,剩下的99%資源就浪費(fèi)了。但是如果你開啟9個(gè)線程同時(shí)上傳圖片,CPU就可以使用9%的資源了。****2、提高程序的工作效率****還是拿朋友圈發(fā)表圖片來(lái)說(shuō),假設(shè)開啟一個(gè)線程上傳一張圖片的時(shí)間是1秒,那么同步的方式上傳9張就需要9秒,但是你開啟9個(gè)線程同時(shí)上傳圖片,那么就只需要1秒就完成了。
缺點(diǎn) 會(huì)影響CPU的性能,因?yàn)镃PU需要在線程之間來(lái)回切換,還會(huì)出現(xiàn)線程安全的問(wèn)題(死鎖)
多線程中的并行與并發(fā)
概念:
并行:多個(gè)處理器或者多核處理器同時(shí)執(zhí)行多個(gè)不同的任務(wù)。****一臺(tái)手機(jī)上開了好幾個(gè)App,這是并行****并發(fā):一個(gè)處理器處理多個(gè)任務(wù)。
比如 雙十一,6.18,下單
為什么多線程會(huì)出現(xiàn)線程安全的問(wèn)題:
原因是有多個(gè)線程在操作共享的數(shù)據(jù)。即一個(gè)線程在操作共享數(shù)據(jù)的過(guò)程中CPU切換到其他線程又對(duì)該數(shù)據(jù)進(jìn)行操作,這就是所謂的多線程并發(fā)。
解決辦法:
把操作數(shù)據(jù)的那段代碼用****synchronized 進(jìn)行同步, 這樣就能保證在同一時(shí)刻只能有一個(gè)線程能夠訪問(wèn)。
線程池
什么是線程池,為什么要有線程池,他有什么優(yōu)點(diǎn)
為了提高工作效率,提高CPU的使用率,我們有的時(shí)候會(huì)創(chuàng)建多個(gè)線程去完成任務(wù),但是線程
的創(chuàng)建和銷毀是非常消耗CPU和內(nèi)存的,因?yàn)樗婕暗脚c操作系統(tǒng)的交互,這樣性價(jià)比太低,
導(dǎo)致創(chuàng)建與銷毀線程的消耗比你實(shí)際要完成的業(yè)務(wù)還大,那么就出現(xiàn)了線程池,在線程池中
的線程每一條線程結(jié)束后,并不會(huì)被銷毀,而是回到線程池中成為空閑等待狀態(tài),等到下一個(gè)
對(duì)象來(lái)使用,
所以一句話總結(jié)線程池,他減少了創(chuàng)建和銷毀線程帶來(lái)的性能開銷,還可以控制最大并發(fā)線程的數(shù)量,避免出現(xiàn)對(duì)內(nèi)存的過(guò)多消耗
線程池是存在于內(nèi)存的堆中
主要了解一下****java.uitl.concurrent.ThreadPoolExecutor****(思瑞得,撲哦,伊克塞Q他)****類,是線程池中最核心的一個(gè)類,
CorePoolSize 線程池的核心線程數(shù)
默認(rèn)情況下,核心線程數(shù)會(huì)在線程中一直存活,即使它們處于閑置狀態(tài)。
maximumPoolSize 線程池所能容納的最大線程數(shù)。
當(dāng)活動(dòng)線程數(shù)達(dá)到這個(gè)數(shù)值后,后續(xù)的新任務(wù)將會(huì)被阻塞。
keepAliveTime 非核心線程閑置時(shí)的超時(shí)時(shí)長(zhǎng),超過(guò)這個(gè)時(shí)長(zhǎng),非核心線程就會(huì)被回收,當(dāng)ThreadPoolExector的allowCoreThreadTimeOut屬性設(shè)置為True時(shí),keepAliveTime同樣會(huì)作用于核心線程。
unit 用于指定keepAliveTime參數(shù)的時(shí)間單位,這是一個(gè)枚舉,常用的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分鐘)等。
TimeUnit.NANOSECONDS 納秒
TimeUnit.MICROSECONDS 微秒
TimeUnit.MILLISECONDS 毫秒
TimeUnit.SECONDS 秒
TimeUnit.MINUTES 分鐘
TimeUnit.HOURS 小時(shí)
TimeUnit.DAYS 天
workQueue 線程池中的任務(wù)隊(duì)列,通過(guò)線程池execute方法提交的Runnable對(duì)象會(huì)存儲(chǔ)在這個(gè)參數(shù)中。
這個(gè)任務(wù)隊(duì)列是BlockQueue類型,屬于阻塞隊(duì)列,就是當(dāng)隊(duì)列為空的時(shí)候,此時(shí)取出任務(wù)的操作會(huì)被阻塞,等待任務(wù)加入隊(duì)列中不為空的時(shí)候,才能進(jìn)行取出操作,而在滿隊(duì)列的時(shí)候,添加操作同樣被阻塞。
核心方法:
execute() 執(zhí)行
submit() 提交
shutdown()
shutdownNow() 關(guān)閉
線程和進(jìn)程區(qū)別、如何進(jìn)行進(jìn)程間通信
首先我闡述以下我所理解的進(jìn)程和線程。
進(jìn)程(Processes)是系統(tǒng)進(jìn)行資源分配的獨(dú)立單元。
線程是 cpu 調(diào)度的基本單元。
我所理解的進(jìn)程和線程的區(qū)別是這樣的。
a) 進(jìn)程中可以創(chuàng)建多個(gè)線程,進(jìn)程是系統(tǒng)資源分配的單位,線程是 cpu 調(diào)度的單位。
b) 進(jìn)程之間不能共享資源,而線程共享所在進(jìn)程的地址空間和其它資源。同時(shí)線程還有自
己的棧和棧指針,程序計(jì)數(shù)器等寄存器。
c) 進(jìn)程有自己獨(dú)立的地址空間,而線程沒(méi)有,線程必須依賴于線程而存在。
d) 進(jìn)程切換的開銷較大,線程相對(duì)較小。
比如當(dāng)我們的在 Android 系統(tǒng)中啟動(dòng)一個(gè)應(yīng)用程序,這個(gè)時(shí)候系統(tǒng)就給這個(gè)應(yīng)用啟動(dòng)一個(gè)進(jìn)
程,同時(shí)再創(chuàng)建一個(gè)主線程。默認(rèn)情況下同一個(gè)應(yīng)用程序的所有組件都在同一進(jìn)程的主線程
中運(yùn)行。如果你需要設(shè)置某個(gè)組件需要再特定的進(jìn)程中執(zhí)行,可以再 manifest 清單文件中
設(shè)置。像 activity,service,receiver 和 provider 都有 android:process 屬性,就是用來(lái)指定該組件
運(yùn)行在哪個(gè)進(jìn)程中。系統(tǒng)不會(huì)為每個(gè)組件創(chuàng)建一個(gè)單獨(dú)的線程,運(yùn)行在同一進(jìn)程中的所有組
件,都在該進(jìn)程的 UI 線程也就是主線程中實(shí)例化,系統(tǒng)從 UI 線程發(fā)出對(duì)每個(gè)組件的調(diào)用。
Android 的 UI toolkit 不是線程安全的,因此不能從工作線程操作你的 UI,你必須從 UI 線程
對(duì)用戶界面操作,UI 線程是單線程模型,所以我們不要阻塞 UI 線程,不要在 UI 線程中執(zhí)行
耗時(shí)操作,不要在非 UI 線程中操作 UI 控件。
我所了解的線程和線程之間的通信有這樣幾種方法:
a) 在 runOnUiThread 方法中調(diào)用 UI 控件的刷新。
b) 用 View.post 方法調(diào)用 UI 控件的刷新。
c) 使用 View.postDelayed 方法調(diào)用 UI 控件的刷新。
d) AsyncTask 實(shí)現(xiàn)異步處理,在 doInBackground 方法中執(zhí)行耗時(shí)操作,在 onPostExecute
中刷新 UI。
e) Handler 實(shí)現(xiàn)線程通信。
f) 第三方插件 EventBus 可以幫我們實(shí)現(xiàn)線程通信。
線程和線程通信我們需要考慮線程安全。一般我們會(huì)采用 synchronization,wait,sleep。
比 如 在 多 線 程 使 用 數(shù) 據(jù) 存 儲(chǔ) 的 時(shí) 候 , 我 們 一 般 可 以 選 擇 線 程 安 全 的 ,
LinkedList,ConcurrentHashMap. 進(jìn)程和進(jìn)程間的通信方式可以分為:
a) Bundle/Intent 傳遞數(shù)據(jù)。
b) 文件共享。
c) Messenger。
d) AIDL 接口編程。
e) ContentProvider。
f) Socket。