目錄:

1. 線程
1.1 基礎(chǔ)概念
說(shuō)起線程的概念的時(shí)候與之相對(duì)應(yīng)的還有另一個(gè)概念--進(jìn)程,那么兩者有什么區(qū)別呢?
- 進(jìn)程:進(jìn)程是操作系統(tǒng)的基礎(chǔ),是系統(tǒng)機(jī)型資源分配和調(diào)度的基本單位。每一個(gè)進(jìn)程都有其獨(dú)立的代碼和數(shù)據(jù)空間,一個(gè)進(jìn)程里面有一個(gè)或多個(gè)線程。在Android系統(tǒng)當(dāng)中,進(jìn)程就是應(yīng)用程序的本體。
- 線程:線程也被可以叫做輕量級(jí)的進(jìn)程,線程擁有自己的計(jì)數(shù)器以及堆棧等屬性,且可以訪問(wèn)共享的內(nèi)存變量。如在一個(gè)應(yīng)用程序當(dāng)中,對(duì)于網(wǎng)絡(luò)加載,IO操作等任務(wù)都是在不同的線程當(dāng)中進(jìn)行。
除了線程的概念,還有一些在多線程的處理當(dāng)中經(jīng)常出現(xiàn)的名詞:
- 主線程:JVM調(diào)用程序main()所產(chǎn)生的線程。
- 當(dāng)前線程:這個(gè)是容易混淆的概念。一般指通過(guò)Thread.currentThread()來(lái)獲取的進(jìn)程。
- 后臺(tái)線程:指為其他線程提供服務(wù)的線程,也稱為守護(hù)線程。JVM的垃圾回收線程就是一個(gè)后臺(tái)線程。用戶線程和守護(hù)線程的區(qū)別在于,是否等待主線程依賴于主線程結(jié)束而結(jié)束
- 前臺(tái)線程:是指接受后臺(tái)線程服務(wù)的線程,其實(shí)前臺(tái)后臺(tái)線程是聯(lián)系在一起,就像傀儡和幕后操縱者一樣的關(guān)系??苁乔芭_(tái)線程、幕后操縱者是后臺(tái)線程。由前臺(tái)線程創(chuàng)建的線程默認(rèn)也是前臺(tái)線程??梢酝ㄟ^(guò)isDaemon()和setDaemon()方法來(lái)判斷和設(shè)置一個(gè)線程是否為后臺(tái)線程。
1.2 線程狀態(tài)轉(zhuǎn)換

線程整個(gè)生命周期當(dāng)中大致可以分為5個(gè)狀態(tài)(有些版本是6個(gè),本質(zhì)都是一樣的):
- 新建狀態(tài):線程被創(chuàng)建,調(diào)用start方法之前的狀態(tài)。
- 就緒狀態(tài):線程對(duì)象調(diào)用了start方法后會(huì)進(jìn)入就緒狀態(tài),等待獲取CPU的資源。
- 運(yùn)行狀態(tài):獲取到運(yùn)行所需的CPU資源,開(kāi)始執(zhí)行代碼。
- 阻塞狀態(tài):在運(yùn)行時(shí)由于某種原因進(jìn)入失去了CPU所分配的資源,暫時(shí)停止運(yùn)行,知道再次獲得資源。
- 等待阻塞:線程執(zhí)行wait()方法后進(jìn)入等待狀態(tài),釋放鎖。
- 同步阻塞:當(dāng)線程獲取同步鎖時(shí)發(fā)現(xiàn)鎖被占用,則進(jìn)入同步阻塞狀態(tài)。
- 運(yùn)行阻塞:線程調(diào)用sleep(),或者join等方法進(jìn)入阻塞狀態(tài)。
- 死亡狀態(tài):線程代碼執(zhí)行完畢或者拋出異常,結(jié)束運(yùn)行。
1.3 線程構(gòu)建
線程構(gòu)建有三種方式。
繼承Therad類(lèi)
第一種實(shí)現(xiàn)方式便是直接繼承Thread類(lèi),具體的實(shí)現(xiàn)步驟如下:
(1)定義Thread的子類(lèi),重寫(xiě)run方法,run方法的方法體就代表了線程要完成的任務(wù)。因此,run()方法被稱為執(zhí)行體。
(2)創(chuàng)建Thread子類(lèi)的實(shí)例,即創(chuàng)建線程對(duì)象。
(3)調(diào)用對(duì)象的start()方法啟動(dòng)線程。
代碼如下
//1. 繼承Thread類(lèi)
public class TestThread extends Thread {
private String mName;
public TestThread(String name) {
this.mName = name;
}
//2. 重寫(xiě)run()方法,在run()方法中執(zhí)行線程任務(wù)。
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
System.out.println("name is " + mName + i);
}
}
public static void main(String[] args) {
//3.創(chuàng)建Thread對(duì)象,調(diào)用start方法
//這里需要注意,調(diào)用start方法不代表線程立即就會(huì)執(zhí)行,這時(shí)候只是進(jìn)入到了就緒狀態(tài),
//當(dāng)獲得CPU資源之后會(huì)進(jìn)入到運(yùn)行狀態(tài)
TestThread mThread = new TestThread("testThread");
mThread.start();
}
}
實(shí)現(xiàn)Runnable接口
第二種方式就是實(shí)現(xiàn)Runnable接口,具體操作步驟如下:
(1)自定義類(lèi)并實(shí)現(xiàn)Runnable接口。
(2)創(chuàng)建Thread子類(lèi)的實(shí)例,實(shí)現(xiàn)Runnable接口的對(duì)象作為參數(shù)實(shí)例化該Thread對(duì)象。
(3)調(diào)用Thread實(shí)例的start()方法。
// 1、 自定義類(lèi)并實(shí)現(xiàn)Runnable接口。
public class MutliThread implements Runnable{
public void run(){
System.out.println(ticket--+" is saled by "+name);
}
}
public class TestRunnable{
public static void main(String[] args){
// 2、 創(chuàng)建Thread子類(lèi)的實(shí)例,
MutliThread mTest = new MutliThread();
Thread mThread = new Thread(mTest);
// 3、調(diào)用Thread實(shí)例的start()方法
mThread.start();
}
}
相比于直接繼承Thread類(lèi),更推薦實(shí)現(xiàn)Runnable接口的方式,其優(yōu)點(diǎn)如下:
- 線程池只支持放入
Runnable和Callable的實(shí)現(xiàn)。 - 多個(gè)
Thread對(duì)象共用同一段代碼塊的情況下,實(shí)現(xiàn)Runnable接口更容易實(shí)現(xiàn)線程之間的數(shù)據(jù)共享。 - 避免單繼承的機(jī)制。
實(shí)現(xiàn)Callable接口
Callable接口屬于Executor框架中的功能類(lèi),Callable接口與Runnable接口的功能類(lèi)似,但提供了比Runnable接口更強(qiáng)大的功能:
-
Callable可以再接收任務(wù)后提供一個(gè)返回值,Runnable無(wú)法提供這個(gè)功能。 -
Callable中的call()方法可以拋出異常,而Runnable的run()方法不能跑出異常。 -
Callable方法可以拿到一個(gè)Future對(duì)象,F(xiàn)uture對(duì)象表示一步計(jì)算的結(jié)果,它提供了價(jià)差計(jì)算是否完成的方法。
注意:使用Future的get()方法獲取結(jié)果的時(shí)候,當(dāng)前線程就會(huì)阻塞,直到call()方法返回結(jié)果。
來(lái)看一下代碼實(shí)現(xiàn)
//1. 實(shí)現(xiàn)Callable<V>接口的call方法,V為返回值的類(lèi)型
public class TestCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(5000);
return "call finish";
}
public static void main(String[] args) {
//2. 創(chuàng)建線程池(后面的內(nèi)容我們會(huì)詳細(xì)介紹)
ExecutorService mService = Executors.newFixedThreadPool(1);
//3. 創(chuàng)建TestCallable對(duì)象實(shí)例
TestCallable callable = new TestCallable();
//4. 通過(guò)Future獲取線程結(jié)構(gòu)
Future<String> future = mService.submit(callable);
System.out.println("程序開(kāi)始運(yùn)行");
try {
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("程序終止");
}
}
}
如果有對(duì)線程的返回值加以控制的需求,推薦使用這種方式,一般情況下我們采用實(shí)現(xiàn)
Runnable接口的方式
1.4 線程常用方法
- start():使線程開(kāi)始執(zhí)行。
- setName(String name):設(shè)置線程的名字為name值。
- setPriority(int priority):設(shè)置線程優(yōu)先級(jí)。
- sleep(long millis):使線程睡眠,放棄CPU的使用權(quán),但不會(huì)釋放
鎖。 - yield():使當(dāng)前線程回到就緒狀態(tài),將CPU使用權(quán)交給同級(jí)或者更高級(jí)線程使用。
注意:
yield()方法在交出CPU使用權(quán)以后又會(huì)和其他線程一同競(jìng)爭(zhēng)資源,可能會(huì)再次執(zhí)行當(dāng)前線程。sleep(millis)方法則是在指定時(shí)間內(nèi)不再競(jìng)爭(zhēng)資源。且yield()方法不會(huì)將資源出讓給更低優(yōu)先級(jí)的線程,sleep(millis)方法可以。
- join():等待線程終止。
- currentThread():獲取當(dāng)前線程。
- interrupt():使線程中的中斷標(biāo)志位置為中斷,如果需要中斷線程,需要在線程當(dāng)中對(duì)中斷進(jìn)行控制。
- wait():主動(dòng)釋放鎖對(duì)象,同時(shí)使線程休眠,。
- notify():喚起休眠線程,并重新獲取鎖對(duì)象。
相比于
sleep(long millis)方法,wait()方法會(huì)釋放鎖對(duì)象而sleep()方法不會(huì),而wait()方法必須出現(xiàn)在synchronized(Obj){...}語(yǔ)句塊內(nèi)并與notify()方法配合使用,
2. 同步
在線程的使用中,如果存在兩個(gè)或者兩個(gè)以上的線程需要共享同一段數(shù)據(jù),如果多個(gè)線程同時(shí)對(duì)數(shù)據(jù)對(duì)象進(jìn)行操作,就可能會(huì)引發(fā)競(jìng)爭(zhēng)條件,如果進(jìn)行同步處理的話就有可能會(huì)引發(fā)問(wèn)題。
同步的實(shí)現(xiàn)有兩種:Lock以及synchronized。
2.1 Lock
Lock位于java.util.concurrent.locks包下,是Java提供的一個(gè)接口,提供了如下方法:
public interface Lock {
//獲取鎖,如果鎖已經(jīng)被其他線程獲取則等待。
void lock();
//如果線程正在等待獲取鎖,則這個(gè)線程能夠響應(yīng)中斷,當(dāng)兩個(gè)線程同時(shí)通過(guò)lock.lockInterruptibly()想獲取某個(gè)鎖時(shí),
//假若此時(shí)線程A獲取到了鎖,而線程B只有在等待,那么對(duì)線程B調(diào)用threadB.interrupt()方法能夠中斷線程B的等待過(guò)程。
void lockInterruptibly() throws InterruptedException;
//嘗試獲取鎖,如果獲取成功則返回true,如果失敗則返回false
boolean tryLock();
//與tryLoc()類(lèi)似,只不過(guò)是在time響應(yīng)時(shí)間內(nèi)去嘗試獲取鎖,如果獲取失敗返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//釋放鎖,一定要在finally代碼塊當(dāng)中調(diào)用。
void unlock();
//獲取鎖對(duì)應(yīng)的條件對(duì)象
Condition newCondition();
}
在項(xiàng)目當(dāng)中我們常用的是重如鎖ReentrantLock,ReentrantLock類(lèi)對(duì)Lock接口做出了實(shí)現(xiàn)。
使用時(shí)其代碼結(jié)構(gòu)如下:
Lock mLock = new ReentrantLock();
//
mLock.lock();
try{
//同步的代碼塊
......
}
finally{
//釋放鎖對(duì)象
mLock.unlock();
}
需要注意的是在finally當(dāng)中需要調(diào)用unlock()方法,保證在執(zhí)行完代碼塊或者在發(fā)生異常的時(shí)候可以把鎖釋放掉。
在實(shí)際使用時(shí),通常我們會(huì)在線程中設(shè)置條件用于執(zhí)行釋放鎖或者喚起其他線程,這時(shí)候就引入了條件對(duì)象Condition。對(duì)上述代碼塊做如下修改:
Lock mLock = new ReentrantLock();
Condition condition = mLock.newCondition();
mLock.lock();
try{
while(臨界條件){
condition.await();
}
//同步代碼操作
//signalAll()代表喚起所有等待線程,signal()表示喚起隨機(jī)線程
condition.signalAll();
}
finally{
//釋放鎖對(duì)象
mLock.unlock();
}
Java還提供另一種鎖的形式:讀寫(xiě)鎖ReadWriteLock。它分離的讀和寫(xiě)的操作,使用讀操作鎖時(shí)可以允許多個(gè)線程同時(shí)訪問(wèn),使用寫(xiě)操作鎖時(shí)只允許一個(gè)線程進(jìn)行。提高了多個(gè)線程讀操作的效率。
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
其使用方式如下:
ReadWriteLock mLock = new ReentrantReadWriteLock();
public void write(){
lock.writeLock().lock();
try{
//寫(xiě)操作
}finally{
lock.writeLock().unlock();
}
}
public void read1(){
lock.readLock().lock();
try{
//讀操作
}finally{
lock.readLock().unlock();
}
}
public void read2(){
lock.readLock().lock();
try{
//讀操作
}finally{
lock.readLock().unlock();
}
}
2.2 synchronized
Lock的實(shí)現(xiàn)方式優(yōu)點(diǎn)在于它可以讓等待的線程去響應(yīng)中斷以及對(duì)讀寫(xiě)操作的成本有所降低。但是很多情況下我們不需要這樣的控制,這個(gè)時(shí)候就可以直接使用synchronized關(guān)鍵字進(jìn)行控制。
可以用synchronized關(guān)鍵字修飾的內(nèi)容有代碼塊、方法、靜態(tài)方法和類(lèi)。
修飾代碼塊
修飾一個(gè)代碼塊,被修飾的代碼塊稱為同步語(yǔ)句塊,其作用的范圍是大括號(hào){}括起來(lái)的代碼,作用的對(duì)象是調(diào)用這個(gè)代碼塊的對(duì)象.
代碼如下
public class TestSynchronize implements Runnable {
private static int count = 0;
private String name;
TestSynchronize(String name) {
this.name = name;
}
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println("線程名為:" + Thread.currentThread().getName()
+ "----" + name + count++);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
TestSynchronize test1 = new TestSynchronize("Runnable A");
Thread thread1 = new Thread(test1, "threadA");
Thread thread2 = new Thread(test1, "threadB");
thread1.start();
thread2.start();
}
}
運(yùn)行這段代碼輸出結(jié)果
線程名為:threadA----Runnable A0
線程名為:threadA----Runnable A1
線程名為:threadA----Runnable A2
線程名為:threadA----Runnable A3
線程名為:threadA----Runnable A4
線程名為:threadB----Runnable A5
線程名為:threadB----Runnable A6
線程名為:threadB----Runnable A7
線程名為:threadB----Runnable A8
線程名為:threadB----Runnable A9
從輸出結(jié)果可以看到,線程B會(huì)在線程A執(zhí)行結(jié)束后開(kāi)始執(zhí)行,這是因?yàn)?code>線程A和線程B所使用的都是test1對(duì)象的鎖,在線程A執(zhí)行完畢之前,它會(huì)一直占有這個(gè)鎖,直到執(zhí)行完畢,線程B獲得鎖之后才開(kāi)始執(zhí)行。
如果我們對(duì)上面代碼做如下修改
TestSynchronize test1 = new TestSynchronize("Runnable A");
TestSynchronize test2 = new TestSynchronize("Runnable B");
Thread thread1 = new Thread(test1, "threadA");
Thread thread2 = new Thread(test1, "threadB");
輸出結(jié)果為
線程名為:threadA----Runnable A0
線程名為:threadB----Runnable B1
線程名為:threadA----Runnable A2
線程名為:threadB----Runnable B3
線程名為:threadA----Runnable A4
線程名為:threadB----Runnable B5
線程名為:threadB----Runnable B6
線程名為:threadA----Runnable A6
線程名為:threadB----Runnable B7
線程名為:threadA----Runnable A8
這時(shí)候因?yàn)?code>線程A和線程B各自所持有的鎖時(shí)不同的,兩把鎖互不影響,我們可以視為線程A和線程B在同時(shí)運(yùn)行。
修飾方法
修飾方法即是在方法名前加上synchronized關(guān)鍵字,同步的作用范圍為整個(gè)方法,作用的對(duì)象為調(diào)用該方法所在的對(duì)象。將上面實(shí)例的同步方式修改為修飾方法的同步,其代碼結(jié)構(gòu)如下
public synchronized void run() {
for (int i = 0; i < 5; i++) {
try {
System.out.println("線程名為:" + Thread.currentThread().getName()
+ "----" + name + count++);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
輸出結(jié)果與上述代碼相同。
修飾方法有幾點(diǎn)要注意:
- synchronized關(guān)鍵字不能繼承。
- 在定義接口方法時(shí)不能使用synchronized關(guān)鍵字。
- 構(gòu)造方法不能使用synchronized關(guān)鍵字,但可以使用synchronized代碼塊來(lái)進(jìn)行同步。
修飾靜態(tài)方法
修飾一個(gè)靜態(tài)的方法,其作用的范圍與修飾方法相同,為整個(gè)方法代碼,但是其作用的對(duì)象是這個(gè)類(lèi)的所有對(duì)象。在上面的代碼做如下修改
@Override
public void run() {
method();
}
public synchronized static void method(){
for (int i = 0; i < 5; i++) {
try {
System.out.println("線程名為:" + Thread.currentThread().getName()
+ "----" + name + count++);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TestSynchronize test1 = new TestSynchronize("Runnable A");
TestSynchronize test2 = new TestSynchronize("Runnable B");
Thread thread1 = new Thread(test1, "threadA");
Thread thread2 = new Thread(test2, "threadB");
thread1.start();
thread2.start();
}
輸出結(jié)果變?yōu)?/p>
線程名為:threadA----Runnable B0
線程名為:threadA----Runnable B1
線程名為:threadA----Runnable B2
線程名為:threadA----Runnable B3
線程名為:threadA----Runnable B4
線程名為:threadB----Runnable B5
線程名為:threadB----Runnable B6
線程名為:threadB----Runnable B7
線程名為:threadB----Runnable B8
線程名為:threadB----Runnable B9
因?yàn)檫@種方式下synchronized作用的對(duì)象為類(lèi)的所有對(duì)象,所以test1和test2擁有的是同一個(gè)鎖,所以他們運(yùn)行時(shí)也是互斥的,只有在A運(yùn)行完畢之后B才會(huì)運(yùn)行。
修飾類(lèi)
修改一個(gè)類(lèi),其作用的范圍是synchronized后面括號(hào)括起來(lái)的部分,作用的對(duì)象是這個(gè)類(lèi)的所有對(duì)象。其使用方式如下
public void run() {
synchronized (TestSynchronize.class) {
for (int i = 0; i < 5; i++) {
try {
System.out.println("線程名為:" + Thread.currentThread().getName()
+ "----" + name + count++);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
輸出結(jié)果與修飾靜態(tài)方法時(shí)相同。
2.3 volatile
如果所需要同步的對(duì)象只是某一個(gè)或兩個(gè)實(shí)例對(duì)象時(shí),我們可以使用volatile關(guān)鍵字來(lái)代替。
線程讀寫(xiě)內(nèi)存變量模型如下。

根據(jù)圖示,線程A修改變量時(shí)需要讀取本地內(nèi)存中的內(nèi)容,修改完后存儲(chǔ)到本地內(nèi)存在同步到主存當(dāng)中。線程B需要先把主存中的變量讀取到本地內(nèi)存中再進(jìn)行操作。當(dāng)線程A和線程B同步進(jìn)行時(shí),遍會(huì)發(fā)生問(wèn)題。
volatile關(guān)鍵字修飾的變量,保證了修改后的新值得可見(jiàn)性,使得某一線程修改的內(nèi)容可以立即被其他線程獲取到。同時(shí)禁止編譯器和處理器對(duì)指令的重排序,從而確保多線程并發(fā)執(zhí)行的正確性。即volatile關(guān)鍵字可以保證可見(jiàn)性和有序性。
注意:
volatile關(guān)鍵字無(wú)法保持原子性(原子性:即操作是不可中斷的,如賦值和讀取操作),所以在非原子性操作(如自增自減操作)的語(yǔ)句中不可以使用volatile關(guān)鍵字。
3. 線程池
在Java當(dāng)中,通過(guò)線程池對(duì)線程加以控制有三個(gè)優(yōu)點(diǎn):
- 降低資源消耗:通過(guò)重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷(xiāo)毀造成的消耗。
- 提高響應(yīng)速度:當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。
- 提高線程的可管理性:線程是稀缺資源,如果無(wú)限制的創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。
3.1 創(chuàng)建線程池
通過(guò)ThreadPoolExecutor類(lèi)來(lái)構(gòu)建一個(gè)線程池,ThreadPoolExecutor類(lèi)有四個(gè)構(gòu)造方法,構(gòu)造參數(shù)最多的構(gòu)造方法如下:
- public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
各個(gè)參數(shù)對(duì)應(yīng)如下:
- corePoolSize:核心線程數(shù)。如果當(dāng)前運(yùn)行線程數(shù)少于核心線程數(shù),則創(chuàng)建新的線程來(lái)處理任務(wù),如果多于
corePoolSize,則加入到workQueue當(dāng)中。
關(guān)于核心線程數(shù)的配置?
任務(wù)性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開(kāi)處理。CPU密集型任務(wù)配置盡可能少的線程數(shù)量,如配置Ncpu+1個(gè)線程的線程池。IO密集型任務(wù)則由于需要等待IO操作,線程并不是一直在執(zhí)行任務(wù),則配置盡可能多的線程,如2*Ncpu。混合型的任務(wù),如果可以拆分,則將其拆分成一個(gè)CPU密集型任務(wù)和一個(gè)IO密集型任務(wù),只要這兩個(gè)任務(wù)執(zhí)行的時(shí)間相差不是太大,那么分解后執(zhí)行的吞吐率要高于串行執(zhí)行的吞吐率,如果這兩個(gè)任務(wù)執(zhí)行時(shí)間相差太大,則沒(méi)必要進(jìn)行分解。
- maximumPoolSize:線程池所允許的最大線程數(shù),如果當(dāng)任務(wù)隊(duì)列
workQueue滿了,并且當(dāng)前線程數(shù)小于maximumPoolSize時(shí),創(chuàng)建新的線程來(lái)處理任務(wù),直到達(dá)到maximumPoolSize的數(shù)值。 - keepAliveTime:設(shè)置非核心線程的閑置超時(shí)時(shí)間。如果非核心線程數(shù)閑置的時(shí)間超過(guò)
keepAliveTime則回收線程。如果設(shè)置allowCoreThreadTimeOut為true時(shí),這個(gè)策略也會(huì)應(yīng)用到核心線程。 - TimeUnit:
keepAliveTime值得單位??蛇x值包括NANOSECONDS(納米)、MICROSECONDS(微秒)、MILLISECONDS(毫秒)、SECONDS(秒)、MINUTES(分鐘)、HOURS(小時(shí))、DAYS(天)。 - workQueue:任務(wù)隊(duì)列,用來(lái)存儲(chǔ)未執(zhí)行的任務(wù)。
java當(dāng)中一個(gè)提供了七個(gè)阻塞隊(duì)列:
ArrayBlockQueue:由數(shù)組組成的有界阻塞隊(duì)列。
LinkedBlockingQueue:由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列。
PriorityBlockingQueue:支持優(yōu)先級(jí)排序的無(wú)界阻塞隊(duì)列。
DelayQueue:使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的無(wú)界阻塞隊(duì)列。
SynchronousQueue:不存儲(chǔ)元素的阻塞隊(duì)列。
LinkedTransferQueue:由鏈表結(jié)構(gòu)組成的無(wú)界阻塞隊(duì)列。
LinkedBlockingDeque:由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列。
- threadFactory:線程工廠,用來(lái)創(chuàng)建線程,一般不需要設(shè)置。
- RejectedExecutionHandler:表示當(dāng)拒絕處理任務(wù)時(shí)的策略
ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù),但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù),然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過(guò)程)。
ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程處理該任務(wù)。
提交任務(wù)后的線程池執(zhí)行流程如下:

- 提交任務(wù)后,先判斷是否達(dá)到核心線程數(shù),如果沒(méi)有則創(chuàng)建新的線程執(zhí)行任務(wù),如果達(dá)到了執(zhí)行下一步。
- 判斷任務(wù)隊(duì)列是否已滿,如果沒(méi)有滿加入任務(wù)隊(duì)列,如果滿了就執(zhí)行下一步。
- 判斷是否達(dá)到最大線程數(shù),如果沒(méi)有創(chuàng)建新的線程執(zhí)行任務(wù),如果滿了就執(zhí)行飽和策略。
3.2 使用線程池
在線程池的使用中有幾個(gè)比較常用的方法。
- submit():將線程放入線程池中,并提供一個(gè)
Future類(lèi)型的返回值。 - execute():將線程放入線程池中。
- shutdown():調(diào)用該方法后,線程池將不再接收新的線程,當(dāng)線程池中所有線程執(zhí)行完畢后回收線程資源。
- shutdownNow():馬上回收線程池。(慎用!可能會(huì)引起系統(tǒng)異常)
應(yīng)用代碼如下:
public class MyCallable implements Callable<String> {
private String name;
MyCallable(String name) {
this.name = name;
}
public static void main(String[] args) {
//新建一個(gè)核心線程數(shù)為3的線程池
ExecutorService service = Executors.newFixedThreadPool(3);
//results用來(lái)存儲(chǔ)線程結(jié)果
List<Future<String>> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add(service.submit(new MyCallable("Callable" + i)));
}
//關(guān)閉線程池,避免資源浪費(fèi)
service.shutdown();
//遍歷輸出結(jié)果
for (Future<String> future : results) {
try {
System.out.println("result is " + future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
@Override
public String call() throws Exception {
System.out.println("TestThreadCall" + name);
return name;
}
}
輸出結(jié)果
TestThreadCallCallable0
TestThreadCallCallable1
TestThreadCallCallable2
TestThreadCallCallable3
TestThreadCallCallable4
TestThreadCallCallable5
TestThreadCallCallable6
TestThreadCallCallable7
TestThreadCallCallable9
TestThreadCallCallable8
result is Callable0
result is Callable1
result is Callable2
result is Callable3
result is Callable4
result is Callable5
result is Callable6
result is Callable7
result is Callable8
result is Callable9
4. Android的多線程
前面鋪墊了那么多,終于回到了我們的老本行,在Android里面依然可以使用Thread或者線程池的方式實(shí)現(xiàn)線程操作,但是如果涉及到對(duì)UI進(jìn)行操作,還需要引入其他的線程管理方式。
4.1 Handler
定義:Handler主要用于接受子線程發(fā)送的數(shù)據(jù), 并用此數(shù)據(jù)配合主線程更新UI。
背景:在我們的應(yīng)用啟動(dòng)時(shí)便會(huì)產(chǎn)生一個(gè)主線程(UI線程),主要用于界面渲染,UI控制事件分發(fā)等功能。但是在主線程當(dāng)中不可以做類(lèi)似于網(wǎng)絡(luò)請(qǐng)求這種耗時(shí)操作,當(dāng)某個(gè)操作阻塞超過(guò)5秒時(shí)便會(huì)拋出異常,所以只能把這些操作放到子線程來(lái)操作。但是因?yàn)锳ndroid主線程是線程不安全的,所有的UI操作只能放到主線程來(lái)執(zhí)行,由此便產(chǎn)生了Handler。
原理:Android主線程存在一個(gè)消息隊(duì)列,Handler通過(guò)sendMessage或者Post方法送消息,并把消息插入到消息隊(duì)列當(dāng)中。Looper循環(huán)檢測(cè)消息隊(duì)列,當(dāng)發(fā)現(xiàn)有未處理的消息時(shí)便回調(diào)到創(chuàng)建Handler的線程中重寫(xiě)的handleMessage方法中去執(zhí)行。
Handler的使用
Post方式
- 新建
Thread處理耗時(shí)操作。 - 創(chuàng)建一個(gè) handler,通過(guò) handler.post/postDelay,投遞創(chuàng)建的 Runnable,在 run 方法中進(jìn)行更新 UI 操作。
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//處理線程耗時(shí)操作
handler.post(new Runnable() {
@Override
public void run() {
//主線程更新UI操作
}
});
}
});
thread.start();
sendMessage方式
- 創(chuàng)建線程處理耗時(shí)操作。
- 創(chuàng)建Message對(duì)象并進(jìn)行設(shè)置。
- 調(diào)用sendMessage發(fā)送消息。
- 創(chuàng)建Hnadler并重寫(xiě)handleMessage方法,對(duì)消息進(jìn)行處理。
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//根據(jù)msg.what進(jìn)行消息劃分
switch (msg.what) {
case 1:
//進(jìn)行UI操作
break;
}
}
};
public class TestThread extends Thread {
@Override
public void run() {
super.run();
//處理耗時(shí)操作
//新建Message實(shí)例,
Message msg = Message.obtain();
msg.obj = data;
//設(shè)置 msg.what標(biāo)志位,方便進(jìn)行消息類(lèi)別劃分。
msg.what=1;
//發(fā)送消息
handler.sendMessage(msg);
}
}
TestThread thread = new TestThread();
thread.start();
4.2 AsyncTask
定義:AsyncTask是android提供的輕量級(jí)的異步類(lèi),在類(lèi)中實(shí)現(xiàn)異步操作,并提供接口反饋當(dāng)前異步執(zhí)行的程度(可以通過(guò)接口實(shí)現(xiàn)UI進(jìn)度更新),最后反饋執(zhí)行的結(jié)果給UI主線程。
直接看示例分析
public class TestAsyncTask extends AsyncTask<String,Float,String> {
/**
* AsyncTask的三個(gè)參數(shù)分別為Params, Progress, Result
* Params: 任務(wù)啟動(dòng)時(shí)輸入的參數(shù)類(lèi)型
* Progress: 任務(wù)進(jìn)度的返回類(lèi)型
* Result: 后臺(tái)任務(wù)的返回結(jié)果列席
* 當(dāng)某個(gè)參數(shù)不需要傳遞參數(shù)時(shí),可以使用Void來(lái)代替
*/
/**
* onPreExecute()方法在后臺(tái)任務(wù)進(jìn)行之前執(zhí)行,一般用于處理部分初始化的操作
* 在主線程進(jìn)行。
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* 該方法用于在后臺(tái)處理耗時(shí)操作,不可以進(jìn)行UI操作,可以通過(guò)publishProgress(Progress...)方法返回任務(wù)進(jìn)度
* 在子線程進(jìn)行。
* @param strings 參數(shù)類(lèi)型,對(duì)應(yīng)AsyncTask<Params, Progress, Result>的第一個(gè)參數(shù)
* @return
*/
@Override
protected String doInBackground(String... strings) {
return null;
}
/**
* 該方法可以對(duì)UI進(jìn)行操作,可以利用傳進(jìn)來(lái)的進(jìn)度對(duì)UI進(jìn)行更新
* 在主線程進(jìn)行
* @param values
*/
@Override
protected void onProgressUpdate(Float... values) {
super.onProgressUpdate(values);
}
/**
* 用來(lái)處理返回的結(jié)果,可以進(jìn)行UI操作
* 在主線程進(jìn)行
* @param s
*/
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
}
}
new TestAsyncTask().execute();
整個(gè)任務(wù)的執(zhí)行流程為:
onPreExecute() --> doInBackground() --> publishProgress() --> onProgressUpdate() --> onPostExecute()
注意:AsyncTask對(duì)象和execute()方法必須在UI線程調(diào)用,且一個(gè)任務(wù)實(shí)例只可以執(zhí)行一次。
總結(jié)
本篇內(nèi)容簡(jiǎn)單介紹了Android當(dāng)中用到的多線程的知識(shí),且大部分都屬于java當(dāng)中的內(nèi)容,熟練掌握多線程的內(nèi)容無(wú)論是在以后的工作中還是在面試中都是很有必要的。