程序是系統進行資源配置設定和排程的基本機關。而線程是程式運作的最小機關。
一個程序裡面會有多個線程共享程序的資源,而這又會導緻一系列的資料安全和一緻性問題,就需要我們認真去學習并發體系的相關知識去保證臨界區資源的安全性。
那麼java裡面是怎麼實作多線程的呢?
java實作了這樣一個類,名叫Thread,是java的線程對象
進入到Thread源碼中可以看到源碼提供了兩種建立多線程的方式
方式一:繼承Thread類
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
PrimeThread p = new PrimeThread(143);
p.start();
方式二: 實作Runnable接口
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
将我們要運作的代碼寫到run()方法裡面,這樣建立的線程就會執行run()函數體裡面的代碼
使用繼承Thread類實作的話,缺陷就在于java的單繼承限制,而實作Runnable接口之後我們可以繼承其他的類來實作更多的功能,擴充性更好。
通過Thread源碼可以看到,有多種構造方法
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiQDOxEzX3xCZlhXam9VbsUmepNXZy9CXwJWZ3xCdh1mcvZ2Lc1zaHRGcWdUYuVzVa9GczoVdG1mWfVGc5RHLwIzX39GZhh2csATMflHLwEzX4xSZz91ZsAzMfRHLGZkRGZkRfJ3bs92YskmNhVTYykVNQJVMRhXVEF1X0hXZ0xiNx8VZ6l2cssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2ATNxIjYxYTZ5YTY0ImNzYzX1IjNzQTM5AzLcFTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
其中一個,參數為實作了Runnable接口的執行個體
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
我們可以看到,我們隻需要傳遞一個實作了Runnable接口的執行個體,就可以實作多線程。是以我随便找了一個實作了Runnable接口的類來實驗驗證。證明确實可以。
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
for(int i=1;i<=20;i++){
System.out.printf("I am TimerTask-1:%d\n",i);
}
}
};
Thread threadThree = new Thread(timerTask);
threadThree.start();
我們進一步檢視Thread的源碼,發現有這樣的字段
/* What will be run. */
private Runnable target;
在上面的那個構造函數中又會調用init()方法,而init()方法中又會有如下的代碼,将我們傳入的執行個體賦給Thread.target
this.target = target
而Thread 源碼中重寫了這個run()方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
是以說Thread.run()方法實際上還是使用的我們傳入的實作了Runnable接口的執行個體的run()方法
方式三:實作callable接口(有傳回值)
public static class CallbleTest implements Callable<Integer>{
@Override
public Integer call() throws Exception{
return new Random().nextInt();
}
}
//使用實作了Callable接口的類
CallbleTest callbleTest = new CallbleTest();
//下面這樣是錯誤的,Thread類裡面沒有Callable接口的類的構造方法,隻有Runnable接口的
Thread threadFive = new Thread(callbleTest);
threadFive.start();
但是這樣是錯誤的,因為Thread的構造函數是這樣子的,隻能向Thread傳遞實作了Runnable接口的執行個體
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
我們可以使用Future來接收多線程的執行結果。
//建立線程池
ExecutorService executor = Executors.newFixedThreadPool(10);
//送出任務,由future 傳回并發執行結果
Future<Integer> future = executor.submit(new CallbleTest());
System.out.println(future.isDone());
System.out.println(future.get());
System.out.println(future.isDone());
在《并發程式設計之美中》,作者還向我們推薦了FutureTask,可以在任務執行完畢後傳回一個結果。
其實FutureTask 實作了RunnableFuture接口( 繼承了Runnable 和Future接口),是以可以傳入Thread的構造方法。
//《并發程式設計之美》推薦
FutureTask<Integer> futureTask = new FutureTask<>(new CallbleTest());
new Thread(futureTask).start();
try{
int result = futureTask.get();
System.out.printf("利用futureTask執行實作了callable接口的線程,傳回值:%d",result);
}catch (ExecutionException e){
e.printStackTrace();
}
實驗代碼:
import java.util.Random;
import java.util.TimerTask;
import java.util.concurrent.*;
public class ThreadTest {
public static class MyThread extends Thread{
@Override
public void run(){
for(int i=1;i<=20;i++){
System.out.printf("I am MyThread:%d\n",i);
}
}
}
public static class RunnableTest implements Runnable{
@Override
public void run() {
for(int i=1;i<=20;i++){
System.out.printf("I am RunnableTest:%d\n",i);
}
}
}
public static class CallbleTest implements Callable<Integer>{
@Override
public Integer call() throws Exception{
return new Random().nextInt();
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyThread thread = new MyThread();
thread.start();
RunnableTest runnableTest = new RunnableTest();
Thread threadTwo = new Thread(runnableTest);
threadTwo.start();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
for(int i=1;i<=20;i++){
System.out.printf("I am TimerTask-1:%d\n",i);
}
}
};
TimerTask timerTaskCopy = new TimerTask() {
@Override
public void run() {
for(int i=1;i<=20;i++){
System.out.printf("I am TimerTask-2:%d\n",i);
}
}
};
Thread threadThree = new Thread(timerTask);
threadThree.start();
Thread threadFour = new Thread(timerTaskCopy);
threadFour.start();
// 使用實作了Callable接口的類
CallbleTest callbleTest = new CallbleTest();
// 下面這樣是錯誤的,Thread類裡面沒有Callable接口的類的構造方法,隻有Runnable接口的
// Thread threadFive = new Thread(callbleTest);
// threadFive.start();
//建立線程池
ExecutorService executor = Executors.newFixedThreadPool(10);
//送出任務,由future 傳回并發執行結果
Future<Integer> future = executor.submit(new CallbleTest());
System.out.printf("線程執行是否完畢:%b\n",future.isDone());
System.out.printf("線程執行結果:%d\n",future.get());
System.out.printf("線程執行是否完畢:%b\n",future.isDone());
//《并發程式設計之美》推薦
FutureTask<Integer> futureTask = new FutureTask<>(new CallbleTest());
new Thread(futureTask).start();
try{
int result = futureTask.get();
System.out.printf("利用futureTask執行實作了callable接口的線程,傳回值:%d",result);
}catch (ExecutionException e){
e.printStackTrace();
}
}
}
實驗結果如下,如果感覺不明顯,可以增加i周遊的範圍