AQS架構提供的另外一個優秀機制是鎖擷取逾時的支援,當大量線程對某一鎖競争時可能導緻某些線程在很長一段時間都擷取不了鎖,在某些場景下可能希望如果線程在一段時間内不能成功擷取鎖就取消對該鎖的等待以提高性能,這時就需要用到逾時機制。在JDK1.5之前還沒有juc工具,當時的并發控制職能通過JVM内置的synchronized關鍵詞實作鎖,但對一些特殊要求卻力不從心,例如逾時取消控制。JDK1.5開始引入juc工具完美解決了此問題,而這正得益于并發基礎架構AQS提供了逾時的支援。
為了更精确地保證時間間隔統計的準确性,實作時使用了System.nanoTime()更為精确的方法,它能精确到納秒級别。逾時機制的思想就是在不斷進行鎖競争的同時記錄競争的時間,一旦時間段超過指定的時間則停止輪詢直接傳回,傳回前對等待隊列中對應節點進行取消操作。往下看實作的邏輯,
if(嘗試擷取鎖失敗) {
long lastTime = System.nanoTime();
建立node
使用CAS方式把node插入到隊列尾部
while(true){
if(嘗試擷取鎖成功 并且 node的前驅節點為頭節點){
把目前節點設定為頭節點
跳出循環
}else{
if (nanosTimeout <= 0){
取消等待隊列中此節點
跳出循環
}
使用CAS方式修改node前驅節點的waitStatus辨別為signal
if(修改成功)
if(nanosTimeout > spinForTimeoutThreshold)
阻塞目前線程nanosTimeout納秒
long now = System.nanoTime();
nanosTimeout -= now - lastTime;
lastTime = now;
上面正是在前面章節鎖的擷取邏輯中添加逾時處理,核心邏輯是不斷循環減去處理的時間消耗,一旦小于0就取消節點并跳出循環,其中有兩點必須要注意,一個是真正的阻塞時間應該是扣除了競争入隊的時間後剩餘的時間,保證阻塞事件的準确性,我們可以看到每次循環都會減去相應的處理時間;另外一個是關于spinForTimeoutThreshold變量閥值,它是決定使用自旋方式消耗時間還是使用系統阻塞方式消耗時間的分割線,juc工具包作者通過測試将預設值設定為1000ns,即如果在成功插入等待隊列後剩餘時間大于1000ns則調用系統底層阻塞,否則不調用系統底層,取而代之的是僅僅讓之在Java應用層不斷循環消耗時間,屬于優化的措施。
至此AQS架構在擷取鎖的過程中提供了逾時機制,逾時的支援讓Java在并發方面提供了更完善的機制,更多的并發政策滿足開發者更多需求。
<a target="_blank" href="https://item.jd.com/12185360.html">點選訂購作者書籍《Tomcat核心設計剖析》</a>