實際應用中,多線程非常有用,例如,QQ音樂就是一個多線程程序,我們可以一邊聽音樂,一般下載音樂,還可以同時播放MV等非常方便。一個Web服務器通過多線程同時處理多個請求,比如Tomcat就是多線程的。
前言
前段時間推出的JAVA8新特性文章收到大家廣泛關注和好評,非常感謝各位支持,這段時間苦思冥想,決定輸出一波Java多線程技能點,希望可以在大家的工作和面試中有所幫助!本篇文章為多線程系列第一章,主要講解一下幾點:
多線程好處和應用場景
多線程的相關概念和術語
Java線程創建方式
Thread類詳解,線程的常用方法
線程5種狀態和6種狀態,兩種版本解釋
線程狀態之間轉換
Java設計者寫過一個很有影響力的白皮書,用來解釋設計的初衷,并發布了一個簡短的摘要,分為11個術語:
- 簡單性
- 面向對象
- 分布式
- 健壯性
- 安全性
- 體系結構中立
- 可移植性
- 解釋型
- 高性能
- 多線程
- 動態性
其中多線程就是本次要接觸的,白皮書中對多線程的解釋:
多線程可以帶來更好的交互響應和實時行為。
如今,我們非常關注并發性,因為摩爾定律行將完結。我們不再追求更快的處理器,而是著眼于獲得更多的處理器,而且要讓它們一直保持工作。不過,可以看到,大多數編程語言對于這個問題并沒有顯示出足夠的重視。Java在當時很超前。它是第一個支持并發程序設計的主流語言。從白皮書中可以看到,它的出發點稍有些不同。當時,多核處理器還很神秘,而Web編程才剛剛起步,處理器要花很長時間等待服務器響應,需要并發程序設計來確保用戶界面不會"凍住"。并發程序設計絕非易事,不過Java在這方面表現很出色,可以很好地管理這個工作。
在操作系統中有多任務【multitasking】,在同一刻運行多個程序【應用】的能力。例如,在聽音樂的同時可以邊打游戲,邊寫代碼。如今我們的電腦大多都是多核CPU,但是,并發執行的進程【正在執行的應用】數目并不是由CPU數目制約的。操作系統將CPU的時間片分配給每一個進程,給人并行處理的感覺。
相關概念
程序【program】:為了完成特定任務,用某種語言編寫的一組指令的集合。程序就是一堆代碼,一組數據和指令集,是一個靜態的概念。就說我們程序員寫的那玩意。比如:安裝在電腦或者手機上的各種軟件,今日頭條、抖音、懂車帝等,如果一個程序支持多線程,這個程序就是一個多線程程序
進程【Process】:是程序的一次執行過程或者說是正在運行的程序,是一個動態概念,進程存在生命周期,也就是說程序隨著程序的終止而銷毀
線程【Thread】:線程是進程中的實際運作的單位,是進程的一條流水線,是程序的實際執行者,是最小的執行單位。通常在一個進程中可以包含若干個線程,當然一個進程中至少有一個線程。線程是CPU調度和執行的最小單位
CPU時間片:時間片即CPU分配給各個程序的時間,每個進程被分配一個時間段,稱作它的時間片,即該進程允許運行的時間,使各個程序從表面上看是同時進行的,如果在時間片結束時進程還在運行,則CPU將被剝奪并分配給另一個進程。如果進程在時間片結束前阻塞或結束,則CPU當即進行切換。而不會造成CPU資源浪費
并行【parallel】:多個任務同時進行,并行必須有多核才能實現,否則只能是并發,比如:多名學生有問題,同時有多名老師可以輔導解決
串行【serial】:一個程序處理完當前進程,按照順序接著處理下一個進程,一個接著一個進行,比如:多名學生有問題,只有一名老師,需要挨個解決
并發【concurrency】:同一個對象被多個線程同時操作。(這是一種假并行。即一個CPU的情況下,在同一個時間點,CPU只能執行一個代碼,因為切換的很快,所以就有同時執行的錯覺),比如:多名學生有問題,只有一個老師,他一會處理A同學,一會處理B同學,一會處理C同學,頻繁切換,看起來好似在同時處理學生問題
多線程意義
實際應用中,多線程非常有用,例如,QQ音樂就是一個多線程程序,我們可以一邊聽音樂,一般下載音樂,還可以同時播放MV等非常方便。一個Web服務器通過多線程同時處理多個請求,比如Tomcat就是多線程的。
注意:程序會因為引入多線程而變的復雜,多線程同時會帶來一些問題,需要我們解決
多線程應用場景
- 程序需要同時執行兩個或多個任務。
- 程序需要實現一些需要等待的任務時,如用戶輸入、文件讀寫操作、網絡操作、搜索等。
- 需要一些后臺運行的程序時。
多線程多數在瀏覽器、Web服務器、數據庫、各種專用服務器【如游戲服務器】、分布式計算等場景出現。
在使用Java編寫后臺服務時,如果遇到并發較高、需要后臺任務、需要長時間處理大數據等情況都可以創建線程單獨的線程處理這些事項,多線程的目的就在于提高處理速度,減少用戶等待時間
- 后臺記錄日志,創建額外線程來記錄日志
- 大量用戶請求,創建多個線程共同處理請求
- 下載大文件,可以創建單獨的線程,不影響正常的業務流暢度
- ......
多線程創建方式
線程創建有4種方式:
方式1:繼承Thread類
方式2:實現Runnable接口
方式3:實現Callable接口
方式4:使用線程池【這塊后邊單獨說,它更像是管理線程的手段】
繼承Thread
步驟:
- 自定義類繼承Thread類
- 重寫run方法,run方法就是線程的功能執行體
- 創建線程對象,調用start方法啟動線程
- 啟動線程之后,線程不一定會立即執行,需要得到CPU分配的時間片,也就是拿到CPU執行權限才會執行
JDK源碼中,Thread類定義實現了Runnable接口
所以知道重寫的run方法從哪來的了吧!就是從Runnable接口中來的
需求:創建線程計算10以內的偶數
線程類:
public class ThreadTest extends Thread{
// run方法是 線程體,啟動線程時會運行run()方法中的代碼
@Override
public void run() {
// 輸出10以內偶數
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
System.out.println(i);
}
}
}
}
測試類:
測試類中輸出了一句話:主線程
public class ThreadMain {
public static void main(String[] args) {
// 1、創建線程對象
ThreadTest t1 = new ThreadTest();
// 2、調用start方法啟動線程
t1.start();
System.out.println("主線程");
}
}
打印結果:
實現Runnable接口
步驟:
- 自定義類實現Runnable接口
- 實現run方法
- 創建實現類對象
- 創建Thread對象,在構造方法中傳入實現類對象作為參數
- 調用Thread對象的start方法啟動線程
同樣的需求打印10以內的偶數
實現類:
public class RunnableImpl implements Runnable{
@Override
public void run() {
// 輸出10以內偶數
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
System.out.println(i);
}
}
}
}
測試類:
public class RunnableMain {
public static void main(String[] args) {
// 1、創建實現類對象
RunnableImpl runnable = new RunnableImpl();
// 2、創建線程對象,接收實現類,因為實現類中的run方法承載了線程的功能
Thread t1 = new Thread(runnable);
// 3、啟動線程
t1.start();
// 主線程
System.out.println("主線程");
}
}
Callable接口
FutureTask類:
RunnableFuture接口:
步驟:
- 新建類實現Callable接口,并指定泛型類型,類型就是線程計算之后的結果的類型
- 實現call方法,call方法跟run方法類似,不過該方法有返回值和異常拋出,都是來封裝線程功能體的
- 在測試類中創建實現類對象,并且創建 FutureTask 對象將實現類對象當做構造方法參數傳入
- 創建Thread線程對象,將 FutureTask 對象當做構造方法參數傳入,并調用start方法啟動線程
- 可以通過 FutureTask 對象的get方法獲取線程的運算結果
案例:還是計算10以內的偶數,這一次將計算結果返回,因為有多個數據所以返回數據用集合存儲,則Callable接口的泛型類型應該是集合
實現類:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
// 1、實現Callable,指明泛型類型
public class CallableImpl implements Callable<List<Integer>> {
// 2、線程返回Integer類型數據,拋出異常
@Override
public List<Integer> call() throws Exception {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
// 3、偶數存儲到集合中
list.add(i);
}
}
// 4、返回集合
return list;
}
}
測試類:
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableMain {
public static void main(String[] args) {
// 1、創建Callable實現類對象
CallableImpl callable = new CallableImpl();
// 2、創建 FutureTask對象傳入 callable
// FutureTask 實現 了 RunnableFuture,RunnableFuture實現了Runnable接口和Future接口
FutureTask<List<Integer>> task = new FutureTask<>(callable);
// 3、將 task 傳入線程對象
Thread t1 = new Thread(task);
// 4、啟動線程
t1.start();
// 5、獲取線程返回數據
try {
List<Integer> list = task.get();
System.out.println(list);
} catch (Exception e) {
e.printStackTrace();
}
}
}
三種實現方式區別
- Java單繼承的特性決定,使用實現接口的方式創建線程更靈活
- Callable實現call方法有返回值和異常拋出,方便定位問題,配合FutureTask可以獲取線程運算結果,而run方法沒有返回值,沒有異常拋出
- 如果線程運行結束后不需要返回值,則推薦使用實現Runnable接口方式
小貼士:有不少文章中寫到【實現的方式更適合用來處理多個線程有共享數據的情況】,很多小伙伴也拿去背,這句話怎么看都不對吧,多線程共享數據不加鎖,不同步怎么著也不能避免線程安全問題!
線程開辟
- 開辟線程需要通過Thread類創建對象
- 啟動線程需要Thread對象調用start方法
- 線程的功能可以裝在Thread類的run方法或者Runnable接口實現類的run方法類中
- 線程開辟需要在另一個線程中開啟,新開辟的線程稱為子線程
小貼士:對于Java應用程序java.exe來講,至少會存在三個線程:
main主線程
gc垃圾回收線程
異常處理線程,如果發生異常會影響主線程。
線程狀態
線程的狀態網上有 5種狀態 和 6種狀態 兩個版本
五種狀態版本:是基于現代操作系統線程狀態角度解釋的
新建:當一個Thread類或其子類的對象被聲明并創建時,新生的線程對象處于新建狀態
就緒:處于新建狀態的線程被start后,將進入線程隊列等待CPU時間片,此時它已具備了運行的條件,只是沒分配到CPU資源
運行:當就緒的線程被調度并獲得CPU資源時,便進入運行狀態,run方法定義了線程的操作和功能
阻塞:在某種特殊情況下,被人為掛起或執行輸入輸出操作時,讓出CPU并臨時終止自己的執行,進入阻塞狀態
死亡:線程完成了它的全部工作或線程被提前強制性地中止或出現異常導致結束
在JDK5的時候Thread類中定義了一個State枚舉類,其中定義了6種線程狀態,這是Java官方定義的Java線程的6種狀態
1)NEW:處于NEW狀態的線程此時尚未啟動。只是通過new Thread()創建了線程對象,并未調用start()方法
2)RUNNABLE:Java線程的 RUNNABLE 狀態其實是包括了傳統操作系統線程的 就緒(ready) 和 運行(running) 兩個狀態的。處于 RUNNABLE 狀態的線程可能在 Java 虛擬機中運行,也有可能在等待 CPU 分配資源
3)BLOCKED:阻塞狀態。處于 BLOCKED 狀態的線程正等待鎖的釋放以進入同步區,就好比你去食堂打飯,只有一個窗口你就得排隊,等前邊的人結束之后你完成打飯
4)WAITING :等待狀態。處于等待狀態的線程變成 RUNNABLE 狀態需要其他線程喚醒
可以通過調用一下三個方法進入等待狀態:
- Object.wait():使當前線程處于等待狀態直到另一個線程喚醒它;
- Thread.join():使當前線程等待另一個線程執行完畢之后再繼續執行,底層調用的是 Object 實例的 wait() 方法;
- LockSupport.park():除非獲得調用許可,否則禁用當前線程進行線程調度
5)TIMED_WAITING:超時等待狀態。線程等待一個具體的時間,時間到后會被自動喚醒。
調用如下方法會使線程進入超時等待狀態:
- Thread.sleep(long millis):使當前線程睡眠指定時間,sleep() 方法不會釋放當前鎖,但會讓出 CPU,所以其他不需要爭奪鎖的線程可以獲取 CPU 執行;
- Object.wait(long timeout):線程休眠指定時間,等待期間可以通過 notify() / notifyAll() 喚醒;
- Thread.join(long millis):等待當前線程最多執行 millis 毫秒,如果 millis 為 0,則會一直執行;
- LockSupport.parkNanos(long nanos): 除非獲得調用許可,否則禁用當前線程進行線程調度指定時間;
- LockSupport.parkUntil(long deadline):同上,也是禁止線程進行調度指定時間;
6)TERMINATED:終止狀態。此時線程已執行完畢。
其實等待和鎖定狀態可以被籠統的稱為阻塞狀態,就是停著不動了嘛,在回答面試題時建議回答6種狀態版本,就是是JDK源碼中定義的,一來有官方支持,二來證明咱看過一點源碼。
狀態轉換
- 新建狀態的線程調用start方法進入到運行狀態
- 運行狀態線程如果遇到Object.wait()、Thread.join()或者LockSupport.park()方法則會放棄CPU執行權進入等待狀態,這個裝需要被喚醒之后才會再次進入就緒狀態獲得到CPU時間片進入運行狀態
- 運行狀態的線程遇到Thread.sleep(long)、Object.wait(long)、Thread.join(long)等方法,也就是可以傳入時間的,就會進入超時等待狀態,達到時間之后就會自動進入就緒狀態,當CPU執行就進入運行狀態
- 運行狀態的線程如果被同步代碼塊或者同步方法包裹,執行時如果釋放鎖資源,就會進入阻塞狀態或者叫鎖定狀態,只有再次獲取到鎖資源時才會進入就緒狀態,等到CPU時間片后進入運行狀態
- 執行完的線程就會進入終止狀態,線程結束
線程之間的狀態轉換可以參考下圖
Thread類詳解
成員變量
變量名 |
類型 |
作用 |
name |
volatile String |
線程名稱 |
priority |
int |
線程的優先級,默認為5,范圍1-10 |
threadQ |
Thread |
|
eetop |
long |
|
single_step |
boolean |
是否單步執行 |
daemon |
boolean |
守護線程狀態,默認為false |
stillborn |
boolean |
JVM狀態,默認為false |
target |
target |
將被執行的Runnable實現類 |
group |
ThreadGroup |
當前線程的線程組 |
contextClassLoader |
ClassLoader |
這個線程上下文的類加載器 |
inheritedAccessControlContext |
AccessControlContext |
該線程繼承的AccessControlContext |
threadInitNumber |
static int |
用于匿名線程的自動編號 |
threadLocals |
ThreadLocal.ThreadLocalMap |
屬于此線程的ThreadLocal,這個映射關系通過ThreadLocal維持 |
inheritableThreadLocals |
ThreadLocal.ThreadLocalMap |
這個線程的InheritableThreadLocal,其映射關系通過InheritableThreadLocal維持 |
stackSize |
long |
此線程的請求的堆棧的大小,如果創建者的請求堆棧大小為0,則不指定堆棧大小,由jvm來自行決定。一些jvm會忽略這個參數。 |
nativeParkEventPointer |
long |
在本機線程終止后持續存在的jvm私有狀態。 |
tid |
long |
線程的ID |
threadSeqNumber |
static long |
用于生成線程的ID |
threadStatus |
volatile int |
java線程狀態,0表示未啟動 |
parkBlocker |
volatile Object |
提供給LockSupport調用的參數 |
blocker |
volatile Interruptible |
此線程在可中斷的IO操作中被阻塞的對象,阻塞程序的中斷方法應該在設置了這個線程中斷狀態之后被調用 |
常量
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
常量名 |
數據類型 |
作用 |
MIN_PRIORITY |
int |
線程最低優先級 |
NORM_PRIORITY |
int |
分配給線程的默認優先級 |
MAX_PRIORITY |
int |
線程最大優先級 |
Thread構造方法
從源碼看出Thread類一共有9個構造方法,除第三個為default修飾【同包可用】,其他都是public
構造方法 |
作用 |
Thread() |
分配新的Thread對象 |
Thread(Runnable target) |
傳入Runnable接口實現類,之后由JVM啟動線程 |
Thread(Runnable target, AccessControlContext acc) |
在傳入Runnable的時候還可以指定AccessControlContext |
Thread(ThreadGroup group, Runnable target) |
指定線程組和Runnable接口 |
Thread(String name) |
指定線程名字,默認是【Thread-下一個線程編號,從0開始】 |
Thread(ThreadGroup group, String name) |
指定線程組和線程名字 |
Thread(Runnable target, String name) |
指定Runnable接口和線程名 |
Thread(ThreadGroup group, Runnable target, String name) |
指定線程組,Runnable接口和線程名 |
Thread(ThreadGroup group, Runnable target, String name,long stackSize) |
指定線程組,Runnable接口,線程名和此線程請求的堆棧大小,默認為0 |
Thread常用方法
方法 |
返回值類型 |
作用 |
start() |
void |
啟動線程 |
run() |
void |
重寫的Runnable接口方法,封裝線程的功能體 |
currentThread() |
Thread |
靜態方法,獲取當前線程 |
getName() |
String |
獲取線程名 |
setName(String name) |
void |
設置線程名 |
yield() |
void |
主動釋放當前線程的執行權 |
join() |
void |
在線程中插入執行另一個線程,該線程被阻塞,直到插入執行的線程完全執行完畢以后,該線程才繼續執行下去 |
sleep(long millis) |
void |
線程休眠一段時間 |
isAlive() |
boolean |
判斷線程是否還存活 |
isDaemon() |
boolean |
判斷是否為守護線程 |
stop() |
void |
過時方法。當執行此方法時,強制結束當前線程,因過于粗暴,會引發很多問題所以棄用 |
setDaemon(boolean on) |
void |
設置為守護線程 |
getPriority() |
int |
獲取線程優先級 |
setPriority(int newPriority) |
void |
設置線程優先級 |
設置線程名
實現類:
public class RunnableImpl implements Runnable{
@Override
public void run() {
// 輸出10以內偶數
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
// 獲取當前線程
Thread thread = Thread.currentThread();
// 獲取線程名
String threadName = thread.getName();
System.out.println(threadName + "===>" + i);
}
}
}
}
測試類:
public class RunnableMain {
public static void main(String[] args) {
// 1、創建實現類對象
RunnableImpl runnable = new RunnableImpl();
// 2、 創建線程對象,并指定線程名
Thread t1 = new Thread(runnable, "線程1");
// 3、啟動線程
t1.start();
System.out.println(Thread.currentThread().getName() + "主線程");
}
}
運行結果:
或者通過setName()方法設置線程名
public class RunnableMain {
public static void main(String[] args) {
// 1、創建實現類對象
RunnableImpl runnable = new RunnableImpl();
// 2、 創建線程對象,不指定名字
Thread t1 = new Thread(runnable);
// 設置線程名
t1.setName("線程1");
// 3、啟動線程
t1.start();
System.out.println(Thread.currentThread().getName() + "主線程");
}
}
如果不設置線程名,默認為【"Thread-" + nextThreadNum()】,nextThreadNum方法使用 threadInitNumber靜態變量,默認從0開始,每次+1
不設置線程名運行效果如下
sleep方法
sleep方法可以讓線程阻塞指定的毫秒數。時間到了后,線程進入就緒狀態。sleep可用來研模擬網絡延時,倒計時等。每一個對象都有一個鎖,sleep不會釋放鎖,鎖的概念后邊會詳細講解
實現類:
public class RunnableImpl implements Runnable{
@Override
public void run() {
// 輸出10以內偶數
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
try {
// 休眠1秒
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "===>" + i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
測試類:
public class RunnableMain {
public static void main(String[] args) {
// 1、創建實現類對象
RunnableImpl runnable = new RunnableImpl();
// 2、 創建線程對象,不指定名字
Thread t1 = new Thread(runnable,"線程1");
// 3、啟動線程
t1.start();
}
}
運行結果:
"善用"sleep年入百萬不是夢:
yield方法
提出申請釋放CPU資源,至于能否成功釋放取決于JVM決定,調用yield()方法后,線程仍然處于RUNNABLE狀態,線程不會進入阻塞狀態,保留了隨時被調用的權利
實現類:
public class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "開始執行");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "執行結束");
}
}
測試類:
public class RunnableMain {
public static void main(String[] args) {
// 1、創建實現類對象
RunnableImpl runnable = new RunnableImpl();
// 2、 創建線程對象,不指定名字
Thread t1 = new Thread(runnable,"線程1");
Thread t2 = new Thread(runnable,"線程2");
// 3、啟動線程
t1.start();
t2.start();
}
}
運行結果:
第五次執行是線程2執行開始結束后輸出的線程1開始結束,這就說明CPU并沒有切換到別的線程,說明并沒有釋放CPU資源
join方法
將當前的線程掛起,當前線程阻塞,待其他的線程執行完畢,當前線程才能執行,可以把join()方法理解為插隊,誰插到前面,誰先執行
在很多情況下,主線程創建并啟動子線程,如果子線程中要進行大量的耗時運算,主線程將可能早于子線程結束。如果主線程需要知道子線程的執行結果時,就需要等待子線程執行結束了。主線程可以sleep(xx),但這樣的xx時間不好確定,因為子線程的執行時間不確定,join()方法比較合適這個場景
public class RunnableMain {
public static void main(String[] args) {
// 1、lambda創建線程
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
// 模擬耗時操作
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "join方法===>" + i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
// 2、 啟動線程
t1.start();
try {
// t1調用join 方法
t1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("main線程");
}
}
運行結果:
設置優先級
改變、獲取線程的優先級。Java提供一個線程調度器來監控程序中啟動后進入就緒狀態的所有線程,線程調度器按照優先級決定應該調度哪個線程來執行。線程的優先級用數據表示,范圍1~10。線程的優先級高只是表示他的權重大,獲取CPU執行權的幾率大。先設置線程的優先級,在執行start()方法
public class RunnableMain {
public static void main(String[] args) {
// 1、lambda創建線程
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "線程優先級" + Thread.currentThread().getPriority());
},"線程1");
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "線程優先級" + Thread.currentThread().getPriority());
},"線程2");
// 2、設置線程優先級
t1.setPriority(1);
t2.setPriority(10);
// 3、 啟動線程
t1.start();
t2.start();
System.out.println("main線程");
}
}
結束線程
JDK提供的【stop()、destroy()】兩種方法已廢棄,不推薦再使用。推薦線程自動停止下來,就比如上邊的所有案例,都是執行完了run方法中的所有代碼之后線程就自然結束了。如果線程需要循環執行,建議使用一個標識位變量進行終止,當flag=false時,則終止線程運行
比如:定義一個名為【線程1】的子線程,當主線程執行3次循環之后,線程1停止運行
實現類:
public class RunnableImpl implements Runnable{
// boolean變量標記是否需要繼續執行
private boolean flag = true;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
// 循環執行,flag為false時停止
while (flag) {
System.out.println(Thread.currentThread().getName() + "正在運行");
}
}
}
測試類:
public class RunnableMain {
public static void main(String[] args) {
RunnableImpl runnable = new RunnableImpl();
Thread t1 = new Thread(runnable, "線程1");
t1.start();
for (int i = 0; i < 5; i++) {
System.out.println("主線程====》" + i);
// 當循環三次時
if(i == 3) {
// 設置flag值為false
runnable.setFlag(false);
}
}
}
}
總結
- 掌握多線程的使用場景和術語
- 熟練創建和啟動線程
- 掌握線程狀態和狀態之間轉換
- 掌握Thread類中的常用方法如:join、sleep、yield等