1、概念

1.1 行程
-
程式運行的基本單元,
-
系統資源分配和調度的基本單位,
-
有自己獨立的地址空間,
-
多個行程可并發執行,
1.2 執行緒
-
程式執行的最小單位,
-
CPU調度和分派的基本單位,
-
沒有獨立的地址空間,多個執行緒共享地址空間,
-
多個執行緒可并發執行,某一個執行緒可以創建和撤銷另外的執行緒,
1.3 行程和執行緒的區別
-
地址空間:同一行程的執行緒共享本行程的地址空間,而行程之間則是獨立的地址空間,
-
資源擁有:同一行程內的執行緒共享本行程的資源如記憶體、I/O、CPU等,但是行程之間的資源是獨立的,
-
一個行程崩潰后,在保護模式下不會對其他行程產生影響,但是一個執行緒崩潰整個行程都死掉,所以使用多行程可以保證其他模塊的正常運行,
-
行程切換時,消耗的資源大,效率不高,所以涉及到頻繁的切換時,使用執行緒要好于行程,同樣如果要求同時進行并且又要共享某些變數的并發操作,只能用執行緒不能用行程,
-
執行程序:每個獨立的行程程有一個程式運行的入口、順序執行序列和程式入口,但是執行緒不能獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制,
-
都可以并發執行,
-
一個程式至少有一個行程,一個行程至少有一個執行緒,
可以將系統看成一個工廠,行程就是車間,多行程就是多個車間,執行緒就是流水線,多行程就是多個車間,多執行緒就是多個流水線
2、Android中的行程
2.1 行程
當應用程式組件啟動并且該應用程式沒有任何其他組件在運行時,Android 系統會為該應用程式啟動一個新的 Linux 行程,并使用單個執行執行緒, 默認情況下,同一應用程式的所有組件都在同一行程和執行緒(稱為“主”執行緒)中運行,
2.2 行程的等級(生命周期)
2.2.1 前臺行程(Foreground process)
它表明用戶正在與該行程進行互動操作優先級是最高的,Android系統-依據下面的條件來將一個行程標記為前臺行程:
-
有一個Activity且正在執行**onResume()**方法(用戶正在與其互動),
-
有一個Service且正在執行(onCreate()、onStart()、onDestroy())之一的方法,
-
有一個BroadcastReceiver且正在執行 onReceive() 方法,
2.2.2 可見行程(Visible process)
它表明雖然該行程沒有持有任何前臺組件,但是它還是能夠影響到用戶看得到的界面,android系統依據下面的條件將一個行程標記為可見行程:
-
有一個Activity且它不在互動,但仍可見(其 onPause() 方法已被呼叫),例如,當一個activity啟動了一個dialog,這個activity就被對話框擋在后面,
-
有一個**Service正在執行Service.startForeground()**的方法,
-
托管系統用于用戶知道的特定功能的服務,例如動態壁紙、輸入法服務等,
2.2.3 服務行程(Service process)
持有已使用 startService() 方法啟動的Service, 雖然這些行程對用戶來說并不直接可見,但它們一般都在做用戶關心的事情(如后臺網路資料上傳或下載)
2.2.4 快取行程(Cached process)
快取行程是當前不需要的行程,因此當其他地方需要記憶體等資源時,系統可以根據需要隨意終止它,
-
持有一個或多個不可見Activity(呼叫了onStop()方法),通常情況下都會有很多后臺行程,當記憶體不足的時候,在所有的后臺行程里面,會按照LRU(最近使用)規則,優先回收最長時間沒有使用過的行程,
在決定如何對流程進行分類時,系統將根據在流程中當前活動的所有組件中找到的最重要的級別來做出決定,
行程的優先級也可以基于行程對它的其他依賴性而增加,
2.3 多行程
默認情況下,同一應用程式的所有組件都在同一行程中運行,大多數應用程式不應更改這一點,但是,如果你發現需要控制某個組件屬于哪個行程,則可以在 < application> 中進行,
每種型別的組件元素 (< activity >、< service>、< receiver> 和 < provider>) 的清單條目都支持 android:process 屬性,該屬性可以指定該組件應在其中運行的行程,
< application> 元素還支持 android:process 屬性,以設定適用于所有組件的默認值,
Android 的一個不同尋常的基本特性是應用程式行程的生命周期不受應用程式本身直接控制,相反,它是由系統通過系統知道正在運行的應用程式部分的組合、這些東西對用戶的重要性以及系統中可用的總記憶體量來確定的,
默認行程就是主行程,其他行程一般來說都是子行程,
2.3.1 多行程產生多個Application
如果注冊的四大組件中的任意一個組件時用到了多行程,運行該組件時,都會創建一個新的Application物件,對于多行程重復創建Application這種情況,只需要在該類中對當前行程加以判斷即可,

com.scc.demo(12095):com.scc.demo行程名;12095行程id
代碼實作:AndroidMainfest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.scc.demo">
<application
android:name=".SccApp"
...
android:theme="@style/Theme.Demo">
<activity android:name=".actvitiy.MainActivity"
>
...
</activity>
<activity android:name=".actvitiy.TouchActivity"
android:process="com.scc.touch.wudi"/>
<activity android:name=".actvitiy.ViewActivity"
android:process=":view"/>
...
</application>
</manifest>
根據默認行程名和當前行程名比較是否進行初始化,
public class SccApp extends Application {
@RequiresApi(api = Build.VERSION_CODES.P)
@Override
public void onCreate() {
super.onCreate();
String name = getProcessName();
MLog.e("ProcessName:"+name);
getProcessName("com.scc.demo");
}
public void getProcessName(String processName){
ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> processInfos = activityManager.getRunningAppProcesses();
if(processInfos!=null)
{
for(ActivityManager.RunningAppProcessInfo processInfo:processInfos){
MLog.e(processInfo.processName);
if(processName == processInfo.processName){
init();
}
}
}
}
//初始化
private void init(){
CrashReport.initCrashReport(getApplicationContext(), "70594a1ff8", false);
}
}
2.4 行程間通信
-
Bundle
-
檔案共享
-
AIDL
-
Messenger
-
Content Provider
-
Socket
3、Android中的執行緒
執行緒分為兩種:
-
UI/Main Thread (主執行緒)
-
Worker Thread(作業執行緒)
3.1 UI/Main Thread (主執行緒)
啟動應用程式時,系統會為應用程式創建一個執行執行緒,稱為 "main",該執行緒非常重要,因為它負責將事件發送到適當的用戶界面小部件,包括繪圖事件,與Android UI toolkit (來自Android.widget和Android.view包的組件)互動的執行緒,
因此,主執行緒有時也稱為UI執行緒,但是,在特殊情況下,應用程式的主執行緒可能不是它的UI執行緒,
注意:構建工具將@MainThread和 @UiThread注釋視為可互換的,因此你可以@UiThread 從@MainThread方法中呼叫方法,反之亦然,但是,在系統應用程式在不同執行緒上具有多個視圖的情況下,UI 執行緒可能與主執行緒不同,因此,你應該 @UiThread 使用 @MainThread.
在同一行程中運行的所有組件都在UI執行緒中實體化,
此外,Android UI toolkit不是執行緒安全的,因此,你不能從作業執行緒操作UI—你必須從UI執行緒對用戶界面執行所有操作,因此,Android的單執行緒模型只有兩條規則:
-
不要阻塞UI執行緒;
-
不要從UI執行緒外部訪問Android UI toolkit,
3.1.1 阻塞UI執行緒
如果所有事情都發生在UI執行緒中,那么執行長時間操作(如網路訪問或資料庫查詢)將阻塞整個UI,
發生ANR的原因:
-
Activity超過5秒無回應;
-
BroadcastReceiver超過10秒無回應,
3.1.2 Worker Thread操作UI
@Override
protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread);
//Worker Thread(作業執行緒)
new Thread(new Runnable() {
@Override
public void run() {
//操作UI執行緒
Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
}
}).start();
}
運行后直接報錯:
2021-10-12 14:47:47.495 4122-4247/com.scc.demo E/AndroidRuntime: FATAL EXCEPTION: Thread-7
Process: com.scc.demo, PID: 4122
java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()
at android.widget.Toast$TN.<init>(Toast.java:895)
at android.widget.Toast.<init>(Toast.java:205)
at android.widget.Toast.makeText(Toast.java:597)
at android.widget.Toast.makeText(Toast.java:566)
at com.scc.demo.actvitiy.ThreadActivity$1.run(ThreadActivity.java:18)
at java.lang.Thread.run(Thread.java:919)
3.2 Worker Thread(作業執行緒)
因不能阻塞主執行緒,但是有些耗時操作(如加載圖片、網路請求等)非即時相應的則可以通過作業執行緒來執行,
注意,你不能從UI執行緒或"主"執行緒以外的任何執行緒更新UI,
為了解決這個問題,Android提供了幾種從其他執行緒訪問UI執行緒的方法:
-
Activity.runOnUiThread(Runnable)
-
View.post(Runnable)
-
View.postDelayed(Runnable, long)
3.2.1 樣例:子執行緒訪問UI執行緒
public class ThreadActivity extends ActivityBase{
TextView tvName;
@Override
protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread);
tvName = findViewById(R.id.tv_name);
tvName.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
csThread();
startThread();
}
});
}
private void csThread(){
//Worker Thread(作業執行緒)
new Thread(new Runnable() {
@Override
public void run() {
//這樣寫直接報錯
tvName.setText("我是Worker Thread---行路難!行路難!");
// ------強大的分割線------
// 下面幾種方式都沒問題
//第一種:Activity.runOnUiThread(Runnable)
runOnUiThread(new Runnable() {
@Override
public void run() {
tvName.setText("我是Worker Thread---行路難!行路難!");
Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
}
});
//第二種:View.post(Runnable)
tvName.post(new Runnable() {
@Override
public void run() {
tvName.setText("我是Worker Thread---行路難!行路難!");
Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
}
});
//第三種:View.postDelayed(Runnable, long)
tvName.postDelayed(new Runnable() {
@Override
public void run() {
tvName.setText("我是Worker Thread---行路難!行路難!");
Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
}
},1000);
//第四種:Handler
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
tvName.setText("我是Worker Thread---行路難!行路難!");
Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
}
});
}
}).start();
}
}
子執行緒直接操作主執行緒報錯資訊:
理論上應該拿 3.1.2 Worker Thread 操作UI 時的報錯資訊,既然都能通過這種方式解決,就多舉一個,
2021-10-12 16:02:51.754 8635-8676/com.scc.demo E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.scc.demo, PID: 8635
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8798)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1606)
at android.view.View.requestLayout(View.java:25390)
...
at android.widget.TextView.checkForRelayout(TextView.java:9719)
at android.widget.TextView.setText(TextView.java:6311)
...
at com.scc.demo.actvitiy.ThreadActivity$2.run(ThreadActivity.java:31)
at java.lang.Thread.run(Thread.java:923)
3.2.2 Android提供了幾種從其他執行緒訪問UI執行緒的方法原始碼
Activity.runOnUiThread(Runnable)
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
View.post(Runnable)
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
View.postDelayed(Runnable, long)
public boolean postDelayed(Runnable action, long delayMillis) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.postDelayed(action, delayMillis);
}
getRunQueue().postDelayed(action, delayMillis);
return true;
}
你會發現他們都是使用 Handler 來完成的,所以在 3.2.1 的樣例中咱可以使用 new Handler() 來完成更新 UI,
3.3 執行緒的狀態
-
new:新建狀態,new出來,還沒有呼叫start,
-
Runnable:可運行狀態,呼叫start進入可運行狀態,可能運行也可能沒有運行,取決于作業系統的調度,
-
Blocked:阻塞狀態,被鎖阻塞,暫時不活動,阻塞狀態是執行緒阻塞在進入synchronized關鍵字修飾的方法或代碼塊(獲取鎖)時的狀態,
-
Waiting:等待狀態,不活動,不運行任何代碼,等待執行緒調度器調度,wait sleep,
-
Timed Waiting:超時等待,在指定時間自行回傳,
-
Terminated:終止狀態,包括正常終止和例外終止,
3.4 開啟執行緒的三種方式
-
1:繼承Thread重寫run方法,
-
2:實作Runnable重寫run方法,
-
3:實作Callable重寫call方法,
private void startThread(){
//第一種:繼承Thread重寫run方法
new MyThread().start();
//第二種:實作Runnable重寫run方法
new Thread(new MyRunanble()).start();
//第三種:實作Callable重寫call方法
FutureTask<Integer> ft = new FutureTask<Integer>(new MyCallable());
new Thread(ft).start();
}
class MyThread extends Thread{
@Override
public void run() {
MLog.e(this.getClass().getName());
}
}
class MyRunanble implements Runnable{
@Override
public void run() {
MLog.e(this.getClass().getName());
}
}
class MyCallable implements Callable {
@Override
public Object call() throws Exception {
MLog.e(this.getClass().getName());
return null;
}
}
小結
實作Callable和實作Runnable類似,但是功能更強大,具體表現在:
-
可以在任務結束后提供一個回傳值,Runnable不行,
-
call方法可以拋出例外,Runnable的run方法不行,
-
可以通過運行Callable得到的Fulture物件監聽目標執行緒呼叫call方法的結果,得到回傳值,(fulture.get(),呼叫后會阻塞,直到獲取到回傳值),
3.5 run()和start()方法區別
-
run():方法只是執行緒的主體方法,和普通方法一樣,不會創建新的執行緒,
-
start():只有呼叫start()方法,才會啟動一個新的執行緒,新執行緒才會呼叫run()方法,執行緒才會開始執行,
3.6 wait、notify、notifyAll
-
wait():釋放obj的鎖,導致當前的執行緒等待,直接其他執行緒呼叫此物件的notify()或notifyAll()方法,
-
notify(): 喚醒在此物件監視器上等待的單個執行緒
-
notifyAll(): 通知所有等待該競爭資源的執行緒
注意:當要呼叫wait()或notify()/notifyAll()方法時,一定要放到synchronized(obj)代碼中,否則會報錯java.lang.IllegalMonitorStateException,當呼叫obj.notify/notifyAll后,呼叫執行緒依舊持有obj鎖,因此等待執行緒雖被喚醒,但仍無法獲得obj鎖,直到呼叫執行緒退出synchronized塊,釋放obj鎖后,其他等待執行緒才有機會獲得鎖繼續執行,
3.7 join、sleep、wait
-
join()方法在等待的程序中釋放物件鎖,
-
sleep()方法在睡眠時不釋放物件鎖,
-
wait():釋放物件鎖
3.8 執行緒阻塞
-
1:執行緒執行了Thread.sleep(int millsecond)方法,放棄CPU,睡眠一段時間,一段時間過后恢復執行;
-
2:執行緒執行一段同步代碼,但無法獲得相關的同步鎖,只能進入阻塞狀態,等到獲取到同步鎖,才能恢復執行;
-
3:執行緒執行了一個物件的wait()方法,直接進入阻塞態,等待其他執行緒執行notify()/notifyAll()操作;
-
4:執行緒執行某些IO操作,因為等待相關資源而進入了阻塞態,如System.in,但沒有收到鍵盤的輸入,則進入阻塞態,
-
5:執行緒禮讓,Thread.yield()方法,暫停當前正在執行的執行緒物件,把執行機會讓給相同或更高優先級的執行緒,但并不會使執行緒進入阻塞態,執行緒仍處于可執行態,隨時可能再次分得CPU時間,
-
6:執行緒自閉,join()方法,在當前執行緒呼叫另一個執行緒的join()方法,則當前執行緒進入阻塞態,直到另一個執行緒運行結束,當前執行緒再由阻塞轉為就緒態,
-
7:執行緒執行suspend()使執行緒進入阻塞態,必須resume()方法被呼叫,才能使執行緒重新進入可執行狀態,
3.9 執行緒中斷
使用 interrupt()中斷,但呼叫interrupt()方法只是傳遞中斷請求訊息,并不代表要立馬停止目標執行緒,然后通過拋出InterruptedException來喚醒它,
public class Thread {
// 中斷當前執行緒
public void interrupt();
// 判斷當前執行緒是否被中斷
public boolen isInterrupt();
// 清除當前執行緒的中斷狀態,并回傳之前的值
public static boolen interrupted();
}
3.10 執行緒池ThreadPoolExecutor
執行緒池的作業原理:執行緒池可以減少創建和銷毀執行緒的次數,從而減少系統資源的消耗,
當一個任務提交到執行緒池時:
-
1:首先判斷核心執行緒池中的執行緒是否已經滿了,如果沒滿,則創建一個核心執行緒執行任務,否則進入下一步,
-
2:判斷作業佇列是否已滿,沒有滿則加入作業佇列,否則執行下一步,
-
3:判斷執行緒數是否達到了最大值,如果不是,則創建非核心執行緒執行任務,否則執行飽和策略,默認拋出例外,
3.11 執行緒池的種類
FixedThreadPool:可重用固定執行緒數的執行緒池,只有核心執行緒,沒有非核心執行緒,核心執行緒不會被回收,有任務時,有空閑的核心執行緒就用核心執行緒執行,沒有則加入佇列排隊,
SingleThreadExecutor:單執行緒執行緒池,只有一個核心執行緒,沒有非核心執行緒,當任務到達時,如果沒有運行執行緒,則創建一個執行緒執行,如果正在運行則加入佇列等待,可以保證所有任務在一個執行緒中按照順序執行,和FixedThreadPool的區別只有數量,
CachedThreadPool:按需創建的執行緒池,沒有核心執行緒,非核心執行緒有Integer.MAX_VALUE個,每次提交任務如果有空閑執行緒則由空閑執行緒執行,沒有空閑執行緒則創建新的執行緒執行,適用于大量的需要立即處理的并且耗時較短的任務,
ScheduledThreadPoolExecutor:繼承自ThreadPoolExecutor,用于延時執行任務或定期執行任務,核心執行緒數固定,執行緒總數為Integer.MAX_VALUE,
3.12 保證執行緒安全
執行緒安全性體現在三個方法:
原子性:提供互斥訪問,同一時刻只能有一個線和至資料進行操作,
JDK中提供了很多atomic類,如AtomicInteger\AtomicBoolean\AtomicLong,它們是通過CAS完成原子性, JDK提供鎖分為兩種:synchronized依賴JVM實作鎖,該關鍵字作用物件的作用范圍內同一時刻只能有一個執行緒進行操作,另一種是LOCK,是JDK提供的代碼層面的鎖,依賴CPU指令,代表性是ReentrantLock,
可見性:一個執行緒對主記憶體的修改及時被其他執行緒看到,
JVM提供了synchronized和volatile,volatile的可見性是通過記憶體屏障和禁止重排序實作的,volatile會在寫操作時,在寫操作后加一條store屏障指令,將本地記憶體中的共享變數值重繪到主記憶體;會在讀操作時,在讀操作前加一條load指令,從記憶體中讀取共享變數,
有序性:指令沒有被編譯器重排序,
可通過volatile、synchronized、Lock保證有序性,
3.12 volatile、synchronized、Lock、ReentrantLock
volatile:解決變數在多個執行緒間的可見性,但不能保證原子性,只能用于修飾變數,不會發生阻塞,volatile能屏蔽編譯指令重排,不會把其后面的指令排到記憶體屏障之前的位置,也不會把前面的指令排到記憶體屏障的后面,多用于并行計算的單例模式,volatile規定CPU每次都必須從記憶體讀取資料,不能從CPU快取中讀取,保證了多執行緒在多CPU計算中永遠拿到的都是最新的值,
synchronized:互斥鎖,操作互斥,并發執行緒過來,串行獲得鎖,串行執行代碼,解決的是多個執行緒間訪問共享資源的同步性,可保證原子性,也可間接保證可見性,因為它會將私有記憶體和公有記憶體中的資料做同步,可用來修飾方法、代碼塊,會出現阻塞,synchronized發生例外時,會自動釋放執行緒占有的鎖,因此不會導致死鎖現象發生,非公平鎖,每次都是相互爭搶資源,
lock:一個介面,lock可以讓等待鎖的執行緒回應中斷,在發生例外時,如果沒有主動通過unLock()去釋放鎖,則可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖,
ReentrantLoc:可重入鎖,鎖的分配機制是基于執行緒的分配,而不是基于方法呼叫的分配,ReentrantLock有tryLock方法,如果鎖被其他執行緒持有,回傳false,可避免形成死鎖,對代碼加鎖的顆粒會更小,更節省資源,提高代碼性能,ReentrantLock可實作公平鎖和非公平鎖,公平鎖就是先來的先獲取資源,ReentrantReadWriteLock用于讀多寫少的場合,且讀不需要互斥場景,
相關推薦
??Android Runtime (ART) 和 Dalvik??
??Android Apk 的打包程序 ??
?? Android Apk的啟動程序??
??startActivity原始碼分析(含啟動新應用) ??
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/310451.html
標籤:其他
