Java併發03:多執行緒實現三方式:繼承Thread類、實現Runnable介面、實現Callable介面
阿新 • • 發佈:2019-01-31
本章主要對Java多執行緒實現的三種方式進行學習。
1.序言
在JDK5版本之前,提供了兩種多執行緒的實現方式:
- 繼承Thread類,重寫run()方法
- 實現Runnable介面,實現run()方法
這兩種種方式的本質都是一個:實現Runnable介面。
在JDK5版本時,提供了一種新的多執行緒實現方式:
- Future介面+Callable介面+Executor介面
下面分別對這三種實現方式進行學習。
2.實現Runnable介面
2.1.Runnable介面定義
我們先來看以下Runnable介面的定義:
package java.lang;
/**
* The <code>Runnable</code> interface should be implemented by any
* class whose instances are intended to be executed by a thread. The
* class must define a method of no arguments called <code>run</code>.
* <p>
* This interface is designed to provide a common protocol for objects that
* wish to execute code while they are active. For example,
* <code>Runnable</code> is implemented by class <code>Thread</code>.
* Being active simply means that a thread has been started and has not
* yet been stopped.
* <p>
* ...
*/
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
*/
public abstract void run();
}
總結:
- 如果希望一個類的例項被當做一個執行緒執行,那麼這個類必須實現Runnable介面。
- Runnable介面被設計成執行緒設計的一種公共協議。
- Thread類就是一種Runnable介面的實現。
- 當一個實現了Runnable介面的執行緒類開始執行,就會自動呼叫run()方法。
- 實現Runnable介面,必須重寫run()方法。
- 建議在run()方法中存放所有的業務程式碼,做到執行緒控制與業務流程的分離。
- run()方法返回型別為Void,引數為空。
- run()方法不能丟擲異常。
2.2.通過實現Runnable介面實現執行緒
實現Runnable實現執行緒的語法:
//定義
public class MyRunnableImpl implements Runnable {
@Override
public void run(){
//..
}
}
//使用
new Thread(new MyRunnableImpl()).start();
說明:
- Runnable介面中只定義了run()方法,其他屬性和方法,如name等,需要自己去定義。
- Runnable實現類本身並不能啟動,需要Thread()類的協助。
例項場景:
建立5個執行緒,每個執行緒隨機執行一段時間後結束。
例項程式碼:
/**
* <p>自定義執行緒02:實現Runnable介面</p>
* @author hanchao 2018/3/8 23:04
**/
public class MyRunnableImpl implements Runnable{
private static final Logger LOGGER = Logger.getLogger(MyRunnableImpl.class);
/** 執行緒名(需要手動指定) */
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public MyRunnableImpl(String name) {
this.name = name;
}
/**
* <p>義務程式碼在run()方法中,此方法無返回值</p>
* @author hanchao 2018/3/8 23:07
**/
@Override
public void run() {
Integer interval = RandomUtils.nextInt(1000,5000);
LOGGER.info("執行緒[" + this.getName() + "]正在執行,預計執行" + interval + "...");
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
LOGGER.info("...執行緒[" + this.getName() + "]執行結束");
}
}
/**
* <p>自定義執行緒實現類測試</p>
* @author hanchao 2018/3/8 23:07
**/
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
//通過new建立一個執行緒
Runnable runnable = new MyRunnableImpl("MyRunnableImpl-" + i);
//通過new Thread.start()啟動執行緒
new Thread(runnable).start();
}
}
}
執行結果:
2018-03-12 10:11:19 INFO MyRunnableImpl:40 - 執行緒[MyRunnableImpl-0]正在執行,預計執行1442...
2018-03-12 10:11:19 INFO MyRunnableImpl:40 - 執行緒[MyRunnableImpl-1]正在執行,預計執行1250...
2018-03-12 10:11:19 INFO MyRunnableImpl:40 - 執行緒[MyRunnableImpl-2]正在執行,預計執行3603...
2018-03-12 10:11:19 INFO MyRunnableImpl:40 - 執行緒[MyRunnableImpl-3]正在執行,預計執行3290...
2018-03-12 10:11:19 INFO MyRunnableImpl:40 - 執行緒[MyRunnableImpl-4]正在執行,預計執行3758...
2018-03-12 10:11:20 INFO MyRunnableImpl:46 - ...執行緒[MyRunnableImpl-1]執行結束
2018-03-12 10:11:21 INFO MyRunnableImpl:46 - ...執行緒[MyRunnableImpl-0]執行結束
2018-03-12 10:11:22 INFO MyRunnableImpl:46 - ...執行緒[MyRunnableImpl-3]執行結束
2018-03-12 10:11:23 INFO MyRunnableImpl:46 - ...執行緒[MyRunnableImpl-2]執行結束
2018-03-12 10:11:23 INFO MyRunnableImpl:46 - ...執行緒[MyRunnableImpl-4]執行結束
3.繼承Thread類
3.1.Thread類定義
先來看Thread類的定義:
/**
* A <i>thread</i> is a thread of execution in a program. The Java
* Virtual Machine allows an application to have multiple threads of
* execution running concurrently.
* <p>
* ...
*/
public
class Thread implements Runnable {
//...
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* ...
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
說明:
- Thread執行緒類實際上是實現的Runnable介面。
3.2.通過繼承Thread類實現執行緒
語法:
//定義
public class MyThread extends Thread {
@Override
public void run(){
//..
}
}
//使用
new MyThread().start();
說明:
- 相較於Runnable類,Thread類提供了一系列方法(後續章節中會學習),都可以在自定義執行緒中通過
super
來呼叫。 - 雖然Thread類提供了一些方法,簡化了執行緒開發。但是通過繼承的方式實現執行緒,會增加程式的耦合性,不利於維護。
例項場景:
建立5個執行緒,每個執行緒隨機執行一段時間後結束。
例項程式碼:
/**
* <p>自定義執行緒01:繼承自Thread</p>
* @author hanchao 2018/3/8 22:54
**/
public class MyThread extends Thread {
private static final Logger LOGGER = Logger.getLogger(MyThread.class);
/**
* <p>重寫Thread類的構造器,用以給執行緒命名</br>
* 此種方式無需定義name變數以指定執行緒名,因為父類Thread中已有。</p>
* @author hanchao 2018/3/8 22:59
**/
public MyThread(String name) {
super(name);
}
/**
* <p>業務程式碼寫在run()方法中,此方法無返回值</p>
* @author hanchao 2018/3/8 22:55
**/
@Override
public void run(){
//run()方法無法丟擲異常
// public void run() throws Exception{
Integer interval = RandomUtils.nextInt(1000,9000);
LOGGER.info("執行緒[" + super.getName() + "]正在執行,預計執行" + interval + "...");
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
LOGGER.info("...執行緒[" + super.getName() + "]執行結束");
}
}
/**
* <p>測試自定義執行緒</p>
* @author hanchao 2018/3/8 22:57
**/
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
//通過new建立一個執行緒
Thread thread = new MyThread("MyThread-" + i);
//通過start()啟動執行緒
thread.start();
}
}
}
執行結果:
2018-03-12 10:18:35 INFO MyThread:31 - 執行緒[MyThread-3]正在執行,預計執行6561...
2018-03-12 10:18:35 INFO MyThread:31 - 執行緒[MyThread-4]正在執行,預計執行7464...
2018-03-12 10:18:35 INFO MyThread:31 - 執行緒[MyThread-1]正在執行,預計執行4806...
2018-03-12 10:18:35 INFO MyThread:31 - 執行緒[MyThread-2]正在執行,預計執行5214...
2018-03-12 10:18:35 INFO MyThread:31 - 執行緒[MyThread-0]正在執行,預計執行8557...
2018-03-12 10:18:40 INFO MyThread:37 - ...執行緒[MyThread-1]執行結束
2018-03-12 10:18:40 INFO MyThread:37 - ...執行緒[MyThread-2]執行結束
2018-03-12 10:18:42 INFO MyThread:37 - ...執行緒[MyThread-3]執行結束
2018-03-12 10:18:43 INFO MyThread:37 - ...執行緒[MyThread-4]執行結束
2018-03-12 10:18:44 INFO MyThread:37 - ...執行緒[MyThread-0]執行結束
4.實現Callable介面
4.1.JDK5之前執行緒實現的弊端
先來分析之前兩種實現方式的弊端。
通過分析Runnable介面的定義,很容易總結出來:
- 沒有返回值:如果想要獲取某個執行結果,需要通過共享變數等方式,需要做更多的處理。
- 無法丟擲異常:不能宣告式的丟擲異常,增加了某些情況下的程式開發複雜度。
- 無法手動取消執行緒:只能等待執行緒執行完畢或達到某種結束條件,無法直接取消執行緒任務。
為了解決以上的問題,在JDK5版本的java.util.concurretn包中,引入了新的執行緒實現機制:Callable介面。
4.2.Callable介面定義
我們先來看一下Callable介面的語法:
package java.util.concurrent;
/**
* A task that returns a result and may throw an exception.
* Implementors define a single method with no arguments called
* {@code call}.
*
* <p>The {@code Callable} interface is similar to {@link
* java.lang.Runnable}, in that both are designed for classes whose
* instances are potentially executed by another thread. A
* {@code Runnable}, however, does not return a result and cannot
* throw a checked exception.
* ...
*/
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
總結:
- Callable介面更傾向於針對任務這個概念。一個任務其實就是一個執行緒。
- Callable介面實現的任務必須返回一個結果並且丟擲一個異常。
- Callable介面的實現類需要重寫一個無參的方法Call()。
- Callable介面與Runnable介面類似,都是為了實現多執行緒而設計的。
- Runnable介面沒有返回值,也無法丟擲異常。
- Callable介面是一個泛型介面。
4.3.通過實現Callable介面實現執行緒
實現Callable介面的語法:
//定義
public class MyCallableImpl implements Callable<T> {
@Override
public T call() throws Exception {
//...
}
}
//使用
//一般配置Executor使用,Executor提供執行緒池服務
ExecutorService executor = new ....
//一般配置Future介面使用,Future用於儲存返回結果
//向執行緒池提交一個任務,並把此任務的執行情況儲存在future中
Futrue future = executor.submit(new MyCallableImple());
//獲取返回結果
future.get();
//關閉執行緒池和任務
executor.shutdwon();
說明:
- Future、Callable一般與Executor結合使用。
- Callable介面用於定義任務類,並在Call()方法中定義業務程式碼。
- Executor介面負責執行緒池的管理(計劃在後續章節進行學習)。
- Future介面負責保持在Executor執行緒池中執行的Callable任務的執行狀態。
- Callable介面實現類,通過executor.submit()向執行緒池提交任務。
- Future介面通過get()方法獲取執行結果(一直等待知道結果產生)。
- 一定要記得通過executor.shutdwon()關閉執行緒池。推薦在finally中進行這個操作。
例項場景:
定義一個最大執行緒數為5的執行緒池,執行5個任務,獲取並打印出每個執行緒的執行時間。
例項程式碼:
/**
* <p>自定義執行緒03:實現Callable介面</p>
*
* @author hanchao 2018/3/12 8:56
**/
//注意,Callable是一個泛型介面
public class MyCallableImpl implements Callable<Integer> {
private static final Logger LOGGER = Logger.getLogger(MyCallableImpl.class);
/**
* <p>實現Callable需要重寫call方法,此方法有返回值</p>
*
* @author hanchao 2018/3/12 8:59
**/
@Override
public Integer call() throws Exception {
Integer interval = RandomUtils.nextInt(1000, 5000);
Thread.sleep(interval);
return interval;
}
/**
* <p>實現Callable示例</p>
*
* @author hanchao 2018/3/12 9:00
**/
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
//Future、Callable一般與Executor結合使用
//Executor負責建立執行緒池服務
//實現Callable介面形成的執行緒類,負責處理業務邏輯,並將處理結果返回
//Future介面負責接收Callable介面返回的值
ExecutorService executor = Executors.newFixedThreadPool(5);
try {
//定義一組返回值
Future<Integer>[] futures = new Future[5];
//向執行緒池提交任務
for (int i = 0; i < 5; i++) {
//注意Future的引數化型別要與Callable的引數化型別一致
futures[i] = executor.submit(new MyCallableImpl());
}
//輸出執行結果
for (int i = 0; i < 5; i++) {
LOGGER.info(futures[i].get());
}
}finally {//將關閉執行緒池放在finally中,最大限度保證執行緒安全
//記得關閉這個執行緒池
executor.shutdown();
}
}
}
執行結果:
2018-03-12 10:44:28 INFO MyCallableImpl:50 - 1564
2018-03-12 10:44:30 INFO MyCallableImpl:50 - 2992
2018-03-12 10:44:30 INFO MyCallableImpl:50 - 1629
2018-03-12 10:44:30 INFO MyCallableImpl:50 - 1454
2018-03-12 10:44:30 INFO MyCallableImpl:50 - 1941
4.4.更多的Future方法
前面只展示了Future類的get()方法。
為了更詳細的體現[實現Callable介面]這種方式的優點,對Future介面的其他方法進行簡單學習。
Future介面的主要方法如下:
- isDone():判斷任務是否完成。
- isCancelled():判斷任務是否取消。
- get():獲取計算結果(一致等待,直至得到結果)。
- cancel(true):取消任務。
- get(long,TimeUnit):規定時間內獲取計算結果(在long時間內等待結果,如果得到則返回;如果未得到,則結束,並丟擲TimeoutException異常)。
說明:
- get(long,TimeUnit)的第一個引數是最大超時時間,第二個是時間單位,可以通過enum TimeUnit獲取。
下面通過兩段程式碼進行例項學習。
程式碼段1:學習了isDone、isCancelled、get()方法
System.out.println();
//Future介面方法簡單展示: isDone/isCancelled/get()
//建立單執行緒池
ExecutorService executor1 = Executors.newSingleThreadExecutor();
//向執行緒池提交任務
Future<Integer> future = executor1.submit(new MyCallableImpl());
try {
//計算執行時間
Long begin = System.currentTimeMillis();
LOGGER.info("future開始執行任務...當前時間:" + begin);
LOGGER.info("通過future.isDone()判斷任務是否計算完成:" + future.isDone());
LOGGER.info("通過future.isCancelled()判斷任務是否取消:" + future.isCancelled());
LOGGER.info("通過future.get()獲取任務的計算結果(從任務開始就一直等待,直至有返回值):" + future.get());
LOGGER.info("future結束執行任務...共計用時:" + (System.currentTimeMillis() - begin) + "ms..\n");
}finally {//將關閉執行緒池放在finally中,最大限度保證執行緒安全
LOGGER.info("通過future.isDone()判斷任務是否計算完成:" + future.isDone());
LOGGER.info("通過future.isCancelled()判斷任務是否取消:" + future.isCancelled());
//記得關閉這個執行緒池
executor1.shutdown();
}
程式碼段1執行結果:
2018-03-12 11:02:28 INFO MyCallableImpl:66 - future開始執行任務...當前時間:1520823748695
2018-03-12 11:02:28 INFO MyCallableImpl:67 - 通過future.isDone()判斷任務是否計算完成:false
2018-03-12 11:02:28 INFO MyCallableImpl:68 - 通過future.isCancelled()判斷任務是否取消:false
2018-03-12 11:02:31 INFO MyCallableImpl:69 - 通過future.get()獲取任務的計算結果(從任務開始就一直等待,直至有返回值):2830
2018-03-12 11:02:31 INFO MyCallableImpl:70 - future結束執行任務...共計用時:2843ms..
2018-03-12 11:02:31 INFO MyCallableImpl:72 - 通過future.isDone()判斷任務是否計算完成:true
2018-03-12 11:02:31 INFO MyCallableImpl:73 - 通過future.isCancelled()判斷任務是否取消:false
程式碼段2:學習了isDone、isCancelled、cancel()、get(long,TimeUnit)方法
//get(long,TimeUnit):最多等待多長時間就不再等待
//建立單執行緒池
ExecutorService executor2 = Executors.newSingleThreadExecutor();
//向執行緒池提交任務
Future<Integer> future2 = executor2.submit(new MyCallableImpl());
Long begin2 = System.currentTimeMillis();
try {
LOGGER.info("future開始執行任務...當前時間:" + begin2);
LOGGER.info("通過future.get(long,TimeUnit)獲取任務的計算結果(5秒鐘之後再獲取結果):" + future2.get(500,TimeUnit.MILLISECONDS));
LOGGER.info("future結束執行任務...共計用時:" + (System.currentTimeMillis() - begin2) + "ms..\n");
}catch (TimeoutException e){
LOGGER.info("在限定時間內沒有等到查詢結果,不再等待..");
//關閉任務
LOGGER.info("當前任務狀態:future2.isDone() = " + future2.isDone());
LOGGER.info("當前任務狀態:future2.isCancelled() = " + future2.isCancelled());
LOGGER.info("通過future.cancel()取消這個任務:");
future2.cancel(true);
LOGGER.info("當前任務狀態:future2.isDone() = " + future2.isDone());
LOGGER.info("當前任務狀態:future2.isCancelled() = " + future2.isCancelled());
//關閉執行緒池
executor2.shutdown();
//意料之中的結果,無需列印日誌
//e.printStackTrace();
}
程式碼段2執行結果:
2018-03-12 11:03:15 INFO MyCallableImpl:85 - future開始執行任務...當前時間:1520823795611
2018-03-12 11:03:16 INFO MyCallableImpl:89 - 在限定時間內沒有等到查詢結果,不再等待..
2018-03-12 11:03:16 INFO MyCallableImpl:91 - 當前任務狀態:future2.isDone() = false
2018-03-12 11:03:16 INFO MyCallableImpl:92 - 當前任務狀態:future2.isCancelled() = false
2018-03-12 11:03:16 INFO MyCallableImpl:93 - 通過future.cancel()取消這個任務:
2018-03-12 11:03:16 INFO MyCallableImpl:95 - 當前任務狀態:future2.isDone() = true
2018-03-12 11:03:16 INFO MyCallableImpl:96 - 當前任務狀態:future2.isCancelled() = true