第一章 併發程式設計執行緒基礎(一)
第一章 併發程式設計執行緒基礎
1.1 什麼是執行緒
在討論什麼是執行緒之前,我們有必要先說一下什麼是程序,因為執行緒是程序中的一個實體,因為執行緒是不會獨立存在的。那麼何為程序?程序(Process)就是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。那麼何為執行緒?執行緒就是程序的一個執行路徑,是CPU排程和分派的基本單位,它是比程序更小的能獨立執行的基本單位。一個程序中至少含有一個執行緒,執行緒中的多個執行緒共享程序中的資源。
作業系統在分配 資源的時候是把資源分配給程序,但是CPU的這個資源是比較特殊的,它是被分配給了執行緒,因為真正要佔用CPU資源是執行緒,也就是說執行緒是CPU分配的基本單位
程序和執行緒之間的關係可以如圖所示:
如上圖所示:我們可以看到一個程序裡面可以包含多個執行緒,多個執行緒可以共享程序裡面的堆和方法區的資源,而且每一個執行緒都有自己的程式計數器和棧區。
程式計數器是一塊記憶體的區域,用來記錄執行緒要執行的指令地址。另外每一個執行緒都有自己的棧資源,用來儲存該執行緒的區域性變數,這些區域性變數為該執行緒私有的,其他的執行緒是訪問不了的,除此之外,棧區域還可以用來存放執行緒的呼叫棧幀。
而程序裡面的堆區域
1.2 執行緒的建立和執行
在Java中主要有3種方式來建立執行緒:
- 繼承Thread類,並且重寫run()方法;
- 實現Runnable介面,並且重寫run()方法;
- 使用FutureTask方式,實現Callable介面;
首先,我們先來看一下繼承Thread類的這種方式:
public class ThreadTest {
/** 繼承Thread並重寫run方法 */
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello Thread!");
}
}
public static void main(String[]args){
/** 1.建立執行緒 */
MyThread myThread = new MyThread();
/** 2.呼叫start方法啟動執行緒 */
myThread.start();
}
}
從上面的這段程式碼,我們可以看出:MyThread 繼承了Thread 類,並且重寫了Thread裡面的run方法,在main函式裡面建立MyThread 的例項,並且呼叫了例項的start方法來啟動執行緒,其實在呼叫start方法之後,執行緒不會立即處於執行的狀態,而是噹噹前執行緒分配到CPU資源的時候,才會真正的處於執行的狀態,當run()方法執行完畢了,該執行緒才會處於終止的狀態。
- 使用繼承的這種建立執行緒的好處就是:在run()方法裡面獲取當前的執行緒可以直接用this關鍵字來進行獲取,而可以不用Thread.currentThread()來進行獲取。
- 不好的地方就是因為Java不支援多繼承,如果繼承了Thread類,就不能繼承其他的類。而且多個執行緒執行同樣的程式碼的時候,需要new多個MyThread 的例項。
實現Runnable介面的方式來建立執行緒:
程式碼如下:
public class ThreadTest {
/** 繼承Thread並重寫run方法 */
public static class RunnableTask implements Runnable{
@Override
public void run() {
System.out.println("Hello Thread!");
Thread.currentThread();
}
}
public static void main(String[]args){
/** 1.建立實現Runnable介面這個類的例項 */
RunnableTask runnableTask = new RunnableTask();
/** 2.建立Thread的這個例項,並且runnableTask傳入,把呼叫start方法啟動執行緒 */
new Thread(runnableTask).start();
new Thread(runnableTask).start();
}
}
- 上面的這種建立方式共用了一個runnableTask例項裡面的run()方法裡面的邏輯,而且RunnableTask的這個類還可以繼承其他的類。
- 以上兩種建立方式都有一個共同的特點:那就沒有返回值。
使用FutureTask方式,實現Callable介面;
程式碼如下:
public class ThreadTest {
/** 繼承Thread並重寫run方法 */
public static class CallerTask implements Callable<String> {
@Override
public String call() throws Exception {
return "hello";
}
}
public static void main(String[]args){
/** 1.建立非同步任務 */
FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
/** 2.建立Thread例項,並啟動執行緒 */
new Thread(futureTask).start();
/** 3.等待執行緒執行完畢,並返回執行結果 */
String result = null;
try {
result = futureTask.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
- 如上程式碼所示:CallerTask類實現了Callable介面,並且重寫了call()方法。在main方法裡面建立了一個FutureTask例項,然後使用建立好的FutureTask例項作為任務建立執行緒並且啟動它,最後,我們可以通過futureTask.get()來獲取執行完畢的返回結果;
小結:
- 使用繼承的方式的好處就是方便傳參,我們可以在子類裡面新增成員變數,然後可以通過set方法的方式或者是用構造方法的方式來進行引數的傳遞,不好的地方就是Java不支援多繼承,如果繼承了Thread類,那麼子類則不能繼承其他的類。
- 而如果使用了實現Runnable介面的方式,則只能使用主執行緒裡面被宣告為final的變數,而且還可以是繼承其他的類。
- 使用FutureTask的方式則可以獲取到任務執行完成後的返回值。