參考鏈接:
多線程的三種實(shí)現(xiàn)方式
- 繼承Thread類,重寫run函數(shù)方法
class MyThread extends Thread {
@Override
public void run() {
super.run();
}
}
- 實(shí)現(xiàn)Runnable接口,重寫run函數(shù)方法
class MyThread implements Runnable{
@Override
public void run() {
}
}
- 實(shí)現(xiàn)Callable接口,重寫call函數(shù)方法,ExecutorService、Callable购裙、Future實(shí)現(xiàn)有返回結(jié)果的多線程
class MyThread <T> implements Callable<T> {
@Override
public T call() {
return null;
}
}
- Callable和Runnable的不同之處:
①Callable規(guī)定的方法是call(),而Runnable規(guī)定的方法是run().
②Callable的任務(wù)執(zhí)行后可返回值,而Runnable的任務(wù)是不能返回值的
③call()方法可拋出異常坯台,而run()方法是不能拋出異常的。
④運(yùn)行Callable任務(wù)可拿到一個(gè)Future對(duì)象瘫寝,F(xiàn)uture表示異步計(jì)算的結(jié)果蜒蕾。通過Future對(duì)象可了解任務(wù)執(zhí)行情況,可取消任務(wù)的執(zhí)行。
Callable和Runable詳解見鏈接:
Android(Java)之多線程結(jié)果返回——Future 焕阿、FutureTask咪啡、Callable、Runnable
如何停止一個(gè)線程
- 創(chuàng)建一個(gè)標(biāo)識(shí)(flag)暮屡,當(dāng)線程完成你所需要的工作后瑟匆,可以將標(biāo)識(shí)設(shè)置為退出標(biāo)識(shí)
- 使用Thread的interrupt()方法和nterrupted()方法,兩者配合break退出循環(huán)栽惶,或者return來(lái)停止線程愁溜,有點(diǎn)類似標(biāo)識(shí)(flag)
- 可以使用try-catch語(yǔ)句,在try-catch語(yǔ)句中拋出異常外厂,強(qiáng)行停止線程進(jìn)入catch語(yǔ)句冕象,這種方法可以將錯(cuò)誤向上拋,使線程停止事件得以傳播
Thread線程狀態(tài)和相關(guān)方法
- 可運(yùn)行(runnable):線程對(duì)象創(chuàng)建后汁蝶,線程調(diào)用start()方法渐扮。該狀態(tài)的線程位于可運(yùn)行線程池中论悴,等待被線程調(diào)度選中,獲取cpu的使用權(quán)
- 運(yùn)行(running):可運(yùn)行狀態(tài)(runnable)的線程獲得了cpu使用權(quán)墓律,執(zhí)行程序代碼
- 阻塞(block):線程因?yàn)槟撤N原因放棄了cpu使用權(quán)膀估,即讓出了cpu使用權(quán),暫時(shí)停止運(yùn)行耻讽,直到線程進(jìn)入可運(yùn)行(runnable)狀態(tài)察纯,才有機(jī)會(huì)再次獲得cpu使用權(quán)轉(zhuǎn)到運(yùn)行(running)狀態(tài)。阻塞的情況分三種:
(1)等待阻塞:運(yùn)行(running)的線程執(zhí)行o.wait()方法针肥,JVM會(huì)把該線程放入等待隊(duì)列(waitting queue)中
(2)同步阻塞:運(yùn)行(running)的線程在獲取對(duì)象的同步鎖時(shí)饼记,若該同步鎖被別的線程占用,則JVM會(huì)把該線程放入鎖池(lock pool)中
其他阻塞:運(yùn)行(running)的線程執(zhí)行Thread.sleep(long ms)或t.join()方法慰枕,或者發(fā)出了I/O請(qǐng)求時(shí)具则,JVM會(huì)把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時(shí)具帮、join()等待線程終止或者超時(shí)博肋、或者I/O處理完畢時(shí),線程重新轉(zhuǎn)入可運(yùn)行(runnable)狀態(tài)
(3)死亡(dead):線程run()蜂厅、main() 方法執(zhí)行結(jié)束束昵,或者因異常退出了run()方法,則該線程結(jié)束生命周期葛峻,且死亡的線程不可再次復(fù)生
sleep和wait的區(qū)別
- sleep()是Thread類的方法,wait()是Object類中的方法;
- 調(diào)用sleep()锹雏,在指定的時(shí)間里,暫停程序的執(zhí)行术奖,讓出CPU給其他線程礁遵,當(dāng)超過時(shí)間的限制后,又重新恢復(fù)到運(yùn)行狀態(tài)采记,在這個(gè)過程中佣耐,線程不會(huì)釋放對(duì)象鎖;調(diào)用wait()時(shí),線程會(huì)釋放對(duì)象鎖唧龄,進(jìn)入此對(duì)象的等待鎖池中兼砖,只有此對(duì)象調(diào)用notify()時(shí),線程進(jìn)入運(yùn)行狀態(tài)
方法介紹:
- wait() :使一個(gè)線程處于等待狀態(tài)既棺,并且釋放所有持有對(duì)象的lock鎖讽挟,直到notify()/notifyAll()被喚醒后放到鎖定池(lock blocked pool ),釋放同步鎖使線 程回到可運(yùn)行狀態(tài)(Runnable)丸冕。
- sleep():使一個(gè)線程處于睡眠狀態(tài)耽梅,是一個(gè)靜態(tài)方法,調(diào)用此方法要捕捉Interrupted異常胖烛,醒來(lái)后進(jìn)入runnable狀態(tài)眼姐,等待JVM調(diào)度诅迷。
- notify():使一個(gè)等待狀態(tài)的線程喚醒,注意并不能確切喚醒等待狀態(tài)線程众旗,是由JVM決定且不按優(yōu)先級(jí)罢杉。
- notifyAll():使所有等待狀態(tài)的線程喚醒,注意并不是給所有線程上鎖贡歧,而是讓它們競(jìng)爭(zhēng)滩租。
- join():使一個(gè)線程中斷,IO完成會(huì)回到Runnable狀態(tài)艘款,等待JVM的調(diào)度。
- Synchronized():使Running狀態(tài)的線程加同步鎖使其進(jìn)入(lock blocked pool ),同步鎖被釋放進(jìn)入可運(yùn)行狀態(tài)(Runnable)沃琅。
如何實(shí)現(xiàn)線程同步
1. Synchronized方法
當(dāng)用此關(guān)鍵字修飾方法時(shí)哗咆, 內(nèi)置鎖會(huì)保護(hù)整個(gè)方法。在調(diào)用該方法前益眉,需要獲得內(nèi)置鎖晌柬,否則就處于阻塞狀態(tài)。注: synchronized關(guān)鍵字也可以修飾靜態(tài)方法郭脂,此時(shí)如果調(diào)用該靜態(tài)方法年碘,將會(huì)鎖住整個(gè)類。
- 同步方法:給一個(gè)方法增加synchronized修飾符之后就可以使它成為同步方法展鸡,這個(gè)方法可以是靜態(tài)方法和非靜態(tài)方法屿衅,但是不能是抽象類的抽象方法,也不能是接口中的接口方法莹弊。當(dāng)任意一個(gè)線程進(jìn)入到一個(gè)對(duì)象的任意一個(gè)同步方法時(shí)涤久,這個(gè)對(duì)象的所有同步方法都被鎖定了,在此期間忍弛,其他任何線程都不能訪問這個(gè)對(duì)象的任意一個(gè)同步方法响迂,直到這個(gè)線程執(zhí)行完它所調(diào)用的同步方法并從中退出,從而導(dǎo)致它釋放了該對(duì)象的同步鎖之后细疚。在一個(gè)對(duì)象被某個(gè)線程鎖定之后蔗彤,其他線程是可以訪問這個(gè)對(duì)象的所有非同步方法的。
- 同步塊:同步塊是通過鎖定一個(gè)指定的對(duì)象疯兼,來(lái)對(duì)同步塊中包含的代碼進(jìn)行同步然遏;而同步方法是對(duì)這個(gè)方法塊里的代碼進(jìn)行同步,而這種情況下鎖定的對(duì)象就是同步方法所屬的主體對(duì)象自身吧彪。如果這個(gè)方法是靜態(tài)同步方法呢啦鸣?那么線程鎖定的就不是這個(gè)類的對(duì)象了,也不是這個(gè)類自身来氧,而是這個(gè)類對(duì)應(yīng)的java.lang.Class類型的對(duì)象诫给。同步方法和同步塊之間的相互制約只限于同一個(gè)對(duì)象之間香拉,所以靜態(tài)同步方法只受它所屬類的其它靜態(tài)同步方法的制約,而跟這個(gè)類的實(shí)例(對(duì)象)沒有關(guān)系中狂。
如果一個(gè)對(duì)象既有同步方法凫碌,又有同步塊,那么當(dāng)其中任意一個(gè)同步方法或者同步塊被某個(gè)線程執(zhí)行時(shí)胃榕,這個(gè)對(duì)象就被鎖定了盛险,其他線程無(wú)法在此時(shí)訪問這個(gè)對(duì)象的同步方法,也不能執(zhí)行同步塊勋又。synchronized 關(guān)鍵字用于保護(hù)共享數(shù)據(jù)苦掘。
2. 使用特殊域變量(volatile)實(shí)現(xiàn)線程同步
volatile關(guān)鍵字為域變量的訪問提供了一種免鎖機(jī)制,相當(dāng)于告訴虛擬機(jī)該域可能會(huì)被其他線程更新楔壤,因此每次使用該域就要重新計(jì)算鹤啡,而不是使用寄存器中的值,不能用來(lái)修飾final類型的變量
3. 使用重入鎖ReentrantLock類實(shí)現(xiàn)線程同步
ReentrantLock類是可重入蹲嚣、互斥递瑰、實(shí)現(xiàn)了Lock接口的鎖,方法:
ReentrantLock() : 創(chuàng)建一個(gè)ReentrantLock實(shí)例
lock() : 獲得鎖
unlock() : 釋放鎖隙畜,通常在finally代碼釋放鎖
private Lock lock = new ReentrantLock();
public void save(int money) {
lock.lock();
try{
account += money;
}finally{
lock.unlock();
}
}
4. 使用ThreadLocal局部變量實(shí)現(xiàn)線程同步
如果使用ThreadLocal管理變量抖部,則每一個(gè)使用該變量的線程都獲得該變量的副本,副本之間相互獨(dú)立议惰,這樣每一個(gè)線程都可以隨意修改自己的變量副本慎颗,而不會(huì)對(duì)其他線程產(chǎn)生影響。
private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue(){
return 100;
}
};
ThreadLocal與同步機(jī)制都是為了解決多線程中相同變量的訪問沖突問題言询,前者采用以”空間換時(shí)間”的方法哗总,后者采用以”時(shí)間換空間”的方式
5. 使用阻塞隊(duì)列LinkedBlockingQueue實(shí)現(xiàn)線程同步
LinkedBlockingQueue是一個(gè)基于已連接節(jié)點(diǎn)的,范圍任意的blocking queue倍试。 隊(duì)列是先進(jìn)先出的順序(FIFO
LinkedBlockingQueue 類常用方法:
LinkedBlockingQueue() : 創(chuàng)建一個(gè)容量為Integer.MAX_VALUE 的 LinkedBlockingQueue
put(E e) : 在隊(duì)尾添加一個(gè)元素讯屈,如果隊(duì)列滿則阻塞
size() : 返回隊(duì)列中的元素個(gè)數(shù)
take() : 移除并返回隊(duì)頭元素,如果隊(duì)列空則阻塞
代碼實(shí)例: 實(shí)現(xiàn)商家生產(chǎn)商品和買賣商品的同步
當(dāng)隊(duì)列滿時(shí):
add()方法會(huì)拋出異常
offer()方法返回false
put()方法會(huì)阻塞
6. 使用原子變量AtomicXxx實(shí)現(xiàn)線程同步
Xxx 可以是 String ,Integer等
原子操作就是指將讀取變量值县习、修改變量值涮母、保存變量值看成一個(gè)整體來(lái)操作即-這幾種行為要么同時(shí)完成,要么都不完成躁愿。
AtomicInteger類常用方法:
AtomicInteger(int initialValue) : 創(chuàng)建具有給定初始值的新的AtomicInteger
addAddGet(int dalta) : 以原子方式將給定值與當(dāng)前值相加
get() : 獲取當(dāng)前值
原子操作主要有:
對(duì)于引用變量和大多數(shù)原始變量(long和double除外)的讀寫操作叛本;
對(duì)于所有使用volatile修飾的變量(包括long和double)的讀寫操作。
7. 使用線程池進(jìn)行管理及優(yōu)化
(1). Android HandlerThread
public class HandlerThreadActivity extends AppCompatActivity{
private HandlerThread mCheckMsgThread;
private Handler mCheckMsgHandler;
//與UI線程管理的handler
private Handler mHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread_handler);
//創(chuàng)建后臺(tái)線程
initBackThread();
}
private void initBackThread()
{
mCheckMsgThread = new HandlerThread("check-message-coming");
mCheckMsgThread.start();
mCheckMsgHandler = new Handler(mCheckMsgThread.getLooper())
{
@Override
public void handleMessage(Message msg)
{
checkForUpdate();
if (isUpdateInfo)
{
mCheckMsgHandler.sendEmptyMessageDelayed(MSG_UPDATE_INFO, 1000);
}
}
};
}
/**
* 模擬從服務(wù)器解析數(shù)據(jù)
*/
private void checkForUpdate()
{
try
{
//模擬耗時(shí)
Thread.sleep(1000);
mHandler.post(new Runnable()
{
@Override
public void run()
{
String result = "實(shí)時(shí)更新中彤钟,當(dāng)前大盤指數(shù):<font color='red'>%d</font>";
result = String.format(result, (int) (Math.random() * 3000 + 1000));
mTvServiceInfo.setText(Html.fromHtml(result));
}
});
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
@Override
protected void onDestroy()
{
super.onDestroy();
//釋放資源
mCheckMsgThread.quit();
}
}
HandlerThread 的源碼
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
Looper.loop()的核心代碼:
while (true) {
Message msg = queue.next(); // might block
if (msg != null) {
if (msg.target == null) {
// No target is a magic identifier for the quit message.
return;
}
long wallStart = 0;
long threadStart = 0;
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
wallStart = SystemClock.currentTimeMicro();
threadStart = SystemClock.currentThreadTimeMicro();
}
msg.target.dispatchMessage(msg);
public void quit() {
Message msg = Message.obtain();
// NOTE: By enqueueing directly into the message queue, the
// message is left with a null target. This is how we know it is
// a quit message.
mQueue.enqueueMessage(msg, 0);
}
是一個(gè)無(wú)限循環(huán)来候,退出循環(huán)的條件是:msg.target == null;
也就是說(shuō),如果我們向此looper的MessageQueue發(fā)送一個(gè)target為null的message逸雹,就可以停止這個(gè)線程的遠(yuǎn)行营搅。
停止HandlerThread的方法就是使用quit方法云挟,具體調(diào)用形式如下:
mHandlerThread.getLooper().quit();
(2). 線程池管理
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
}).start();
- 這樣new出來(lái)的匿名對(duì)象會(huì)存在一些問題:
由于是匿名的,無(wú)法對(duì)它進(jìn)行管理如果需要多次執(zhí)行這個(gè)操作就new多次转质,可能創(chuàng)建多個(gè)园欣,占用系統(tǒng)資源無(wú)法執(zhí)行更多的操作 - 使用線程池的好處:
可以重復(fù)利用存在的線程,減少系統(tǒng)的開銷休蟹;利用線程池可以執(zhí)行定時(shí)砂代、并發(fā)數(shù)的控制 - 線程池的作用:
線程池作用就是限制系統(tǒng)中執(zhí)行線程的數(shù)量哪工。
原理:根據(jù)系統(tǒng)的環(huán)境情況字柠,可以自動(dòng)或手動(dòng)設(shè)置線程數(shù)量孽鸡,達(dá)到運(yùn)行的最佳效果;少了浪費(fèi)了系統(tǒng)資源盈魁,多了造成系統(tǒng)擁擠效率不高翔怎。用線程池控制線程數(shù)量,其他線程排隊(duì)等候备埃。一個(gè)任務(wù)執(zhí)行完畢姓惑,再?gòu)年?duì)列的中取最前面的任務(wù)開始執(zhí)行褐奴。若隊(duì)列中沒有等待進(jìn)程按脚,線程池的這一資源處于等待。當(dāng)一個(gè)新任務(wù)需要運(yùn)行時(shí)敦冬,如果線程池中有等待的工作線程辅搬,就可以開始運(yùn)行了;否則進(jìn)入等待隊(duì)列脖旱。
為什么要用線程池:
1.減少了創(chuàng)建和銷毀線程的次數(shù)堪遂,每個(gè)工作線程都可以被重復(fù)利用,可執(zhí)行多個(gè)任務(wù)萌庆。
2.可以根據(jù)系統(tǒng)的承受能力溶褪,調(diào)整線程池中工作線線程的數(shù)目,防止因?yàn)橄倪^多的內(nèi)存践险,而把服務(wù)器癱瘓(每個(gè)線程需要大約
1MB內(nèi)存猿妈,線程開的越多,消耗的內(nèi)存也就越大巍虫,最后死機(jī))彭则。
Java通過Executors提供四種線程池
- newCachedThreadPool創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過處理需要占遥,可靈活回收空閑線程俯抖,若無(wú)可回收,則新建線程瓦胎。
- newFixedThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池芬萍,可控制線程最大并發(fā)數(shù)尤揣,超出的線程會(huì)在隊(duì)列中等待。
- newScheduledThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池担忧,支持定時(shí)及周期性任務(wù)執(zhí)行芹缔。
- newSingleThreadExecutor 創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來(lái)執(zhí)行任務(wù)瓶盛,保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行最欠。
java線程池的使用參考鏈接:
https://www.cnblogs.com/dolphin0520/p/3932921.html