Java多線程從創(chuàng)建開始—創(chuàng)建和使用以及了解多線程的切換策略
方式一:繼承Thread類
- 繼承Thread類
- 重寫run方法,在run方法中添加想要線程執(zhí)行的內(nèi)容
- 創(chuàng)建繼承Thread類的類對象
- 調(diào)用start方法,一個對象只允許調(diào)用一次start方法,否則報錯java.lang.IllegalThreadStateException
- 每一個對象調(diào)用一次start方法就是一個線程
注意事項:
- 用戶手動調(diào)用run()方法并不會啟動線程,必須通過start()方法告訴JVM虛擬機使用run()方法啟動線程
- 為什么要重寫run方法呢? 因為run方法是用來封裝被線程執(zhí)行的代碼
- run()和start()方法的區(qū)別? run():封裝線程執(zhí)行的代碼,直接調(diào)用,相當于普通方法的調(diào)用 start():啟動線程,然后由JVM調(diào)用此線程的run()方法啟動線程
public class MultithreadingTest extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("這是線程"+i+currentThread().getName());
}
}
public static void main(String[] args) {
MultithreadingTest mt=new MultithreadingTest();
mt.start();//Exception in thread "main" java.lang.IllegalThreadStateException
MultithreadingTest mt1=new MultithreadingTest();
mt1.start();
//mt.start();
}
}
方式二:實現(xiàn)Runable接口
- 實現(xiàn)Runnable接口
- 重寫run方法,在run方法中添加想要線程執(zhí)行的內(nèi)容
- 創(chuàng)建實現(xiàn)Runnable接口的類對象
- 創(chuàng)建多個Thread實例對象,將實現(xiàn)了Runnable接口的實例對象放入Thread實例對象中
- 調(diào)用start方法,一個Thread實例對象只能調(diào)用一次start方法,否則報錯java.lang.IllegalThreadStateException
- 每個Thread對象調(diào)用一次start方法就代表一個線程
class RunnableTest implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("這是Runnable線程"+i+"-->"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
RunnableTest rt=new RunnableTest();
Thread t1=new Thread(rt,"t1");
Thread t2=new Thread(rt,"t2");
Thread t3=new Thread(rt,"t3");
t1.start();
t2.start();
t3.start();
}
}
方式三:實現(xiàn)Callable接口
- 實現(xiàn)Callable接口,需要返回值類型,返回值為泛型類型,如果沒有泛型則默認為Object
- 重寫call方法,需要拋出異常
- 創(chuàng)建繼承了Callable接口的類對象
- 創(chuàng)建FutureTask對象,傳入繼承了Callable接口的類對象
- new Thread(FutureTask的實例對象).start()
- 如果需要取call方法的返回值,則使用get方法
class CallableTest implements Callable {
@Override
public Object call() throws Exception {
int num=0;
for (int i = 1; i <= 100; i++) {
if (i%2==0) {
System.out.println(i);
num += i;
}
}
return num;
}
public static void main(String[] args) {
CallableTest ct=new CallableTest();
FutureTask ft=new FutureTask(ct);
new Thread(ft).start();
try {
int i= (Integer) ft.get();
System.out.println("總和為:"+i);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
方式四:線程池的方式
- 實現(xiàn)Callable接口,需要返回值類型,返回值為泛型類型,如果沒有泛型則默認為Object
- 重寫call方法,需要拋出異常
- 創(chuàng)建繼承了Callable接口的類對象
- 創(chuàng)建執(zhí)行服務ExecutorService類對象(創(chuàng)建線程池),并設定線程數(shù)
- 提交執(zhí)行服務es.submit(),提交數(shù)不可超過設定的線程數(shù),超過的線程數(shù)不生效關閉服務es.shutdownNow()
/**
* 線程池相關API:ExecutorService和Excutors
* ExcutorService:真正的線程池接口;常見的子類ThreadPoolExcutor
* 1.void execute(Runnable command):執(zhí)行任務/命令,沒有返回值,一般用來執(zhí)行Runnable
* 2.<T>Future<T> submit(Callable<T> task):執(zhí)行任務,有返回值,一般用來執(zhí)行Callable
* 3.void shutdown():關閉連接池
* Executors:工具類、線程池的工廠類,用于創(chuàng)建并返回不同類型的線程池
* 1.Executors.newCachedThreadPool():創(chuàng)建一個可根據(jù)需要創(chuàng)建新線程的線程池
* 2.Executors.newFixedThreadPool(n):創(chuàng)建一個可重用固定線程數(shù)的線程池
* 3.Executors.newSingleThreadExecutor():創(chuàng)建一個只有一個線程的線程池
* 4.Executors.newScheduledThreadPool(n):創(chuàng)建一個線程池,它可安排在給定延遲后運行命令或定期地執(zhí)行
*/
class CallAbleTest implements Callable<Boolean>{
@Override
public Boolean call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("這是Callable線程"+i+"-->"+Thread.currentThread().getName());
}
return true;
}
public static void main(String[] args) {
CallAbleTest ct=new CallAbleTest();
//創(chuàng)建執(zhí)行服務
ExecutorService es= Executors.newFixedThreadPool(3);
//提交執(zhí)行
Future<Boolean> f1=es.submit(ct);
Future<Boolean> f2=es.submit(ct);
Future<Boolean> f3=es.submit(ct);
Future<Boolean> f4=es.submit(ct);
//獲取結果
try {
boolean r1=f1.get();
boolean r2=f2.get();
boolean r3=f3.get();
boolean r4=f4.get();
System.out.println("r1 = " + r1+" r2 = " + r2+" r3 = " + r3+" r4="+r4);;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//關閉服務
es.shutdownNow();
}
}
Thread和Runnable對比
重點說明:
在開發(fā)中Thread和Runnable優(yōu)先選擇實現(xiàn)Runnable接口
原因:
- 實現(xiàn)的方式?jīng)]有類的單繼承的局限性
- 實現(xiàn)的方式更適合處理多個線程有共享數(shù)據(jù)的情況
舉例說明:成員變量共同使用,如果不定義為static,則繼承Thread類就是各自使用各自的,但是實現(xiàn)Runnable接口,只需要創(chuàng)建一次對象,成員變量也相當于是共享的
實現(xiàn)Callable接口比Runnable強大 原因:
- call方法有返回值
- call方法可以拋出異常并捕獲(使用get方法)
線程池的創(chuàng)建重點:
- 線程池的大小一般為服務器核數(shù)*2+有效磁盤數(shù)
- 線程池內(nèi)的線程可以頻繁使用,使用完了會歸還而不會銷毀(重復利用)
- 減少頻繁創(chuàng)建和銷毀消耗大量的時間(空間換時間)
- 線程池的設計可以便于管理,避免多用戶創(chuàng)建導致的宕機
出現(xiàn)線程安全問題的原因: 當多條語句在操作同一個線程共享數(shù)據(jù)時,一個線程對多條語句只執(zhí)行了一部分,還沒有執(zhí)行完,另一個線程參與進來執(zhí)行,導致共享數(shù)據(jù)錯誤
解決辦法: 對多條操作共享數(shù)據(jù)的語句,只能讓一個線程都執(zhí)行完,在執(zhí)行過程中不允許其他線程參與執(zhí)行
多線程調(diào)度策略
- 分時調(diào)度模型:
時間片策略:所有線程輪流使用CPU的使用權,平均分配每個線程占用的CPU的時間片
優(yōu)先級調(diào)度策略:根據(jù)線程的優(yōu)先級來分配CPU資源,高優(yōu)先級的線程優(yōu)先執(zhí)行
同優(yōu)先級線程是先進先出的隊列,使用時間片策略;不同優(yōu)先級線程采用優(yōu)先級調(diào)度策略
- 搶占式調(diào)度模型:
對高優(yōu)先級的使用優(yōu)化調(diào)度的搶占式策略
搶占式策略:優(yōu)先讓優(yōu)先級高的線程使用CPU,如果線程的優(yōu)先級相同,那么會隨機選擇一個,優(yōu)先級高的線程獲取CPU時間片相對多一些
- 線程的優(yōu)先級等級
MAX_PRIORITY:10【最高優(yōu)先級】
MIN_PRIORITY:1【最低優(yōu)先級】
NORM_PRIORITY:5【默認優(yōu)先級】
- 優(yōu)先級的方法
getPriority():返回線程優(yōu)先值
setPriority(int newPriority):改變線程的優(yōu)先級,數(shù)越大優(yōu)先級越高
- 說明:
線程創(chuàng)建時,繼承父線程的優(yōu)先級
低優(yōu)先級只是獲得調(diào)度的概率低,并非一定是在高優(yōu)先級線程之后才被調(diào)用
對應Windows線程優(yōu)先級
Java線程優(yōu)先級 | Windows線程優(yōu)先級 |
1(Thread.MIN_PRIORITY) | THREAD_PRIORITY_LOWEST |
2 | THREAD_PRIORITY_LOWEST |
3 | THREAD_PRIORITY_BELOW_NORMAL |
5(Thread.NORM_PRIORITY) | THREAD_PRIORITY_NORMAL |
6 | THREAD_PRIORITY_ABOVE_NORMAL |
7 | THREAD_PRIORITY_ABOVE_NORMAL |
8 | THREAD_PRIORITY_HIGHEST |
9 | THREAD_PRIORITY_HIGHEST |
10(Thread.MAX_PRIORITY) | THREAD_PRIORITY_GRITICAL |
優(yōu)先級的方法
- getPriority():返回線程優(yōu)先值
- setPriority(int newPriority):改變線程的優(yōu)先級,數(shù)越大優(yōu)先級越高
說明:
- 線程創(chuàng)建時,繼承父線程的優(yōu)先級
- 低優(yōu)先級只是獲得調(diào)度的概率低,并非一定是在高優(yōu)先級線程之后才被調(diào)用