目錄:
1. 線程
1.1 基礎(chǔ)概念
說起線程
的概念的時(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ù)器以及堆棧等屬性,且可以訪問共享的內(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è)是容易混淆的概念油猫。一般指通過Thread.currentThread()來獲取的進(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)線程恤煞。可以通過isDaemon()和setDaemon()方法來判斷和設(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資源竿裂,開始執(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類
第一種實(shí)現(xiàn)方式便是直接繼承Thread
類吏垮,具體的實(shí)現(xiàn)步驟如下:
(1)定義Thread
的子類障涯,重寫run方法,run方法的方法體就代表了線程要完成的任務(wù)膳汪。因此唯蝶,run()方法被稱為執(zhí)行體。
(2)創(chuàng)建Thread
子類的實(shí)例遗嗽,即創(chuàng)建線程對(duì)象粘我。
(3)調(diào)用對(duì)象的start()
方法啟動(dòng)線程。
代碼如下
//1. 繼承Thread類
public class TestThread extends Thread {
private String mName;
public TestThread(String name) {
this.mName = name;
}
//2. 重寫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)自定義類并實(shí)現(xiàn)Runnable接口氮昧。
(2)創(chuàng)建Thread子類的實(shí)例,實(shí)現(xiàn)Runnable接口的對(duì)象作為參數(shù)實(shí)例化該Thread對(duì)象浦楣。
(3)調(diào)用Thread實(shí)例的start()方法袖肥。
// 1、 自定義類并實(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子類的實(shí)例澎迎,
MutliThread mTest = new MutliThread();
Thread mThread = new Thread(mTest);
// 3庐杨、調(diào)用Thread實(shí)例的start()方法
mThread.start();
}
}
相比于直接繼承Thread
類,更推薦實(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框架中的功能類帖烘,Callable
接口與Runnable
接口的功能類似柳击,但提供了比Runnable
接口更強(qiáng)大的功能:
-
Callable
可以再接收任務(wù)后提供一個(gè)返回值氛什,Runnable
無法提供這個(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é)果蒿秦。
來看一下代碼實(shí)現(xiàn)
//1. 實(shí)現(xiàn)Callable<V>接口的call方法烤镐,V為返回值的類型
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. 通過Future獲取線程結(jié)構(gòu)
Future<String> future = mService.submit(callable);
System.out.println("程序開始運(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():使線程開始執(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){...}
語句塊內(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ā)問題房交。
同步的實(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í)通過lock.lockInterruptibly()想獲取某個(gè)鎖時(shí)硬霍,
//假若此時(shí)線程A獲取到了鎖帜慢,而線程B只有在等待,那么對(duì)線程B調(diào)用threadB.interrupt()方法能夠中斷線程B的等待過程唯卖。
void lockInterruptibly() throws InterruptedException;
//嘗試獲取鎖粱玲,如果獲取成功則返回true,如果失敗則返回false
boolean tryLock();
//與tryLoc()類似拜轨,只不過是在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
類對(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還提供另一種鎖的形式:讀寫鎖ReadWriteLock
悼瓮。它分離的讀和寫的操作戈毒,使用讀操作鎖時(shí)可以允許多個(gè)線程同時(shí)訪問,使用寫操作鎖時(shí)只允許一個(gè)線程進(jìn)行谤牡。提高了多個(gè)線程讀操作的效率副硅。
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
其使用方式如下:
ReadWriteLock mLock = new ReentrantReadWriteLock();
public void write(){
lock.writeLock().lock();
try{
//寫操作
}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ì)讀寫操作的成本有所降低。但是很多情況下我們不需要這樣的控制翅萤,這個(gè)時(shí)候就可以直接使用synchronized
關(guān)鍵字進(jìn)行控制恐疲。
可以用synchronized
關(guān)鍵字修飾的內(nèi)容有代碼塊
腊满、方法
、靜態(tài)方法
和類
培己。
修飾代碼塊
修飾一個(gè)代碼塊碳蛋,被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號(hào){}括起來的代碼省咨,作用的對(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é)束后開始執(zhí)行,這是因?yàn)?code>線程A和線程B
所使用的都是test1
對(duì)象的鎖零蓉,在線程A
執(zhí)行完畢之前笤受,它會(huì)一直占有這個(gè)鎖,直到執(zhí)行完畢敌蜂,線程B
獲得鎖之后才開始執(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代碼塊來進(jìn)行同步嘹害。
修飾靜態(tài)方法
修飾一個(gè)靜態(tài)的方法撮竿,其作用的范圍與修飾方法相同吮便,為整個(gè)方法代碼,但是其作用的對(duì)象是這個(gè)類的所有對(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ì)象為類的所有對(duì)象髓需,所以test1
和test2
擁有的是同一個(gè)鎖,所以他們運(yùn)行時(shí)也是互斥的房蝉,只有在A運(yùn)行完畢之后B才會(huì)運(yùn)行僚匆。
修飾類
修改一個(gè)類,其作用的范圍是synchronized后面括號(hào)括起來的部分搭幻,作用的對(duì)象是這個(gè)類的所有對(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)鍵字來代替松申。
線程讀寫內(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ā)生問題琉历。
volatile
關(guān)鍵字修飾的變量,保證了修改后的新值得可見性水醋,使得某一線程修改的內(nèi)容可以立即被其他線程獲取到旗笔。同時(shí)禁止編譯器和處理器對(duì)指令的重排序,從而確保多線程并發(fā)執(zhí)行的正確性拄踪。即volatile
關(guān)鍵字可以保證可見性和有序性换团。
注意:
volatile
關(guān)鍵字無法保持原子性(原子性:即操作是不可中斷的,如賦值和讀取操作)宫蛆,所以在非原子性操作(如自增自減操作)的語句中不可以使用volatile
關(guān)鍵字艘包。
3. 線程池
在Java
當(dāng)中,通過線程池對(duì)線程加以控制有三個(gè)優(yōu)點(diǎn):
- 降低資源消耗:通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗耀盗。
- 提高響應(yīng)速度:當(dāng)任務(wù)到達(dá)時(shí)想虎,任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。
- 提高線程的可管理性:線程是稀缺資源叛拷,如果無限制的創(chuàng)建舌厨,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性忿薇,使用線程池可以進(jìn)行統(tǒng)一的分配裙椭,調(diào)優(yōu)和監(jiān)控。
3.1 創(chuàng)建線程池
通過ThreadPoolExecutor
類來構(gòu)建一個(gè)線程池署浩,ThreadPoolExecutor
類有四個(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)建新的線程來處理任務(wù)炊汤,如果多于
corePoolSize
,則加入到workQueue
當(dāng)中弊攘。
關(guān)于核心線程數(shù)的配置抢腐?
任務(wù)性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開處理。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í)間相差太大,則沒必要進(jìn)行分解打肝。
- maximumPoolSize:線程池所允許的最大線程數(shù)脂新,如果當(dāng)任務(wù)隊(duì)列
workQueue
滿了,并且當(dāng)前線程數(shù)小于maximumPoolSize
時(shí)粗梭,創(chuàng)建新的線程來處理任務(wù)争便,直到達(dá)到maximumPoolSize
的數(shù)值。 - keepAliveTime:設(shè)置非核心線程的閑置超時(shí)時(shí)間断医。如果非核心線程數(shù)閑置的時(shí)間超過
keepAliveTime
則回收線程滞乙。如果設(shè)置allowCoreThreadTimeOut
為true時(shí),這個(gè)策略也會(huì)應(yīng)用到核心線程鉴嗤。 - TimeUnit:
keepAliveTime
值得單位斩启。可選值包括NANOSECONDS(納米)醉锅、MICROSECONDS(微秒)兔簇、MILLISECONDS(毫秒)、SECONDS(秒)硬耍、MINUTES(分鐘)垄琐、HOURS(小時(shí))、DAYS(天)经柴。 - workQueue:任務(wù)隊(duì)列狸窘,用來存儲(chǔ)未執(zhí)行的任務(wù)。
java當(dāng)中一個(gè)提供了七個(gè)阻塞隊(duì)列:
ArrayBlockQueue:由數(shù)組組成的有界阻塞隊(duì)列口锭。
LinkedBlockingQueue:由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列朦前。
PriorityBlockingQueue:支持優(yōu)先級(jí)排序的無界阻塞隊(duì)列。
DelayQueue:使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的無界阻塞隊(duì)列鹃操。
SynchronousQueue:不存儲(chǔ)元素的阻塞隊(duì)列。
LinkedTransferQueue:由鏈表結(jié)構(gòu)組成的無界阻塞隊(duì)列春哨。
LinkedBlockingDeque:由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列荆隘。
- threadFactory:線程工廠,用來創(chuàng)建線程赴背,一般不需要設(shè)置椰拒。
- RejectedExecutionHandler:表示當(dāng)拒絕處理任務(wù)時(shí)的策略
ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常晶渠。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù),但是不拋出異常燃观。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù)褒脯,然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過程)。
ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程處理該任務(wù)缆毁。
提交任務(wù)后的線程池執(zhí)行流程如下:
- 提交任務(wù)后番川,先判斷是否達(dá)到核心線程數(shù),如果沒有則創(chuàng)建新的線程執(zhí)行任務(wù)脊框,如果達(dá)到了執(zhí)行下一步颁督。
- 判斷任務(wù)隊(duì)列是否已滿,如果沒有滿加入任務(wù)隊(duì)列浇雹,如果滿了就執(zhí)行下一步沉御。
- 判斷是否達(dá)到最大線程數(shù),如果沒有創(chuàng)建新的線程執(zhí)行任務(wù)昭灵,如果滿了就執(zhí)行飽和策略吠裆。
3.2 使用線程池
在線程池的使用中有幾個(gè)比較常用的方法。
- submit():將線程放入線程池中烂完,并提供一個(gè)
Future
類型的返回值硫痰。 - 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用來存儲(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)中不可以做類似于網(wǎng)絡(luò)請(qǐng)求這種耗時(shí)操作脯颜,當(dāng)某個(gè)操作阻塞超過5秒時(shí)便會(huì)拋出異常,所以只能把這些操作放到子線程來操作贩据。但是因?yàn)锳ndroid主線程是線程不安全的栋操,所有的UI操作只能放到主線程來執(zhí)行闸餐,由此便產(chǎn)生了Handler
。
原理:Android主線程存在一個(gè)消息隊(duì)列矾芙,Handler
通過sendMessage
或者Post
方法送消息舍沙,并把消息插入到消息隊(duì)列當(dāng)中。Looper
循環(huán)檢測(cè)消息隊(duì)列剔宪,當(dāng)發(fā)現(xiàn)有未處理的消息時(shí)便回調(diào)到創(chuàng)建Handler
的線程中重寫的handleMessage
方法中去執(zhí)行拂铡。
Handler的使用
Post方式
- 新建
Thread
處理耗時(shí)操作。 - 創(chuàng)建一個(gè) handler歼跟,通過 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并重寫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)行消息類別劃分硬梁。
msg.what=1;
//發(fā)送消息
handler.sendMessage(msg);
}
}
TestThread thread = new TestThread();
thread.start();
4.2 AsyncTask
定義:AsyncTask是android提供的輕量級(jí)的異步類,在類中實(shí)現(xiàn)異步操作,并提供接口反饋當(dāng)前異步執(zhí)行的程度(可以通過接口實(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ù)類型
* Progress: 任務(wù)進(jìn)度的返回類型
* Result: 后臺(tái)任務(wù)的返回結(jié)果列席
* 當(dāng)某個(gè)參數(shù)不需要傳遞參數(shù)時(shí),可以使用Void來代替
*/
/**
* onPreExecute()方法在后臺(tái)任務(wù)進(jìn)行之前執(zhí)行阶剑,一般用于處理部分初始化的操作
* 在主線程進(jìn)行跃巡。
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* 該方法用于在后臺(tái)處理耗時(shí)操作,不可以進(jìn)行UI操作牧愁,可以通過publishProgress(Progress...)方法返回任務(wù)進(jìn)度
* 在子線程進(jìn)行素邪。
* @param strings 參數(shù)類型,對(duì)應(yīng)AsyncTask<Params, Progress, Result>的第一個(gè)參數(shù)
* @return
*/
@Override
protected String doInBackground(String... strings) {
return null;
}
/**
* 該方法可以對(duì)UI進(jìn)行操作猪半,可以利用傳進(jìn)來的進(jìn)度對(duì)UI進(jìn)行更新
* 在主線程進(jìn)行
* @param values
*/
@Override
protected void onProgressUpdate(Float... values) {
super.onProgressUpdate(values);
}
/**
* 用來處理返回的結(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)容無論是在以后的工作中還是在面試中都是很有必要的。