一、執行緒的狀態
執行緒的狀態包括新建(初始狀態)、就緒、運行、死亡(終止)、阻塞;
(1)簡化版本

(2)結合java執行緒方法版本

(2)執行緒通信
-
wait():導致當前執行緒等待,直到其他執行緒呼叫該同步監視器的notify()方法或notifyAll()方法來喚醒該執行緒,該wait()方法有3種形式——無時間引數的wait(一直等待,直到其他執行緒通知),帶毫秒引數的wait和帶毫秒、毫微秒引數的wait(這兩種方法都是等待指定時間后自動蘇醒),呼叫wait()方法的當前執行緒會釋放對該同步監視器的鎖定,
-
notify():喚醒在此同步監視器上等待的單個執行緒,如果所有執行緒都在此同步監視器上等待,則會選擇喚醒其中一個執行緒,選擇是任意性的,只有當前執行緒放棄對該同步監視器的鎖定后(使用wait()方法),才可以執行被喚醒的執行緒,
-
notifyAll():喚醒在此同步監視器上等待的所有執行緒,只有當前執行緒放棄對該同步監視器的鎖定后,才可以執行被喚醒的執行緒,
使用示例:
package test; public class ThreadComm { public static boolean WASHED = false; public static void wash(int i) { System.out.println(i + "已經洗手"); WASHED = true; } public static void eat(int i) { System.out.println(i + "已經吃飯"); WASHED = false; } public static void main(String[] args) { // wash執行緒 for (int i = 0; i <= 5; i++) { int j = i; new Thread(new Runnable() { @Override public void run() { doWash(j); doEat(j); } private synchronized void doWash(int i) { if (!WASHED) {// 如果還沒洗手,就執行洗手操作,否則,阻塞當前執行緒,直到吃飯完成 ThreadComm.wash(i); notifyAll(); } else { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } }// doWash private synchronized void doEat(int i) { if (WASHED) {// 已經洗完手,喚起當前吃飯執行緒 ThreadComm.eat(i); notifyAll(); } else { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } }// doEat }).start(); }// for } }
二、執行緒池
(1)常用執行緒池的類結構

普通執行緒執行完,就會進入TERMINATED銷毀掉,而執行緒池就是創建一個緩沖池存放執行緒,執行結束以后,該執行緒并不會死亡,而是再次回傳執行緒池中成為空閑狀態,等候下次任務來臨,這使得執行緒池比手動創建執行緒有著更多的優勢:
- 降低系統資源消耗,通過重用已存在的執行緒,降低執行緒創建和銷毀造成的消耗;
- 提高系統回應速度,當有任務到達時,通過復用已存在的執行緒,無需等待新執行緒的創建便能立即執行;
- 方便執行緒并發數的管控,因為執行緒若是無限制的創建,可能會導致記憶體占用過多而產生OOM;
- 節省cpu切換執行緒的時間成本(需要保持當前執行執行緒的現場,并恢復要執行執行緒的現場),
- 提供更強大的功能,延時定時執行緒池,(eg:ScheduledThreadPoolExecutor可以代替Timer執行定時任務)
(2)執行緒池的作業狀態

- RUNNING:初始化狀態是RUNNING,執行緒池被一旦被創建,就處于RUNNING狀態,并且執行緒池中的任務數為0,RUNNING狀態下,能夠接收新任務,以及對已添加的任務進行處理,
- SHUTDOWN:SHUTDOWN狀態時,不接收新任務,但能處理已添加的任務,呼叫執行緒池的shutdown()介面時,執行緒池由RUNNING -> SHUTDOWN,
- STOP:不接收新任務,不處理已添加的任務,并且會中斷正在處理的任務,呼叫執行緒池的shutdownNow()介面時,執行緒池由(RUNNING 或 SHUTDOWN ) -> STOP,
注意:運行中的任務還會列印,直到結束,因為調的是Thread.interrupt
- TIDYING:所有的任務已終止,佇列中的”任務數量”為0,執行緒池會變為TIDYING,執行緒池變為TIDYING狀態時,會執行鉤子函式terminated(),可以通過多載terminated()函式來實作自定義行為,
- TERMINATED:執行緒池處在TIDYING狀態時,執行完terminated()之后,就會由 TIDYING -> TERMINATED
(3)執行緒池原理

- 添加任務,如果執行緒池中執行緒數沒達到coreSize,直接創建新執行緒執行
- 達到core,放入queue
- queue已滿,未達到maxSize繼續創建執行緒
- 達到maxSize,根據reject策略處理
- 超時后,執行緒被釋放,下降到coreSize
(4)執行緒池原始碼分析
1)執行緒池是如何保證執行緒不被銷毀的呢?
如果佇列中沒有任務時,核心執行緒會一直阻塞在獲取任務的方法,直到回傳任務,而任務執行完后,又會進 下一輪 work.runWork()中回圈
驗證:秘密就藏在核心原始碼里 ThreadPoolExecutor.getTask()
//work.runWork(): while (task != null || (task = getTask()) != null) //work.getTask(): boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
2)那么執行緒池中的執行緒會處于什么狀態?
答案:RUNNABLE,WAITING
驗證:起一個執行緒池,放置一個任務sleep,debug查看結束前后的狀態
//debug add watcher: ((ThreadPoolExecutor) poolExecutor).workers.iterator().next().thread.getState()
ThreadPoolExecutor poolExecutor = Executors.newFixedThreadPool(5); poolExecutor.execute(new Runnable() { public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }); System.out.println("ok");
3)核心執行緒與非核心執行緒有區別嗎?
答案:沒有,被銷毀的執行緒和創建的先后無關,即便是第一個被創建的核心執行緒,仍然有可能被銷毀
驗證:看原始碼,每個works在runWork的時候去getTask,在getTask內部,并沒有針對性的區分當前work是否是核 心執行緒或者類似的標記,只要判斷works數量超出core,就會呼叫poll(),否則take()
(5)執行緒池調優
1)Executors剖析
1.1)newCachedThreadPool
//core=0 //max=Integer //timeout=60s //queue=1 //也就是只要執行緒不夠用,就一直開,不用就全部釋放,執行緒數0‐max之間彈性伸縮 //注意:任務并發太高且耗時較長時,造成cpu高消耗,同時要警惕OOM return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
1.2)newFixedThreadPool
//core=max=指定數量 //timeout=0 //queue=無界鏈表 //也就是說,執行緒數一直保持制定數量,不增不減,永不超時 //如果不夠用,就沿著佇列一直追加上去,排隊等候 //注意:并發太高時,容易造成長時間等待無回應,如果任務臨時變數資料過多,容易OOM return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory);
1.3)newSingleThreadExecutor
//core=max=1 //timeout=0 //queue=無界鏈表 //只有一個執行緒在慢吞吞的干活,可以認為是fix的特例 //適用于任務零散提交,不緊急的情況 new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
1.4)newScheduledThreadPool
//core=制定數 //max=Integer //timeout=0 //queue=DelayedWorkQueue(重點!) //用于任務調度,DelayedWorkQueue限制住了任務可被獲取的時機(getTask方法),也就實作了時間控制 super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory);
2)優化建議
2.1)corePoolSize
基本執行緒數,一旦有任務進來,在core范圍內會立刻創建執行緒進入作業,所以這個值應該參考業務并發量在絕大多數時間內的并發情況,同時分析任務的特性,
高并發,執行時間短的,要盡可能小的執行緒數,如配置CPU個數+1,減少執行緒背景關系的切換,因為它不怎么占時 間,讓少量執行緒快跑干活,
并發不高、任務執行時間長的要分開看:如果時間都花在了IO上,那就調大CPU,如配置兩倍CPU個數+1,不能 讓CPU閑下來,執行緒多了并行處理更快,如果時間都花在了運算上,運算的任務還很重,本身就很占cpu,那盡量 減少cpu,減少切換時間,參考第一條,
如果高并發,執行時間還很長……
2.2)workQueue
任務佇列,用于傳輸和保存等待執行任務的阻塞佇列,這個需要根據你的業務可接受的等待時間,是一個需要權衡 時間還是空間的地方,如果你的機器cpu資源緊張,jvm記憶體夠大,同時任務又不是那么緊迫,減少coresize,加大 這里,如果你的cpu不是問題,對記憶體比較敏感比較害怕記憶體溢位,同時任務又要求快點回應,那么減少這里,
2.3)maximumPoolSize
執行緒池最大數量,這個值和佇列要搭配使用,如果你采用了無界佇列,這個引數失效,同時要注意,佇列盛滿,同 時達到max的時候,再來的任務可能會丟失(下面的handler會講), 如果你的任務波動較大,同時對任務波峰來的時候,實時性要求比較高,也就是來的很突然并且都是著急的,那么 調小佇列,加大這里,如果你的任務不那么著急,可以慢慢做,那就扔佇列吧, 佇列與max是一個權衡,佇列空間換時間,多花記憶體少占cpu,輕視任務緊迫度,max舍得cpu執行緒開銷,少占記憶體,給任務最快的回應,
2.4)keepaliveTime
執行緒存活保持時間,超出該時間后,執行緒會從max下降到core,很明顯,這個決定了你養閑人所花的代價,如果 你不缺cpu,同時任務來的時間沒法琢磨,波峰波谷的間隔比較短,經常性的來一波,那么實當的延長銷毀時間, 避免頻繁創建和銷毀執行緒帶來的開銷,如果你的任務波峰出現后,很長一段時間不再出現,間隔比較久,那么要適當調小該值,讓閑著不干活的執行緒盡快銷毀,不要占據資源,
2.5)threadFactory(自定義展示實體)
執行緒工廠,用于創建新執行緒,threadFactory創建的執行緒也是采用new Thread()方式,threadFactory創建的執行緒名都具有統一的風格:pool-m-thread-n(m為執行緒池的編號,n為執行緒池內的執行緒編號),如果需要自己定義執行緒 的某些屬性,如個性化的執行緒名,可以在這里動手,一般不需要折騰它,
2.6)handler
執行緒飽和策略,當執行緒池和佇列都滿了,再加入執行緒會執行此策略,默認不處理的話會扔出例外,打進日志,這個與任務處理的資料重要程度有關,如果資料是可丟棄的,那不需要額外處理,如果資料極其重要,那需要在這里采取措施防止資料丟失,如扔訊息佇列或者至少詳細打入日志檔案可追蹤,
優化總結:
1)執行緒池的執行緒數量設定不宜過大,因為一旦執行緒池的作業執行緒總數超過系統所擁有的處理器數量,就會導致過多的背景關系切換,
2)慎用Executors,尤其如newCachedThreadPool,這個方法如果任務過多會無休止創建過多線 程,增加了背景關系的切換,最好根據業務情況,自己創建執行緒池引數,
(6)執行緒使用
package test; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.ThreadPoolExecutor; public class Test { public static void main(String[] args) { // 繼承Thread ThreadTest th1 = new ThreadTest(); th1.setName("thread"); th1.start(); // 實作Runnable RunnableTest runnable = new RunnableTest(); Thread th2 = new Thread(runnable); th2.setName("runnable"); th2.start(); // 實作Callable<> 介面,java5新增,可回傳執行結果 CallableTest callable = new CallableTest(); FutureTask<Integer> future = new FutureTask<>(callable); new Thread(future, "callable").start(); try { Integer r = future.get(); System.out.println(r); } catch (Exception e) { e.printStackTrace(); } // 執行緒池 ExecutorService pool = Executors.newFixedThreadPool(10); ThreadPoolExecutor executor = (ThreadPoolExecutor) pool; executor.execute(new PoolHandler()); } } // 方式一 class ThreadTest extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } // 方式二 class RunnableTest implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } // 方式三 class CallableTest implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); sum += i; } return sum; } } /** * 方式四 執行緒池實作方式 * 注意:使用執行緒池時,使用實作Runnable的方式可避免java中單一繼承造成的局限性 */ class PoolHandler implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } }
查閱和參考了不少資料,感謝各路大佬分享,如需轉載請注明出處,謝謝:https://www.cnblogs.com/huyangshu-fs/p/11374573.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/282068.html
標籤:其他
下一篇:聊聊webservice
