本博客系列是學習並發編程過程中的記錄總結。由於文章比較多,寫的時間也比較散,所以我整理了個目錄貼(傳送門),方便查閱。
在Java中有多種方式可以實現多線程編程(記得這是一道常問的面試題,特別是在應屆生找工作的時候被問的頻率就更高了)。
- 繼承Thread類並重寫run方法;
- 實現Runnable接口,並將這個類的實例當做一個target構造Thread類
- 實現Callable接口;
繼承Thread類
通過繼承Thread類來實現多線程編程很容易。下面代碼中MyThread
類繼承了Thread
類,並重寫了run方法。
但是這種方式不是很建議使用,其中最主要的一個原因就是Java是單繼承模式,MyThread
類繼承了Thread
類之后就不能再繼承其他類了。另外任務與代碼沒有分離,當多個線程執行一樣的任務時需要多份任務代碼。所以使用implement的形式比繼承的方式更好。后面會講到使用Runnable接口實現多線程。
public class MyThread extends Thread {
public static final int THREAD_COUNT = 5;
public static void main(String[] args) {
List<Thread> threadList = new ArrayList<>();
for (int i = 0; i < THREAD_COUNT; i++) {
Thread thread = new MyThread();
thread.setName("myThread--"+i);
threadList.add(thread);
}
threadList.forEach(var->{var.start();});
}
@Override
public void run() {
super.run();
System.out.println("my thread name is:"+Thread.currentThread().getName());
Random random = new Random();
int sleepTime = random.nextInt(5);
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+" end after "+sleepTime+" seconds");
}
}
}
實現Runnable接口實現多線程
下面我們就通過實現Runnable接口的形式來改造下上面的代碼。
可以發現,通過實現Runnable接口實現多線程編程也非常方便。但是不需要再繼承Thread類,減少了耦合。同時new了一個Runner對象后,這個對象可以比較方便地在各個線程之間共享。因此相對於繼承Thread的方式,更加推薦使用Runnable接口的方式實現多線程編程。
public class MyThread {
public static final int THREAD_COUNT = 5;
public static void main(String[] args) {
List<Thread> threadList = new ArrayList<>();
Runner runner = new Runner();
for (int i = 0; i < THREAD_COUNT; i++) {
Thread thread = new Thread(runner);
thread.setName("myThread--"+i);
threadList.add(thread);
}
threadList.forEach(var->{var.start();});
}
public static class Runner implements Runnable{
@Override
public void run() {
System.out.println("my thread name is:"+Thread.currentThread().getName());
Random random = new Random();
int sleepTime = random.nextInt(5);
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+" end after "+sleepTime+" seconds");
}
}
}
}
實現Callable接口
上面介紹了兩種方式都可以很方便地實現多線程編程。但是這兩種方式也有幾個很明顯的缺陷:
- 沒有返回值:如果想要獲取某個執行結果,需要通過共享變量等方式,需要做更多的處理。
- 無法拋出異常:不能聲明式的拋出異常,增加了某些情況下的程序開發復雜度。
- 無法手動取消線程:只能等待線程執行完畢或達到某種結束條件,無法直接取消線程任務。
為了解決以上的問題,在JDK5版本的java.util.concurretn
包中,引入了新的線程實現機制:Callable接口。
@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接口的介紹,其實這個接口的功能是和Runnable一樣的,和Runnable接口最主要區別就是:
- Callable接口可以有返回值;
- Callable接口可以拋出異常;
下面通過使用Callable接口的方式來改造下上面的代碼:
public class MyThread {
public static final int THREAD_COUNT = 5;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
Runner runner = new Runner();
for (int i = 0; i < THREAD_COUNT; i++) {
Future<Integer> submit = executorService.submit(runner);
//get方法會一直阻塞等到線程執行結束
System.out.println(submit.get());
}
executorService.shutdown();
}
public static class Runner implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("my thread name is:"+Thread.currentThread().getName());
Random random = new Random();
int sleepTime = random.nextInt(500);
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+" end after "+sleepTime+" seconds");
}
return sleepTime;
}
}
}
上面代碼中,我們使用Future
類來獲取返回結果。Future接口的主要方法如下:
- isDone():判斷任務是否完成。
- isCancelled():判斷任務是否取消。
- get():獲取計算結果(一致等待,直至得到結果)。
- cancel(true):取消任務。
- get(long,TimeUnit):規定時間內獲取計算結果(在long時間內等待結果,如果得到則返回;如果未得到,則結束,並拋出TimeoutException異常)。