####4、HandlerThread:
handlerThread = new HandlerThread("MyNewThread");//自定義線程名稱
handlerThread.start();
mOtherHandler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg){
if (msg.what == 0x124){
try {
Log.d("HandlerThread", Thread.currentThread().getName());
Thread.sleep(5000);//模拟耗時任務
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
HandlerThread的好處是代碼看起來沒前面的版本那麼亂,相對簡潔一點。還有一個好處就是通過handlerThread.quit()或者quitSafely()使線程結束自己的生命周期。
####4、AsyncTask:
具體的使用代碼就不貼上來了,可以去看我的一篇博文。但值得一說的是,上面說過HandlerThread隻開一條線程,任務都被阻塞在一個隊列中,那麼就會使阻塞的任務延遲了,而AsyncTask開啟線程的方法asyncTask.execute()預設是也是開啟一個線程和一個隊列的,不過也可以通過asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0)開啟一個含有5個新線程的線程池,也就是說有個5個隊列了,假如說你執行第6個耗時任務時,除非前面5個都還沒執行完,否則任務是不會阻塞的,這樣就可以大大減少耗時任務延遲的可能性,這也是它的優點所在。當你想多個耗時任務并發的執行,那你更應該選擇AsyncTask。
####5、IntentService:
最後是IntentService,相信很多人也不陌生,它是Service的子類,用法跟Service也差不多,就是實作的方法名字不一樣,耗時邏輯應放在onHandleIntent(Intent intent)的方法體裡,它同樣有着退出啟動它的Activity後不會被系統殺死的特點,而且當任務執行完後會自動停止,無須手動去終止它。例如在APP裡我們要實作一個下載下傳功能,當退出頁面後下載下傳不會被中斷,那麼這時候IntentService就是一個不錯的選擇了。
閱讀全文文末領取免費高階Android學習資料及高清思維腦圖!
###線程狀态
1、wait()。使一個線程處于等待狀态,并且釋放所有持有對象的lock鎖,直到notify()/notifyAll()被喚醒後放到鎖定池(lock blocked pool ),釋放同步鎖使線程回到可運作狀态(Runnable)。
2、sleep()。使一個線程處于睡眠狀态,是一個靜态方法,調用此方法要捕捉Interrupted異常,醒來後進入runnable狀态,等待JVM排程。
3、notify()。使一個等待狀态的線程喚醒,注意并不能确切喚醒等待狀态線程,是由JVM決定且不按優先級。
4、allnotify()。使所有等待狀态的線程喚醒,注意并不是給所有線程上鎖,而是讓它們競争。
5、join()。使一個線程中斷,IO完成會回到Runnable狀态,等待JVM的排程。
6、Synchronized()。使Running狀态的線程加同步鎖使其進入(lock blocked pool ),同步鎖被釋放進入可運作狀态(Runnable)。
注意:當線程在runnable狀态時是處于被排程的線程,此時的排程順序是不一定的。Thread類中的yield方法可以讓一個running狀态的線程轉入runnable。
###基礎概念
1、 并行。多個cpu執行個體或多台機器同時執行一段代碼,是真正的同時。
2、并發。通過cpu排程算法,讓使用者看上去同時執行,實際上從cpu操作層面不是真正的同時。
3、線程安全。指在并發的情況之下,該代碼經過多線程使用,線程的排程順序不影響任何結果。線程不安全就意味着線程的排程順序會影響最終結果,比如某段代碼不加事務去并發通路。
4、線程同步。指的是通過人為的控制和排程,保證共享資源的多線程通路成為線程安全,來保證結果的準确。如某段代碼加入@synchronized關鍵字。線程安全的優先級高于性能優化。
5、原子性。一個操作或者一系列操作,要麼全部執行要麼全部不執行。資料庫中的“事物”就是個典型的院子操作。
6、可見性。當一個線程修改了共享屬性的值,其它線程能立刻看到共享屬性值的更改。比如JMM分為主存和工作記憶體,共享屬性的修改過程是在主存中讀取并複制到工作記憶體中,在工作記憶體中修改完成之後,再重新整理主存中的值。若線程A在工作記憶體中修改完成但還來得及重新整理主存中的值,這時線程B通路該屬性的值仍是舊值。這樣可見性就沒法保證。
7、有序性。程式運作時代碼邏輯的順序在實際執行中不一定有序,為了提高性能,編譯器和處理器都會對代碼進行重新排序。前提是,重新排序的結果要和單線程執行程式順序一緻。
###Synchronized 同步
由于java的每個對象都有一個内置鎖,當用此關鍵字修飾方法時, 内置鎖會保護整個方法。在調用該方法前,需要獲得内置鎖,否則就處于阻塞狀态。補充: synchronized關鍵字也可以修飾靜态方法,此時如果調用該靜态方法,将會鎖住整個類。
1、方法同步。給方法增加synchronized修飾符就可以成為同步方法,可以是靜态方法、非靜态方法,但不能是抽象方法、接口方法。小示例:
public synchronized void aMethod() {
// do something
}
public static synchronized void anotherMethod() {
// do something
}
使用詳解:
線程在執行同步方法時是具有排它性的。當任意一個線程進入到一個對象的任意一個同步方法時,這個對象的所有同步方法都被鎖定了,在此期間,其他任何線程都不能通路這個對象的任意一個同步方法,直到這個線程執行完它所調用的同步方法并從中退出,進而導緻它釋放了該對象的同步鎖之後。在一個對象被某個線程鎖定之後,其他線程是可以通路這個對象的所有非同步方法的。
2、塊同步。同步塊是通過鎖定一個指定的對象,來對塊中的代碼進行同步;同步方法和同步塊之間的互相制約隻限于同一個對象之間,靜态同步方法隻受它所屬類的其它靜态同步方法的制約,而跟這個類的執行個體沒有關系。如果一個對象既有同步方法,又有同步塊,那麼當其中任意一個同步方法或者同步塊被某個線程執行時,這個對象就被鎖定了,其他線程無法在此時通路這個對象的同步方法,也不能執行同步塊。
3、使用方法同步保護共享資料。示例:
public class ThreadTest implements Runnable{
public synchronized void run(){
for(int i=0;i<10;i++) {
System.out.print(" " + i);
}
}
public static void main(String[] args) {
Runnable r1 = new ThreadTest();
Runnable r2 = new ThreadTest();
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}}
示例詳解:
代碼中可見,run()被加上了synchronized 關鍵字,但保護的并不是共享資料。因為程式中兩個線程對象 t1、t2 其實是另外兩個線程對象 r1、r2 的線程,這個聽起來繞,但是一眼你就能看明白;因為不同的線程對象的資料是不同的,即 r1,r2 有各自的run()方法,是以輸出結果就無法預知。這時使用 synchronized 關鍵字可以讓某個時刻隻有一個線程可以通路該對象synchronized資料。每個對象都有一個“鎖标志”,當這個對象的一個線程通路這個對象的某個synchronized 資料時,這個對象的所有被synchronized 修飾的資料将被上鎖(因為“鎖标志”被目前線程拿走了),隻有目前線程通路完它要通路的synchronized 資料時,目前線程才會釋放“鎖标志”,這樣同一個對象的其它線程才有機會通路synchronized 資料。
接下來,我們把 r2 給注釋掉, 即隻保留一個 r 對象。如下:
public class ThreadTest implements Runnable{
public synchronized void run(){
for(int i=0;i<10;i++){
System.out.print(" " + i);
}
}
public static void main(String[] args){
Runnable r = new ThreadTest();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}}
示例詳解:
如果你運作1000 次這個程式,它的輸出結果也一定每次都是:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。因為這裡的synchronized 保護的是共享資料。t1,t2 是同一個對象(r)的兩個線程,當其中的一個線程(例如:t1)開始執行run()方法時,由于run()受synchronized保護,是以同一個對象的其他線程(t2)無法通路synchronized 方法(run 方法)。隻有當t1執行完後t2 才有機會執行。
4、使用塊同步,示例:
public class ThreadTest implements Runnable{
public void run(){
synchronized(this){ //與上面示例不同于關鍵字使用
for(int i=0;i<10;i++){
System.out.print(" " + i);
}
}
}
public static void main(String[] args){
Runnable r = new ThreadTest();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
示例詳解:
這個與上面示例的運作結果也一樣的。這裡是把保護範圍縮到最小,this 代表 ‘這個對象’ 。沒有必要把整個run()保護起來,run()中的代碼隻有一個for循環,是以隻要保護for 循環就可以了。
最後,再看一個示例:
public class ThreadTest implements Runnable{
public void run(){
for(int k=0;k<5;k++){
System.out.println(Thread.currentThread().getName()+ " : for loop : " + k);
}
synchronized(this){
for(int k=0;k<5;k++) {
System.out.println(Thread.currentThread().getName()+ " : synchronized for loop : " + k);
}} }
public static void main(String[] args){
Runnable r = new ThreadTest();
Thread t1 = new Thread(r,"t1_name");
Thread t2 = new Thread(r,"t2_name");
t1.start();
t2.start();
} }
//運作結果:
t1_name : for loop : 0
t1_name : for loop : 1
t1_name : for loop : 2
t2_name : for loop : 0
t1_name : for loop : 3
t2_name : for loop : 1
t1_name : for loop : 4
t2_name : for loop : 2
t1_name : synchronized for loop : 0
t2_name : for loop : 3
t1_name : synchronized for loop : 1
t2_name : for loop : 4
t1_name : synchronized for loop : 2
t1_name : synchronized for loop : 3
t1_name : synchronized for loop : 4
t2_name : synchronized for loop : 0
t2_name : synchronized for loop : 1
t2_name : synchronized for loop : 2
t2_name : synchronized for loop : 3
t2_name : synchronized for loop : 4
示例詳解:
第一個for 循環沒有受synchronized 保護。對于第一個for 循環,t1,t2 可以同時通路。運作結果表明t1 執行到了k=2 時,t2 開始執行了。t1 首先執行完了第一個for 循環,此時t2還沒有執行完第一個for 循環(t2 剛執行到k=2)。t1 開始執行第二個for 循環,當t1的第二個for 循環執行到k=1 時,t2 的第一個for 循環執行完了。t2 想開始執行第二個for 循環,但由于t1 首先執行了第二個for 循環,這個對象的鎖标志自然在t1 手中(synchronized 方法的執行權也就落到了t1 手中),在t1 沒執行完第二個for 循環的時候,它是不會釋放鎖标志的。是以t2 必須等到t1 執行完第二個for 循環後,它才可以執行第二個for 循環。
###Volatile 同步
a.volatile關鍵字為域變量的通路提供了一種免鎖機制
b.使用volatile修飾域相當于告訴虛拟機該域可能會被其他線程更新