1. 程式人生 > >第一章 併發程式設計執行緒基礎(一)

第一章 併發程式設計執行緒基礎(一)

第一章 併發程式設計執行緒基礎

1.1 什麼是執行緒

在討論什麼是執行緒之前,我們有必要先說一下什麼是程序,因為執行緒是程序中的一個實體,因為執行緒是不會獨立存在的。那麼何為程序?程序(Process)就是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎那麼何為執行緒?執行緒就是程序的一個執行路徑,是CPU排程和分派的基本單位,它是比程序更小的能獨立執行的基本單位。一個程序中至少含有一個執行緒,執行緒中的多個執行緒共享程序中的資源。


作業系統在分配 資源的時候是把資源分配給程序,但是CPU的這個資源是比較特殊的,它是被分配給了執行緒,因為真正要佔用CPU資源是執行緒,也就是說執行緒是CPU分配的基本單位

。eg:在Java中,當我們啟動了一個main函式的時候,就是啟動了一個JVM的程序,而main函式所在的執行緒就是這個程序裡面的一個執行緒,也稱為主執行緒。


程序和執行緒之間的關係可以如圖所示:
在這裡插入圖片描述
如上圖所示:我們可以看到一個程序裡面可以包含多個執行緒,多個執行緒可以共享程序裡面的堆和方法區的資源,而且每一個執行緒都有自己的程式計數器和棧區。
程式計數器是一塊記憶體的區域,用來記錄執行緒要執行的指令地址。另外每一個執行緒都有自己的棧資源,用來儲存該執行緒的區域性變數,這些區域性變數為該執行緒私有的,其他的執行緒是訪問不了的,除此之外,棧區域還可以用來存放執行緒的呼叫棧幀。
而程序裡面的堆區域

是程序中最大的一塊記憶體,堆裡面的資源是被所有的執行緒所共享,是在程序被建立的時候分配的,堆裡面主要是存放的是使用new關鍵字建立的例項物件。 而程序裡面的方法區則主要用來存放JVM載入的類、常量以及靜態變數等資訊,也是被所有執行緒所共享的。

1.2 執行緒的建立和執行

在Java中主要有3種方式來建立執行緒:

  1. 繼承Thread類,並且重寫run()方法;
  2. 實現Runnable介面,並且重寫run()方法;
  3. 使用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的方式則可以獲取到任務執行完成後的返回值。