1. 程式人生 > 實用技巧 >java多執行緒&&併發面試108問(中)

java多執行緒&&併發面試108問(中)

歡迎關注
CSDN:程式設計師小羊
微信公眾號:程式設計師小羊
部落格園:程式設計師小羊

目錄

53、執行緒基本方法

執行緒相關的基本方法有 wait, notify, notifyAll, sleep, join, yield 等。

54、執行緒等待(wait)

呼叫該方法的執行緒進入 WAITING 狀態,只有等待另外執行緒的通知或被中斷才會返回,需要注意的是呼叫 wait()方法後, 會釋放物件的鎖。因此, wait 方法一般用在同步方法或同步程式碼塊中。

55、執行緒睡眠(sleep)

sleep 導致當前執行緒休眠,與 wait 方法不同的是 sleep 不會釋放當前佔有的鎖,sleep(long)會導致執行緒進入 TIMED-WATING 狀態,而 wait()方法會導致當前執行緒進入 WATING 狀態

56、執行緒讓步(yield)

yield 會使當前執行緒讓出 CPU 執行時間片,與其他執行緒一起重新競爭 CPU 時間片。一般情況下, 優先順序高的執行緒有更大的可能性成功競爭得到 CPU 時間片, 但這又不是絕對的,有的作業系統對執行緒優先順序並不敏感。

57、執行緒中斷(interrupt)

中斷一個執行緒,其本意是給這個執行緒一個通知訊號,會影響這個執行緒內部的一箇中斷標識位。 這個執行緒本身並不會因此而改變狀態(如阻塞,終止等)。

  1. 呼叫 interrupt()方法並不會中斷一個正在執行的執行緒。也就是說處於 Running 狀態的執行緒並不會因為被中斷而被終止,僅僅改變了內部維護的中斷標識位而已。
  2. 若呼叫 sleep()而使執行緒處於 TIMED-WATING 狀態,這時呼叫 interrupt()方法,會丟擲
    InterruptedException,從而使執行緒提前結束 TIMED-WATING 狀態。
  3. 許多宣告丟擲 InterruptedException 的方法(如 Thread.sleep(long mills 方法)),丟擲異常前,都會清除中斷標識位,所以丟擲異常後,呼叫 isInterrupted()方法將會返回 false。
  4. 中斷狀態是執行緒固有的一個標識位,可以通過此標識位安全的終止執行緒。比如,你想終止一個執行緒 thread 的時候,可以呼叫 thread.interrupt()方法,線上程的 run 方法內部可以根據 thread.isInterrupted()的值來優雅的終止執行緒。

58、Join 等待其他執行緒終止

join() 方法,等待其他執行緒終止,在當前執行緒中呼叫一個執行緒的 join() 方法,則當前執行緒轉為阻塞狀態,回到另一個執行緒結束,當前執行緒再由阻塞狀態變為就緒狀態,等待 cpu 的寵幸。

59、為什麼要用 join()方法?

很多情況下,主執行緒生成並啟動了子執行緒,需要用到子執行緒返回的結果,也就是需要主執行緒需要在子執行緒結束後再結束,這時候就要用到 join() 方法 。

System.out.println(Thread.currentThread().getName() + "執行緒執行開始!"); 
Thread6 thread1 = new Thread6();
thread1.setName("執行緒 B"); thread1.join();
System.out.println("這時 thread1 執行完畢之後才能執行主執行緒");

60、執行緒喚醒(notify)

Object 類中的 notify() 方法, 喚醒在此物件監視器上等待的單個執行緒,如果所有執行緒都在此物件上等待,則會選擇喚醒其中一個執行緒,選擇是任意的,並在對實現做出決定時發生,執行緒通過呼叫其中一個 wait() 方法,在物件的監視器上等待, 直到當前的執行緒放棄此物件上的鎖定,才能繼續執行被喚醒的執行緒,被喚醒的執行緒將以常規方式與在該物件上主動同步的其他所有執行緒進行競爭。類似的方法還有 notifyAll() ,喚醒再次監視器上等待的所有執行緒。

61、執行緒其他方法

  1. sleep():強迫一個執行緒睡眠N毫秒。
  2. isAlive(): 判斷一個執行緒是否存活。
  3. join(): 等待執行緒終止。
  4. activeCount(): 程式中活躍的執行緒數。
  5. enumerate(): 列舉程式中的執行緒。
  6. currentThread(): 得到當前執行緒。
  7. isDaemon(): 一個執行緒是否為守護執行緒。
  8. setDaemon(): 設定一個執行緒為守護執行緒。 (使用者執行緒和守護執行緒的區別在於,是否等待主執行緒依賴於主執行緒結束而結束)
  9. setName(): 為執行緒設定一個名稱。
  10. wait(): 強迫一個執行緒等待。
  11. notify(): 通知一個執行緒繼續執行。
  12. setPriority(): 設定一個執行緒的優先順序。
  13. getPriority()::獲得一個執行緒的優先順序。

62、程序

(有時候也稱做任務)是指一個程式執行的例項。在 Linux 系統中,執行緒就是能並行執行並且與他們的父程序(建立他們的程序)共享同一地址空間(一段記憶體區域)和其他資源的輕量 級的程序。

63、上下文

是指某一時間點 CPU 暫存器和程式計數器的內容。

64、暫存器

是 CPU 內部的數量較少但是速度很快的記憶體(與之對應的是 CPU 外部相對較慢的 RAM 主記憶體)。暫存器通過對常用值(通常是運算的中間值)的快速訪問來提高計算機程式執行的速度。

65、程式計數器

是一個專用的暫存器, 用於表明指令序列中 CPU 正在執行的位置,存的值為正在執行的指令的位置或者下一個將要被執行的指令的位置,具體依賴於特定的系統。

66、PCB-“切換楨”

上下文切換可以認為是核心(作業系統的核心)在 CPU 上對於程序(包括執行緒)進行切換,上下文切換過程中的資訊是儲存在程序控制塊(PCB, process control block)中的。 PCB 還經常被稱作“切換楨”(switchframe)。 資訊會一直儲存到 CPU 的記憶體中,直到他們被再次使用。

67、上下文切換的活動

  1. 掛起一個程序,將這個程序在 CPU 中的狀態(上下文)儲存於記憶體中的某處。
  2. 在記憶體中檢索下一個程序的上下文並將其在 CPU 的暫存器中恢復。
  3. 跳轉到程式計數器所指向的位置(即跳轉到程序被中斷時的程式碼行),以恢復該程序在程式中。

68、引起執行緒上下文切換的原因

  1. 當前執行任務的時間片用完之後,系統 CPU 正常排程下一個任務;
  2. 當前執行任務碰到 IO 阻塞,排程器將此任務掛起,繼續下一任務;
  3. 多個任務搶佔鎖資源,當前任務沒有搶到鎖資源,被排程器掛起,繼續下一任務;
  4. 使用者程式碼掛起當前任務,讓出 CPU 時間;
  5. 硬體中斷;

69、同步鎖

當多個執行緒同時訪問同一個資料時,很容易出現問題。為了避免這種情況出現,我們要保證執行緒同步互斥,就是指併發執行的多個執行緒,在同一時間內只允許一個執行緒訪問共享資料。 Java 中可以使用 synchronized 關鍵字來取得一個物件的同步鎖。

70、死鎖

何為死鎖,就是多個執行緒同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。

71、執行緒池原理

執行緒池做的工作主要是控制執行的執行緒的數量,處理過程中將任務放入佇列,然後線上程建立後啟動這些任務,如果執行緒數量超過了最大數量超出數量的執行緒排隊等候,等其它執行緒執行完畢, 再從佇列中取出任務來執行。 他的主要特點為: 執行緒複用; 控制最大併發數; 管理執行緒。

72、執行緒復

每一個 Thread 的類都有一個 start 方法。 當呼叫 start 啟動執行緒時 Java 虛擬機器會呼叫該類的 run
方法。 那麼該類的 run() 方法中就是呼叫了 Runnable 物件的 run() 方法。 我們可以繼承重寫
Thread 類,在其 start 方法中新增不斷迴圈呼叫傳遞過來的 Runnable 物件。 這就是執行緒池的實現原理。 迴圈方法中不斷獲取 Runnable 是用 Queue 實現的,在獲取下一個 Runnable 之前可以是阻塞的 。

73、執行緒池的組成

一般的執行緒池主要分為以下 4 個組成部分:

  1. 執行緒池管理器:用於建立並管理執行緒池
  2. 工作執行緒:執行緒池中的執行緒
  3. 任務介面:每個任務必須實現的介面,用於工作執行緒排程其執行
  4. 任務佇列:用於存放待處理的任務,提供一種緩衝機制
    Java 中的執行緒池是通過 Executor 框架實現的,該框架中用到了 Executor, Executors, ExecutorService, ThreadPoolExecutor , Callable 和 Future、 FutureTask 這幾個類。

    ThreadPoolExecutor 的構造方法如下:
public ThreadPoolExecutor(
	int corePoolSize,
	int maximumPoolSize, 
	long keepAliveTime, 
	TimeUnit unit, 
	BlockingQueue<Runnable> workQueue) {
	this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, xecutors.defaultThreadFactory(), defaultHandler);
}
  1. corePoolSize:指定了執行緒池中的執行緒數量。
  2. maximumPoolSize:指定了執行緒池中的最大執行緒數量。
  3. keepAliveTime:當前執行緒池數量超過 corePoolSize 時,多餘的空閒執行緒的存活時間,即多次時間內會被銷燬。
  4. unit: keepAliveTime 的單位。
  5. workQueue:任務佇列,被提交但尚未被執行的任務。
  6. threadFactory:執行緒工廠,用於建立執行緒,一般用預設的即可。
  7. handler:拒絕策略,當任務太多來不及處理,如何拒絕任務。

74、拒絕策略

執行緒池中的執行緒已經用完了,無法繼續為新任務服務,同時,等待佇列也已經排滿了,再也塞不下新任務了。這時候我們就需要拒絕策略機制合理的處理這個問題。
JDK 內建的拒絕策略如下:

  1. AbortPolicy : 直接丟擲異常,阻止系統正常執行。
  2. CallerRunsPolicy : 只要執行緒池未關閉,該策略直接在呼叫者執行緒中,運行當前被丟棄的任務。顯然這樣做不會真的丟棄任務,但是,任務提交執行緒的效能極有可能會急劇下降。
  3. DiscardOldestPolicy : 丟棄最老的一個請求,也就是即將被執行的一個任務,並嘗試再次提交當前任務。
  4. DiscardPolicy : 該策略默默地丟棄無法處理的任務,不予任何處理。如果允許任務丟失,這是最好的一種方案。
    以上內建拒絕策略均實現了 RejectedExecutionHandler 介面,若以上策略仍無法滿足實際需要,完全可以自己擴充套件 RejectedExecutionHandler 介面。

75、Java 執行緒池工作過程

  1. 執行緒池剛建立時,裡面沒有一個執行緒。任務佇列是作為引數傳進來的。不過,就算佇列裡面有任務,執行緒池也不會馬上執行它們。
  2. 當呼叫 execute() 方法新增一個任務時,執行緒池會做如下判斷:
    a. 如果正在執行的執行緒數量小於 corePoolSize,那麼馬上建立執行緒執行這個任務;
    b. 如果正在執行的執行緒數量大於或等於 corePoolSize,那麼將這個任務放入佇列;
    c. 如果這時候佇列滿了,而且正在執行的執行緒數量小於 maximumPoolSize,那麼還是要建立非核心執行緒立刻執行這個任務;
    d. 如果佇列滿了,而且正在執行的執行緒數量大於或等於 maximumPoolSize,那麼執行緒池會丟擲異常 RejectExecutionException。
  3. 當一個執行緒完成任務時,它會從佇列中取下一個任務來執行。
  4. 當一個執行緒無事可做,超過一定的時間(keepAliveTime)時,執行緒池會判斷,如果當前執行的執行緒數大於 corePoolSize,那麼這個執行緒就被停掉。所以執行緒池的所有任務完成後,它最終會收縮到 corePoolSize 的大小。

76、JAVA 阻塞佇列原理

阻塞佇列,關鍵字是阻塞,先理解阻塞的含義,在阻塞佇列中,執行緒阻塞有這樣的兩種情況:

  1. 當佇列中沒有資料的情況下,消費者端的所有執行緒都會被自動阻塞(掛起),直到有資料放入佇列。
  2. 當佇列中填滿資料的情況下,生產者端的所有執行緒都會被自動阻塞(掛起),直到佇列中有空的位置,執行緒被自動喚醒。
    阻塞佇列的主要方法 :

丟擲異常:丟擲一個異常;
特殊值:返回一個特殊值(null 或 false,視情況而定) 則塞:在成功操作之前,一直阻塞執行緒
超時:放棄前只在最大的時間內阻塞
插入操作

  1. public abstract boolean add(E paramE): 將指定元素插入此佇列中(如果立即可行且不會違反容量限制),成功時返回 true,如果當前沒有可用的空間,則丟擲 IllegalStateException。如果該元素是 NULL,則會丟擲 NullPointerException 異常。
  2. public abstract boolean offer(E paramE): 將指定元素插入此佇列中(如果立即可行且不會違反容量限制),成功時返回 true,如果當前沒有可用的空間,則返回 false。
  3. public abstract void put(E paramE) throws InterruptedException: 將指定元素插入此佇列中,將等待可用的空間(如果有必要)
public void put(E paramE) throws InterruptedException { 
	checkNotNull(paramE);
	ReentrantLock localReentrantLock = this.lock; localReentrantLock.lockInterruptibly();
	try {
		while (this.count == this.items.length)
		this.notFull.await();//如果佇列滿了,則執行緒阻塞等待enqueue(paramE);
		localReentrantLock.unlock();
	} finally {
		localReentrantLock.unlock();
	}
}
  1. offer(E o, long timeout, TimeUnit unit): 可以設定等待的時間, 如果在指定的時間內, 還不能往佇列中加入 BlockingQueue, 則返回失敗。
    獲取資料操作:
  2. poll(time):取走 BlockingQueue 裡排在首位的物件,若不能立即取出,則可以等 time 引數規定的時間,取不到時返回 null;
  3. poll(long timeout, TimeUnit unit): 從 BlockingQueue 取出一個隊首的物件, 如果在指定時間內, 佇列一旦有資料可取, 則立即返回佇列中的資料。否則直到時間超時還沒有資料可取,返回失敗。
  4. take():取走 BlockingQueue 裡排在首位的物件,若 BlockingQueue 為空,阻斷進入等待狀態直到 BlockingQueue 有新的資料被加入。
  5. drainTo():一次性從 BlockingQueue 獲取所有可用的資料物件(還可以指定獲取資料的個數),通過該方法,可以提升獲取資料效率;不需要多次分批加鎖或釋放鎖。

77、Java 中的阻塞佇列

  1. ArrayBlockingQueue :由陣列結構組成的有界阻塞佇列。
  2. LinkedBlockingQueue :由連結串列結構組成的有界阻塞佇列。
  3. PriorityBlockingQueue :支援優先順序排序的無界阻塞佇列。
  4. DelayQueue:使用優先順序佇列實現的無界阻塞佇列。
  5. SynchronousQueue:不儲存元素的阻塞佇列。
  6. LinkedTransferQueue:由連結串列結構組成的無界阻塞佇列。
  7. LinkedBlockingDeque:由連結串列結構組成的雙向阻塞佇列

78、ArrayBlockingQueue(公平、非公平)

用陣列實現的有界阻塞佇列。此佇列按照先進先出(FIFO)的原則對元素進行排序。 預設情況下不保證訪問者公平的訪問佇列,所謂公平訪問佇列是指阻塞的所有生產者執行緒或消費者執行緒,當佇列可用時,可以按照阻塞的先後順序訪問佇列,即先阻塞的生產者執行緒,可以先往佇列裡插入元素,先阻塞的消費者執行緒,可以先從佇列裡獲取元素。通常情況下為了保證公平性會降低吞吐量。我們可以使用以下程式碼建立一個公平的阻塞佇列


ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);

79、LinkedBlockingQueue(兩個獨立鎖提高併發)

基於連結串列的阻塞佇列,同 ArrayListBlockingQueue 類似,此佇列按照先進先出(FIFO)的原則對元素進行排序。而 LinkedBlockingQueue 之所以能夠高效的處理併發資料,還因為其對於生產者端和消費者端分別採用了獨立的鎖來控制資料同步,這也意味著在高併發的情況下生產者和消費者可以並行地操作佇列中的資料,以此來提高整個佇列的併發效能。LinkedBlockingQueue 會預設一個類似無限大小的容量(Integer.MAX_VALUE)

80、PriorityBlockingQueue(compareTo 排序實現優先)

是一個支援優先順序的無界佇列。預設情況下元素採取自然順序升序排列。 可以自定義實現
compareTo()方法來指定元素進行排序規則,或者初始化 PriorityBlockingQueue 時,指定構造引數 Comparator 來對元素進行排序。需要注意的是不能保證同優先順序元素的順序。]]>java多執行緒&amp;併發面試108問(下)http://www.cnblogs.com/cxyxy/archive/2020/07/07/13260437.htmldc:creator程式設計師小羊</dc:creator>程式設計師小羊Tue, 07 Jul 2020 06:01:00 GMThttp://www.cnblogs.com/cxyxy/archive/2020/07/07/13260437.html<![CDATA[> 歡迎關注

CSDN:程式設計師小羊
微信公眾號:程式設計師小羊

目錄

80、PriorityBlockingQueue(compareTo 排序實現優先)

是一個支援優先順序的無界佇列。預設情況下元素採取自然順序升序排列。 可以自定義實現compareTo()方法來指定元素進行排序規則,或者初始化 PriorityBlockingQueue 時,指定構造引數 Comparator 來對元素進行排序。需要注意的是不能保證同優先順序元素的順序。.

81、DelayQueue(快取失效、定時任務 )

是一個支援延時獲取元素的無界阻塞佇列。佇列使用 PriorityQueue 來實現。佇列中的元素必須實現 Delayed 介面,在建立元素時可以指定多久才能從佇列中獲取當前元素。只有在延遲期滿時才能從佇列中提取元素。我們可以將 DelayQueue 運用在以下應用場景:

  1. 快取系統的設計:可以用 DelayQueue 儲存快取元素的有效期,使用一個執行緒迴圈查詢
    DelayQueue,一旦能從 DelayQueue 中獲取元素時,表示快取有效期到了。
  2. 定 時 任 務 調 度 : 使 用 DelayQueue 保 存 當 天 將 會 執 行 的 任 務 和 執 行 時 間 , 一 旦 從
    DelayQueue 中獲取到任務就開始執行,從比如 TimerQueue 就是使用 DelayQueue 實現的

82、SynchronousQueue(不儲存資料、可用於傳遞資料)

是一個不儲存元素的阻塞佇列。每一個 put 操作必須等待一個 take 操作,否則不能繼續新增元素。SynchronousQueue 可以看成是一個傳球手,負責把生產者執行緒處理的資料直接傳遞給消費者執行緒。佇列本身並不儲存任何元素,非常適合於傳遞性場景,比如在一個執行緒中使用的資料,傳遞給另 外 一 個 線 程 使 用 , SynchronousQueue 的 吞 吐 量 高 於 LinkedBlockingQueue 和ArrayBlockingQueue。

83、LinkedTransferQueue

是 一 個 由 鏈 表 結 構 組 成 的 無 界 阻 塞 TransferQueue 隊 列 。 相 對 於 其 他 阻 塞 隊 列 ,
LinkedTransferQueue 多了 tryTransfer 和 transfer 方法。

  1. transfer 方法: 如果當前有消費者正在等待接收元素(消費者使用 take()方法或帶時間限制的poll()方法時), transfer 方法可以把生產者傳入的元素立刻 transfer(傳輸)給消費者。如果沒有消費者在等待接收元素, transfer 方法會將元素存放在佇列的 tail 節點,並等到該元素被消費者消費了才返回。
  2. tryTransfer 方法。則是用來試探下生產者傳入的元素是否能直接傳給消費者。如果沒有消費者等待接收元素,則返回 false。和 transfer 方法的區別是 tryTransfer 方法無論消費者是否接收,方法立即返回。而 transfer 方法是必須等到消費者消費了才返回。
    對於帶有時間限制的 tryTransfer(E e, long timeout, TimeUnit unit)方法,則是試圖把生產者傳入的元素直接傳給消費者,但是如果沒有消費者消費該元素則等待指定的時間再返回,如果超時還沒消費元素,則返回 false,如果在超時時間內消費了元素,則返回 true。

84、LinkedBlockingDeque

是一個由連結串列結構組成的雙向阻塞佇列。所謂雙向佇列指的你可以從佇列的兩端插入和移出元素。雙端佇列因為多了一個操作佇列的入口,在多執行緒同時入隊時,也就減少了一半的競爭。相比其 他的阻塞佇列, LinkedBlockingDeque 多了 addFirst, addLast, offerFirst, offerLast, peekFirst, peekLast 等方法,以 First 單詞結尾的方法,表示插入,獲取(peek)或移除雙端佇列的第一個元素。以 Last 單詞結尾的方法,表示插入,獲取或移除雙端佇列的最後一個元素。另外插入方法 add 等同於 addLast,移除方法 remove 等效於 removeFirst。但是 take 方法卻等同於 takeFirst,不知道是不是 Jdk 的 bug,使用時還是用帶有 First 和 Last 字尾的方法更清楚。
在初始化 LinkedBlockingDeque 時可以設定容量防止其過渡膨脹。另外雙向阻塞佇列可以運用在
“工作竊取”模式中。

85、在 java 中守護執行緒和本地執行緒區別

java 中的執行緒分為兩種:守護執行緒(Daemon)和使用者執行緒(User)。
任何執行緒都可以設定為守護執行緒和使用者執行緒,通過方法 Thread.setDaemon(boolon);true 則把該執行緒設定為守護執行緒,反之則為使用者執行緒。Thread.setDaemon() 必須在 Thread.start()之前呼叫,否則執行時會丟擲異常。
兩者的區別:
唯一的區別是判斷虛擬機器( JVM)何時離開,Daemon 是為其他執行緒提供服務,如果全部的 User Thread 已經撤離,Daemon 沒有可服務的執行緒,JVM 撤離。也可以理解為守護執行緒是 JVM 自動建立的執行緒(但不一定),使用者執行緒是程式建立的執行緒;比如 JVM 的垃圾回收執行緒是一個守護執行緒,當所有執行緒已經撤離,不再產生垃圾,守護執行緒自然就沒事可幹了,當垃圾回收執行緒是 Java 虛擬機器上僅剩的執行緒時,Java 虛擬機器會自動離開。
擴充套件:
Thread Dump 打印出來的執行緒資訊,含有 daemon 字樣的執行緒即為守護
程序,可能會有:服務守護程序、編譯守護程序、windows 下的監聽 Ctrl+break
的守護程序、Finalizer 守護程序、引用處理守護程序、GC 守護程序。

86、執行緒與程序的區別?

程序是作業系統分配資源的最小單元,執行緒是作業系統排程的最小單元。一個程式至少有一個程序,一個程序至少有一個執行緒。

87、什麼是多執行緒中的上下文切換?

多執行緒會共同使用一組計算機上的 CPU,而執行緒數大於給程式分配的 CPU 數量時, 為了讓各個執行緒都有執行的機會,就需要輪轉使用 CPU。不同的執行緒切換使用 CPU 發生的切換資料等就是上下文切換。

88、死鎖與活鎖的區別,死鎖與飢餓的區別?

死鎖:是指兩個或兩個以上的程序(或執行緒)在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。
產生死鎖的必要條件:

  1. 互斥條件:所謂互斥就是程序在某一時間內獨佔資源。
  2. 請求與保持條件:一個程序因請求資源而阻塞時,對已獲得的資源保持不放。
  3. 不剝奪條件:程序已獲得資源,在末使用完之前,不能強行剝奪。
  4. 迴圈等待條件:若干程序之間形成一種頭尾相接的迴圈等待資源關係。
    活鎖:任務或者執行者沒有被阻塞,由於某些條件沒有滿足,導致一直重複嘗試, 失敗,嘗試,失敗。
    活鎖和死鎖的區別在於,處於活鎖的實體是在不斷的改變狀態,所謂的“活”, 而處於死鎖的實體表現為等待;活鎖有可能自行解開,死鎖則不能。
    飢餓:一個或者多個執行緒因為種種原因無法獲得所需要的資源,導致一直無法執行的狀態。

Java 中導致飢餓的原因:

  1. 高優先順序執行緒吞噬所有的低優先順序執行緒的 CPU 時間。
  2. 執行緒被永久堵塞在一個等待進入同步塊的狀態,因為其他執行緒總是能在它之前持續地對該同步塊進行訪問。
  3. 執行緒在等待一個本身也處於永久等待完成的物件(比如呼叫這個物件的 wait 方法),因為其他執行緒總是被持續地獲得喚醒。

89、Java 中用到的執行緒排程演算法是什麼?

採用時間片輪轉的方式。可以設定執行緒的優先順序,會對映到下層的系統上面的優先順序上,如非特別需要,儘量不要用,防止執行緒飢餓。

90、什麼是執行緒組,為什麼在 Java 中不推薦使用?

ThreadGroup 類,可以把執行緒歸屬到某一個執行緒組中,執行緒組中可以有執行緒物件, 也可以有執行緒組,組中還可以有執行緒,這樣的組織結構有點類似於樹的形式。
為什麼不推薦使用?因為使用有很多的安全隱患吧,沒有具體追究,如果需要使用,推薦使用執行緒池。

91、為什麼使用 Executor 框架?

每次執行任務建立執行緒 new Thread()比較消耗效能,建立一個執行緒是比較耗時、耗資源的。
呼叫 new Thread()建立的執行緒缺乏管理,被稱為野執行緒,而且可以無限制的建立, 執行緒之間的相互競爭會導致過多佔用系統資源而導致系統癱瘓,還有執行緒之間的頻繁交替也會消耗很多系統資源。
接使用 new Thread() 啟動的執行緒不利於擴充套件,比如定時執行、定期執行、定時定期執行、執行緒中斷等都不便實現。

92、在 Java 中 Executor 和 Executors 的區別?

Executors 工具類的不同方法按照我們的需求建立了不同的執行緒池,來滿足業務的需求。
Executor 介面物件能執行我們的執行緒任務。
ExecutorService 介面繼承了 Executor 介面並進行了擴充套件,提供了更多的方法我們能獲得任務執行的狀態並且可以獲取任務的返回值。使用 ThreadPoolExecutor 可以建立自定義執行緒池。
Future 表示非同步計算的結果,他提供了檢查計算是否完成的方法,以等待計算的完成,並可以使用 get()方法獲取計算的結果。

93、如何在 Linux 上查詢哪個執行緒使用的 CPU 時間最長?

參考:
https://blog.csdn.net/ZYC88888/article/details/79993243

94、什麼是原子操作?在 Java Concurrency API 中有哪些原子類(atomic classes)?

原子操作(atomic operation)意為”不可被中斷的一個或一系列操作” 。處理器使用基於對快取加鎖或匯流排加鎖的方式來實現多處理器之間的原子操作。在 Java 中可以通過鎖和迴圈 CAS 的方式來實現原子操作。 CAS 操作——
Compare & Set,或是 Compare & Swap,現在幾乎所有的 CPU 指令都支援 CAS的原子操作。

原子操作是指一個不受其他操作影響的操作任務單元。原子操作是在多執行緒環境下避免資料不一致必須的手段。
int++並不是一個原子操作,所以當一個執行緒讀取它的值並加 1 時,另外一個執行緒有可能會讀到之前的值,這就會引發錯誤。為了解決這個問題,必須保證增加操作是原子的,在 JDK1.5 之前我們可以使用同步技術來做到這一點。到 JDK1.5,
java.util.concurrent.atomic 包提供了 int 和long 型別的原子包裝類,它們可以自動的保證對於他們的操作是原子的並且不需要使用同步。

java.util.concurrent 這個包裡面提供了一組原子類。其基本的特性就是在多執行緒環境下,當有多個執行緒同時執行這些類的例項包含的方法時,具有排他性,即當某個執行緒進入方法,執行其中的指令時,不會被其他執行緒打斷,而別的執行緒就像自旋鎖一樣,一直等到該方法執行完成,才由 JVM 從等待佇列中選擇一個另一個執行緒進入,這只是一種邏輯上的理解。

原 子 類 :AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference 原子陣列:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
原子屬性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
解決 ABA 問題的原子類:AtomicMarkableReference(通過引入一個 boolean來反映中間有沒有變過),AtomicStampedReference(通過引入一個 int 來累加來反映中間有沒有變過)

95、Java Concurrency API 中的 Lock 介面(Lock interface)是什麼?對比同步它有什麼優勢?

Lock 介面比同步方法和同步塊提供了更具擴充套件性的鎖操作。他們允許更靈活的結構,可以具有完全不同的性質,並且可以支援多個相關類的條件物件。
它的優勢有:
可以使鎖更公平
可以使執行緒在等待鎖的時候響應中斷
可以讓執行緒嘗試獲取鎖,並在無法獲取鎖的時候立即返回或者等待一段時間可以在不同的範圍,以不同的順序獲取和釋放鎖

整體上來說 Lock 是 synchronized 的擴充套件版,Lock 提供了無條件的、可輪詢的(tryLock 方法)、定時的(tryLock 帶參方法)、可中斷的
(lockInterruptibly)、可多條件佇列的(newCondition 方法)鎖操作。另外 Lock 的實現類基本都支援非公平鎖(預設)和公平鎖,synchronized 只支援非公平鎖,當然,在大部分情況下,非公平鎖是高效的選擇。

96、什麼是 Executors 框架?

Executor 框架是一個根據一組執行策略呼叫,排程,執行和控制的非同步任務的框架。無限制的建立執行緒會引起應用程式記憶體溢位。所以建立一個執行緒池是個更好的的解決方案,因為可以限制執行緒的數量並且可以回收再利用這些執行緒。利用Executors 框架可以非常方便的建立一個執行緒池。

97、什麼是阻塞佇列?阻塞佇列的實現原理是什麼?如何使用阻塞佇列來實現生產者-消費者 模型?

阻塞佇列(BlockingQueue)是一個支援兩個附加操作的佇列。這兩個附加的操作是:在佇列為空時,獲取元素的執行緒會等待佇列變為非 空。當佇列滿時,儲存元素的執行緒會等待佇列可用。阻塞佇列常用於生產者和消費者的場景,生產者是往佇列裡新增元素的執行緒,消費者是從佇列裡拿元素的執行緒。阻塞佇列就是生產者存放元素的容器,而消費者也只從容器裡拿元素。
JDK7 提供了 7 個阻塞佇列。分別是:
ArrayBlockingQueue :一個由陣列結構組成的有界阻塞佇列。
LinkedBlockingQueue :一個由連結串列結構組成的有界阻塞佇列。
PriorityBlockingQueue :一個支援優先順序排序的無界阻塞佇列。
DelayQueue:一個使用優先順序佇列實現的無界阻塞佇列。
SynchronousQueue:一個不儲存元素的阻塞佇列。
LinkedTransferQueue:一個由連結串列結構組成的無界阻塞佇列。LinkedBlockingDeque:一個由連結串列結構組成的雙向阻塞佇列。
Java 5 之前實現同步存取時,可以使用普通的一個集合,然後在使用執行緒的協作和執行緒同步可以實現生產者,消費者模式,主要的技術就是用好,wait ,notify,notifyAll,sychronized 這些關鍵字。而在 java 5 之後,可以使用阻塞佇列來實現,此方式大大簡少了程式碼量,使得多執行緒程式設計更加容易,安全方面也有保障。
BlockingQueue 介面是 Queue 的子介面,它的主要用途並不是作為容器,而是作為執行緒同步的的工具,因此他具有一個很明顯的特性,當生產者執行緒試圖向BlockingQueue 放入元素時,如果佇列已滿,則執行緒被阻塞,當消費者執行緒試圖從中取出一個元素時,如果佇列為空, 則該執行緒會被阻塞,正是因為它所具有這個特性,所以在程式中多個執行緒交替向 BlockingQueue 中放入元素,取出元素,它可以很好的控制執行緒之間的通訊。
阻塞佇列使用最經典的場景就是 socket 客戶端資料的讀取和解析,讀取資料的執行緒不斷將資料放入佇列,然後解析執行緒不斷從佇列取資料解析。

98、什麼是 Callable 和 Future?

Callable 介面類似於 Runnable,從名字就可以看出來了,但是 Runnable 不會返回結果,並且無法丟擲返回結果的異常,而 Callable 功能更強大一些,被執行緒執行後,可以返回值,這個返回值可以被 Future 拿到,也就是說,Future 可以拿到非同步執行任務的返回值。可以認為是帶有回撥的 Runnable。
Future 介面表示非同步任務,是還沒有完成的任務給出的未來結果。所以說 Callable
用於產生結果,Future 用於獲取結果。

99、什麼是 FutureTask?使用 ExecutorService 啟動任務。

在 Java 併發程式中 FutureTask 表示一個可以取消的非同步運算。它有啟動和取消運算、查詢運算是否完成和取回運算結果等方法。只有當運算完成的時候結果才能取回,如果運算尚未完成 get 方法將會阻塞。一個 FutureTask 物件可以對呼叫了 Callable 和 Runnable 的物件進行包裝,由於 FutureTask 也是呼叫了 Runnable介面所以它可以提交給 Executor 來執行。

100、什麼是併發容器的實現?

何為同步容器:可以簡單地理解為通過 synchronized 來實現同步的容器,如果有多個執行緒呼叫同步容器的方法,它們將會序列執行。比如Vector,Hashtable,以及 Collections.synchronizedSet,synchronizedList 等方法返回的容器。可以通過檢視 Vector,Hashtable 等這些同步容器的實現程式碼,可以看到這些容器實現執行緒安全的方式就是將它們的狀態封裝起來,並在需要同步的方法上加上關鍵字
synchronized。
併發容器使用了與同步容器完全不同的加鎖策略來提供更高的併發性和伸縮性,例如在 ConcurrentHashMap 中採用了一種粒度更細的加鎖機制,可以稱為分段鎖,在這種鎖機制下,允許任意數量的讀執行緒併發地訪問 map,並且執行讀操作的執行緒和寫操作的執行緒也可以併發的訪問 map,同時允許一定數量的寫操作執行緒併發地修改 map,所以它可以在併發環境下實現更高的吞吐量。

101、多執行緒同步和互斥有幾種實現方法,都是什麼?

執行緒同步是指執行緒之間所具有的一種制約關係,一個執行緒的執行依賴另一個執行緒的訊息,當它沒有得到另一個執行緒的訊息時應等待,直到訊息到達時才被喚醒。執行緒互斥是指對於共享的程序系統資源,在各單個執行緒訪問時的排它性。當有若干個執行緒都要使用某一共享資源時,任何時刻最多隻允許一個執行緒去使用,其它要使用該資源的執行緒必須等待,直到佔用資源者釋放該資源。執行緒互斥可以看成是一種特殊的執行緒同步。

執行緒間的同步方法大體可分為兩類:使用者模式和核心模式。顧名思義,核心模式就是指利用系統核心物件的單一性來進行同步,使用時需要切換核心態與使用者態,而使用者模式就是不需要切換到核心態,只在使用者態完成操作。使用者模式下的方法有:原子操作(例如一個單一的全域性變數),臨界區。核心模式下的方法有:事件,訊號量,互斥量。

102、什麼是競爭條件?你怎樣發現和解決競爭?

當多個程序都企圖對共享資料進行某種處理,而最後的結果又取決於程序執行的順序時,則我們認為這發生了競爭條件(race condition)。

103、為什麼我們呼叫 start()方法時會執行 run()方法,為什麼我們不能直接呼叫 run()方法?

當你呼叫 start()方法時你將建立新的執行緒,並且執行在 run()方法裡的程式碼。但是如果你直接呼叫 run()方法,它不會建立新的執行緒也不會執行呼叫執行緒的程式碼,只會把 run 方法當作普通方法去執行。

104、Java 中你怎樣喚醒一個阻塞的執行緒?

在 Java 發展史上曾經使用 suspend()、resume()方法對於執行緒進行阻塞喚醒,但隨之出現很多問題,比較典型的還是死鎖問題。解決方案可以使用以物件為目標的阻塞,即利用 Object 類的 wait()和 notify()方法實現執行緒阻塞。
首先,wait、notify 方法是針對物件的,呼叫任意物件的 wait()方法都將導致執行緒阻塞,阻塞的同時也將釋放該物件的鎖,相應地,呼叫任意物件的 notify()方法則將隨機解除該物件阻塞的執行緒,但它需要重新獲取改物件的鎖,直到獲取成功才能往下執行;其次,wait、notify 方法必須在 synchronized 塊或方法中被呼叫,並且要保證同步塊或方法的鎖物件與呼叫 wait、notify 方法的物件是同一個,如此一來在呼叫 wait 之前當前執行緒就已經成功獲取某物件的鎖,執行 wait 阻塞後當前執行緒就將之前獲取的物件鎖釋放。

105、在 Java 中 CycliBarriar 和 CountdownLatch 有什麼區別?

CyclicBarrier 可以重複使用,而 CountdownLatch 不能重複使用。 Java 的 concurrent 包裡面的 CountDownLatch 其實可以把它看作一個計數器,只不過這個計數器的操作是原子操作,同時只能有一個執行緒去操作這個計數器,也就是同時只能有一個執行緒去減這個計數器裡面的值。你可以向 CountDownLatch 物件設定一個初始的數字作為計數值,任何呼叫這個物件上的 await()方法都會阻塞,直到這個計數器的計數值被其他的執行緒減為 0 為止。

所以在當前計數到達零之前,await 方法會一直受阻塞。之後,會釋放所有等待的執行緒,await 的所有後續呼叫都將立即返回。這種現象只出現一次——計數無法被重置。如果需要重置計數,請考慮使用 CyclicBarrier。

CountDownLatch 的一個非常典型的應用場景是:有一個任務想要往下執行,但必須要等到其他的任務執行完畢後才可以繼續往下執行。假如我們這個想要繼續往下執行的任務呼叫一個 CountDownLatch 物件的 await()方法,其他的任務執行完自己的任務後呼叫同一個CountDownLatch 物件上的 countDown()方法,這個呼叫 await()方法的任務將一直阻塞等待,直到這個 CountDownLatch 物件的計數值減到 0 為止。

CyclicBarrier 一個同步輔助類,它允許一組執行緒互相等待,直到到達某個公共屏障點 (common barrier point)。在涉及一組固定大小的執行緒的程式中,這些執行緒必須不時地互相等待,此時 CyclicBarrier 很有用。因為該 barrie在釋放等待執行緒後可以重用,所以稱它為迴圈 的barrier。

106、什麼是不可變物件,它對寫併發應用有什麼幫助

不可變物件(Immutable Objects)即物件一旦被建立它的狀態(物件的資料,也即物件屬性值)就不能改變,反之即為可變物件(Mutable
Objects)。不可變物件的類即為不可變類(Immutable Class)。Java 平臺類庫中包含許多不可變類,如 String、基本型別的包裝類、
BigInteger 和 BigDecimal 等。不可變物件天生是執行緒安全的。它們的常量(域)是在建構函式中建立的。既然它們的狀態無法修改,這些常量永遠不會變。
不可變物件永遠是執行緒安全的。
只有滿足如下狀態,一個物件才是不可變的; 它的狀態不能在建立後再被修改;
所有域都是 final 型別;並且,
它被正確建立(建立期間沒有發生 this 引用的逸出)。

107、Java 中用到的執行緒排程演算法是什麼?

計算機通常只有一個 CPU,在任意時刻只能執行一條機器指令,每個執行緒只有獲得CPU 的使用權才能執行指令.所謂多執行緒的併發執行,其實是指從巨集觀上看,各個執行緒輪流獲得 CPU 的使用權,分別執行各自的任務.在執行池中,會有多個處於就緒狀態的執行緒在等待 CPU,JAVA 虛擬機器的一項任務就是負責執行緒的排程,執行緒排程是指按照特定機制為多個執行緒分配CPU 的使用權.
有兩種排程模型:分時排程模型和搶佔式排程模型。分時排程模型是指讓所有的執行緒輪流獲得 cpu 的使用權,並且平均分配每個執行緒佔用的CPU 的時間片這個也比較好理解。 java 虛擬機器採用搶佔式排程模型,是指優先讓可執行池中優先順序高的執行緒佔用CPU,如果可執行池中的執行緒優先順序相同,那麼就隨機選擇一個執行緒,使其佔用CPU。處於執行狀態的執行緒會一直執行,直至它不得不放棄 CPU。

108、什麼是執行緒組,為什麼在 Java 中不推薦使用?

執行緒組和執行緒池是兩個不同的概念,他們的作用完全不同,前者是為了方便執行緒的管理,後者是為了管理執行緒的生命週期,複用執行緒,減少建立銷燬執行緒的開銷。

歡迎關注
CSDN:程式設計師小羊
微信公眾號:程式設計師小羊
部落格園:程式設計師小羊