
一、Android中的Thread
定義:線程,可以看作是進(jìn)程的一個實體,是CPU調(diào)度和分派的基本單位,它是比進(jìn)程更小的能獨立運行的基本單位。
1.1 Thread主要函數(shù)
| 函數(shù)名 | 作用 |
|---|---|
| run() | 線程運行時所執(zhí)行的代碼 |
| start() | 啟動線程 |
| sleep()/sleep(long millis) | 線程休眠,進(jìn)入阻塞狀態(tài),sleep方法不會釋放鎖(其它線程不會進(jìn)入synchronized方法體或方法塊,不釋放鎖需要try/catch) |
| yield() | 線程交出CPU,但是不會阻塞而是重置為就緒狀態(tài),不會釋放鎖 |
| join()/join(long millis)/join(long millis,int nanoseconds) | 線程插隊,當(dāng)該子線程執(zhí)行完畢后接著執(zhí)行其它 |
| wait() | 進(jìn)入阻塞狀態(tài),釋放鎖(其它線程可以進(jìn)入synchronized方法體或方法塊,釋放鎖不需要try/catch) |
| interrupt() | 中斷線程,注意只能中斷阻塞狀態(tài)的線程 |
| getId() | 獲取當(dāng)前線程的id |
| getName()/setName() | 獲取和設(shè)置線程的name |
| getPriority()/setPriority() | 獲取和設(shè)置線程的優(yōu)先級,范圍1-10,默認(rèn)是5 |
| setDaemon()/isDaemo() | 設(shè)置和獲取是否守護(hù)線程 |
| currentThread() | 靜態(tài)函數(shù)獲取當(dāng)前線程 |
1.2 Thread的幾種狀態(tài)
- 新建狀態(tài)(new):實例化之后進(jìn)入該狀態(tài);
- 就緒狀態(tài)(Runnable):線程調(diào)用start()之后就緒狀態(tài)等待cpu執(zhí)行,注意這時只是表示可以運行并不代表已經(jīng)運行;
- 運行狀態(tài)(Running):線程獲得cpu的執(zhí)行,開始執(zhí)行run()方法的代碼;
- 阻塞狀態(tài)(Blocked):線程由于各種原因進(jìn)入阻塞狀態(tài):join()、sleep()、wait()、等待觸發(fā)條件、等待由別的線程占用的鎖;
- 死亡狀態(tài)(Dead):線程運行完畢或異常退出,可使用isAlive()獲取狀態(tài)。
二、Android中Thread的使用
這里寫出三種使用方式:
1. 繼承Thread,重寫run()方法。
使用時直接new并且start()。(不知道什么時候run()方法變成重寫了,以前可是沒有 @Override)
public class MyThread extends Thread{
@Override
public void run() {
super.run();
// do something
}
}
// Thread使用
public void goThread(){
new MyThread().start();
}
2. 實現(xiàn)Runnable,重寫run()方法來執(zhí)行任務(wù)。
public class MyRunnable implements Runnable{
@Override
public void run() {
// do something
}
}
new Thread(new MyRunnable()).start();
另一種啟動方式
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
3. 通過Handler啟動線程。
首先定義好Handler和Runnable :
private int count = 0;
private Handler mHandler = new Handler();
private Runnable runnable = new Runnable() {
@Override
public void run() {
Log.i("download",Thread.currentThread().getName()+":"+count);
count ++;
mHandler.postDelayed(runnable,1000); // 執(zhí)行后延遲1000毫秒再次執(zhí)行,count已++
}
};
然后使用mHandler的post()方法執(zhí)行線程。當(dāng)上方的Runnable執(zhí)行后里面定義了mHandler.postDelayed(runnable,1000);開啟延遲1000毫秒后再次執(zhí)行。
findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHandler.post(runnable); // handler運行runnable
}
});
三、Thread使用其它問題
3.1 如何終止線程
1. 使用boolean變量作為標(biāo)記
如下,如果變量stop為true則跳出run()方法。關(guān)鍵字volatile表示這個變量隨時都有可能發(fā)生變化,主要是表示同步,也就是同一時刻只能由一個線程來修改該變量的值。
public class ThreadStopTest extends Thread {
public volatile boolean stop = false;
@Override
public void run() {
super.run();
while (!stop){
// thread runing
}
}
}
2. 使用interrupt()
該方法可以用來中斷進(jìn)程,然后就可以終止線程,使用該方法分兩種情況,線程正常運行狀態(tài)和阻塞狀態(tài)。
(1) 線程正常運行狀態(tài)
要注意的是interrupt()方法只是中斷線程而不是結(jié)束線程,在線程正常運行狀態(tài)下使用該方法是不能結(jié)束線程的,正確的做法是判斷線程是否被中斷來決定是否執(zhí)行代碼。這種方法類似于自定義標(biāo)記??匆幌率纠?/p>
...
// 定義開始和結(jié)束線程的方法,與按鈕綁定
public void goThread(){
if(null == myThread){
myThread = new MyThread();
}
myThread.start();
}
private void stopThread() {
if(null != myThread && myThread.isAlive()){
myThread.interrupt();
myThread = null;
}
}
public class MyThread extends Thread{
@Override
public void run() {
super.run();
int i = 0;
// 判斷狀態(tài),如果被打斷則跳出并將線程置空
while (!isInterrupted()){
i++;
Log.i("thread",Thread.currentThread().getName()+":Running()_Count:"+i);
}
}
}
(2) 線程阻塞狀態(tài)
先看下面的例子,重新寫了一個SleepThread,開啟和停止的代碼省略。這個線程里每間隔1s循環(huán)增加i的值并打印。
public class SleepThread extends Thread{
@Override
public void run() {
super.run();
int i = 0;
while(true){
try {
i++;
Thread.sleep(1000);
Log.i("thread",Thread.currentThread().getName()+":Running()_Count:"+i);
} catch (InterruptedException e) {
e.printStackTrace();
Log.i("thread",Thread.currentThread().getName()+"異常拋出,停止線程");
break;
}
}
}
}
當(dāng)我開始和終止該線程后Log如下:
10-14 19:46:57.562 32024-32227/com.sky.androidthreadapp I/thread: Thread-16142:Running()_Count:1
10-14 19:46:58.562 32024-32227/com.sky.androidthreadapp I/thread: Thread-16142:Running()_Count:2
...
10-14 19:47:01.564 32024-32227/com.sky.androidthreadapp I/thread: Thread-16142:Running()_Count:5
10-14 19:47:01.931 32024-32227/com.sky.androidthreadapp I/thread: Thread-16142異常拋出,停止線程
可以看到調(diào)用interrupt()方法后拋出InterruptedException異常,同時break跳出循環(huán)達(dá)到停止跑代碼的作用。原理是當(dāng)線程進(jìn)入阻塞狀態(tài)時,調(diào)用interrupt()方法會拋出異常,利用這個異常來跳出循環(huán)。
(3) 兩種狀態(tài)一起處理
此外,把兩種狀態(tài)的處理結(jié)合在一起是比較好的,這樣既可以及時判斷線程狀態(tài)又可以捕獲異常來跳出循環(huán)。
public class SleepThread extends Thread{
@Override
public void run() {
super.run();
int i = 0;
while(!isInterrupted()){ // 判斷線程是否被打斷
try {
i++;
Thread.sleep(1000);
Log.i("thread",Thread.currentThread().getName()+":Running()_Count:"+i);
} catch (InterruptedException e) {
e.printStackTrace();
Log.i("thread",Thread.currentThread().getName()+"異常拋出,停止線程");
break;// 拋出異常跳出循環(huán)
}
}
}
}
3. 使用stop()方法終止線程
恕我直言,這個方法就忽略吧,這個相當(dāng)于強制關(guān)機,萬一數(shù)據(jù)丟失得不償失。
3.2 線程安全與線程同步
1. 什么是線程安全問題
簡單地說,線程安全問題是指多個線程訪問同一代碼或數(shù)據(jù),造成結(jié)果和數(shù)據(jù)的錯亂或與期望的結(jié)果不同所產(chǎn)生的問題。
2. 如何解決線程安全問題
基本上所有的并發(fā)模式在解決線程安全問題的問題上,都采用“序列化訪問臨界資源”的方案,即在同一時刻只能有一個線程訪問臨界資源(多個線程可能同時訪問的數(shù)據(jù)或資源),也稱同步互斥訪問。
(1) synchronized 關(guān)鍵字,保證同時刻只有一個線程進(jìn)入該方法或者代碼塊,使用方式(Tips:java中有很多方式來實現(xiàn)線程同步,我們常用的synchronized是效率最低的...但是它方便啊):
- 線程
run()方法中要執(zhí)行的代碼方法添加synchronized關(guān)鍵字,注意要添加在方法的返回值前。
int count = 100;
private synchronized void count() {
if (count > 0) {
Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
} else {
isRunning = false;
}
}
- 同步代碼塊的形式使用。
private void count() {
synchronized (this) {
if (count > 0) {
Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
} else {
isRunning = false;
}
}
}
(2) 特殊域變量volatile修飾變量:告訴虛擬機該變量隨時可能更新,因此使用時每次都會重新計算,而不是使用寄存器的值。volatile不會提供任何原子操作,它也不能用來修飾final類型的變量。(不能完全保證線程安全)
private volatile int count = 1000;
(3)使用重入鎖實現(xiàn)線程同步。
- ReentrantLock() : 創(chuàng)建一個ReentrantLock實例
- lock() :獲得鎖
- unlock() : 釋放鎖
private void count() {
lock.lock();
if (count > 0) {
Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
} else {
isRunning = false;
}
lock.unlock();
}
(4)ThreadLocal管理變量。如果一個變量使用ThreadLocal進(jìn)行管理,每一個使用該變量的線程都會獲得該變量的副本,副本之間相互獨立,所以每個線程都可以修改變量而不會對其它線程造成影響。
private static ThreadLocal<Integer> number = new ThreadLocal<Integer>(){
// 重寫方法,設(shè)置默認(rèn)值
@Override
protected Integer initialValue() {
return 1;
}
// 自定義方法設(shè)置變量值
public void saveNumber(int newNumber){
number.set(number.get() + newNumber);
}
// 自定義方法獲取變量值
public int getNumber(){
return number.get();
}
};
相關(guān)文章:
Android多線程:理解和簡單使用總結(jié)