本文作者:黃海燕闽晦,叩丁狼高級(jí)講師。原創(chuàng)文章提岔,轉(zhuǎn)載請(qǐng)注明出處仙蛉。
1.創(chuàng)建并啟動(dòng)線程的6種方式:
1)繼承Thread類(lèi)創(chuàng)建線程
2)實(shí)現(xiàn)Runnable接口創(chuàng)建線程
3)使用Callable和FutureTask創(chuàng)建線程
4)使用線程池,例如用Executor框架
5)Spring實(shí)現(xiàn)多線程(底層是線程池)
6)定時(shí)器Timer (底層封裝了一個(gè)TimerThread對(duì)象)
1.1 繼承Thread類(lèi)創(chuàng)建線程
1.1.1繼承Thread類(lèi)方式創(chuàng)建線程的實(shí)現(xiàn)步驟:
步驟:
1):定義一個(gè)類(lèi)A繼承于java.lang.Thread類(lèi).
2):在A類(lèi)中覆蓋Thread類(lèi)中的run方法.
3):我們?cè)趓un方法中編寫(xiě)需要執(zhí)行的操作---->run方法里的,線程執(zhí)行體.
4):在main方法(線程)中,創(chuàng)建線程對(duì)象,并啟動(dòng)線程.
????????創(chuàng)建線程類(lèi)對(duì)象: A類(lèi) a = new A類(lèi)();
????????調(diào)用線程對(duì)象的start方法: a.start();//啟動(dòng)一個(gè)線程
注意:千萬(wàn)不要調(diào)用run方法,如果調(diào)用run方法好比是對(duì)象調(diào)用方法,依然還是只有一個(gè)線程,并沒(méi)有開(kāi)啟新的線程.
1.1.2需求:使用兩個(gè)線程實(shí)現(xiàn)邊聽(tīng)歌邊打游戲
實(shí)現(xiàn)代碼:
//音樂(lè)線程
public class MusicThread {
public static void main(String[] args) {
//創(chuàng)建游戲線程對(duì)象
GameThread game = new GameThread();
//啟動(dòng)游戲線程
game.start();
while(true){
System.out.println(Thread.currentThread().getName()+"聽(tīng)音樂(lè)!");
}
}
}
//游戲線程
class GameThread extends Thread{
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName()+"打游戲!");
}
}
}
注意:有的小伙伴可能覺(jué)得音樂(lè)線程沒(méi)有啟動(dòng),在這里其實(shí)音樂(lè)線程已經(jīng)啟動(dòng)起來(lái)了,而啟動(dòng)音樂(lè)線程的對(duì)象就是我們的JVM,此處main方法其實(shí)啟動(dòng)的時(shí)候會(huì)創(chuàng)建一個(gè)主線程去執(zhí)行main方法,所以我在這里使用主線程作為了我的音樂(lè)線程.
1.2 實(shí)現(xiàn)Runnable接口創(chuàng)建線程
1.2.1實(shí)現(xiàn)Runnable接口方式創(chuàng)建線程的實(shí)現(xiàn)步驟:
1):定義一個(gè)類(lèi)A實(shí)現(xiàn)于java.lang.Runnable接口,注意A類(lèi)不是線程類(lèi).
2):在A類(lèi)中覆蓋Runnable接口中的run方法.
3):我們?cè)趓un方法中編寫(xiě)需要執(zhí)行的操作---->run方法里的,線程執(zhí)行體.
4):在main方法(線程)中,創(chuàng)建線程對(duì)象,并啟動(dòng)線程.
????????創(chuàng)建線程類(lèi)對(duì)象: Thread t = new Thread(new A());
????????調(diào)用線程對(duì)象的start方法: t.start();
1.2.2需求:使用兩個(gè)線程實(shí)現(xiàn)邊聽(tīng)歌邊打游戲
實(shí)現(xiàn)代碼:
//音樂(lè)線程
public class MusicThread {
public static void main(String[] args) {
//創(chuàng)建游戲線程對(duì)象
Thread game = new Thread(new Game());
//啟動(dòng)游戲線程
game.start();
while(true){
System.out.println(Thread.currentThread().getName()+"聽(tīng)音樂(lè)!");
}
}
}
//游戲
class Game implements Runnable{
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName()+"打游戲!");
}
}
}
1.2.3 繼承方式和實(shí)現(xiàn)方式的區(qū)別
- 1)繼承方式是一個(gè)類(lèi)繼承了Thread后成為線程類(lèi)的子類(lèi),實(shí)現(xiàn)方式是一個(gè)類(lèi)實(shí)現(xiàn)Runnable接口,但是這個(gè)類(lèi)不是線程類(lèi),因?yàn)樵擃?lèi)沒(méi)有start等方法.
- 2)啟動(dòng)的時(shí)候繼承方式直接調(diào)用自己的start方法,實(shí)現(xiàn)方式是借助了Thread中的start方法啟動(dòng)的,自身沒(méi)有start方法
- 3)繼承方式調(diào)用的run方法是通過(guò)方法覆蓋,通過(guò)繼承方式實(shí)現(xiàn)的,運(yùn)行的時(shí)候先找子類(lèi),沒(méi)有最后才運(yùn)行父類(lèi)的run方法.實(shí)現(xiàn)方式是執(zhí)行Thread的run方法,而Thread中的run方法調(diào)用了實(shí)現(xiàn)類(lèi)中的run方法,使用過(guò)組合關(guān)系的方法調(diào)用實(shí)現(xiàn)的.
1.3實(shí)現(xiàn) Callable 接口
1.3.1使用Callable和FutureTask創(chuàng)建線程的實(shí)現(xiàn)步驟:
- 1)定義一個(gè)Callable接口的實(shí)現(xiàn)類(lèi)
- 2)創(chuàng)建Callable實(shí)現(xiàn)類(lèi)對(duì)象傳遞給FutureTask構(gòu)造器
- 3)將FutureTask對(duì)象傳遞給Thread構(gòu)造器
- 4)Thread對(duì)象調(diào)用start方法啟動(dòng)線程
- 5)通過(guò)FutureTask對(duì)象的get方法獲取線程運(yùn)行的結(jié)果
注意:
???? Future就是對(duì)于具體的Runnable或者Callable任務(wù)的執(zhí)行結(jié)果進(jìn)行取消碱蒙、查詢是否完成荠瘪、獲取結(jié)果。必要時(shí)可以通過(guò)get方法獲取執(zhí)行結(jié)果赛惩,該方法會(huì)阻塞直到任務(wù)返回結(jié)果哀墓。使用場(chǎng)景:使用多線程計(jì)算結(jié)果并返回該結(jié)果。
1.3.2需求:使用2個(gè)線程異步計(jì)算1-1000,000內(nèi)之和
實(shí)現(xiàn)代碼:
public class CallableDemo {
public static void main(String[] args) throws Exception {
//1.創(chuàng)建并啟動(dòng)線程
Callable<Integer> call1 = new CallableImpl(0, 50000);
Callable<Integer> call2 = new CallableImpl(50001, 100000);
FutureTask<Integer> f1 = new FutureTask<>(call1);
FutureTask<Integer> f2 = new FutureTask<>(call2);
new Thread(f1).start();
new Thread(f2).start();
//2.獲取每一個(gè)線程的結(jié)果
int ret1 = f1.get();
int ret2 = f2.get();
int ret= ret1+ret2;
System.out.println(ret);
}
}
class CallableImpl implements Callable<Integer>{
private int min;
private int max;
public CallableImpl(int min, int max) {
this.min = min;
this.max = max;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = min; i <= max; i++) {
sum+=i;
}
return sum;
}
}
1.3.3Callable和Runnable的區(qū)別如下:
Callable定義的方法是call坊秸,而Runnable定義的方法是run麸祷。
Callable的call方法可以有返回值澎怒,而Runnable的run方法不能有返回值褒搔。
Callable的call方法可拋出異常,而Runnable的run方法不能拋出異常喷面。
注意:
????FutureTask為Runnable的實(shí)現(xiàn)類(lèi)
????FutureTask可以視為一個(gè)閉鎖(門(mén)閂)星瘾,因?yàn)橹挥挟?dāng)線程運(yùn)行完才會(huì)出現(xiàn)結(jié)果。
1.4使用線程池(Executor框架【后面詳細(xì)講解Executor框架】)
???? 線程池惧辈,顧名思義就是一個(gè)池子里面放了很多的線程琳状,我們用就將線程從里面拿出來(lái),使用完畢就放回去池子中盒齿。設(shè)計(jì)和數(shù)據(jù)庫(kù)連接池相似念逞,存在靜態(tài)工廠方法用于創(chuàng)建各種線程池。
操作步驟:
1)使用Executors工具類(lèi)中的靜態(tài)工廠方法用于創(chuàng)建線程池
??????newFixedThreadPool:創(chuàng)建可重用且固定線程數(shù)的線程池边翁,
??????newScheduledThreadPool:創(chuàng)建一個(gè)可延遲執(zhí)行或定期執(zhí)行的線程池
??????newCachedThreadPool:創(chuàng)建可緩存的線程池
2)使用execute方法啟動(dòng)線程
3)使用shutdown方法等待提交的任務(wù)執(zhí)行完成并后關(guān)閉線程翎承。
代碼演示如下:
public class Demo4 {
public static void main(String[] args) {
Executor executor = Executors.newFixedThreadPool(5);
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
((ExecutorService) executor).shutdown();
}
}
1.5 Spring實(shí)現(xiàn)多線程
????在Spring3之后,Spring引入了對(duì)多線程的支持符匾,如果你使用的版本在3.1以前叨咖,應(yīng)該還是需要通過(guò)傳統(tǒng)的方式來(lái)實(shí)現(xiàn)多線程的。從Spring3同時(shí)也是新增了Java的配置方式,而且Java配置方式也逐漸成為主流的Spring的配置方式甸各。
代碼演示如下:
導(dǎo)入的包:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
</dependencies>
配置類(lèi):
@Configuration
@ComponentScan("cn.wolfcode")
@EnableAsync //允許使用異步任務(wù)
public class SpringConfig {}
服務(wù)類(lèi):
@Service
public class SpringService {
@Async // 這里進(jìn)行標(biāo)注為異步任務(wù)垛贤,在執(zhí)行此方法的時(shí)候,會(huì)單獨(dú)開(kāi)啟線程來(lái)執(zhí)行
public void dowork1() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
@Async
public void dowork2() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
測(cè)試類(lèi):
public class SpringThreadDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
SpringService bean = context.getBean(SpringService.class);
bean.dowork1();
bean.dowork2();
}
}
注意:此時(shí)會(huì)出現(xiàn)一個(gè)DEBUG信息
在這里DEBUG信息不是什么錯(cuò)誤趣倾,不會(huì)影響代碼的正常運(yùn)行聘惦,其實(shí)可以不用管的,但是為什么出現(xiàn)這個(gè)問(wèn)題呢儒恋?
????Spring的定時(shí)任務(wù)調(diào)度器會(huì)通過(guò)BeanFactory.getBean的方法來(lái)嘗試獲取一個(gè)注冊(cè)過(guò)的TaskExecutor對(duì)象來(lái)做任務(wù)調(diào)度部凑,獲取不到TaskExecutor對(duì)象再嘗試找ScheduledExecutorService 對(duì)象,都找不到就報(bào)DEBUG信息碧浊。報(bào)錯(cuò)之后就找自己本身默認(rèn)的scheduler定時(shí)器對(duì)象涂邀,這個(gè)舉動(dòng)其實(shí)是做一個(gè)提醒作用,所以如果沒(méi)有強(qiáng)迫癥可以不用管它箱锐。
1.5.1解決Spring使用多線程的報(bào)錯(cuò)信息
強(qiáng)迫癥患者想要解決怎么辦比勉,三種方式:
1.在log4j文件中加入log4j.logger.org.springframework.scheduling = INFO(治標(biāo)不治本)
2.在本配置文件或者配置類(lèi)中設(shè)置一個(gè)bean
3.配置類(lèi)實(shí)現(xiàn)AsyncConfigurer接口并覆蓋其getAsyncExecutor方法
1.6 定時(shí)器
嚴(yán)格來(lái)說(shuō)定時(shí)器(Timer)不是線程,他只是調(diào)度線程的一種工具,它里面封裝了一個(gè)線程驹止,所以我們可以使用定時(shí)器來(lái)使用線程浩聋。
操作步驟:
1)創(chuàng)建Timer 對(duì)象
2)調(diào)用schedule方法
3)傳入TimerTask子類(lèi)對(duì)象
代碼演示如下:
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}, 100, 100);
想獲取更多技術(shù)干貨,請(qǐng)前往叩丁狼官網(wǎng):http://www.wolfcode.cn/all_article.html