面試必備——Java多執行緒與併發(一)
阿新 • • 發佈:2021-03-01
1.程序和執行緒的
(1)由來
1)序列
最初的計算機只能接受一些特定的指令,使用者輸入一個指令,計算機就做出一個操作。當用戶在思考或者輸入時,計算機就在等待。顯然這樣效率低下,在很多時候,計算機都處在等待狀態。2)批處理
提高計算機的效率,不用等待使用者的輸入,把一系列需要操作的指令寫下來,形成一個清單,一次性交給計算機,計算機不斷讀取指令進行相應的操作,就這樣,批處理作業系統誕生了。使用者將多個需要執行的程式寫在磁帶上,然後交由計算機去讀取並逐個執行這些程式,並將輸出結果寫在另一個磁帶上。存在問題:
當有兩個任務A和B,任務A執行到一半的過程中,需要讀取大量的資料輸入(I/O操作),而此時CPU只能等任務A讀取完資料再能繼續進行,這樣就白白浪費了CPU資源。於是人們就想,能否在任務A讀取資料的過程中,讓任務B去執行,當任務A讀取完資料之後,暫停任務B,讓任務A繼續執行? 這時候又出現了幾個問題:記憶體中始終都只有一個程式在執行,而想要解決上述問題,必然要在記憶體中裝入多個程式,如何處理呢?多個程式使用的資料如何辨別?當一個程式暫停後,隨後怎麼恢復到它之前執行的狀態呢?此時,程序應運而生.3)程序
4)執行緒
(2)區別
程序是資源分配的最小單位(程序之間互不干擾),執行緒是CPU排程的最小單位(執行緒間互相切換)。- 所有與程序相關的資源,都被記錄在PCB(程序控制塊)中
-
程序是搶佔處理器的排程單位;執行緒屬於某個程序,共享其資源
-
執行緒只由堆疊暫存器、程式計數器和TCB組成
總結
- 執行緒不能看做獨立應用,而程序可看做獨立應用
- 程序有獨立的地址空間,相互不影響,執行緒只是程序的不同執行路徑
- 程序資料分開,共享複雜,同步簡單;執行緒共享簡單,同步複雜
- 執行緒沒有獨立的地址空間,多程序的程式比多執行緒程式健壯(程序出現問題不會影響其他程序,可靠高;一個執行緒掛掉,整個程序也會掛掉,可靠低)
- 程序的切換比執行緒的切換開銷大(程序單獨佔有一定的記憶體地址空間,程序的建立和銷燬不僅需要儲存暫存器和棧資訊,還需要資源的分配回收以及頁排程,開銷較大;執行緒只需要儲存暫存器和棧資訊,開銷較小)
(3)JAVA程序和執行緒的關係
- Java對作業系統提供的功能進行封裝,包括程序和執行緒
- 執行一個程式會產生一個程序,程序包含至少一個執行緒
- 每個程序對應一個JVM例項,多個執行緒共享JVM裡的堆,JVM是多執行緒的
- Java採用單執行緒程式設計模型,程式會自動建立主執行緒
- 主執行緒可以建立子執行緒,原則上要後於子執行緒完成執行,因為要執行各種關閉動作
2.執行緒的start和run的區別
- 呼叫start()方法會建立一個新的子執行緒並啟動
- run()方法只是Thread的一個普通方法的呼叫
3.Thread和Runnable是什麼關係
- Thread是實現了Runnable介面的類,通過Thread的start()方法可以給Runnable的run()方法附上多執行緒的特性
- 因Java類的單一繼承原則,推薦多使用Runnable介面(為了提升系統的可拓展性,通過使業務類實現Runnable介面,將業務邏輯封裝在run方法裡,後續可以給普通類附上多執行緒的特性)
4.如何實現處理執行緒的返回值
(1)主執行緒等待法:實現簡單,我們可以通過實現迴圈等待的邏輯;缺點是變數多的時候會顯得臃腫,無法精準控制
1 public class CycleWait implements Runnable { 2 3 private String value; 4 5 @Override 6 public void run() { 7 try { 8 Thread.currentThread().sleep(5000); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 value = "we have date now"; 13 } 14 15 public static void main(String[] args) throws InterruptedException { 16 CycleWait cw = new CycleWait(); 17 Thread t = new Thread(cw); 18 t.start(); 19 //當值為null的時候一直迴圈,直到有值時才退出迴圈 20 21 while (cw.value == null) { 22 23 Thread.currentThread().sleep(100); 24 25 } 26 System.out.println("value:" + cw.value); // 沒有前面的迴圈,可能取出的值為null 27 } 28 }View Code
(2)使用Thread類的join()阻塞當前執行緒以等待子執行緒處理完畢:實現更簡單,缺點是力度不夠細,無法精確控制
1 public static void main(String[] args) throws InterruptedException { 2 CycleWait cw = new CycleWait(); 3 Thread t = new Thread(cw); 4 t.start(); 5 t.join(); 6 System.out.println("value:" + cw.value); 7 }View Code
(3)通過Callable介面實現:JDK5.0新增的,具體可以通過FutureTask或執行緒池獲取
1 public class myCallable implements Callable { 2 @Override 3 public String call() throws Exception { 4 String value = "test"; 5 System.out.println("Ready to work"); 6 Thread.currentThread().sleep(3000); 7 System.out.println("task done"); 8 return value; 9 } 10 }View Code
- FutureTask
1 public static void main(String[] args) throws ExecutionException, InterruptedException { 2 FutureTask<String> ft = new FutureTask<String>(new myCallable()); 3 new Thread(ft).start(); 4 if (!ft.isDone()) { 5 System.out.println("task has not finished, please wait!"); 6 } 7 System.out.println("task reture:" + ft.get()); 8 }View Code
- 執行緒池
1 public static void main(String[] args) { 2 ExecutorService executorService = Executors.newCachedThreadPool(); 3 Future<String> future = executorService.submit(new myCallable()); 4 if (!future.isDone()) { 5 System.out.println("task has not finished, please wait!"); 6 } 7 try { 8 System.out.println("task reture:" + future.get()); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } catch (ExecutionException e) { 12 e.printStackTrace(); 13 } finally { 14 executorService.shutdown(); 15 } 16 }View Code
執行結果
1 task has not finished, please wait! 2 Ready to work 3 task done 4 task reture:test
5.執行緒的狀態(6個)
(1)新建(New):建立後尚未啟動的執行緒的狀態
(2)執行(Runnable):包含Running和Ready(Running:正在執行;Ready:等待CPU分配執行時間)
(3)無限期等待(Waiting):不會被分配CPU執行時間,需要顯示被喚醒
- 沒有設定Timeout引數的Object.wait()方法
- 沒有設定Timeout引數的Thread.join()方法
- LockSupport.park()方法
(4)限期等待(Timed Waiting):在一定時間後由系統自動喚醒
- Thread.sleep()方法
- 設定了Timeout引數的Object.wait()方法
- 設定了Timeout引數的Thread.join()方法
- LockSupport.parkNanos()方法
- LockSupport.parkUntil()方法
(5)阻塞(Blocked):等待獲取排它鎖
(6)結束(Terminated):已終止執行緒的狀態,執行緒已經結束執行
6.sleep和wait的區別
(1)基本差別
- sleep是Thread類的方法,wait是Object類中定義的方法
- sleep()方法可以在任何地方使用
- wait()方法只能在synchronized方法或synchronized塊中使用
(2)最主要的本質區別
- Thread.sleep只會讓出CPU,不會釋放鎖
- Object.wait不僅會讓出CPU,還會釋放鎖(這個方法要寫在synchronized裡面,因為要獲得鎖,才能釋放鎖)
7.notify和notifyall的區別
(1)需要先了解的兩個概念
- 鎖池EntryList
- 等待池WaitSet
(2)區別
- notifyAll會讓所有處於等待池的執行緒全部進入鎖池去競爭獲取鎖的機會
- notify只會隨機選取一個處於等待池中的執行緒進入鎖池去競爭獲取鎖的機會
8.yield相關
當呼叫Thread.yield()函式時,會給執行緒排程器一個當前執行緒願意讓出CPU使用的暗示,但是執行緒排程器可能會忽略這個暗示,該方法不釋放鎖。
1 public static void main(String[] args) { 2 Runnable yieldTask = () -> { 3 for (int i = 0; i <= 10; i++) { 4 System.out.println(Thread.currentThread().getName() + i); 5 if (i == 5){ 6 Thread.yield(); // 暗示執行緒排程器願意讓出CPU使用,但最終決定權還是線上程排程器中 7 } 8 } 9 }; 10 Thread thread1 = new Thread(yieldTask,"A"); 11 Thread thread2 = new Thread(yieldTask,"B"); 12 thread1.start(); 13 thread2.start(); 14 }View Code
9.interrupt相關
(1)設計理念
一個執行緒不應該由其他執行緒來強制中斷或停止,而是應該由執行緒自己自行停止,是一種比較溫柔的做法,它更類似一個標誌位。其作用不是中斷執行緒,而是通知執行緒應該中斷了,具體到底中斷還是繼續執行,應該由被通知的執行緒自己處理。(2)如何中斷執行緒
1)已經被拋棄的方法
- 通過呼叫stop()方法停止執行緒
- 通過呼叫suspend()和resume()方法
2)目前使用的方法
呼叫interrupt(),通知執行緒應該中斷了- 如果執行緒處於被阻塞狀態,那麼執行緒立即退出被阻塞狀態,並丟擲一個InterruptedException異常
- 如果執行緒處於正常活動狀態,那麼會將該執行緒的中斷標誌設定為true。被設定中斷標誌的執行緒將繼續正常執行,不受影響
- 在正常執行任務時,經常檢查本執行緒的中斷標誌位,如果被設定了中斷標誌就自行停止執行緒
- 呼叫Thread.interrupted() 方法後執行緒恢復非中斷狀態,即Thread.currentThread().isInterrupted()是false