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

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

安卓中多線程梳理

一、HandlerThread梳理

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

二、IntentService梳理

三、線程池梳理

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

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

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

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

五、線程中斷

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

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

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

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

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

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

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

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

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

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

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

八、volatile能實現(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)加載,不會產(chǎn)生對應(yīng)的變化,所以計算出來的結(jié)果會和預(yù)期不一樣。
對于volatile修飾的變量,jvm虛擬機只是保證從主內(nèi)存加載到線程工作內(nèi)存的值是最新的。

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

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

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

a.最好兩個都不用,使用一種java.util.concurrent包提供的機制,
b.如果synchronized關(guān)鍵字能滿足用戶的需求,就用synchronized,因為它能簡化代碼
c.如果需要更高級的功能,就用ReentrantLock類,此時要注意及時釋放鎖,否則會出現(xiàn)死鎖,通常在finally代碼釋放鎖

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

如果使用ThreadLocal管理變量,則每一個使用該變量的線程都獲得該變量的副本, 副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變量副本,而不會對其他線程產(chǎn)生影響。
ThreadLocal 類的常用方法
ThreadLocal() //創(chuàng)建一個線程本地變量
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)存是被所有線程共享的運行時內(nèi)存區(qū)域,存在可見性的問題。
線程之間共享變量存儲在主存中,每個線程都有一個私有的本地內(nèi)存,本地內(nèi)存存儲了該線程共享變量的副本(本地內(nèi)存是一個抽象概念,并不真實存在),兩個線程要通信的話,首先A線程把本地內(nèi)存更新過的共享變量更新到主存中,然后B線程去主存中讀取A線程更新過的共享變量,也就是說假設(shè)線程A執(zhí)行了i = 1這行代碼更新主線程變量i的值,會首先在自己的工作線程中堆變量i進行賦值,然后再寫入主存當(dāng)中,而不是直接寫入主存

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

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

可見性:
指線程之間的可見性,既一個線程修改的狀態(tài)對另一個線程是可見的。volatile修飾可以保證可見性,它會保證修改的值會立即被更新到主存,所以對其他線程是可見的,普通的共享變量不能保證可見性,因為被修改后不會立即寫入主存,何時被寫入主存是不確定的,所以其他線程去讀取的時候可能讀到的還是舊值

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

十四 為什么HashMap線程不安全

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

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

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

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

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

1.發(fā)送方進程通過系統(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機制存在2個問題:
1.需要進行2次數(shù)據(jù)拷貝,第1次是從發(fā)送方用戶空間拷貝到內(nèi)核緩存區(qū),第2次是從內(nèi)核緩存區(qū)拷貝到接收方用戶空間。
2.接收方進程不知道事先要分配多大的空間來接收數(shù)據(jù),可能存在空間上的浪費。

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

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

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

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

十九、雙親委托模式

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

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

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

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

死鎖的四個必要條件

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

避免死鎖的方法

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

二十二、App啟動流程

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

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

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

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

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