Java入門系列-21-多執行緒
什麼是執行緒
在作業系統中,一個應用程式的執行例項就是程序,程序有獨立的記憶體空間和系統資源,在工作管理員中可以看到程序。
執行緒是CPU排程和分派的基本單位,也是程序中執行運算的最小單位,可完成一個獨立的順序控制流程,當然一個程序中可以有多個執行緒。
多執行緒:一個程序中同時運行了多個執行緒,每個執行緒用來完成不同的工作。多個執行緒交替佔用CPU資源,並非真正的並行執行。
使用多執行緒能充分利用CPU的資源,簡化程式設計模型,帶來良好的使用者體驗。
一個程序啟動後擁有一個主執行緒,主執行緒用於產生其他子執行緒,而且主執行緒必須最後完成執行,它執行各種關閉動作。
在Java中main()方法為主執行緒入口,下面使用 Thread 類檢視主執行緒名。
public class MainThread {
public static void main(String[] args) {
//獲取當前執行緒
Thread t=Thread.currentThread();
System.out.println("當前執行緒名字:"+t.getName());
//自定義執行緒名字
t.setName("MyThread");
System.out.println("當前執行緒名字:"+t.getName());
}
}
建立執行緒
在Java中建立執行緒有兩種方式 1.繼承 java.lang.Thread 類 2.實現 java.lang.Runnable 介面
1.繼承 Thread 類建立執行緒
(1)定義MyThread類繼承Thread類
(2)重寫run()方法,編寫執行緒執行體
public class MyThread extends Thread{
//重寫run方法
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
(3)建立執行緒物件,呼叫start()方法啟動執行緒
public class TestMyThread {
public static void main(String[] args) {
MyThread myThread=new MyThread();
//啟動執行緒
myThread.start();
}
}
多個執行緒同時啟動後是交替執行的,執行緒每次執行時長由分配的CPU時間片長度決定
修改 TestMyThread.java 觀察多執行緒交替執行
public class TestMyThread {
public static void main(String[] args) {
MyThread myThread1=new MyThread();
MyThread myThread2=new MyThread();
myThread1.start();
myThread2.start();
}
}
多執行幾次觀察效果
啟動執行緒能否直接呼叫 run()方法? 不能,呼叫run()方法只會是主執行緒執行。呼叫start()方法後,子執行緒執行run()方法,主執行緒和子執行緒並行交替執行。
2.實現 Runnable 介面建立執行緒
(1)定義MyRunnable類實現Runnable介面
(2)實現run()方法,編寫執行緒執行體
public class MyRunnable implements Runnable{
//實現 run方法
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
(3)建立執行緒物件,呼叫start()方法啟動執行緒
public class TestMyRunnable {
public static void main(String[] args) {
Runnable runnable=new MyRunnable();
//建立執行緒,傳入runnable
Thread t=new Thread(runnable);
t.start();
}
}
執行緒的生命週期
建立狀態:執行緒建立完成,比如 MyThread thread=new MyThread
就緒狀態:執行緒物件呼叫 start() 方法,執行緒會等待CPU分配執行時間,並沒有立馬執行
執行狀態:執行緒分配到了執行時間,進入執行狀態。執行緒在執行中發生禮讓 (yield)
會回到就緒狀態
阻塞狀態:執行過程中遇到IO操作或程式碼 Thread.sleep()
,阻塞後的執行緒不能直接回到執行狀態,需要重新進入就緒狀態等待資源的分配。
死亡狀態:自然執行完畢或外部干涉終止執行緒
執行緒排程
執行緒排程指按照特定機制為多個執行緒分配CPU的使用權
執行緒排程常用方法
方法 | 說明 |
---|---|
setPriority(int newPriority) | 更改執行緒的優先順序 |
static void sleep(long millis) | 在指定的毫秒數內讓當前正在執行的執行緒休眠 |
void join() | 等待該執行緒終止 |
static void yield() | 暫停當前正在執行的執行緒物件,並執行其他執行緒 |
void interrupt() | 中斷執行緒 |
boolean isAlive() | 測試執行緒是否處於活動狀態 |
執行緒優先順序的設定
執行緒優先順序由1~10表示,1最低,預設有限級為5。優先順序高的執行緒獲得CPU資源的概率較大。
public class TestPriority {
public static void main(String[] args) {
Thread t1=new Thread(new MyRunnable(),"執行緒A");
Thread t2=new Thread(new MyRunnable(),"執行緒B");
//最大優先順序
t1.setPriority(Thread.MAX_PRIORITY);
//最小優先順序
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
執行緒休眠
讓執行緒暫時睡眠指定時長,執行緒進入阻塞狀態,睡眠時間過後執行緒會再進入可執行狀態。
休眠時長以毫秒為單位,呼叫sleep()方法需要處理 InterruptedException異常。
public class TestSleep {
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
System.out.println("第 "+i+" 秒");
try {
//讓當前執行緒休眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
強制執行
使用 join() 方法實現,可以認為是執行緒的插隊,會先強制執行插隊的執行緒。
public class JoinThread implements Runnable{
@Override
public void run() {
for (int i = 1; i <=10; i++) {
System.out.println("執行緒名:"+Thread.currentThread().getName()+" i:"+i);
}
System.out.println("插隊執行緒執行完畢!");
}
}
public class TestJoin {
public static void main(String[] args) {
Thread joinThread=new Thread(new JoinThread(),"插隊的執行緒");
//啟動後與主執行緒交替執行
joinThread.start();
for (int i = 1; i <= 10; i++) {
if (i==5) {
try {
System.out.println("====開始插隊強制執行====");
joinThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("執行緒名:"+Thread.currentThread().getName()+" i:"+i);
}
System.out.println("主執行緒執行完畢!");
}
}
最一開始執行,主執行緒 main 和 "插隊的執行緒"是交替執行。當主執行緒的迴圈到第5次的時候,呼叫 "插隊的執行緒"的join方法,開始強制執行"插隊的執行緒",待"插隊的執行緒"執行完後,才繼續恢復 main 執行緒的迴圈。
執行緒禮讓
使用 yield() 方法實現,禮讓後會暫停當前執行緒,轉為就緒狀態,其他具有相同優先順序的執行緒獲得執行機會。
下面我們實現Runnable介面,在run方法中實現禮讓,建立兩個執行緒,達到某種條件時禮讓。
public class YieldThread implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("執行緒名:"+Thread.currentThread().getName()+" i:"+i);
//當前執行緒執行到5後發生禮讓
if (i==5) {
System.out.println(Thread.currentThread().getName()+" 禮讓:");
Thread.yield();
}
}
}
}
public class TestYield {
public static void main(String[] args) {
Thread t1=new Thread(new YieldThread(),"A");
Thread t2=new Thread(new YieldThread(),"B");
t1.start();
t2.start();
}
}
只是提供一種可能,不能保證一定會實現禮讓
執行緒同步
首先看一個多線共享同一個資源引發的問題
倉庫有10個蘋果,小明、小紅、小亮每次可以從倉庫中拿1個蘋果,拿完蘋果後倉庫中的蘋果數量-1。
先編寫倉庫資源類,實現介面
//這個實現類將被多個執行緒物件共享
public class ResourceThread implements Runnable{
private int num=10;
@Override
public void run() {
while(true) {
if (num<=0) {
break;
}
num--;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿走一個,還剩餘:"+num);
}
}
}
編寫測試類,建立兩個執行緒物件,共享同一個資源
public class TestResource {
public static void main(String[] args) {
ResourceThread resource=new ResourceThread();
//使用同一個Runnable實現類物件
Thread t1=new Thread(resource,"小明");
Thread t2=new Thread(resource,"小紅");
Thread t3=new Thread(resource,"小亮");
t1.start();
t2.start();
t3.start();
}
}
執行後我們發現,每次拿完蘋果後的剩餘數量出現了問題,使用同步方法可以解決這個問題。
語法:
訪問修飾符 synchronized 返回型別 方法名(引數列表){
……
}
synchronized 就是為當前的執行緒宣告一個鎖
修改 ResourceThread.java 實現同步
//這個實現類將被多個執行緒物件共享
public class ResourceThread implements Runnable{
private int num=10;
private boolean isHave=true;
@Override
public void run() {
while(isHave) {
take();
}
}
//同步方法
public synchronized void take() {
if (num<=0) {
isHave=false;
return;
}
num--;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿走一個,還剩餘:"+num);
}
}
實現同步的第二種方式同步程式碼塊
語法:
synchronized(syncObject){
//需要同步的程式碼
}
syncObject為需同步的物件,通常為this
修改 ResourceThread.java 改為同步程式碼塊
//這個實現類將被多個執行緒物件共享
public class ResourceThread implements Runnable{
private int num=10;
private boolean isHave=true;
@Override
public void run() {
while(isHave) {
synchronized(this) {
if (num<=0) {
isHave=false;
return;
}
num--;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿走一個,還剩餘:"+num);
}
}
}
}