前言
在整個Android學習生涯中,回憶起來,有哪些知識是我們記憶深刻的,已經深入骨髓?啟動的目標Activity必須在清單文件中注冊,請求網絡必須添加網絡權限,又或者其它權限在6.0以后必須動態(tài)申請,etc...我想這些都是每個Android人都已經刻在骨子里了。仔細想想,你是否又想起了,主線程不能操作耗時任務,對,你激動的拍了一下自己的大腿,就是這個卵“主線程不能操作耗時任務”,是的,尤其在我們初學Android的時候,會經常犯的毛病,但我們經常使用的線程究竟是怎么一個東西,我們是否有過深入思考,又或者我們是否思考過,我們有沒有正確使用線程,在以往使用的過程中是否可以再進一步優(yōu)化,etc...帶著這一系列疑問,還是有必要深入了解一下Android中的多線程。為了徹底整明白這些知識,還是從java線程基礎開始。
什么是進程和線程
1,什么是進程?
進程是一個具有一定獨立功能的程序在一個數(shù)據集上的一次動態(tài)執(zhí)行的過程,是操作系統(tǒng)進行資源分配和調度的一個獨立單位,是應用程序運行的載體。進程是一種抽象的概念,從來沒有統(tǒng)一的標準定義。
進程一般由程序、數(shù)據集合和進程控制塊三部分組成。這些太官方,以正在使用的windows操作系統(tǒng)為例,打開任務管理器,我們就可以清楚的知道進程究竟是什么:

我們可以清楚的看到,目前操作系統(tǒng)一共起了94個進程,有我們開啟的10個應用,和操作系統(tǒng)自己起的84個后臺進程正在運行。我們通常會說一個應用就是一個進程,其實不太準確,因為一個應用只有跑起來后才會有一個活著的進程。
2,什么是線程?
線程是程序執(zhí)行中一個單一的順序控制流程,是程序執(zhí)行流的最小單元,是處理器調度和分派的基本單位。一個進程可以有一個或多個線程,各個線程之間共享程序的內存空間(也就是所在進程的內存空間)。一個標準的線程由線程ID、當前指令指針(PC)、寄存器和堆棧組成。而進程由內存空間(代碼、數(shù)據、進程空間、打開的文件)和一個或多個線程組成。簡單理解就是線程是用來執(zhí)行任務的。

從上圖中,可以清楚知道wps下面就有7個線程。簡單的說就是一個進程下面包含多個線程。
3,任務調度器
線程是什么?要理解這個概念,需要先了解一下操作系統(tǒng)的一些相關概念。大部分操作系統(tǒng)(如Windows、Linux)的任務調度是采用時間片輪轉的搶占式調度方式,也就是說一個任務執(zhí)行一小段時間后強制暫停去執(zhí)行下一個任務,每個任務輪流執(zhí)行。任務執(zhí)行的一小段時間叫做時間片,任務正在執(zhí)行時的狀態(tài)叫運行狀態(tài),任務執(zhí)行一段時間后強制暫停去執(zhí)行下一個任務,被暫停的任務就處于就緒狀態(tài)等待下一個屬于它的時間片的到來。這樣每個任務都能得到執(zhí)行,由于CPU的執(zhí)行效率非常高,時間片非常短,在各個任務之間快速地切換,給人的感覺就是多個任務在“同時進行”,這也就是我們所說的并發(fā)(別覺得并發(fā)有多高深,它的實現(xiàn)很復雜,但它的概念很簡單,就是一句話:多個任務同時執(zhí)行)。多任務運行過程的示意圖如下:

拋開上面的圖以及概念性東西,在我們實際編程中,假如同時起了A,B,C三個線程執(zhí)行一些任務,是不是每個線程完成的順序都是隨機的,這就應對了上面的時間片輪轉機制。
4,進程與線程的關系
地址空間:
線程共享本進程的地址空間,而進程之間是獨立的地址空間。
資源:
線程共享本進程的資源如內存、I/O、cpu等,不利于資源的管理和保護,而進程之間的資源是獨立的,能很好的進行資源管理和保護。
健壯性:
多進程要比多線程健壯,一個進程崩潰后,在保護模式下不會對其他進程產生影響,但是一個線程崩潰整個進程都死掉。
可并發(fā)性:
兩者均可并發(fā)執(zhí)行。
切換時:
進程切換時,消耗的資源大,效率高。所以涉及到頻繁的切換時,使用線程要好于進程。同樣如果要求同時進行并且又要共享某些變量的并發(fā)操作,只能用線程不能用進程。
其他:
線程是處理器調度的基本單位,但是進程不是。
我們先用下面這張圖,也許會更直觀:

5,單線程和多線程
總之,線程和進程都是一種抽象的概念,線程是一種比進程更小的抽象,線程和進程都可用于實現(xiàn)并發(fā)。
在早期的操作系統(tǒng)中并沒有線程的概念,進程是能擁有資源和獨立運行的最小單位,也是程序執(zhí)行的最小單位。它相當于一個進程里只有一個線程,進程本身就是線程。所以線程有時被稱為輕量級進程(Lightweight Process,LWP)。

我們試想一下,如果沒有多線程,我們一個app只有一個主線程,它需要渲染UI和請求網絡,我想我們是無法忍受這樣一個過程的。
cpu核心數(shù)和線程的關系
在配置電腦的時候,就會關注一點我們電腦是幾核幾線程,cpu核心數(shù)和線程有什么關系呢?
上面提到的時間片輪轉的調度方式說一個任務執(zhí)行一小段時間后強制暫停去執(zhí)行下一個任務,每個任務輪流執(zhí)行。很多操作系統(tǒng)的書都說“同一時間點只有一個任務在執(zhí)行”。那有人可能就要問雙核處理器呢?難道兩個核不是同時運行嗎?
其實“同一時間點只有一個任務在執(zhí)行”這句話是不準確的,至少它是不全面的。那多核處理器的情況下,線程是怎樣執(zhí)行呢?這就需要了解內核線程。
多核(心)處理器是指在一個處理器上集成多個運算核心從而提高計算能力,也就是有多個真正并行計算的處理核心,每一個處理核心對應一個內核線程。
內核線程(Kernel Thread,KLT)就是直接由操作系統(tǒng)內核支持的線程,這種線程由內核來完成線程切換,內核通過操作調度器對線程進行調度,并負責將線程的任務映射到各個處理器上。一般一個處理核心對應一個內核線程,比如單核處理器對應一個內核線程,雙核處理器對應兩個內核線程,四核處理器對應四個內核線程。
現(xiàn)在的電腦一般是雙核四線程、四核八線程,是采用超線程技術將一個物理處理核心模擬成兩個邏輯處理核心,對應兩個內核線程,所以在操作系統(tǒng)中看到的CPU數(shù)量是實際物理CPU數(shù)量的兩倍,如你的電腦是雙核四線程,打開“任務管理器\性能”可以看到4個CPU的監(jiān)視器,四核八線程可以看到8個CPU的監(jiān)視器。

四核八線程在Windows10下查看的結果
超線程技術就是利用特殊的硬件指令,把一個物理芯片模擬成兩個邏輯處理核心,讓單個處理器都能使用線程級并行計算,進而兼容多線程操作系統(tǒng)和軟件,減少了CPU的閑置時間,提高的CPU的運行效率。這種超線程技術(如雙核四線程)由處理器硬件的決定,同時也需要操作系統(tǒng)的支持才能在計算機中表現(xiàn)出來。
程序一般不會直接去使用內核線程,而是去使用內核線程的一種高級接口——輕量級進程(Lightweight Process,LWP),輕量級進程就是我們通常意義上所講的線程,也被叫做用戶線程。由于每個輕量級進程都由一個內核線程支持,因此只有先支持內核線程,才能有輕量級進程。用戶線程與內核線程的對應關系有三種模型:一對一模型、多對一模型、多對多模型,在這以4個內核線程、3個用戶線程為例對三種模型進行說明。
java中的多線程
1,java中線程的創(chuàng)建
網上回答這個問題有各種答案,我們還是翻一下java jdk源碼提供的注釋,感覺有的人真是誤人子弟:
There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows:
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
The following code would then create a thread and start it running:
PrimeThread p = new PrimeThread(143);
p.start();
The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started. The same example in this other style looks like the following:
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
The following code would then create a thread and start it running:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
Every thread has a name for identification purposes. More than one thread may have the same name. If a name is not specified when a thread is created, a new name is generated for it.
Unless otherwise noted, passing a null argument to a constructor or method in this class will cause a NullPointerException to be thrown.
Since:
JDK1.0
See Also:
Runnable, Runtime.exit(int), run(), stop()
Author:
unascribed
所以建議那些網上亂說一通的,記得看一下jdk源碼提供的注釋。
總結
上面我們主要介紹了進程和線程的一些基本概念,因為面試基本是必問的知識點,最后也拿了jdk源碼中的注釋來說明線程的兩種創(chuàng)建方式,尤其是線程的兩種創(chuàng)建方式,面試問的太多了,所以當我們知識點存在盲區(qū)的時候翻看一下源碼是最好的解決辦法,網上人云亦云的太多了,亂七八糟。在后面我們也將全面復習java中的多線程知識點,如線程生命周期,線程間通信,以及在Android中的應用等。