引
隨著開發專案中業務功能的增加,必然某些功能會涉及到執行緒以及併發程式設計的知識點。筆者就在現在的公司接觸到了很多軟硬體結合和socket通訊的專案了,很多的功能運用到了串列埠通訊程式設計,串列埠通訊程式設計的安卓端就是基於執行緒的方式和硬體保持通訊的。
關於Java執行緒,先了解一下Java執行緒的生命週期和物種基本狀態,先上一張經典的圖
上圖也比較直觀的繪製了關於Java執行緒的生命週期同時也囊括了Java執行緒的重點知識點。
Java執行緒的五種狀態:
新建狀態(New):
當執行緒物件建立後,執行緒即進入新建狀態,如:Thread t = new MyThread();
就緒狀態(Runnable):
當執行緒物件的start()方法(t.start();)呼叫時,此時執行緒進入就緒狀態。處於就緒狀態的執行緒只能說明執行緒此時已經做好準備,隨時等待CPU的排程並不是說 t.start(): 後執行緒就會立馬執行;
執行狀態(Running):
當CPU開始排程已經處於就緒狀態的執行緒時,此時執行緒才真正的開始他的工作,即進入了執行狀態。注意(敲黑板!):就緒狀態是進入到執行狀態的唯一入口。也就是說執行緒想進入執行狀態,那執行緒就必須先處於就緒狀態。
阻塞狀態(Blocked):
處於執行狀態的執行緒處於某種原因呢,暫時放棄了對CPU的使用權,停止了執行,此時也就進入了阻塞狀態,知道執行緒再次進入到就緒狀態,才有機會被CPU呼叫進入到執行狀態。而根據造成阻塞的原因不同,分為了一下三種阻塞:
-
等待阻塞:執行狀態的執行緒執行到了wate()方法,使執行緒進入了等待阻塞狀態。
-
同步阻塞:執行緒在獲取synchronized同步鎖失敗(因為鎖被其他執行緒佔用),他就會進入同步阻塞狀態。
-
其他阻塞:通過執行緒的sleep()和join()或發出了I/O請求,執行緒進入到了阻塞狀態。當sleep()超時、join()等待執行緒終止或超時、I/O處理完畢時,執行緒就會再次進入就緒狀態。
死亡狀態(Dead):
執行緒執行完了或者在執行中因異常退出了run()方法,該執行緒就走完了他的一生了。
Java多執行緒的建立和啟動
Java執行緒有三種常見的基本建立方式
繼承Thread類,重寫其run()方法
class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
for (i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}複製程式碼
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Thread mThread1 = new MyThread();// 建立一個新的執行緒myThread1此執行緒進入新建狀態
Thread mThread2 = new MyThread();// 建立一個新的執行緒myThread2此執行緒進入新建狀態
mThread1.start(); // 呼叫start()方法使得執行緒進入就緒狀態
mThread2.start(); // 呼叫start()方法使得執行緒進入就緒狀態
}
}
}
}複製程式碼
實現Runnable介面,並重寫實現其run()方法
不必想太多,這個run()方法同樣是我們的執行緒執行體。重寫完run方法後建立介面例項,並把該例項作為建立執行緒的target建立執行緒。沒看懂沒關係,直接上程式碼。
class MyRunnable implements Runnable {
private int i = 0;
@Override
public void run() {
for (i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}複製程式碼
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Runnable myRunnable = new MyRunnable(); // 建立一個Runnable實現類的物件
Thread thread1 = new Thread(myRunnable); // 將myRunnable作為Thread target建立新的執行緒
Thread thread2 = new Thread(myRunnable);
thread1.start(); // 呼叫start()方法使得執行緒進入就緒狀態
thread2.start();
}
}
}
}複製程式碼
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Runnable myRunnable = new MyRunnable();
Thread thread = new MyThread(myRunnable);
thread.start();
}
}
}
}
class MyRunnable implements Runnable {
private int i = 0;
@Override
public void run() {
System.out.println("in MyRunnable run");
for (i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
class MyThread extends Thread {
private int i = 0;
public MyThread(Runnable runnable){
super(runnable);
}
@Override
public void run() {
System.out.println("in MyThread run");
for (i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}複製程式碼
@Override
public void run() {
if (target != null) {
target.run();
}
}複製程式碼
用Callable和Future介面建立執行緒
具體是建立Callable介面的實現類,並實現call()方法。並使用FutureTask類來包裝Callable實現類的物件,且以此FutureTask物件作為Thread物件的target來建立執行緒。有點繞~直接看程式碼吧~
public class ThreadTest {
public static void main(String[] args) {
Callable<Integer> myCallable = new MyCallable(); // 建立MyCallable物件
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask來包裝MyCallable物件
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Thread thread = new Thread(ft); //FutureTask物件作為Thread物件的target建立新的執行緒
thread.start(); //執行緒進入到就緒狀態
}
}
System.out.println("主執行緒for迴圈執行完畢..");
try {
int sum = ft.get(); //取得新建立的新執行緒中的call()方法返回的結果
System.out.println("sum = " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int i = 0;
// 與run()方法不同的是,call()方法具有返回值
@Override
public Integer call() {
int sum = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
}複製程式碼
public class FutureTask<V> implements RunnableFuture<V> {
/*
....
*/
}複製程式碼
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}複製程式碼
這樣我們就能清楚的看得出他們之間的關係。發現FutureTask類實際上是同時實現了Runnable和Future介面,使得他有了雙重特性了,通過Runnable特性,可以作為Thread物件的target,而Future特性,使得其可以取得新建立執行緒中的call()方法的返回值。
原因在於通過ft.get()方法獲取子執行緒call()方法的返回值時,當子執行緒此方法還未執行完畢,ft.get()方法會一直阻塞,直到call()方法執行完畢才能取到返回值。
上述主要講解了三種常見的執行緒建立方式,對於執行緒的啟動而言,都是呼叫執行緒物件的start()方法,需要特別注意的是:不能對同一執行緒物件兩次呼叫start()方法。
執行緒的開啟暫時就講這麼多了,後面的文章還會繼續講述Java執行緒之如何優雅的關閉執行緒。
That's all Thank you~
----- End -----
更多好文
請掃描下面二維碼
歡迎關注~