當(dāng)前位置:首頁 > IT技術(shù) > 移動(dòng)平臺(tái) > 正文

安卓中多線程梳理
2022-02-14 10:54:26

安卓中多線程梳理

一、HandlerThread梳理

(1)解決主線程耗時(shí)問題
(2)避免內(nèi)存泄漏

二、IntentService梳理

三、線程池梳理

核心線程最大數(shù)量:
計(jì)算密集型=Ncpu+1,但是這種做法導(dǎo)致的多一個(gè)cpu上下文切換。

1、優(yōu)點(diǎn):
(1)線程復(fù)用,減少內(nèi)存開銷
(2)限定最大并發(fā)量,避免阻塞
(3)便于管理線程
2、分類
(1)FixedThreadPool
(2)CachedThreadPool
(3)ScheduledThreadPool
(4)SingleThreadExecutor

四、線程創(chuàng)建的常用方法

(1)繼承Thread重寫run方法
(2)實(shí)現(xiàn)Runnable重寫run方法
(3)實(shí)現(xiàn)Callable重寫call方法

五、線程中斷

public boolean isInterrupted() //判斷中斷標(biāo)識位是否是true,不會(huì)改變標(biāo)識位
public static boolean interrupted() //判斷當(dāng)前線程是否被中斷,并且該方法調(diào)用結(jié)束的時(shí)候會(huì)清空中斷標(biāo)識位

public void interrupt() //將中斷標(biāo)識位設(shè)置為true

線程的六種狀態(tài),New 、Runnable、 Blocked、 Waiting、 Timed Waiting、 Terminated,那么在這六種狀態(tài)下調(diào)用線程中斷的代碼會(huì)怎樣呢,New和Terminated狀態(tài)下,線程不會(huì)理會(huì)線程中斷的請求,既不會(huì)設(shè)置標(biāo)記位,在Runnable和Blocked狀態(tài)下調(diào)用interrupt會(huì)將標(biāo)志位設(shè)置位true,在Waiting和Timed Waiting狀態(tài)下會(huì)發(fā)生InterruptedException異常,針對這個(gè)異常我們?nèi)绾翁幚恚?br/> 1.在catch語句中通過interrupt設(shè)置中斷狀態(tài),因?yàn)榘l(fā)生中斷異常時(shí),中斷標(biāo)志位會(huì)被復(fù)位,我們需要重新將中斷標(biāo)志位設(shè)置為true,這樣外界可以通過這個(gè)狀態(tài)判斷是否需要中斷線程

try{
    ....
}catch(InterruptedException e){
    Thread.currentThread().interrupt();
}

2.更好的做法是,不捕獲異常,直接拋出給調(diào)用者處理,這樣更靈活

六、Thread為什么不能用stop方法停止線程

從SUN的官方文檔可以得知,調(diào)用Thread.stop()方法是不安全的,這是因?yàn)楫?dāng)調(diào)用Thread.stop()方法時(shí),會(huì)發(fā)生下面兩件事:
1.即刻拋出 ThreadDeath異常,在線程的run()方法內(nèi),任何一點(diǎn)都有可能拋出ThreadDeath Error,包括在catch或finally語句中。
2.釋放該線程所持有的所有的鎖。調(diào)用thread.stop()后導(dǎo)致了該線程所持有的所有鎖的突然釋放,那么被保護(hù)數(shù)據(jù)就有可能呈現(xiàn)不一致性,其他線程在使用這些被破壞的數(shù)據(jù)時(shí),有可能導(dǎo)致一些很奇怪的應(yīng)用程序錯(cuò)誤。

七、同步方法和同步代碼塊

1、同步方法
即有synchronized關(guān)鍵字修飾的方法, 由于java的每個(gè)對象都有一個(gè)內(nèi)置鎖,當(dāng)用此關(guān)鍵字修飾方法時(shí),內(nèi)置鎖會(huì)保護(hù)整個(gè)方法。在調(diào)用該方法前,需要獲得內(nèi)置鎖,否則就處于阻塞狀態(tài)。

    public synchronized void sync(){
        .....
    }

注:synchronized關(guān)鍵字也可以修飾靜態(tài)方法,此時(shí)如果調(diào)用該靜態(tài)方法,將會(huì)鎖住整個(gè)類(類鎖)
2、即有synchronized關(guān)鍵字修飾的語句塊,被該關(guān)鍵字修飾的語句塊會(huì)自動(dòng)被加上內(nèi)置鎖,從而實(shí)現(xiàn)同步(對象鎖)
synchronized(object){
}

八、volatile能實(shí)現(xiàn)多線程同步嗎

count++;
對應(yīng)的字節(jié)碼是

    GETSTATIC ThreadTest/Counts.count : I
    ICONST_1
    IADD
    PUTSTATIC ThreadTest/Counts.count : I

read and load從主存復(fù)制變量到當(dāng)前工作內(nèi)存
use and assign 執(zhí)行代碼,改變共享變量值
store and write 用工作內(nèi)存數(shù)據(jù)刷新主存相關(guān)內(nèi)容
但是這一些操作并不是原子性,也就是 在read and load之后,如果主內(nèi)存count變量發(fā)生修改之后,線程工作內(nèi)存中的值由于已經(jīng)加載,不會(huì)產(chǎn)生對應(yīng)的變化,所以計(jì)算出來的結(jié)果會(huì)和預(yù)期不一樣。
對于volatile修飾的變量,jvm虛擬機(jī)只是保證從主內(nèi)存加載到線程工作內(nèi)存的值是最新的。

九、使用重入鎖實(shí)現(xiàn)線程同步

在JavaSE5.0中新增了一個(gè)java.util.concurrent包來支持同步。ReentrantLock類是可重入、互斥、實(shí)現(xiàn)了Lock接口的鎖, 它與使用synchronized方法和快具有相同的基本行為和語義,并且擴(kuò)展了其能力
釋放鎖ReentrantLock()還有一個(gè)可以創(chuàng)建公平鎖的構(gòu)造方法,但由于能大幅度降低程序運(yùn)行效率,不推薦使用

十、關(guān)于Lock對象和synchronized關(guān)鍵字的選擇

a.最好兩個(gè)都不用,使用一種java.util.concurrent包提供的機(jī)制,
b.如果synchronized關(guān)鍵字能滿足用戶的需求,就用synchronized,因?yàn)樗芎喕a
c.如果需要更高級的功能,就用ReentrantLock類,此時(shí)要注意及時(shí)釋放鎖,否則會(huì)出現(xiàn)死鎖,通常在finally代碼釋放鎖

十一、使用局部變量實(shí)現(xiàn)線程同步

如果使用ThreadLocal管理變量,則每一個(gè)使用該變量的線程都獲得該變量的副本, 副本之間相互獨(dú)立,這樣每一個(gè)線程都可以隨意修改自己的變量副本,而不會(huì)對其他線程產(chǎn)生影響。
ThreadLocal 類的常用方法
ThreadLocal() //創(chuàng)建一個(gè)線程本地變量
get() //返回此線程局部變量的當(dāng)前線程副本中的值initialValue() :set(T value) :

//只改Bank類,其余代碼與上同
        public class Bank{
            //使用ThreadLocal類管理共享變量account
            private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                @Override
                protected Integer initialValue(){
                    return 100;
                }
            };
            public void save(int money){
                account.set(account.get()+money);
            }
            public int getAccount(){
                return account.get();
            }
        }

十二、java內(nèi)存模型

堆內(nèi)存是被所有線程共享的運(yùn)行時(shí)內(nèi)存區(qū)域,存在可見性的問題。
線程之間共享變量存儲(chǔ)在主存中,每個(gè)線程都有一個(gè)私有的本地內(nèi)存,本地內(nèi)存存儲(chǔ)了該線程共享變量的副本(本地內(nèi)存是一個(gè)抽象概念,并不真實(shí)存在),兩個(gè)線程要通信的話,首先A線程把本地內(nèi)存更新過的共享變量更新到主存中,然后B線程去主存中讀取A線程更新過的共享變量,也就是說假設(shè)線程A執(zhí)行了i = 1這行代碼更新主線程變量i的值,會(huì)首先在自己的工作線程中堆變量i進(jìn)行賦值,然后再寫入主存當(dāng)中,而不是直接寫入主存

十三、原子性 可見性 有序性

原子性:
對基本數(shù)據(jù)類型的讀取和賦值操作是原子性操作,這些操作不可被中斷,是一步到位的,例如x=3是原子性操作,而y = x就不是,它包含兩步:第一讀取x,第二將x寫入工作內(nèi)存;x++也不是原子性操作,它包含三部,第一,讀取x,第二,對x加1,第三,寫入內(nèi)存。原子性操作的類如:AtomicInteger AtomicBoolean AtomicLong AtomicReference

可見性:
指線程之間的可見性,既一個(gè)線程修改的狀態(tài)對另一個(gè)線程是可見的。volatile修飾可以保證可見性,它會(huì)保證修改的值會(huì)立即被更新到主存,所以對其他線程是可見的,普通的共享變量不能保證可見性,因?yàn)楸恍薷暮蟛粫?huì)立即寫入主存,何時(shí)被寫入主存是不確定的,所以其他線程去讀取的時(shí)候可能讀到的還是舊值

有序性:
Java中的指令重排序(包括編譯器重排序和運(yùn)行期重排序)可以起到優(yōu)化代碼的作用,但是在多線程中會(huì)影響到并發(fā)執(zhí)行的正確性,使用volatile可以保證有序性,禁止指令重排volatile可以保證可見性 有序性,但是無法保證原子性,在某些情況下可以提供優(yōu)于鎖的性能和伸縮性,替代sychronized關(guān)鍵字簡化代碼,但是要嚴(yán)格遵循使用條件。

十四 為什么HashMap線程不安全

hash碰撞與擴(kuò)容導(dǎo)致
HashMap的底層存儲(chǔ)結(jié)構(gòu)是一個(gè)Entry數(shù)組,每個(gè)Entry又是一個(gè)單鏈表,一旦發(fā)生Hash沖突的的時(shí)候,HashMap采用拉鏈法解決碰撞沖突,因?yàn)閔ashMap的put方法不是同步的,所以他的擴(kuò)容方法也不是同步的,在擴(kuò)容過程中,會(huì)新生成一個(gè)新的容量的數(shù)組,然后對原數(shù)組的所有鍵值對重新進(jìn)行計(jì)算和寫入新的數(shù)組,之后指向新生成的數(shù)組。當(dāng)多個(gè)線程同時(shí)檢測到hashmap需要擴(kuò)容的時(shí)候就會(huì)同時(shí)調(diào)用resize操作,各自生成新的數(shù)組并rehash后賦給該map底層的數(shù)組table,結(jié)果最終只有最后一個(gè)線程生成的新數(shù)組被賦給table變量,其他線程的均會(huì)丟失。而且當(dāng)某些線程已經(jīng)完成賦值而其他線程剛開始的時(shí)候,就會(huì)用已經(jīng)被賦值的table作為原始數(shù)組,這樣也會(huì)有問題。擴(kuò)容的時(shí)候 可能會(huì)引發(fā)鏈表形成環(huán)狀結(jié)構(gòu)

十五 Binder的內(nèi)存拷貝過程

相比其他的IPC通信,比如消息機(jī)制、共享內(nèi)存、管道、信號量等,Binder僅需一次內(nèi)存拷貝,即可讓目標(biāo)進(jìn)程讀取到更新數(shù)據(jù),同共享內(nèi)存一樣相當(dāng)高效,其他的IPC通信機(jī)制大多需要2次內(nèi)存拷貝。

Binder內(nèi)存拷貝的原理為:A為Binder客戶端,在IPC調(diào)用前,需將其用戶空間的數(shù)據(jù)拷貝到Binder驅(qū)動(dòng)的內(nèi)核空間,由于進(jìn)程B在打開Binder設(shè)備(/dev/binder)時(shí),已將Binder驅(qū)動(dòng)的內(nèi)核空間映射(mmap)到自己的進(jìn)程空間,所以進(jìn)程B可以直接看到Binder驅(qū)動(dòng)內(nèi)核空間的內(nèi)容改動(dòng)

十六 傳統(tǒng)IPC機(jī)制的通信原理(2次內(nèi)存拷貝)

1.發(fā)送方進(jìn)程通過系統(tǒng)調(diào)用(copy_from_user)將要發(fā)送的數(shù)據(jù)存拷貝到內(nèi)核緩存區(qū)中。
2.接收方開辟一段內(nèi)存空間,內(nèi)核通過系統(tǒng)調(diào)用(copy_to_user)將內(nèi)核緩存區(qū)中的數(shù)據(jù)拷貝到接收方的內(nèi)存緩存區(qū)。

傳統(tǒng)IPC機(jī)制存在2個(gè)問題:
1.需要進(jìn)行2次數(shù)據(jù)拷貝,第1次是從發(fā)送方用戶空間拷貝到內(nèi)核緩存區(qū),第2次是從內(nèi)核緩存區(qū)拷貝到接收方用戶空間。
2.接收方進(jìn)程不知道事先要分配多大的空間來接收數(shù)據(jù),可能存在空間上的浪費(fèi)。

十七 Java內(nèi)存模型

Java內(nèi)存模型(即Java Memory Model,簡稱JMM)本身是一種抽象的概念,并不真實(shí)存在,它描述的是一組規(guī)則或規(guī)范,通過這組規(guī)范定義了程序中各個(gè)變量(包括實(shí)例字段,靜態(tài)字段和構(gòu)成數(shù)組對象的元素)的訪問方式。由于JVM運(yùn)行程序的實(shí)體是線程,而每個(gè)線程創(chuàng)建時(shí)JVM都會(huì)為其創(chuàng)建一個(gè)工作內(nèi)存(有些地方稱為??臻g),用于存儲(chǔ)線程私有的數(shù)據(jù),而Java內(nèi)存模型中規(guī)定所有變量都存儲(chǔ)在主內(nèi)存,主內(nèi)存是共享內(nèi)存區(qū)域,所有線程都可以訪問,但線程對變量的操作(讀取賦值等)必須在工作內(nèi)存中進(jìn)行,首先要將變量從主內(nèi)存拷貝的自己的工作內(nèi)存空間,然后對變量進(jìn)行操作,操作完成后再將變量寫回主內(nèi)存,不能直接操作主內(nèi)存中的變量,工作內(nèi)存中存儲(chǔ)著主內(nèi)存中的變量副本拷貝,前面說過,工作內(nèi)存是每個(gè)線程的私有數(shù)據(jù)區(qū)域,因此不同的線程間無法訪問對方的工作內(nèi)存,線程間的通信(傳值)必須通過主內(nèi)存來完成

十八、什么情況下會(huì)觸發(fā)類的初始化

1、遇到new,getstatic,putstatic,invokestatic這4條指令;
2、使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用;
3、初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類沒有進(jìn)行過初始化,則先初始化其父類(注意!如果其父類是接口的話,則不要求初始化父類);
4、當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類;
5、當(dāng)使用jdk1.7的動(dòng)態(tài)語言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getstatic

十九、雙親委托模式

類加載器查找class所采用的是雙親委托模式,所謂雙親委托模式就是判斷該類是否已經(jīng)加載,如果沒有則不是自身去查找而是委托給父加載器進(jìn)行查找,這樣依次進(jìn)行遞歸,直到委托到最頂層的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了該Class,就會(huì)直接返回,如果沒找到,則繼續(xù)依次向下查找,如果還沒找到則最后交給自身去查找

二十、雙親委托模式的好處

1.避免重復(fù)加載,如果已經(jīng)加載過一次Class,則不需要再次加載,而是直接讀取已經(jīng)加載的Class
2.更加安全,確保,java核心api中定義類型不會(huì)被隨意替換,比如,采用雙親委托模式可以使得系統(tǒng)在Java虛擬機(jī)啟動(dòng)時(shí)舊加載了String類,也就無法用自定義的String類來替換系統(tǒng)的String類,這樣便可以防止核心`API庫被隨意篡改。

二十一、死鎖的產(chǎn)生條件,如何避免死鎖

死鎖的四個(gè)必要條件

1.互斥條件:
一個(gè)資源每次只能被一個(gè)進(jìn)程使用
2.請求與保持條件:
進(jìn)程已經(jīng)保持了至少一個(gè)資源,但又提出了新的資源請求,而該資源 已被其他進(jìn)程占有,此時(shí)請求進(jìn)程被阻塞,但對自己已獲得的資源保持不放。
3.不可剝奪條件:
進(jìn)程所獲得的資源在未使用完畢之前,不能被其他進(jìn)程強(qiáng)行奪走,即只能 由獲得該資源的進(jìn)程自己來釋放(只能是主動(dòng)釋放)。
4.循環(huán)等待條件:
若干進(jìn)程間形成首尾相接循環(huán)等待資源的關(guān)系

避免死鎖的方法

系統(tǒng)對進(jìn)程發(fā)出每一個(gè)系統(tǒng)能夠滿足的資源申請進(jìn)行動(dòng)態(tài)檢查,并根據(jù)檢查結(jié)果決定是否分配資源,如果分配后系統(tǒng)可能發(fā)生死鎖,則不予分配,否則予以分配,這是一種保證系統(tǒng)不進(jìn)入死鎖狀態(tài)的動(dòng)態(tài)策略。
一般來說互斥條件是無法破壞的,所以在預(yù)防死鎖時(shí)主要從其他三個(gè)方面入手
(1)破壞請求和保持條件:
在系統(tǒng)中不允許進(jìn)程在已獲得某種資源的情況下,申請其他資源,即要想出一個(gè)辦法,阻止進(jìn)程在持有資源的同時(shí)申請其它資源。
(2)破壞不可搶占條件:
允許對資源實(shí)行搶奪。
(3)破壞循環(huán)等待條件
對系統(tǒng)所有資源進(jìn)行線性排序并賦予不同的序號,這樣我們便可以規(guī)定進(jìn)程在申請資源時(shí)必須按照序號遞增的順序進(jìn)行資源的申請,當(dāng)以后要申請時(shí)需檢查要申請的資源的編號大于當(dāng)前編號時(shí),才能進(jìn)行申請。

二十二、App啟動(dòng)流程

1.App啟動(dòng)時(shí),AMS會(huì)檢查這個(gè)應(yīng)用程序所需要的進(jìn)程是否存在,不存在就會(huì)請求Zygote進(jìn)程啟動(dòng)需要的應(yīng)用程序進(jìn)程Zygote進(jìn)程接收到AMS請求并通過fock自身創(chuàng)建應(yīng)用程序進(jìn)程,這樣應(yīng)用程序進(jìn)程就會(huì)獲取虛擬機(jī)的實(shí)例,還會(huì)創(chuàng)建Binder線程池(ProcessState.startThreadPool())和消息循環(huán)(ActivityThread looper.loop)App進(jìn)程,通過Binder IPC向sytem_server進(jìn)程發(fā)起attachApplication請求;system_server進(jìn)程在收到請求后,進(jìn)行一系列準(zhǔn)備工作后,再通過Binder IPC向App進(jìn)程發(fā)送scheduleLaunchActivity請求;App進(jìn)程的binder線程(ApplicationThread)在收到請求后,通過handler向主線程發(fā)送LAUNCH_ACTIVITY消息;Message后,通過反射機(jī)制創(chuàng)建目標(biāo)Activity,并回調(diào)Activity.onCreate()等方法。App便正式啟動(dòng),開始進(jìn)入Activity生命周期,執(zhí)行完onCreate/onStart/onResume方法,UI渲染結(jié)束后便可以看到App的主界面。

二十三、RecyclerView在很多方面能取代ListView,ListView沒什么沒有過時(shí)?

ListView采用的是RecyclerBin的回收機(jī)制在一些輕量級的List顯示時(shí)效率更高

本文摘自 :https://www.cnblogs.com/

開通會(huì)員,享受整站包年服務(wù)立即開通 >