環境資訊中心綜合外電;姜唯 編譯;林大利 審校
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
CS-LogN思維導圖:記錄CS基礎 面試題
開源地址:https://github.com/FISHers6/CS-LogN
線程池相關類
能獲取子線程的運行結果
互斥同步(鎖)
非互斥同(原子類)
併發容器
無同步與不可變方案
降低資源消耗
提高響應速度
提高線程的可管理性
corePoolSize、maximumPoolSize、keepAliveTime、workQueue、threadFactory、handler
圖示
線程添加規則
1.如果線程數量小於corePoolSize,即使工作線程處於空閑狀態,也會創建一個新線程來運行新任務,創建方法是使用threadFactory
2.如果線程數量大於corePoolSize但小於maximumPoolSize,則將任務放入隊列
3.如果workQueue隊列已滿,並且線程數量小於maxPoolSize,則開闢一個非核心新線程來運行任務
4.如果隊列已滿,並且線程數大於或等於maxPoolSize,則拒絕該任務,執行handler
圖示(分別與3個參數比較)
常用線程池
newFixedThreadPool
newSingleThreadExecutor
newCachedThreadPool
newScheduledThreadPool
如何設置初始化線程池的大小?
可根據線程池中的線程
處理任務的不同進行分別估計
CPU 密集型任務
IO 密集型任務
使用線程池的注意事項
線程池的狀態
線程池停止
shutdown
shutdownNow
線程池的組成
1.線程池管理器
2.工作線程
3.任務隊列:無界、有界、直接交付隊列
4.任務接口Task
圖示
Executor家族
Executor頂層接口,只有一個execute方法
ExecutorService繼承了Executor,增加了一些新的方法,比如shutdown擁有了初步管理線程池的功能方法
Executors工具類,來創建,類似Collections
圖示
線程池實現任務復用的原理
線程池對線程作了包裝,不需要啟動線程,不需要重複start線程,只是調用已有線程固定數量的線程來跑傳進來的任務run方法
添加工作線程
重複利用線程執行不同的任務
每個 Thread 維護着一個 ThreadLocalMap 的引用;ThreadLocalMap 是 ThreadLocal 的內部類,用 Entry 來進行存儲;key就對應一個個ThreadLocal
get方法:取出當前線程的ThreadLocalMap,然後調用map.getEntry方法,把ThreadLocal作為key參數傳入,取出對應的value
set方法:往 ThreadLocalMap 設置ThreadLocal對應值
initalValue方法:延遲加載,get的時候設置初始化
圖示
value內存泄漏
原因:ThreadLocal 被 ThreadLocalMap 中的 entry 的 key 弱引用。如果 ThreadLocal 沒有被強引用, 那麼 GC 時 Entry 的 key 就會被回收,但是對應的 value 卻不會回收,就會造成內存泄漏
解決方案:每次使用完 ThreadLocal,都調用它的 remove () 方法,清除value數據。
源碼圖示
引入目的
解決Runnable的缺陷
是什麼如何使用
引入目的
常用方法
使用場景
注意點
Callable和Future的關係
Future相當於一個存儲器,它存儲未來call()任務方法的返回值結果
可以用Future.get方法來獲取Callable接口的執行結果,在call()未執行完畢之前沒調用get的線程會被阻塞
線程池傳入Callable,submit返回Future,get獲取值
FutureTask
FutureTask是一種包裝器,可以把Callable轉化成Future和Runnable,它同時實現了二者的接口。所以既可以作為Runnable任務被線程執行,又可以作為Future得到Callable的返回值
圖示
final修飾變量
被final修飾的變量,意味着值不能被修改。
如果變量是對象,那麼對象的引用不能變,但是對象自身的內容依然可以變化。
賦值時機
屬性被聲明為final后,該變量則只能被賦值一次。且一旦被賦值,final的變量就不能再被改變,如論如何也不會變。
區分為3種
final instance variable(類中的final屬性)
final static variable(類中的static final屬性)
final local variable(方法中的final變量)
為什麼規定時機
final修飾方法(構造方法除外)
final修飾類
ABA問題
CAS+自旋,導致自旋時間過長
改進:通過版本號的機制來解決。每次變量更新的時候,版本號加 1,如AtomicStampedReference的compareAndSet ()
簡介
Lock和Synchronized的異同點
相同點
不同點
Lock 有比 synchronized 更精確的線程語義和更好的性能;高級功能
1 實現原理不同
2 靈活性不同
3 等待時是否可以中斷
可見性
悲觀鎖(互斥同步鎖)
思想
實例
缺點
樂觀鎖
思想
實例
優缺點對比
對比
什麼是可重入
可重入的好處
可重入鎖ReentrantLock與非可重入鎖ThreadPoolExecutor的Worker類對比
公平鎖
介紹
優點
缺點
非公平鎖
介紹
優點
缺點
優缺點對比
源碼分析
排他鎖
介紹
共享鎖
介紹
ReentrantReadWriteLock
讀寫鎖的作用
讀寫鎖的規則
一把鎖兩種方式鎖定
讀線程插隊策略(非公平下)
鎖升級
引入場景
策略
適合場景
阻塞鎖
思想
開銷缺陷
自旋鎖
思想
開銷缺陷
源碼分析
atomic包下的類基本都是自旋鎖的實現
AtomicInteger的實現:自旋鎖實現原理是CAS,Atomic調用Unsafe進行自增add的源碼中的do-while循環就是一個自旋操作,使用CAS如果修改過程中遇到其它線程修改導致沒有秀嘎四成功,就在while里死循環,直至修改成功
圖示
適用場景
介紹
使用場景
JDK1.6 后對synchronized鎖的優化
JDK1.6 對鎖的實現引入了大量的優化,如偏向鎖、輕量級鎖、自旋鎖、適應性自旋鎖、鎖消除、鎖粗化等技術來減少鎖操作的開銷。
偏向鎖
輕量級鎖
重量級鎖
自旋鎖
自適應自旋鎖
鎖消除
鎖粗化
寫代碼時的優化
常用方法
實現原理
缺點
引入目的/改進思想
設計思想
集合類歷史
為什麼需要
為什麼不用HashMap
為什麼不用Collection.synchronizedMap
數據結構與併發策略
JDK1.7
JDK1.8
1.7到1.8改變後有哪些優點
注意事項
引入目的
適合場景
讀寫規則
實現原理
缺點
為什麼使用隊列
併發隊列關係圖示
BlockingQueue阻塞隊列
阻塞隊列是局由自動阻塞功能的隊列,線程安全;take方法移除隊頭,若隊列無數據則阻塞直到有數據;put方法插入元素,如果隊列已滿就無法繼續插入則阻塞直到隊列里有了空閑空間
ArrayBlockQueue
LinkedBlockingQueue
PriorityBlockingQueue
SynchronousQueue
DelayQueue
非阻塞隊列
ConcurrentLinkedQueue
選擇合適的隊列
邊界上看
內存上看
吞吐量上看
控制併發流程的工具類,作用是幫助程序員更容易讓線程之間相互配合,來滿足業務邏輯
併發工具類圖示
作用(事件)
常用方法
作用
常用方法
作用
常用方法
作用(線程)
常用方法
Exclusive(獨佔)
Share(共享)
核心三要素
1.sate
2.控制線程搶鎖和配合的FIFO隊列
3.期望協作工具類去實現的“獲取/釋放”等喚醒分配的方法策略
AQS的用法
AQS在CountDownLatch的應用
內部類Sync繼承AQS
1.state表示門閂倒數的count數量,對應getCount方法獲取
2.釋放方法,countDown方法會讓state減1,直到減為0時就喚醒所有線程。countDown方法調用releaseShared,它調用sync實現的tryReleaseShared,其使用CAS+自旋鎖,來實現安全的計數-1
3.阻塞方法,await會調用sync提供的aquireSharedInterruptly方法,當state不等於0時,最終調用LockUpport的park,它利用Unsafe的park,native方法,把線程加入阻塞隊列
總結
AQS在Semphore的應用
state表示信號量允許的剩餘許可數量
tryAcquire方法,判斷信號量大於0就成功獲取,使用CAS+自旋改變state狀態。如果信號量小於0了,再請求時tryAcquireShared返回負數,調用aquireSharedInterruptly方法就進入阻塞隊列
release方法,調用sync實現的releaseShared,會利用AQS去阻塞隊列喚醒一個線程
總結
AQS在ReentrantLock的應用
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
brokers 中文意思為中間人,在這裏就是指任務隊列本身,接收生產者發來的消息即Task,將任務存入隊列。任務的消費者是Worker,Brokers 就是生產者和消費者存放/拿取產品的地方(隊列)。Celery 扮演生產者和消費者的角色。
常見的 brokers 有 rabbitmq、redis、Zookeeper 等。推薦用Redis或RabbitMQ實現隊列服務。
就是 Celery 中的工作者,執行任務的單元,類似與生產/消費模型中的消費者。它實時監控消息隊列,如果有任務就從隊列中取出任務並執行它。
用於存儲任務的執行結果。隊列中的任務運行完后的結果或者狀態需要被任務發送者知道,那麼就需要一個地方儲存這些結果,就是 Result Stores 了。
常見的 backend 有 redis、Memcached 甚至常用的數據庫都可以。
就是想在隊列中進行的任務,有異步任務和定時任務。一般由用戶、觸發器或其他操作將任務入隊,然後交由 workers 進行處理。
定時任務調度器,根據配置定時將任務發送給Brokers。
1.創建一個celery application 用來定義你的任務列表,創建一個任務文件就叫tasks.py吧。
from celery import Celery
# 配置好celery的backend和broker
app = Celery('task1', backend='redis://127.0.0.1:6379/0', broker='redis://127.0.0.1:6379/0')
#普通函數裝飾為 celery task
@app.task
def add(x, y):
return x + y
如此而來,我們只是定義好了任務函數func函數和worker(celery對象)。worker相當於工作者。
2.啟動Celery Worker來開始監聽並執行任務。broker 我們有了,backend 我們有了,task 我們也有了,現在就該運行 worker 進行工作了,在 tasks.py 所在目錄下運行:
[root@localhost ~]# celery -A tasks worker --loglevel=info # 啟動方法1
[root@localhost ~]# celery -A tasks worker --l debug # 啟動方法2
現在 tasks 這個任務集合的 worker 在進行工作(當然此時broker中還沒有任務,worker此時相當於待命狀態),如果隊列中已經有任務了,就會立即執行。
3.調用任務:要給Worker發送任務,需要調用 delay() 方法。
import time
from tasks import add
# 不要直接add(6, 6),這裏需要用 celery 提供的接口 delay 進行調用
result = add.delay(6, 6)
while not result.ready():
time.sleep(1)
print('task done: {0}'.format(result.get()))
1.celery_config.py:配置文件
from __future__ import absolute_import, unicode_literals
#從python的絕對路徑導入而不是當前的腳本 #在python2和python3做兼容支持的
BROKER_URL = 'redis://127.0.0.1:6379/0'
# 指定結果的接受地址
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0'
2.tasks.py
from __future__ import absolute_import, unicode_literals
#從python的絕對路徑導入而不是當前的腳本 #在python2和python3做兼容支持的
from celery import Celery
# 配置好celery的backend和broker, task1:app的名字。broker
app = Celery('task1', #
broker='redis://127.0.0.1:6379/0', # 消息隊列:連rabbitmq或redis
backend='redis://127.0.0.1:6379/0') # 存儲結果:redis或mongo或其他數據庫
app.config_from_object("celery_config")
app.conf.update( # 給app設置參數
result_expires=3600, # 保存時間為1小時
)
#普通函數裝飾為 celery task
@app.task
def add(x, y):
return x + y
if __name__ == '__main__':
app.start()
3.啟動worker
[root@localhost ~]``# celery -A tasks worker --loglevel=info
4.test.py
import time
from tasks import add
# 不要直接add(4, 4),這裏需要用 celery 提供的接口 delay 進行調用
result = add.delay(6, 6)
print(result.id)
while not result.ready():
time.sleep(1)
print('task done: {0}'.format(result.get()))
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
意法半導體推出新款車規碳化矽(SiC)二極體,以滿足電動汽車和插電式混合動力車(PHEVs,Plug-in Hybrids)等新能源汽車對車載充電器(OBCs,on-board battery chargers)在有限空間內處理大功率的苛刻要求。
| |
新款二極體採用先進的技術可防止高電流突波燒毀裝置,其過電流保護是額定電流的2.5倍,因此設計人員可選用更小、更經濟實惠且可靠性和效能都不會受到影響的電流更小的二極體。此新碳化矽二極體通過車規產品測試,反向擊穿電壓提高到650V,能滿足設計人員和汽車廠商欲降低電壓補償係數的要求,以確保車載充電半導體元件的標準與瞬間峰值電壓之間有充足的安全邊際。 這次推出的650V二極體包括TO-220AC功率封裝的10A STPSC10H065DY和TO-220AC封裝的12A STPSC12H065DY。此外,TO-220AB封裝的STPSC20H065CTY和TO-247封裝的STPSC20H065CWY是內建2個10A二極體的雙二極體(dual-diode )產品,可最大幅度地提升空間利用度並減少車載充電器的重量。
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
![]() |
鴻海於 12 月 22 日公告,以台幣約 24.84 億元入股中國和諧汽車(China Harmony Auto Holding Limited),取得 10.526% 的持股,未來雙方將展開新能源、電動車的通路合作。 和諧汽車為中國進口高級品牌汽車的第二大經銷商,擁有 25 個銷售據點,旗下代理品牌超過 10 個,包括寶馬、勞斯萊斯,MINI、奧迪、法拉利、瑪莎拉蒂等等。 而鴻海集團近來在電動車市場投資與布局也動作頻頻,鴻海集團總裁郭台銘 12 月初率領近 20 人的專家團隊,前往河南首三門峽市的速達電動汽車公司進行實地考察,並駕駛了速達電動車,正式開啟了合作契機。緊接著,富士康與北汽合資電動車租賃公司也傳出獲得中國科技局採用的好消息,成功打入當地電動車租賃市場。 這次,由鴻海主動發布重大訊息,代子公司 Foxconn (Far East) Limited 公告為了與目標公司合作發展新事業,將投資和諧汽車,以港幣約 6.09 億元(約台幣 24.84 億元),每股港幣 4.73 元,取得和諧汽車股份,持股比例將達到 10.526%。 (照片來源:)
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
JDK1.8源碼分析項目(中文註釋)Github地址:
https://github.com/yuanmabiji/jdk1.8-sourcecode-blogs
先舉個例子,我們平時網購買東西,下單後會生成一個訂單號,然後商家會根據這個訂單號發貨,發貨后又有一個快遞單號,然後快遞公司就會根據這個快遞單號將網購東西快遞給我們。在這一過程中,這一系列的單號都是我們收貨的重要憑證。
因此,JDK的Future就類似於我們網購買東西的單號,當我們執行某一耗時的任務時,我們可以另起一個線程異步去執行這個耗時的任務,同時我們可以干點其他事情。當事情幹完后我們再根據future這個”單號”去提取耗時任務的執行結果即可。因此Future也是多線程中的一種應用模式。
擴展: 說起多線程,那麼Future又與Thread有什麼區別呢?最重要的區別就是Thread是沒有返回結果的,而Future模式是有返回結果的。
前面搞明白了什麼是Future,下面我們再來舉個簡單的例子看看如何使用Future。
假如現在我們要打火鍋,首先我們要準備兩樣東西:把水燒開和準備食材。因為燒開水是一個比較漫長的過程(相當於耗時的業務邏輯),因此我們可以一邊燒開水(相當於另起一個線程),一邊準備火鍋食材(主線程),等兩者都準備好了我們就可以開始打火鍋了。
// DaHuoGuo.java
public class DaHuoGuo {
public static void main(String[] args) throws Exception {
FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + ":" + "開始燒開水...");
// 模擬燒開水耗時
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "開水已經燒好了...");
return "開水";
}
});
Thread thread = new Thread(futureTask);
thread.start();
// do other thing
System.out.println(Thread.currentThread().getName() + ":" + " 此時開啟了一個線程執行future的邏輯(燒開水),此時我們可以干點別的事情(比如準備火鍋食材)...");
// 模擬準備火鍋食材耗時
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + ":" + "火鍋食材準備好了");
String shicai = "火鍋食材";
// 開水已經稍好,我們取得燒好的開水
String boilWater = futureTask.get();
System.out.println(Thread.currentThread().getName() + ":" + boilWater + "和" + shicai + "已經準備好,我們可以開始打火鍋啦");
}
}
執行結果如下截圖,符合我們的預期:
從以上代碼中可以看到,我們使用Future主要有以下步驟:
Callable匿名函數實現類對象,我們的業務邏輯在Callable的call方法中實現,其中Callable的泛型是返回結果類型;Callable匿名函數對象作為FutureTask的構造參數傳入,構建一個futureTask對象;futureTask對象作為Thread構造參數傳入並開啟這個線程執行去執行業務邏輯;futureTask對象的get方法得到業務邏輯執行結果。可以看到跟Future使用有關的JDK類主要有FutureTask和Callable兩個,下面主要對FutureTask進行源碼分析。
擴展: 還有一種使用
Future的方式是將Callable實現類提交給線程池執行的方式,這裏不再介紹,自行百度即可。
我們先來看下FutureTask的類結構:
可以看到FutureTask實現了RunnableFuture接口,而RunnableFuture接口又繼承了Future和Runnable接口。因為FutureTask間接實現了Runnable接口,因此可以作為任務被線程Thread執行;此外,最重要的一點就是FutureTask還間接實現了Future接口,因此還可以獲得任務執行的結果。下面我們就來簡單看看這幾個接口的相關api。
// Runnable.java
@FunctionalInterface
public interface Runnable {
// 執行線程任務
public abstract void run();
}
Runnable沒啥好說的,相信大家都已經很熟悉了。
// Future.java
public interface Future<V> {
/**
* 嘗試取消線程任務的執行,分為以下幾種情況:
* 1)如果線程任務已經完成或已經被取消或其他原因不能被取消,此時會失敗並返回false;
* 2)如果任務還未開始執行,此時執行cancel方法,那麼任務將被取消執行,此時返回true;TODO 此時對應任務狀態state的哪種狀態???不懂!!
* 3)如果任務已經開始執行,那麼mayInterruptIfRunning這個參數將決定是否取消任務的執行。
* 這裏值得注意的是,cancel(true)實質並不能真正取消線程任務的執行,而是發出一個線程
* 中斷的信號,一般需要結合Thread.currentThread().isInterrupted()來使用。
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
* 判斷任務是否被取消,在執行任務完成前被取消,此時會返回true
*/
boolean isCancelled();
/**
* 這個方法不管任務正常停止,異常還是任務被取消,總是返回true。
*/
boolean isDone();
/**
* 獲取任務執行結果,注意是阻塞等待獲取任務執行結果。
*/
V get() throws InterruptedException, ExecutionException;
/**
* 獲取任務執行結果,注意是阻塞等待獲取任務執行結果。
* 只不過在規定的時間內未獲取到結果,此時會拋出超時異常
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
Future接口象徵著異步執行任務的結果即執行一個耗時任務完全可以另起一個線程執行,然後此時我們可以去做其他事情,做完其他事情我們再調用Future.get()方法獲取結果即可,此時若異步任務還沒結束,此時會一直阻塞等待,直到異步任務執行完獲取到結果。
// RunnableFuture.java
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
RunnableFuture是Future和Runnable接口的組合,即這個接口表示又可以被線程異步執行,因為實現了Runnable接口,又可以獲得線程異步任務的執行結果,因為實現了Future接口。因此解決了Runnable異步任務沒有返回結果的缺陷。
接下來我們來看下FutureTask,FutureTask實現了RunnableFuture接口,因此是Future和Runnable接口的具體實現類,是一個可被取消的異步線程任務,提供了Future的基本實現,即異步任務執行后我們能夠獲取到異步任務的執行結果,是我們接下來分析的重中之重。FutureTask可以包裝一個Callable和Runnable對象,此外,FutureTask除了可以被線程執行外,還可以被提交給線程池執行。
我們先看下FutureTask類的api,其中重點方法已經紅框框出。
上圖中FutureTask的run方法是被線程異步執行的方法,get方法即是取得異步任務執行結果的方法,還有cancel方法是取消任務執行的方法。接下來我們主要對這三個方法進行重點分析。
思考:
FutureTask覆寫的run方法的返回類型依然是void,表示沒有返回值,那麼FutureTask的get方法又是如何獲得返回值的呢?FutureTask的cancel方法能真正取消線程異步任務的執行么?什麼情況下能取消?因為FutureTask異步任務執行結果還跟Callable接口有關,因此我們再來看下Callable接口:
// Callable.java
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*/
V call() throws Exception;
}
我們都知道,Callable<V>接口和Runnable接口都可以被提交給線程池執行,唯一不同的就是Callable<V>接口是有返回結果的,其中的泛型V就是返回結果,而Runnable接口是沒有返回結果的。
思考: 一般情況下,
Runnable接口實現類才能被提交給線程池執行,為何Callable接口實現類也可以被提交給線程池執行?想想線程池的submit方法內部有對Callable做適配么?
我們首先來看下FutureTask的成員變量有哪些,理解這些成員變量對後面的源碼分析非常重要。
// FutureTask.java
/** 封裝的Callable對象,其call方法用來執行異步任務 */
private Callable<V> callable;
/** 在FutureTask裏面定義一個成員變量outcome,用來裝異步任務的執行結果 */
private Object outcome; // non-volatile, protected by state reads/writes
/** 用來執行callable任務的線程 */
private volatile Thread runner;
/** 線程等待節點,reiber stack的一種實現 */
private volatile WaitNode waiters;
/** 任務執行狀態 */
private volatile int state;
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
// 對應成員變量state的偏移地址
private static final long stateOffset;
// 對應成員變量runner的偏移地址
private static final long runnerOffset;
// 對應成員變量waiters的偏移地址
private static final long waitersOffset;
這裏我們要重點關注下FutureTask的Callable成員變量,因為FutureTask的異步任務最終是委託給Callable去實現的。
思考:
FutureTask的成員變量runner,waiters和state都被volatile修飾,我們可以思考下為什麼這三個成員變量需要被volatile修飾,而其他成員變量又不用呢?volatile關鍵字的作用又是什麼呢?runner,waiters和state了,此時又定義了stateOffset,runnerOffset和waitersOffset變量分別對應runner,waiters和state的偏移地址,為何要多此一舉呢?我們再來看看stateOffset,runnerOffset和waitersOffset變量這三個變量的初始化過程:
// FutureTask.java
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = FutureTask.class;
stateOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("state"));
runnerOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("runner"));
waitersOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("waiters"));
} catch (Exception e) {
throw new Error(e);
}
}
前面講了FutureTask的成員變量,有一個表示狀態的成員變量state我們要重點關注下,state變量表示任務執行的狀態。
// FutureTask.java
/** 任務執行狀態 */
private volatile int state;
/** 任務新建狀態 */
private static final int NEW = 0;
/** 任務正在完成狀態,是一個瞬間過渡狀態 */
private static final int COMPLETING = 1;
/** 任務正常結束狀態 */
private static final int NORMAL = 2;
/** 任務執行異常狀態 */
private static final int EXCEPTIONAL = 3;
/** 任務被取消狀態,對應cancel(false) */
private static final int CANCELLED = 4;
/** 任務中斷狀態,是一個瞬間過渡狀態 */
private static final int INTERRUPTING = 5;
/** 任務被中斷狀態,對應cancel(true) */
private static final int INTERRUPTED = 6;
可以看到任務狀態變量state有以上7種狀態,0-6分別對應着每一種狀態。任務狀態一開始是NEW,然後由FutureTask的三個方法set,setException和cancel來設置狀態的變化,其中狀態變化有以下四種情況:
NEW -> COMPLETING -> NORMAL:這個狀態變化表示異步任務的正常結束,其中COMPLETING是一個瞬間臨時的過渡狀態,由set方法設置狀態的變化;NEW -> COMPLETING -> EXCEPTIONAL:這個狀態變化表示異步任務執行過程中拋出異常,由setException方法設置狀態的變化;NEW -> CANCELLED:這個狀態變化表示被取消,即調用了cancel(false),由cancel方法來設置狀態變化;NEW -> INTERRUPTING -> INTERRUPTED:這個狀態變化表示被中斷,即調用了cancel(true),由cancel方法來設置狀態變化。FutureTask有兩個構造函數,我們分別來看看:
// FutureTask.java
// 第一個構造函數
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
可以看到,這個構造函數在我們前面舉的“打火鍋”的例子代碼中有用到,就是Callable成員變量賦值,在異步執行任務時再調用Callable.call方法執行異步任務邏輯。此外,此時給任務狀態state賦值為NEW,表示任務新建狀態。
我們再來看下FutureTask的另外一個構造函數:
// FutureTask.java
// 另一個構造函數
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
這個構造函數在執行Executors.callable(runnable, result)時是通過適配器RunnableAdapter來將Runnable對象runnable轉換成Callable對象,然後再分別給callable和state變量賦值。
注意,這裏我們需要記住的是FutureTask新建時,此時的任務狀態state是NEW就好了。
前面我們有講到FutureTask間接實現了Runnable接口,覆寫了Runnable接口的run方法,因此該覆寫的run方法是提交給線程來執行的,同時,該run方法正是執行異步任務邏輯的方法,那麼,執行完run方法又是如何保存異步任務執行的結果的呢?
我們現在着重來分析下run方法:
// FutureTask.java
public void run() {
// 【1】,為了防止多線程併發執行異步任務,這裏需要判斷線程滿不滿足執行異步任務的條件,有以下三種情況:
// 1)若任務狀態state為NEW且runner為null,說明還未有線程執行過異步任務,此時滿足執行異步任務的條件,
// 此時同時調用CAS方法為成員變量runner設置當前線程的值;
// 2)若任務狀態state為NEW且runner不為null,任務狀態雖為NEW但runner不為null,說明有線程正在執行異步任務,
// 此時不滿足執行異步任務的條件,直接返回;
// 1)若任務狀態state不為NEW,此時不管runner是否為null,說明已經有線程執行過異步任務,此時沒必要再重新
// 執行一次異步任務,此時不滿足執行異步任務的條件;
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
// 拿到之前構造函數傳進來的callable實現類對象,其call方法封裝了異步任務執行的邏輯
Callable<V> c = callable;
// 若任務還是新建狀態的話,那麼就調用異步任務
if (c != null && state == NEW) {
// 異步任務執行結果
V result;
// 異步任務執行成功還是始遍標誌
boolean ran;
try {
// 【2】,執行異步任務邏輯,並把執行結果賦值給result
result = c.call();
// 若異步任務執行過程中沒有拋出異常,說明異步任務執行成功,此時設置ran標誌為true
ran = true;
} catch (Throwable ex) {
result = null;
// 異步任務執行過程拋出異常,此時設置ran標誌為false
ran = false;
// 【3】設置異常,裏面也設置state狀態的變化
setException(ex);
}
// 【3】若異步任務執行成功,此時設置異步任務執行結果,同時也設置狀態的變化
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
// 異步任務正在執行過程中,runner一直是非空的,防止併發調用run方法,前面有調用cas方法做判斷的
// 在異步任務執行完后,不管是正常結束還是異常結束,此時設置runner為null
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
// 線程執行異步任務后的任務狀態
int s = state;
// 【4】如果執行了cancel(true)方法,此時滿足條件,
// 此時調用handlePossibleCancellationInterrupt方法處理中斷
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
可以看到執行異步任務的run方法主要分為以下四步來執行:
Callable.call方法中,此時直接調用Callable.call方法執行異步任務,然後返回執行結果;set(result);來設置任務執行結果;2)若異步任務執行拋出異常,此時調用setException(ex);來設置異常,詳細分析請見4.4.1小節;FutureTask.cancel(true),此時需要調用handlePossibleCancellationInterrupt方法處理中斷,詳細分析請見4.4.2小節。這裏值得注意的是判斷線程滿不滿足執行異步任務條件時,runner是否為null是調用UNSAFE的CAS方法compareAndSwapObject來判斷和設置的,同時compareAndSwapObject是通過成員變量runner的偏移地址runnerOffset來給runner賦值的,此外,成員變量runner被修飾為volatile是在多線程的情況下, 一個線程的volatile修飾變量的設值能夠立即刷進主存,因此值便可被其他線程可見。
下面我們來看下當異步任務執行正常結束時,此時會調用set(result);方法:
// FutureTask.java
protected void set(V v) {
// 【1】調用UNSAFE的CAS方法判斷任務當前狀態是否為NEW,若為NEW,則設置任務狀態為COMPLETING
// 【思考】此時任務不能被多線程併發執行,什麼情況下會導致任務狀態不為NEW?
// 答案是只有在調用了cancel方法的時候,此時任務狀態不為NEW,此時什麼都不需要做,
// 因此需要調用CAS方法來做判斷任務狀態是否為NEW
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
// 【2】將任務執行結果賦值給成員變量outcome
outcome = v;
// 【3】將任務狀態設置為NORMAL,表示任務正常結束
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
// 【4】調用任務執行完成方法,此時會喚醒阻塞的線程,調用done()方法和清空等待線程鏈表等
finishCompletion();
}
}
可以看到當異步任務正常執行結束后,且異步任務沒有被cancel的情況下,此時會做以下事情:將任務執行結果保存到FutureTask的成員變量outcome中的,賦值結束後會調用finishCompletion方法來喚醒阻塞的線程(哪裡來的阻塞線程?後面會分析),值得注意的是這裏對應的任務狀態變化是NEW -> COMPLETING -> NORMAL。
我們繼續來看下當異步任務執行過程中拋出異常,此時會調用setException(ex);方法。
// FutureTask.java
protected void setException(Throwable t) {
// 【1】調用UNSAFE的CAS方法判斷任務當前狀態是否為NEW,若為NEW,則設置任務狀態為COMPLETING
// 【思考】此時任務不能被多線程併發執行,什麼情況下會導致任務狀態不為NEW?
// 答案是只有在調用了cancel方法的時候,此時任務狀態不為NEW,此時什麼都不需要做,
// 因此需要調用CAS方法來做判斷任務狀態是否為NEW
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
// 【2】將異常賦值給成員變量outcome
outcome = t;
// 【3】將任務狀態設置為EXCEPTIONAL
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
// 【4】調用任務執行完成方法,此時會喚醒阻塞的線程,調用done()方法和清空等待線程鏈表等
finishCompletion();
}
}
可以看到setException(Throwable t)的代碼邏輯跟前面的set(V v)幾乎一樣,不同的是任務執行過程中拋出異常,此時是將異常保存到FutureTask的成員變量outcome中,還有,值得注意的是這裏對應的任務狀態變化是NEW -> COMPLETING -> EXCEPTIONAL。
因為異步任務不管正常還是異常結束,此時都會調用FutureTask的finishCompletion方法來喚醒喚醒阻塞的線程,這裏阻塞的線程是指我們調用Future.get方法時若異步任務還未執行完,此時該線程會阻塞。
// FutureTask.java
private void finishCompletion() {
// assert state > COMPLETING;
// 取出等待線程鏈表頭節點,判斷頭節點是否為null
// 1)若線程鏈表頭節點不為空,此時以“後進先出”的順序(棧)移除等待的線程WaitNode節點
// 2)若線程鏈表頭節點為空,說明還沒有線程調用Future.get()方法來獲取任務執行結果,固然不用移除
for (WaitNode q; (q = waiters) != null;) {
// 調用UNSAFE的CAS方法將成員變量waiters設置為空
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
// 取出WaitNode節點的線程
Thread t = q.thread;
// 若取出的線程不為null,則將該WaitNode節點線程置空,且喚醒正在阻塞的該線程
if (t != null) {
q.thread = null;
//【重要】喚醒正在阻塞的該線程
LockSupport.unpark(t);
}
// 繼續取得下一個WaitNode線程節點
WaitNode next = q.next;
// 若沒有下一個WaitNode線程節點,說明已經將所有等待的線程喚醒,此時跳出for循環
if (next == null)
break;
// 將已經移除的線程WaitNode節點的next指針置空,此時好被垃圾回收
q.next = null; // unlink to help gc
// 再把下一個WaitNode線程節點置為當前線程WaitNode頭節點
q = next;
}
break;
}
}
// 不管任務正常執行還是拋出異常,都會調用done方法
done();
// 因為異步任務已經執行完且結果已經保存到outcome中,因此此時可以將callable對象置空了
callable = null; // to reduce footprint
}
finishCompletion方法的作用就是不管異步任務正常還是異常結束,此時都要喚醒且移除線程等待鏈表的等待線程節點,這個鏈表實現的是一個是Treiber stack,因此喚醒(移除)的順序是”後進先出”即後面先來的線程先被先喚醒(移除),關於這個線程等待鏈表是如何成鏈的,後面再繼續分析。
在4.4小節分析的run方法里的最後有一個finally塊,此時若任務狀態state >= INTERRUPTING,此時說明有其他線程執行了cancel(true)方法,此時需要讓出CPU執行的時間片段給其他線程執行,我們來看下具體的源碼:
// FutureTask.java
private void handlePossibleCancellationInterrupt(int s) {
// It is possible for our interrupter to stall before getting a
// chance to interrupt us. Let's spin-wait patiently.
// 當任務狀態是INTERRUPTING時,此時讓出CPU執行的機會,讓其他線程執行
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield(); // wait out pending interrupt
// assert state == INTERRUPTED;
// We want to clear any interrupt we may have received from
// cancel(true). However, it is permissible to use interrupts
// as an independent mechanism for a task to communicate with
// its caller, and there is no way to clear only the
// cancellation interrupt.
//
// Thread.interrupted();
}
思考: 為啥任務狀態是
INTERRUPTING時,此時就要讓出CPU執行的時間片段呢?還有為什麼要在義務任務執行后才調用handlePossibleCancellationInterrupt方法呢?
前面我們起一個線程在其`run`方法中執行異步任務后,此時我們可以調用`FutureTask.get`方法來獲取異步任務執行的結果。
// FutureTask.java
public V get() throws InterruptedException, ExecutionException {
int s = state;
// 【1】若任務狀態<=COMPLETING,說明任務正在執行過程中,此時可能正常結束,也可能遇到異常
if (s <= COMPLETING)
s = awaitDone(false, 0L);
// 【2】最後根據任務狀態來返回任務執行結果,此時有三種情況:1)任務正常執行;2)任務執行異常;3)任務被取消
return report(s);
}
可以看到,如果任務狀態state<=COMPLETING,說明異步任務正在執行過程中,此時會調用awaitDone方法阻塞等待;當任務執行完后,此時再調用report方法來報告任務結果,此時有三種情況:1)任務正常執行;2)任務執行異常;3)任務被取消。
FutureTask.awaitDone方法會阻塞獲取異步任務執行結果的當前線程,直到異步任務執行完成。
// FutureTask.java
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
// 計算超時結束時間
final long deadline = timed ? System.nanoTime() + nanos : 0L;
// 線程鏈表頭節點
WaitNode q = null;
// 是否入隊
boolean queued = false;
// 死循環
for (;;) {
// 如果當前獲取任務執行結果的線程被中斷,此時移除該線程WaitNode鏈表節點,並拋出InterruptedException
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
// 【5】如果任務狀態>COMPLETING,此時返回任務執行結果,其中此時任務可能正常結束(NORMAL),可能拋出異常(EXCEPTIONAL)
// 或任務被取消(CANCELLED,INTERRUPTING或INTERRUPTED狀態的一種)
if (s > COMPLETING) {
// 【問】此時將當前WaitNode節點的線程置空,其中在任務結束時也會調用finishCompletion將WaitNode節點的thread置空,
// 這裏為什麼又要再調用一次q.thread = null;呢?
// 【答】因為若很多線程來獲取任務執行結果,在任務執行完的那一刻,此時獲取任務的線程要麼已經在線程等待鏈表中,要麼
// 此時還是一個孤立的WaitNode節點。在線程等待鏈表中的的所有WaitNode節點將由finishCompletion來移除(同時喚醒)所有
// 等待的WaitNode節點,以便垃圾回收;而孤立的線程WaitNode節點此時還未阻塞,因此不需要被喚醒,此時只要把其屬性置為
// null,然後其有沒有被誰引用,因此可以被GC。
if (q != null)
q.thread = null;
// 【重要】返回任務執行結果
return s;
}
// 【4】若任務狀態為COMPLETING,此時說明任務正在執行過程中,此時獲取任務結果的線程需讓出CPU執行時間片段
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
// 【1】若當前線程還沒有進入線程等待鏈表的WaitNode節點,此時新建一個WaitNode節點,並把當前線程賦值給WaitNode節點的thread屬性
else if (q == null)
q = new WaitNode();
// 【2】若當前線程等待節點還未入線程等待隊列,此時加入到該線程等待隊列的頭部
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
// 若有超時設置,那麼處理超時獲取任務結果的邏輯
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
// 【3】若沒有超時設置,此時直接阻塞當前線程
else
LockSupport.park(this);
}
}
FutureTask.awaitDone方法主要做的事情總結如下:
awaitDone方法裏面是一個死循環;state>COMPLETING,此時返回任務執行結果;COMPLETING,此時獲取任務結果的線程需讓出CPU執行時間片段;q == null,說明當前線程還未設置到WaitNode節點,此時新建WaitNode節點並設置其thread屬性為當前線程;queued==false,說明當前線程WaitNode節點還未加入線程等待鏈表,此時加入該鏈表的頭部;timed設置為true時,此時該方法具有超時功能,關於超時的邏輯這裏不詳細分析;我們分析到這裏,可以直到執行異步任務只能有一個線程來執行,而獲取異步任務結果可以多線程來獲取,當異步任務還未執行完時,此時獲取異步任務結果的線程會加入線程等待鏈表中,然後調用調用LockSupport.park(this);方法阻塞當前線程。直到異步任務執行完成,此時會調用finishCompletion方法來喚醒並移除線程等待鏈表的每個WaitNode節點,這裏這裏喚醒(移除)WaitNode節點的線程是從鏈表頭部開始的,前面我們也已經分析過。
還有一個特別需要注意的就是awaitDone方法裏面是一個死循環,當一個獲取異步任務的線程進來后可能會多次進入多個條件分支執行不同的業務邏輯,也可能只進入一個條件分支。下面分別舉兩種可能的情況進行說明:
情況1:
當獲取異步任務結果的線程進來時,此時異步任務還未執行完即state=NEW且沒有超時設置時:
q = null,此時進入上面代碼標號【1】的判斷分支,即為當前線程新建一個WaitNode節點;queued = false,此時進入上面代碼標號【2】的判斷分支,即將之前新建的WaitNode節點加入線程等待鏈表中;【3】的判斷分支,即阻塞當前線程;【5】的判斷分支,即返回異步任務執行結果。情況2:
當獲取異步任務結果的線程進來時,此時異步任務已經執行完即state>COMPLETING且沒有超時設置時,此時直接進入上面代碼標號【5】的判斷分支,即直接返回異步任務執行結果即可,也不用加入線程等待鏈表了。
在get方法中,當異步任務執行結束后即不管異步任務正常還是異常結束,亦或是被cancel,此時獲取異步任務結果的線程都會被喚醒,因此會繼續執行FutureTask.report方法報告異步任務的執行情況,此時可能會返回結果,也可能會拋出異常。
// FutureTask.java
private V report(int s) throws ExecutionException {
// 將異步任務執行結果賦值給x,此時FutureTask的成員變量outcome要麼保存着
// 異步任務正常執行的結果,要麼保存着異步任務執行過程中拋出的異常
Object x = outcome;
// 【1】若異步任務正常執行結束,此時返回異步任務執行結果即可
if (s == NORMAL)
return (V)x;
// 【2】若異步任務執行過程中,其他線程執行過cancel方法,此時拋出CancellationException異常
if (s >= CANCELLED)
throw new CancellationException();
// 【3】若異步任務執行過程中,拋出異常,此時將該異常轉換成ExecutionException后,重新拋出。
throw new ExecutionException((Throwable)x);
}
我們最後再來看下FutureTask.cancel方法,我們一看到FutureTask.cancel方法,肯定一開始就天真的認為這是一個可以取消異步任務執行的方法,如果我們這樣認為的話,只能說我們猜對了一半。
// FutureTask.java
public boolean cancel(boolean mayInterruptIfRunning) {
// 【1】判斷當前任務狀態,若state == NEW時根據mayInterruptIfRunning參數值給當前任務狀態賦值為INTERRUPTING或CANCELLED
// a)當任務狀態不為NEW時,說明異步任務已經完成,或拋出異常,或已經被取消,此時直接返回false。
// TODO 【問題】此時若state = COMPLETING呢?此時為何也直接返回false,而不能發出中斷異步任務線程的中斷信號呢??
// TODO 僅僅因為COMPLETING是一個瞬時態嗎???
// b)當前僅當任務狀態為NEW時,此時若mayInterruptIfRunning為true,此時任務狀態賦值為INTERRUPTING;否則賦值為CANCELLED。
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
// 【2】如果mayInterruptIfRunning為true,此時中斷執行異步任務的線程runner(還記得執行異步任務時就把執行異步任務的線程就賦值給了runner成員變量嗎)
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
// 中斷執行異步任務的線程runner
t.interrupt();
} finally { // final state
// 最後任務狀態賦值為INTERRUPTED
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
// 【3】不管mayInterruptIfRunning為true還是false,此時都要調用finishCompletion方法喚醒阻塞的獲取異步任務結果的線程並移除線程等待鏈表節點
} finally {
finishCompletion();
}
// 返回true
return true;
}
以上代碼中,當異步任務狀態state != NEW時,說明異步任務已經正常執行完或已經異常結束亦或已經被cancel,此時直接返回false;當異步任務狀態state = NEW時,此時又根據mayInterruptIfRunning參數是否為true分為以下兩種情況:
mayInterruptIfRunning = false時,此時任務狀態state直接被賦值為CANCELLED,此時不會對執行異步任務的線程發出中斷信號,值得注意的是這裏對應的任務狀態變化是NEW -> CANCELLED。mayInterruptIfRunning = true時,此時會對執行異步任務的線程發出中斷信號,值得注意的是這裏對應的任務狀態變化是NEW -> INTERRUPTING -> INTERRUPTED。最後不管mayInterruptIfRunning為true還是false,此時都要調用finishCompletion方法喚醒阻塞的獲取異步任務結果的線程並移除線程等待鏈表節點。
從FutureTask.cancel源碼中我們可以得出答案,該方法並不能真正中斷正在執行異步任務的線程,只能對執行異步任務的線程發出中斷信號。如果執行異步任務的線程處於sleep、wait或join的狀態中,此時會拋出InterruptedException異常,該線程可以被中斷;此外,如果異步任務需要在while循環執行的話,此時可以結合以下代碼來結束異步任務線程,即執行異步任務的線程被中斷時,此時Thread.currentThread().isInterrupted()返回true,不滿足while循環條件因此退出循環,結束異步任務執行線程,如下代碼:
public Integer call() throws Exception {
while (!Thread.currentThread().isInterrupted()) {
// 業務邏輯代碼
System.out.println("running...");
}
return 666;
}
注意:調用了FutureTask.cancel方法,只要返回結果是true,假如異步任務線程雖然不能被中斷,即使異步任務線程正常執行完畢,返回了執行結果,此時調用FutureTask.get方法也不能夠獲取異步任務執行結果,此時會拋出CancellationException異常。請問知道這是為什麼嗎?
因為調用了FutureTask.cancel方法,只要返回結果是true,此時的任務狀態為CANCELLED或INTERRUPTED,同時必然會執行finishCompletion方法,而finishCompletion方法會喚醒獲取異步任務結果的線程等待列表的線程,而獲取異步任務結果的線程喚醒后發現狀態s >= CANCELLED,此時就會拋出CancellationException異常了。
好了,本篇文章對FutureTask的源碼分析就到此結束了,下面我們再總結下FutureTask的實現邏輯:
Callable接口,在覆寫的call方法中定義需要執行的業務邏輯;Callable接口實現對象傳給FutureTask,然後FutureTask作為異步任務提交給線程執行;FutureTask內部維護了一個狀態state,任何操作(異步任務正常結束與否還是被取消)都是圍繞着這個狀態進行,並隨時更新state任務的狀態;FutureTask.cancel方法時並不能真正停止執行異步任務的線程,只是發出中斷線程的信號。但是只要cancel方法返回true,此時即使異步任務能正常執行完,此時我們調用get方法獲取結果時依然會拋出CancellationException異常。擴展: 前面我們提到了
FutureTask的runner,waiters和state都是用volatile關鍵字修飾,說明這三個變量都是多線程共享的對象(成員變量),會被多線程操作,此時用volatile關鍵字修飾是為了一個線程操作volatile屬性變量值后,能夠及時對其他線程可見。此時多線程操作成員變量僅僅用了volatile關鍵字仍然會有線程安全問題的,而此時Doug Lea老爺子沒有引入任何線程鎖,而是採用了Unsafe的CAS方法來代替鎖操作,確保線程安全性。
我們分析源碼的目的是什麼?除了弄懂FutureTask的內部實現原理外,我們還要借鑒大佬寫寫框架源碼的各種技巧,只有這樣,我們才能成長。
分析了FutureTask源碼,我們可以從中學到:
LockSupport來實現線程的阻塞\喚醒機制;volatile和UNSAFE的CAS方法來實現線程共享變量的無鎖化操作;FutureTask的get(long timeout, TimeUnit unit)的實現邏輯;FutureTask中的任務狀態satate的變化處理的邏輯實現。以上列舉的幾點都是我們可以學習參考的地方。
若您覺得不錯,請無情的轉發和點贊吧!
【源碼筆記】Github地址:
https://github.com/yuanmabiji/Java-SourceCode-Blogs
公眾號【源碼筆記】,專註於Java後端系列框架的源碼分析。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
摘錄自2019年10月31日中央社報導
紐約市議會31日以以42票贊成、6票反對的壓倒性票數,通過將從2022年起禁售鵝肝。 議員認為,為滿足人類口腹之慾而逼迫鴨鵝將肝臟養肥,是很殘忍的事。
這項法律將自2022年10月起生效,禁止任何組織銷售、提供甚至是處理鵝肝。 違者每次違規將被罰500到2000美元。
不過,鵝肝農民大喊不公,揚言將採取法律行動。農民聲稱他們的生產作業並不殘忍,是運動人士誇大了動物承受的苦難。 他們說,他們餵食鴨鵝的玉米量,並未超過牠們自己食用的量。
全球目前已有數國禁止生產鵝肝,包括英國等。鵝肝產業正在探索替代方法,尋找不需利用強迫餵食手段生產鵝肝的辦法。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
摘錄自2019年11月2日中央通訊社美國報導
奧斯卡影后、同時也是倡議人士的珍芳達(Jane Fonda)2日在華府國會參議院前抗議政府處理氣候變遷不力,再度遭警方逮捕。
法新社報導,珍芳達跟記者開玩笑說:「這次我可能會被關上一晚,一晚沒關係,沒什麼大不了。」珍芳達在警方為她戴上塑膠手銬時說,這不是她第一次被捕。
珍芳達和女星羅姍娜艾奎特(Rosanna Arquette)、凱薩琳琪娜(Catherine Keener)等數十名倡議人士,坐在不得示威的聯邦參議院大樓前高呼口號而被捕。
年近82歲的珍芳達自1970年代以來即是和平主義者,目前仍活躍影壇。她說,自己受到瑞典環保少女桑柏格(Greta Thunberg)的感召而加入反氣候變遷運動。
珍芳達表示:「抗爭的方式有很多種,但我深受桑柏格和全世界示威年輕學子的啟發。」
她說:「我是名人,所以利用我的名氣傳達出我們所面臨危機的訊息,這個危機將決定我們孩子和孫子的未來世界是否還適合人類居住。」
曾於1972年親赴越南抗議越戰的珍芳達表示,現在迫切需要採取行動。赴越抗議為她贏得「河內珍」(Hanoi Jane)的反戰明星稱號。
珍芳達說:「我們只剩11年的時間可以扭轉情勢,我們要很勇敢、很團結和堅定。」
穿著一身搶眼紅色大衣的珍芳達說的是,科學研究顯示,人類若要避免引發災難性全球暖化,必須在2030年前大幅減少碳排放量。
珍芳達表示,她已做好至少在1月中旬以前會被一再逮捕的心理準備,因為在那之後,她得回好萊塢拍攝下一季的得獎影集「同妻俱樂部」(Grace and Frankie)。榕
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
停車也可免費充電了,基隆市博愛和成功 2 停車場各提供 2 部電動車充電器,電動車只要停車就可以免費充電。 為推廣綠能載具,基隆市政府向經濟部工業局爭取,在基隆博愛與成功停車場,各裝置 2 部電動車充電器,每部充電器可同時為 2 輛汽車充電。 博愛停車場管理站金微歡指出,民眾停車的同時,就可以免費充電,1 輛車充到飽約需 2 個半小時至 3 小時,使用悠遊卡啟動,就可免費充電,不過,基隆電動車還不普遍,目前 1 個月只有個位數的車輛充電。
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
我是風箏,公眾號「古時的風箏」,一個兼具深度與廣度的程序員鼓勵師,一個本打算寫詩卻寫起了代碼的田園碼農!
文章會收錄在 JavaNewBee 中,更有 Java 後端知識圖譜,從小白到大牛要走的路都在裏面。
程序員上班戴耳機聽歌難道不是正常的嗎,真的還有公司不允許程序員戴耳機的嗎?不戴耳機能寫代碼?
那程序員的耳機里聽的是什麼呢?我採訪了一眾程序員朋友。
鋼鐵程序員王二麻子同學
聽的什麼?我根本就不知道,我只是不想讓別人打擾我
有時候開發確實是比較費腦子的,尤其是遇到複雜邏輯的時候。正當思如泉涌、靈感迸發的時候,旁人看着我坐在那裡一動不動,好像什麼都沒有做,其實我腦子里正在構思一個複雜的流程。
這時候,突然有個人走過來打斷我,前面的思考都白費了,你說傷心不,你說氣人不。
所以,為了防止上面的情況出現,不得不戴上耳機。至於聽什麼,不重要,我只是告訴別人,別過來,我現在沒時間。
生活需要儀式感的劉精神同學
聽什麼不重要,關鍵是儀式感
生活需要儀式感,寫代碼也需要儀式感啊。當我戴上耳機的那一刻,我感覺精神抖擻,彷彿遊離的三魂六魄都回來了,寫代碼更有動力。
要是不戴耳機,感覺渾身無力,只想摸魚,寫代碼什麼的,根本想都想不起來。
心疼自己的高愛己同學
其實我就是不想聽我的机械鍵盤聲音,實在太吵了
你也知道,筆記本自帶的鍵盤總感覺軟綿綿的,敲起來實在不給力,嚴重影響我的工作效率。那必須得買個鍵盤啊,在多個朋友的推薦下,我就買了一個青軸鍵盤。你別說,觸感真不錯,每按一下,都感覺指尖有一股電流滑過,同時伴隨着啪啪啪的聲音,感覺寫代碼效率明顯變高了,更神奇的是 bug 都比以前少了,你說神奇不。
但是呢,有個缺點,就是時間久了吧,感覺稍微有那麼一點點吵,所以我就配了個耳機,從此之後,既得到了直達靈魂的觸感,又不會感覺吵了。更神奇的一點是,以前罵我的同事好像都不罵我了。
真的在聽東西的同學
雖然存在以上幾位同學說的情況,但大多數情況下, 我們是真的在聽東西。比如我,為了聽高品質的,還專門買了網易雲音樂的 VIP 。
為什麼是東西,而不是音樂呢,因為有的人不是在聽音樂。
我聽說有人在寫代碼的時候聽評書,更有厲害的,竟然聽相聲。那我想只有三種可能。
當然畢竟大佬不常有,而普通群眾常用。大部分人聽的確實是音樂。比如我吧,我戴耳機真的是在聽音樂,為了降噪、減少干擾,提高專註力,提升效率。
英文歌
英文歌是大多數程序員的最愛。請看網易雲音樂給我的每日歌曲推薦,除了伍佰的一首中文歌亂入,剩下的都是英文歌。
你真的認為我英語很好嗎,正好相反,之所以英文歌那麼受歡迎,就是因為聽不懂歌詞是什麼意思,這樣才不會被歌詞帶跑偏,沒錯,我們聽的就是這個節奏。
那要是中文歌就不一樣了,比如說當我聽到「你的酒館對我打了烊,子彈在我心頭上了膛」的時候,我就以為真的要打烊了,子彈真的要上膛了,從而引發一系列思考,酒館為什麼對我打烊,子彈為什麼要上膛,生意不做了嗎,刑法基本法則不懂嗎。
為了證明這一點,我到網易雲音樂上搜了「程序員」相關的歌單,點進去一看,大部分也都是英文歌,看來大家英文都不太好。
然後我又搜了「產品狗」相關的歌單,同樣也是英文歌為主,可見我們雖然不太對付,請參考歌單『產品狗如何說服程序猿』和『程序員如何回應產品狗』,但是方法論還是差不多的。
純音樂
純音樂也是很受歡迎的,我有個同事最喜歡聽貝多芬的命運交響曲,我就沒那麼文藝了。我一般都是聽那種激昂的小提琴或者聽完感覺自己馬上要登基了的那種,不容易犯困。
白噪音
小提琴太勁爆,不能常聽,犯困的下午聽聽可以提神,bug 太多又不想改的時候可以聽聽。大多數時候,不需要那麼亢奮,保持內心的平靜就是寫代碼最好的狀態。那就聽聽白噪音好了,比如雨聲、風聲、溪水聲、鳥唱蟬鳴。
我最喜歡的就是雨聲,嘩啦啦的大雨,再配上驚雷,簡直不要太平靜,innerpeace。
比如下面這個,一聽就是一個小時。
每個人都有自己家私藏的歌單,百聽不厭的那種,當然每個人的品位不一樣、口味兒不一樣,比如當年有個哥們兒異常興奮的把他的珍藏歌單分享給我,我當即表示很感動,一定要認真聽一聽。可當我硬着頭皮聽完第二首的時候,我內心是拒絕的,只能委婉的跟哥們兒說:這 TM 什麼玩意兒。當然是不會破壞友誼的小船的,如果是不熟的朋友,那肯定會豎起大拇指,並且連連點頭稱讚:真不錯,品位棒棒噠。
我沒有薦歌啊,向別人推薦歌曲犹如喂別人食物,你覺得好吃別人並一定覺得好吃。下面是我的 2018 年年度聽歌報告,Sophie Zelmani(蘇菲.珊曼妮)的熱門50單曲就是我的保留曲目,白聽不厭,而且更重要的不仔細聽,還是聽不出歌詞的意思,正好適合寫代碼用。
另外,作為一個程序員鼓勵師,為了鼓勵我自己,我也創建了一個「程序員鼓勵師」的歌單,經常拿出來聽聽接收鼓勵。
作為程序員的你,耳機里有什麼特殊的內容嗎?
壯士且慢,先給點個贊吧,總是被白嫖,身體吃不消!
我是風箏,公眾號「古時的風箏」。一個兼具深度與廣度的程序員鼓勵師,一個本打算寫詩卻寫起了代碼的田園碼農!你可選擇現在就關注我,或者看看歷史文章再關注也不遲。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案