參考鏈接:
多線程的三種實現(xiàn)方式
- 繼承Thread類,重寫run函數(shù)方法
class MyThread extends Thread {
@Override
public void run() {
super.run();
}
}
- 實現(xiàn)Runnable接口,重寫run函數(shù)方法
class MyThread implements Runnable{
@Override
public void run() {
}
}
- 實現(xiàn)Callable接口,重寫call函數(shù)方法,ExecutorService、Callable、Future實現(xiàn)有返回結(jié)果的多線程
class MyThread <T> implements Callable<T> {
@Override
public T call() {
return null;
}
}
- Callable和Runnable的不同之處:
①Callable規(guī)定的方法是call(),而Runnable規(guī)定的方法是run().
②Callable的任務執(zhí)行后可返回值,而Runnable的任務是不能返回值的
③call()方法可拋出異常,而run()方法是不能拋出異常的。
④運行Callable任務可拿到一個Future對象,F(xiàn)uture表示異步計算的結(jié)果。通過Future對象可了解任務執(zhí)行情況,可取消任務的執(zhí)行。
Callable和Runable詳解見鏈接:
Android(Java)之多線程結(jié)果返回——Future 、FutureTask、Callable、Runnable
如何停止一個線程
- 創(chuàng)建一個標識(flag),當線程完成你所需要的工作后,可以將標識設置為退出標識
- 使用Thread的interrupt()方法和nterrupted()方法,兩者配合break退出循環(huán),或者return來停止線程,有點類似標識(flag)
- 可以使用try-catch語句,在try-catch語句中拋出異常,強行停止線程進入catch語句,這種方法可以將錯誤向上拋,使線程停止事件得以傳播
Thread線程狀態(tài)和相關(guān)方法

- 可運行(runnable):線程對象創(chuàng)建后,線程調(diào)用start()方法。該狀態(tài)的線程位于可運行線程池中,等待被線程調(diào)度選中,獲取cpu的使用權(quán)
- 運行(running):可運行狀態(tài)(runnable)的線程獲得了cpu使用權(quán),執(zhí)行程序代碼
- 阻塞(block):線程因為某種原因放棄了cpu使用權(quán),即讓出了cpu使用權(quán),暫時停止運行,直到線程進入可運行(runnable)狀態(tài),才有機會再次獲得cpu使用權(quán)轉(zhuǎn)到運行(running)狀態(tài)。阻塞的情況分三種:
(1)等待阻塞:運行(running)的線程執(zhí)行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue)中
(2)同步阻塞:運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池(lock pool)中
其他阻塞:運行(running)的線程執(zhí)行Thread.sleep(long ms)或t.join()方法,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)。當sleep()狀態(tài)超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉(zhuǎn)入可運行(runnable)狀態(tài)
(3)死亡(dead):線程run()、main() 方法執(zhí)行結(jié)束,或者因異常退出了run()方法,則該線程結(jié)束生命周期,且死亡的線程不可再次復生
sleep和wait的區(qū)別
- sleep()是Thread類的方法,wait()是Object類中的方法;
- 調(diào)用sleep(),在指定的時間里,暫停程序的執(zhí)行,讓出CPU給其他線程,當超過時間的限制后,又重新恢復到運行狀態(tài),在這個過程中,線程不會釋放對象鎖;調(diào)用wait()時,線程會釋放對象鎖,進入此對象的等待鎖池中,只有此對象調(diào)用notify()時,線程進入運行狀態(tài)
方法介紹:
- wait() :使一個線程處于等待狀態(tài),并且釋放所有持有對象的lock鎖,直到notify()/notifyAll()被喚醒后放到鎖定池(lock blocked pool ),釋放同步鎖使線 程回到可運行狀態(tài)(Runnable)。
- sleep():使一個線程處于睡眠狀態(tài),是一個靜態(tài)方法,調(diào)用此方法要捕捉Interrupted異常,醒來后進入runnable狀態(tài),等待JVM調(diào)度。
- notify():使一個等待狀態(tài)的線程喚醒,注意并不能確切喚醒等待狀態(tài)線程,是由JVM決定且不按優(yōu)先級。
- notifyAll():使所有等待狀態(tài)的線程喚醒,注意并不是給所有線程上鎖,而是讓它們競爭。
- join():使一個線程中斷,IO完成會回到Runnable狀態(tài),等待JVM的調(diào)度。
- Synchronized():使Running狀態(tài)的線程加同步鎖使其進入(lock blocked pool ),同步鎖被釋放進入可運行狀態(tài)(Runnable)。
如何實現(xiàn)線程同步
1. Synchronized方法
當用此關(guān)鍵字修飾方法時, 內(nèi)置鎖會保護整個方法。在調(diào)用該方法前,需要獲得內(nèi)置鎖,否則就處于阻塞狀態(tài)。注: synchronized關(guān)鍵字也可以修飾靜態(tài)方法,此時如果調(diào)用該靜態(tài)方法,將會鎖住整個類。
- 同步方法:給一個方法增加synchronized修飾符之后就可以使它成為同步方法,這個方法可以是靜態(tài)方法和非靜態(tài)方法,但是不能是抽象類的抽象方法,也不能是接口中的接口方法。當任意一個線程進入到一個對象的任意一個同步方法時,這個對象的所有同步方法都被鎖定了,在此期間,其他任何線程都不能訪問這個對象的任意一個同步方法,直到這個線程執(zhí)行完它所調(diào)用的同步方法并從中退出,從而導致它釋放了該對象的同步鎖之后。在一個對象被某個線程鎖定之后,其他線程是可以訪問這個對象的所有非同步方法的。
- 同步塊:同步塊是通過鎖定一個指定的對象,來對同步塊中包含的代碼進行同步;而同步方法是對這個方法塊里的代碼進行同步,而這種情況下鎖定的對象就是同步方法所屬的主體對象自身。如果這個方法是靜態(tài)同步方法呢?那么線程鎖定的就不是這個類的對象了,也不是這個類自身,而是這個類對應的java.lang.Class類型的對象。同步方法和同步塊之間的相互制約只限于同一個對象之間,所以靜態(tài)同步方法只受它所屬類的其它靜態(tài)同步方法的制約,而跟這個類的實例(對象)沒有關(guān)系。
如果一個對象既有同步方法,又有同步塊,那么當其中任意一個同步方法或者同步塊被某個線程執(zhí)行時,這個對象就被鎖定了,其他線程無法在此時訪問這個對象的同步方法,也不能執(zhí)行同步塊。synchronized 關(guān)鍵字用于保護共享數(shù)據(jù)。
2. 使用特殊域變量(volatile)實現(xiàn)線程同步
volatile關(guān)鍵字為域變量的訪問提供了一種免鎖機制,相當于告訴虛擬機該域可能會被其他線程更新,因此每次使用該域就要重新計算,而不是使用寄存器中的值,不能用來修飾final類型的變量
3. 使用重入鎖ReentrantLock類實現(xiàn)線程同步
ReentrantLock類是可重入、互斥、實現(xiàn)了Lock接口的鎖,方法:
ReentrantLock() : 創(chuàng)建一個ReentrantLock實例
lock() : 獲得鎖
unlock() : 釋放鎖,通常在finally代碼釋放鎖
private Lock lock = new ReentrantLock();
public void save(int money) {
lock.lock();
try{
account += money;
}finally{
lock.unlock();
}
}
4. 使用ThreadLocal局部變量實現(xiàn)線程同步
如果使用ThreadLocal管理變量,則每一個使用該變量的線程都獲得該變量的副本,副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變量副本,而不會對其他線程產(chǎn)生影響。
private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue(){
return 100;
}
};
ThreadLocal與同步機制都是為了解決多線程中相同變量的訪問沖突問題,前者采用以”空間換時間”的方法,后者采用以”時間換空間”的方式
5. 使用阻塞隊列LinkedBlockingQueue實現(xiàn)線程同步
LinkedBlockingQueue是一個基于已連接節(jié)點的,范圍任意的blocking queue。 隊列是先進先出的順序(FIFO
LinkedBlockingQueue 類常用方法:
LinkedBlockingQueue() : 創(chuàng)建一個容量為Integer.MAX_VALUE 的 LinkedBlockingQueue
put(E e) : 在隊尾添加一個元素,如果隊列滿則阻塞
size() : 返回隊列中的元素個數(shù)
take() : 移除并返回隊頭元素,如果隊列空則阻塞
代碼實例: 實現(xiàn)商家生產(chǎn)商品和買賣商品的同步
當隊列滿時:
add()方法會拋出異常
offer()方法返回false
put()方法會阻塞
6. 使用原子變量AtomicXxx實現(xiàn)線程同步
Xxx 可以是 String ,Integer等
原子操作就是指將讀取變量值、修改變量值、保存變量值看成一個整體來操作即-這幾種行為要么同時完成,要么都不完成。
AtomicInteger類常用方法:
AtomicInteger(int initialValue) : 創(chuàng)建具有給定初始值的新的AtomicInteger
addAddGet(int dalta) : 以原子方式將給定值與當前值相加
get() : 獲取當前值
原子操作主要有:
對于引用變量和大多數(shù)原始變量(long和double除外)的讀寫操作;
對于所有使用volatile修飾的變量(包括long和double)的讀寫操作。
7. 使用線程池進行管理及優(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)建后臺線程
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);
}
}
};
}
/**
* 模擬從服務器解析數(shù)據(jù)
*/
private void checkForUpdate()
{
try
{
//模擬耗時
Thread.sleep(1000);
mHandler.post(new Runnable()
{
@Override
public void run()
{
String result = "實時更新中,當前大盤指數(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);
}
是一個無限循環(huán),退出循環(huán)的條件是:msg.target == null;
也就是說,如果我們向此looper的MessageQueue發(fā)送一個target為null的message,就可以停止這個線程的遠行。
停止HandlerThread的方法就是使用quit方法,具體調(diào)用形式如下:
mHandlerThread.getLooper().quit();
(2). 線程池管理
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
}).start();
- 這樣new出來的匿名對象會存在一些問題:
由于是匿名的,無法對它進行管理如果需要多次執(zhí)行這個操作就new多次,可能創(chuàng)建多個,占用系統(tǒng)資源無法執(zhí)行更多的操作 - 使用線程池的好處:
可以重復利用存在的線程,減少系統(tǒng)的開銷;利用線程池可以執(zhí)行定時、并發(fā)數(shù)的控制 - 線程池的作用:
線程池作用就是限制系統(tǒng)中執(zhí)行線程的數(shù)量。
原理:根據(jù)系統(tǒng)的環(huán)境情況,可以自動或手動設置線程數(shù)量,達到運行的最佳效果;少了浪費了系統(tǒng)資源,多了造成系統(tǒng)擁擠效率不高。用線程池控制線程數(shù)量,其他線程排隊等候。一個任務執(zhí)行完畢,再從隊列的中取最前面的任務開始執(zhí)行。若隊列中沒有等待進程,線程池的這一資源處于等待。當一個新任務需要運行時,如果線程池中有等待的工作線程,就可以開始運行了;否則進入等待隊列。
為什么要用線程池:
1.減少了創(chuàng)建和銷毀線程的次數(shù),每個工作線程都可以被重復利用,可執(zhí)行多個任務。
2.可以根據(jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線線程的數(shù)目,防止因為消耗過多的內(nèi)存,而把服務器癱瘓(每個線程需要大約
1MB內(nèi)存,線程開的越多,消耗的內(nèi)存也就越大,最后死機)。
Java通過Executors提供四種線程池
- newCachedThreadPool創(chuàng)建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
- newFixedThreadPool 創(chuàng)建一個定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊列中等待。
- newScheduledThreadPool 創(chuàng)建一個定長線程池,支持定時及周期性任務執(zhí)行。
- newSingleThreadExecutor 創(chuàng)建一個單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務,保證所有任務按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行。
java線程池的使用參考鏈接:
https://www.cnblogs.com/dolphin0520/p/3932921.html