Java多執行緒詳解(面試回顧)
1,什麼是多執行緒
一個程序中可以併發多個執行緒,每條執行緒並行執行不同的任務,多執行緒能滿足程式設計師編寫高效率的程式來達到充分利用 CPU 的目的。
2,執行緒的生命週期
執行緒的五種基本狀態
-
新建狀態(New):執行緒物件建立後,就是這種狀態。
-
就緒狀態(Runnable):也稱可執行狀態,線上程物件呼叫start()方法後,就進入就緒狀態,等待獲取CPU資源。
-
執行狀態(Running):執行狀態,執行緒真正的執行狀態,當獲取CPU資源後,物件呼叫run()方法後進入,就緒狀態是進入到執行狀態的唯一入口,也就是說,執行緒要想進入執行狀態執行,首先必須處於就緒狀態中。
-
阻塞狀態(Blocked):處於執行狀態中的執行緒由於某種原因,暫時放棄對CPU的使用權,停止執行,進入阻塞狀態,直到其再次進入就緒狀態,獲取到CPU資源,才可被再次執行,根據產生阻塞的原因,阻塞狀態可細劃分為三種。
-
等待阻塞:執行狀態中的執行緒執行wait()方法,進入等待狀態。
-
同步阻塞:執行緒在獲取synchronized同步鎖失敗(鎖被其他執行緒佔用),就會進入同步阻塞狀態。
-
其他阻塞:通過呼叫執行緒的sleep()或者join()發出I/O請求時,當sleep()狀態超時或join()等待執行緒終止或者超時時、或者I/O處理完畢時,執行緒就會重新進入就緒狀態。
-
-
死亡狀態(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的區別
-
callable的執行方法體是call(),而runnable是run()
-
callable的任務執行後可返回值,runnable不能
-
call()方法可以丟擲異常,run()方法不能
FutureTask類實際上是同時實現了Runnable和Future介面,由此才使得其具有Future和Runnable雙重特性。通過Runnable特性,可以作為Thread物件的target,而Future特性,使得其可以取得新建立執行緒中的call()方法的返回值。
4,使用執行緒池
在實際工作中,一般都使用執行緒池來建立執行緒,這樣的好處是:
-
執行緒的頻繁建立和銷燬對系統的資源是一種很大的佔用,使用執行緒池可重複利用已建立的執行緒。
-
可以控制執行緒的併發數,規定執行緒池的大小,可以防止大量執行緒因爭奪CPU資源而造成堵塞。
-
執行緒池提供更加靈活的執行緒管理,定時、定期、單執行緒等。
4,執行緒池的建立
執行緒池的結構:
-
Executor:負責執行緒的使用與排程的根介面
-
ExecutorService:Executor的子介面,執行緒池的主要介面
-
AbstractExecutorService:實現了ExecutorService介面,基本實現了ExecutorService其中宣告的所有方法,另有新增其他方法
-
ThreadPoolExecutor:繼承了AbstractExecutorService,主要的常用實現類
-
ScheduledExecutorService:繼承了ExecutorService,負責執行緒排程的介面
-
ScheduledThreadPoolExecutor:繼承了ThreadPoolExecutor同時實現了ScheduledExecutorService
Java為我們提供了一個類來建立執行緒池Executors,主要建立的四種類型執行緒池如下:
-
newCachedThreadPool:建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。
-
newFixedThreadPool:建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
-
newScheduledThreadPool:建立一個定長執行緒池,支援定時及週期性任務執行。
-
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-拒絕策略。