線程的創建及啟動方式
以下只展示較為常見的6中將建方式,更多完整的創建方式見下文:
import java.util.concurrent.*;
public class T02_HowToCreateThread {
/**
* 繼承 Thread, 重寫 run()
*/
static class MyThread extends Thread {
@Override
public void run() {
// super.run();
System.out.println(“Hello MyThread, extends Thread !”);
}
}
/**
* 實現 Runnable, 重寫run()
*/
static class MyRun implements Runnable {
@Override
public void run() {
System.out.println(“Hello MyRun, implements Runnable !”);
}
}
/**
* 實現 Callable, 重寫call()
*/
static class MyCallWithoutV implements Callable {
@Override
public Object call() throws Exception {
System.out.println(“Hello MyCallWithoutV, implements Callable”);
return “success, implements Callable<String>”;
}
}
/**
* 實現 Callable<V>, 重寫call()
*/
static class MyCallWithString implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(“Hello MyCallWithString, implements Callable<String>”);
return “success, implements Callable<String>”;
}
}
public static void main(String[] args) {
//第一種
new MyThread().start();
//第二種
new Thread(new MyRun()).start();
//第三種: Lambda表達式
new Thread(() -> {
System.out.println(“Hello Lambda !”);
}).start();
//第四種
//方式1:相當於繼承了Thread類,作為子類重寫run()實現
new Thread() {
public void run() {
System.out.println(“匿名內部類創建線程方式1…”);
};
}.start();
//方式2:實現Runnable,Runnable作為匿名內部類
new Thread(new Runnable() {
public void run() {
System.out.println(“匿名內部類創建線程方式2…”);
}
} ).start();
//第五種: FutureTask + Callable
new Thread(new FutureTask<String>(new MyCallWithoutV())).start();
new Thread(new FutureTask<>(new MyCallWithString())).start();
//第六種:
Executors.newCachedThreadPool()ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
System.out.println(“Hello Executors.newCachedThreadPool and Lambda !”);
});
executorService.shutdown(); //優雅的關閉線程池, 用shutdown()
}
}
Hello 【Lambda】 !
【匿名內部類】創建線程方式2…
Hello MyThread, 【extends Thread】 !
【匿名內部類】創建線程方式1…
Hello MyRun, 【implements Runnable】 !
Hello MyCallWithoutV, 【implements Callable】
Hello MyCallWithString, 【implements Callable<返回值類型>】
【線程池實現】②
Executors.newCachedThreadPool and Lambda !【定時器方式】定時任務延遲0(即立刻執行),每隔1000ms執行一次
【線程池實現】①
Executors.newFixedThreadPool(線程數), 線程pool-1-thread-1 is running【線程池實現】①
Executors.newFixedThreadPool(線程數), 線程pool-1-thread-1 is running【線程池實現】①
Executors.newFixedThreadPool(線程數), 線程pool-1-thread-1 is running【線程池實現】①
Executors.newFixedThreadPool(線程數), 線程pool-1-thread-1 is running【線程池實現】①
Executors.newFixedThreadPool(線程數), 線程pool-1-thread-1 is running【線程池實現】①
Executors.newFixedThreadPool(線程數), 線程pool-1-thread-4 is running【線程池實現】①
Executors.newFixedThreadPool(線程數), 線程pool-1-thread-1 is running【線程池實現】①
Executors.newFixedThreadPool(線程數), 線程pool-1-thread-3 is running【線程池實現】①
Executors.newFixedThreadPool(線程數), 線程pool-1-thread-2 is running【線程池實現】①
Executors.newFixedThreadPool(線程數), 線程pool-1-thread-5 is running【定時器方式】定時任務延遲0(即立刻執行),每隔1000ms執行一次
【定時器方式】定時任務延遲0(即立刻執行),每隔1000ms執行一次
……
……
【定時器方式】定時任務延遲0(即立刻執行),每隔1000ms執行一次
8種創建方式
1、繼承 Thread 類
extends Thread, @Override run()
無返回值、無法拋出異常
創建: 編寫一個類 MyThread 讓它繼承 Thread 類,並把需要多線程運行的程序放到 public void run() 方法里。
啟動: 在主函數中,new 出 MyThread 類的實例。
運行: 調用 MyThread 類的實例的 start() 方法即可。
/**
* 繼承 Thread, 重寫 run()
*/
static class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println(“Hello MyThread, 【extends Thread】 !”);
}
}
public static void main(String[] args) {
new MyThread().start();
}
2、實現 Runnable 接口
implements Runnable, @Override run()
無返回值、無法拋出異常
創建: 編寫一個類 MyThread 讓它實現 Runnable 接口,並且要重寫 run() 方法(把需要多線程運行的程序放到 public void run() 方法里)。
啟動: 在主函數中,new 出 MyThread 類的實例,new 出Thread 類(帶有 target 的構造方法),把MyThread 類的實例作為參數傳入Thread 類的構造方法里。
運行: 調用 Thread 類的實例的 start() 方法即可。
/**
* 實現 Runnable接口, 重寫run()
*/
static class MyRun implements Runnable {
@Override
public void run() {
System.out.println(“Hello MyRun, 【implements Runnable】 !”);
}
}
public static void main(String[] args) {
new Thread(new MyRun()).start();
}
3、Lambda 表達式
書寫簡便
可以拋出異常(需要有try\catch)
public static void main(String[] args) {
new Thread(() -> {
System.out.println(“Hello 【Lambda】 !”);
}).start();
}
4、匿名內部類的方式(2種方式)
適用於創建啟動線程次數較少的環境,書寫更加簡便
無返回值、無法拋出異常
2種方式
方式①相當於繼承了Thread類,作為子類重寫run()實現
方式②相當於實現了Runnable接口,Runnable作為匿名內部類
public static void main(String[] args) {
//方式1: 相當於繼承了Thread類,作為子類重寫run()實現
new Thread() {
public void run() {
System.out.println(“【匿名內部類】創建線程方式1…”);
};
}.start();
//方式2: 相當於實現了Runnable接口,Runnable作為匿名內部類
new Thread(new Runnable() {
public void run() {
System.out.println(“【匿名內部類】創建線程方式2…”);
}
} ).start();
}
5、FutureTask + Callable
implements Callable<返回值類型>, @Override call()
帶返回值、可拋出異常
創建: 編寫一個類 MyThread 讓它實現 Callable 接口,並且實現 call() 方法,注意 call() 方法是有返回值的。
啟動: new 出Callable 接口的實現類MyCallable,new 出 FutureTask 類的實例 task,把call() 方法的返回值放入FutureTask 類的構造方法里,把 task 放入 new 出的 Thread 構造方法里。
運行: 調用 Thread 類的實例的 start() 方法即可。
public static void main(String[] args) {
new Thread(new FutureTask<String>(new MyCallWithoutV())).start();
new Thread(new FutureTask<>(new MyCallWithString())).start();
}
6、線程池的實現(2種方式)
降低了創建線程和銷毀線程的時間開銷
減少了資源浪費
返回的實際上是ExecutorService,而ExecutorService是Executor的子接口
方式①
Executors.newFixedThreadPool(線程數)
方式②
Executors.newCachedThreadPool()
public static void main(String[] args) {
//方式1
Executors.newFixedThreadPool(線程數)//創建帶有5個線程的線程池
ExecutorService threadPool_1 = Executors.newFixedThreadPool(5);
for(int i = 0 ;i < 10 ; i++) {
threadPool_1.execute(new Runnable() {
public void run() {
System.out.println(“【線程池實現】①
Executors.newFixedThreadPool(線程數), 線程”+Thread.currentThread().getName()+” is running”);}
});
}
threadPool_1.shutdown(); //優雅的關閉線程池, 用shutdown()
//方式2
Executors.newCachedThreadPool()ExecutorService threadPool_2 = Executors.newCachedThreadPool();
threadPool_2.execute(() -> {
System.out.println(“【線程池實現】②
Executors.newCachedThreadPool and Lambda !”);});
threadPool_2.shutdown(); //優雅的關閉線程池, 用shutdown()
}
值得注意的是:
方式2創建的是CachedThreadPool則不需要指定線程數量,線程數量多少取決於線程任務,不夠用則創建線程,夠用則回收。
這2種方式都有個很關鍵的問題,那就是:如果缺少了 shutdown() 銷毀線程池的話,即使程序運行完畢了,但是程序並沒有停止, 原因是 線程池沒有被銷毀。 如果沒有銷毀線程池,會浪費資源的。一般會選擇shutdown()來優雅關閉線程池的。
無論是哪種方式,返回的類型都是ExecutorService。作為Executor的子接口的ExecutorService才有shutdown()方法。
如果返回的是Executor類型,那麼是沒有shutdown()方法的。
Executor只有一個方法,那就是void execute(Runnable command)。
線程池的關閉方法(3個)
ExecutorService里有下面這3種關閉方法:
shutdown():停止接收新任務,原來的任務繼續執行
shutdownNow():停止接收新任務,原來的任務停止執行
awaitTermination(long timeOut, TimeUnit unit):當前線程阻塞
關閉功能“從強到弱”依次是:shuntdownNow() > shutdown() > awaitTermination()
1、優雅的關閉,用 shutdown()
2、想立馬關閉,並得到未執行任務列表,用 shutdownNow()
3、優雅的關閉,並允許關閉聲明後新任務能提交,用 awaitTermination()
線程池的種類(4種)
這裡只展示了FixedThreadPool和CachedThreadPool這兩種線程池,但是,還有其他兩種為SingleThreadPool和ScheduledThreadPool。
01.SingleThreadPool
該線程池只有一條線程來執行任務
應用場景:有順序的任務、任務量少,並且不需要並發執行
02.CachedThreadPool
可緩存的線程池
應用場景:耗時少、任務量大、處理任務速度 > 提交任務速度
03.FixedThreadPool
可重用的定長(固定線程數)的線程池
可控制線程最大並發數,超出的線程會在隊列中等待
可以通過控制最大線程來使服務器達到最大的使用率,又可以保證即時流量的突然增大也不會佔用服務器過多的資源。
應用場景:有其他條件限制或固定要求的任務量
04.ScheduledThreadPool
周期性執行任務的線程池
應用場景:執行周期性任務
7、定時器的方式
Java提供了定時器Timer,但是自帶的定時器有不可控的缺點
這種方式,當任務未執行完畢或我們每次想執行不同任務的時候,實現起來比較麻煩
建議使用作業調度框架quartz
import java.util.Timer;
import java.util.TimerTask;
public class CreateThreadUseTimer {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(“定時任務延遲0(即立刻執行),每隔1000ms執行一次”);
}
}, 0, 1000);
}
}
8、Spring實現多線程
1.Spring通過任務執行器TaskExecutor來實現多線程和並發編程。
2.使用TreadPoolTaskExecutor可實現一個基於線程池的TaskExecutor。
3.實際開發任務一般是非阻礙的,即異步的,所以我們要在配置類中通過@EnableAsync開啟對異步任務的支持,並通過在實際執行的Bean中的方法使用@Async註解來聲明這是一個異步任務。
0. 同步和異步
同步交互:指發送一個請求,需要等待返回,然後才能夠發送下一個請求,有個等待過程;
異步交互:指發送一個請求,不需要等待返回,隨時可以再發送下一個請求,即不需要等待。
區別:一個需要等待,一個不需要等待。在部分情況下,我們的項目開發中都會優先選擇不需要等待的異步交互方式。
1. 引入 Maven 依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.4.4</version>
</dependency>
</dependencies>
2. 異步執行的配置類 AsyncConfig
package com.melodyjerry.thread;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* @classname AsyncConfig
* @description 開啟異步執行的配置類
*/
@Configuration
@EnableAsync //開啟異步執行
@ComponentScan(“com.melodyjerry.thread”)
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
//線程池中的線程的名稱前綴
threadPoolTaskExecutor.setThreadNamePrefix(“SpringBoot線程池的前綴-“);
//線程池的核心線程數大小
threadPoolTaskExecutor.setCorePoolSize(4);
//線程池的最大線程數
threadPoolTaskExecutor.setMaxPoolSize(8);
//等待隊列的大小
threadPoolTaskExecutor.setQueueCapacity(25);
//執行初始化
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}
3. 異步任務的執行類
package com.melodyjerry.thread;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
* @classname AsyncTaskService
* @description 異步任務的執行類
*/
@Service
public class AsyncTaskService {
@Async //異步方法
public void executeAsyncTask(Integer i) {
System.out.println(“執行異步任務: “+i);
}
@Async //異步方法
public void executeAsyncTaskPlus(Integer i) {
System.out.println(“執行異步任務+1: ” + (i+1));
}
}
@Async註解表明該方法是個異步方法。
從Async註解接口可以看到,Target即可以在方法也可以在類型上,如果註解在類型上,表明該類所有的方法都是異步方法。
4. 測試效果 TestThreadApplication
package com.melodyjerry.thread;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @classname TestThreadApplication
* @description 測試異步任務
*/
@SpringBootApplication
public class TestThreadApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(TestThreadApplication.class, args);
AsyncTaskService asyncTaskService = context.getBean(AsyncTaskService.class);
for (int i = 0; i < 10; i++) {
asyncTaskService.executeAsyncTask(i);
asyncTaskService.executeAsyncTaskPlus(i);
}
System.out.println(“This Program has Begun successfully”);
}
}
This Program has Begun successfully
執行異步任務: 0
執行異步任務: 2
執行異步任務+1: 1
執行異步任務+1: 3
執行異步任務: 3
執行異步任務: 4
執行異步任務+1: 5
執行異步任務: 5
執行異步任務+1: 6
執行異步任務+1: 2
執行異步任務: 6
執行異步任務+1: 4
執行異步任務: 7
執行異步任務: 8
執行異步任務+1: 9
執行異步任務: 9
執行異步任務+1: 10
執行異步任務+1: 7
執行異步任務+1: 8
執行異步任務: 1
4種啟動方式
1.new Thread().start();
2.new Thread(Runnable).start();
3.Executors.newCachedThreadPool().execute()
注意:線程池的關閉
4.FutureTask + Callable
能直接調用Thread類的run()方法?
當然可以直接調用,但是如果我們調用了Thread的run()方法,它的行為就會和普通的方法一樣,是在當前線程執行
為了在新的線程中執行我們的代碼,必須使用Thread.start()方法。
原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/281402.html
微信掃一掃
支付寶掃一掃