文章目錄
導讀
本章主要進行創建多線程的三種方式講解,線程狀態的分析。
1. 多線程概述
進程:是一個執行程序,比如迅雷應用程序
多線程:是進程中的執行單元,一個進程中可以有多個線程一起執行,比如迅雷開啓多線程下載多個文件。
java從main方法開始執行,執行該方法的線程稱爲主線程。
JVM啓動時就是多線程,main方法佔一個線程,GC垃圾回收至少佔一個線程,等等。。
多線程可以幫我們提高效率,可以使多個部分代碼同時執行。比如主線程執行代碼,GC線程回收垃圾。
所以說雙核CPU執行更多線程,執行速度更快,快速切換線程,這就是併發,這時內存就是瓶頸,需要更大內存。
2. 多線程的創建
方法一:繼承Thread類
步驟:
- 創建類繼承Thread
- 子類必須重寫run方法,將線程要運行的代碼就放在run方法中
- 創建子類對象調用start方法啓動線程,如果不使用start就沒有開啓新線程,而是順序執行單線程
public class MultipleThread {
public static void main(String[] args) {
SingleThread singleThread = new SingleThread();
//SingleThread singleThread2 = new SingleThread(); //還可以繼續添加線程
singleThread.start();
//singleThread2.start();
//主線程打印
for (int i = 0; i < 60; i++) {
System.out.println("main is running" + i);
}
}
}
//繼承Thread類
class SingleThread extends Thread{
//線程類指定運行方法
@Override
public void run() {
//single線程打印
for (int i = 0; i < 60; i++) {
System.out.println("sing thread running....." + i);
}
}
}
/*
main is running0
main is running1
sing thread running.....0
sing thread running.....1
sing thread running.....2
sing thread running.....3
sing thread running.....4
.....
*/
我們會發現兩個線程交替執行打印
方法二:實現Runnable 接口
- 創建自定義類實現Runnable接口並重寫run方法
- 創建該類對象
- 通過Thread類創建專門的線程對象,並把自定義類對象作爲參數傳入Thread構造方法
- Thread線程類對象調用start方法開啓線程
也就是說創建自定義類表明要運行的代碼,然後創建Thread線程類對象來運行該代碼,該對象只負責運行,而具體運行什麼由自定義類中run方法指定。
public class MultipleThread {
public static void main(String[] args) {
SingleThread st = new SingleThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
System.out.println(Thread.currentThread().getName() + " is running");
}
}
class SingleThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
}
}
實現Runnable創建線程和繼承Thread類區別
當我們繼承Thread類的時候會出現個問題,就是我們的子類可能繼承其他父類。因爲java是單繼承,所以他無法再繼承Thread類來實現多線程。
這個時候Runnable接口就發揮作用了,因爲我們可以通過實現Runnable接口來實現多線程。這也就是接口的優勢,對外提供功能的擴展。
所以創建多線程的最常用方法是調用Runnable接口。
方法三:實現Callable接口
之前我們介紹了兩種創建線程的方法,繼承Thread和實現Runnable接口,JDK5之後我們又添加了一種創建線程池來創建多線程,實現Callable接口。
任務分爲兩種:一種是有返回值的( Callable ),一種是沒有返回值的( Runnable ). Callable與 Future 兩功能是Java在後續版本中爲了適應多併發才加入的,Callable是類似於Runnable的接口,實現Callable接口的類和實現Runnable的類都是可被其他線程執行的任務。
- 無返回值的任務多線程創建是:實現runnable接口的類.使用run方法.
- 有返回值的任務多線程創建是:實現callable接口的類.使用call方法.
實現步驟:
- 創建實現Callable接口的類,創建任務
- 執行任務:創建一個類對象:
Callable<Integer> oneCallable = new SomeCallable<Integer>();
由Callable創建一個FutureTask對象:
FutureTask<Integer> oneTask = new FutureTask<Integer>(oneCallable);
註釋:FutureTask是一個包裝器,它通過接受Callable來創建,它同時實現了Future和Runnable接口。 - 由FutureTask創建Thread對象並開啓:
Thread oneThread = new Thread(oneTask);
oneThread.start();
import java.util.concurrent.*;
// 繼承Callable接口,泛型參數爲要返回的類型
class TaskCallable implements Callable<Integer> {
//重寫call方法類似於run方法執行任務
@Override
public Integer call() throws Exception {
Thread.sleep(3000);
System.out.println("[" + Thread.currentThread().getName() + "] is running");
int result = 0;
for (int i = 0; i < 100; ++i) {
result += i;
}
return result;
}
}
public class CreateThreadWithCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.創建實現了Callable接口的對象
TaskCallable taskCallable = new TaskCallable();
// 2. 新建FutureTask,將線程對象傳入
FutureTask<Integer> futureTask = new FutureTask<>(taskCallable);
// 3. 新建Thread對象把futureTask傳入
Thread thread = new Thread(futureTask);
//4.開啓線程
thread.setName("Task thread");//設置線程名稱
thread.start();//開啓線程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[" + Thread.currentThread().getName() + "] is running");
// 可以調用isDone()判斷任務線程狀態:是否結束
if (!futureTask.isDone()) {
System.out.println("[" + Thread.currentThread().getName() + "]: Task is not done");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int result = 0;
// 因爲有返回值,所以可以調用futureTask.get()方法獲取任務結果,如果任務沒有執行完成則阻塞等待
try {
result = futureTask.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("[" + Thread.currentThread().getName() + "]: Task is done:"+ "result is " + result);
}
}
/*
[main] is running
[main]: Task is not done
[Task thread] is running
[main]: Task is done:result is 4950
*/
使用Callable和Runnable的區別
- Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。
- Callable的call方法可拋出異常,而Runnable的run方法不能拋出異常。
使用Callable和FutureTask的好處
- 可以判斷得知任務線程的運行狀態:
futureTask.isDone()
判斷線程是否還在運行。 - 可以得到返回值:
futureTask.get()
得到任務線程運行的返回值。
3. 線程狀態介紹
-
新建狀態(New):新創建了一個線程對象。
-
運行狀態(Running):運行狀態又分爲運行中和就緒狀態。就緒狀態(ready):該狀態的線程位於可運行線程池中,但是沒有獲得CPU執行權,等待獲取CPU調用,這是CPU自己控制的。當獲取到執行權就開始執行進入運行中狀態。
-
阻塞狀態(Blocked):阻塞狀態是線程因爲某種原因線程主動放棄CPU使用權,暫時停止運行。直到通過其他條件讓線程進入就緒狀態,纔有機會轉到運行狀態。阻塞的情況分三種:
- 等待阻塞(WAITING):運行的線程執行wait()方法,JVM會把該線程放入等待池中。
- 同步阻塞(Blocked):運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池中,比如說兩個線程同時使用一個共享資源,但是這個資源被鎖住了只有一個線程能夠使用,這時另一個線程被阻塞處於等待狀態,等另一個線程釋放鎖時候,他才能繼續執行。
- 超時阻塞(TIME_WAITING):運行的線程執行sleep(long)或join(long)方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。
-
終結狀態:線程執行完了或者因異常退出了run()方法,該線程結束生命週期。
Thread.yield()
方法 暫停當前正在執行的線程對象,yield()只是使當前線程重新回到可執行狀態,所以執行yield()的線程有可能在進入到可執行狀態後馬上又被執行,yield()只能使同優先級或更高優先級的線程有執行的機會。
Object.wait()和Object.wait(long)
:在其他線程調用對象的notify或notifyAll方法前,導致當前線程等待。線程會釋放掉它所佔有的"鎖標誌",從而使別的線程有機會搶佔該鎖。
Thread.Join()
:把指定的線程加入到當前線程,可以將兩個交替執行的線程合併爲順序執行的線程。比如在線程B中調用了線程A的Join()方法,直到線程A執行完畢後,纔會繼續執行線程B。
也就是說進入等待狀態的線程會暫時釋放掉持有的鎖讓鎖池中的其他線程競爭使用。