1. 程式人生 > >Java多執行緒詳解(面試回顧)

Java多執行緒詳解(面試回顧)

1,什麼是多執行緒

一個程序中可以併發多個執行緒,每條執行緒並行執行不同的任務,多執行緒能滿足程式設計師編寫高效率的程式來達到充分利用 CPU 的目的。

2,執行緒的生命週期

 

執行緒的五種基本狀態

  1. 新建狀態(New):執行緒物件建立後,就是這種狀態。

  2. 就緒狀態(Runnable):也稱可執行狀態,線上程物件呼叫start()方法後,就進入就緒狀態,等待獲取CPU資源。

  3. 執行狀態(Running):執行狀態,執行緒真正的執行狀態,當獲取CPU資源後,物件呼叫run()方法後進入,就緒狀態是進入到執行狀態的唯一入口,也就是說,執行緒要想進入執行狀態執行,首先必須處於就緒狀態中。

  4. 阻塞狀態(Blocked):處於執行狀態中的執行緒由於某種原因,暫時放棄對CPU的使用權,停止執行,進入阻塞狀態,直到其再次進入就緒狀態,獲取到CPU資源,才可被再次執行,根據產生阻塞的原因,阻塞狀態可細劃分為三種。

    • 等待阻塞:執行狀態中的執行緒執行wait()方法,進入等待狀態。

    • 同步阻塞:執行緒在獲取synchronized同步鎖失敗(鎖被其他執行緒佔用),就會進入同步阻塞狀態。

    • 其他阻塞:通過呼叫執行緒的sleep()或者join()發出I/O請求時,當sleep()狀態超時或join()等待執行緒終止或者超時時、或者I/O處理完畢時,執行緒就會重新進入就緒狀態。

  5. 死亡狀態(Dead):執行緒執行完了或者因為異常退出了run()方法,該執行緒生命週期結束。

3,執行緒的建立

1,繼承Thread類

繼承Thread類,重寫該類的run()方法,不推薦,由於類的單繼承性,不利於類的擴充套件。

package com.pactera.test;
​
public class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}
package com.pactera.test;
​
public class ThreadTest1 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "+++" + i);
            if (i == 3) {
                //建立執行緒1
                Thread thread1 = new MyThread1();
                //建立執行緒2
                Thread thread2 = new MyThread1();
                //執行緒1進入就緒狀態
                thread1.start();
                //執行緒2進入就緒狀態
                thread2.start();
            }
        }
    }
}

2,實現Runnable介面

實現Runnable介面,並重寫該介面的run()方法,該run()方法同樣是執行緒執行體,建立Runnable實現類的例項,並以此例項作為Thread類的target來建立Thread物件,該Thread物件才是真正的執行緒物件。

package com.pactera.test;
​
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        //執行緒的方法執行體
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}
package com.pactera.test;
​
public class ThreadTest1 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "+++" + i);
            if (i == 3) {
                //建立例項
                MyRunnable myRunnable = new MyRunnable();
                //Thread物件才是真正的例項
                //建立執行緒1
                Thread thread1 = new Thread(myRunnable);
                //建立執行緒2
                Thread thread2 = new Thread(myRunnable);
                //執行緒1進入就緒狀態
                thread1.start();
                //執行緒2進入就緒狀態
                thread2.start();
            }
        }
    }
}

本質上Thread也實現了Runnable介面,並在run()方法裡進行了一個判斷

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

這裡的target就是一開始傳入的myRunnable,所以這裡run()方法執行的依然是MyRunnable類裡面的run()方法。

3,實現Callable介面

建立Callable介面的實現類,並實現clall()方法。並使用FutureTask類來包裝Callable實現類的物件,且以此FutureTask物件作為Thread物件的target來建立執行緒。

package com.pactera.test;
​
import java.util.concurrent.Callable;
​
public class MyCallable implements Callable<Integer> {
​
​
    @Override
    public Integer call() throws Exception {
        int j = 0;
        for (int i = 0; i < 10; i++) {
            j++;
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
        return j;
    }
}
package com.pactera.test;
​
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
​
public class ThreadTest1 {
    public static void main(String[] args) {
        //建立例項
        MyCallable myCallable = new MyCallable();
        //使用FutureTask類包裝
        FutureTask<Integer> future1 = new FutureTask<Integer>(myCallable);
        FutureTask<Integer> future2 = new FutureTask<Integer>(myCallable);
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "+++" + i);
            if (i == 3) {
                //建立執行緒1
                Thread thread1 = new Thread(future1);
                //建立執行緒2
                Thread thread2 = new Thread(future2);
                //執行緒1進入就緒狀態
                thread1.start();
                //執行緒2進入就緒狀態
                thread2.start();
            }
        }
​
        //獲取新建立的執行緒中call()方法返回的結果
        try {
            //在所有的執行緒執行完成之後這裡才會執行,否則就是阻塞狀態,所以結果會一致
            Integer i1 = future1.get();
            Integer i2 = future2.get();
            System.out.println("執行緒1返回的值為:" + i1);
            System.out.println("執行緒2返回的值為:" + i2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

callable與runnable的區別

  1. callable的執行方法體是call(),而runnable是run()

  2. callable的任務執行後可返回值,runnable不能

  3. call()方法可以丟擲異常,run()方法不能

FutureTask類實際上是同時實現了Runnable和Future介面,由此才使得其具有Future和Runnable雙重特性。通過Runnable特性,可以作為Thread物件的target,而Future特性,使得其可以取得新建立執行緒中的call()方法的返回值。

4,使用執行緒池

在實際工作中,一般都使用執行緒池來建立執行緒,這樣的好處是:

  • 執行緒的頻繁建立和銷燬對系統的資源是一種很大的佔用,使用執行緒池可重複利用已建立的執行緒。

  • 可以控制執行緒的併發數,規定執行緒池的大小,可以防止大量執行緒因爭奪CPU資源而造成堵塞。

  • 執行緒池提供更加靈活的執行緒管理,定時、定期、單執行緒等。

4,執行緒池的建立

執行緒池的結構:

  1. Executor:負責執行緒的使用與排程的根介面

  2. ExecutorService:Executor的子介面,執行緒池的主要介面

  3. AbstractExecutorService:實現了ExecutorService介面,基本實現了ExecutorService其中宣告的所有方法,另有新增其他方法

  4. ThreadPoolExecutor:繼承了AbstractExecutorService,主要的常用實現類

  5. ScheduledExecutorService:繼承了ExecutorService,負責執行緒排程的介面

  6. ScheduledThreadPoolExecutor:繼承了ThreadPoolExecutor同時實現了ScheduledExecutorService

Java為我們提供了一個類來建立執行緒池Executors,主要建立的四種類型執行緒池如下:

  1. newCachedThreadPool:建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。

  2. newFixedThreadPool:建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。

  3. newScheduledThreadPool:建立一個定長執行緒池,支援定時及週期性任務執行。

  4. newSingleThreadExecutor:建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。

但是不推薦使用這種方式來建立執行緒池,因為在建立時無法傳入一些核心的配置引數,全都使用預設的,容易造成系統資源浪費,比較好的做法是使用ThreadPoolExecutor來建立,它提供的有參構造如下。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  • corePoolSize - 執行緒池核心池的大小,預設情況下核心執行緒會一直存活,即使處於閒置狀態也不會受存keepAliveTime限制。除非將allowCoreThreadTimeOut設定為true

  • maximumPoolSize - 執行緒池的最大執行緒數。

  • keepAliveTime - 當執行緒數大於核心時,此為終止前多餘的空閒執行緒等待新任務的最長時間。

  • unit - keepAliveTime 的時間單位。

  • workQueue - 用來儲存等待執行任務的佇列。

  • threadFactory - 執行緒工廠。

  • handler-拒絕策略。