日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

線程池,顧名思義,用來存放線程的一個容器

先了解一下線程的生命周期

什么是線程池?線程池ThreadPoolExecutor使用及其原理又是什么?

 

我們為什么要用線程池?

技術的發展無非就是需求推動的,而技術領域的需求大部分都是快!再快!更快!

那么線程池出現的需求也就是痛點是什么呢?

第一、線程的創建和銷毀是要占用一定的資源的,創建線程會直接向系統申請,調用系統函數進行分配資源。操作系統給線程分配內存、列入調度,同時線程還要進行上下文的切換。

第二、在JAVA中,線程的線程棧所占用的內存在Java堆外,不受Java程序控制,只受系統資源限制,默認一個線程的線程棧大小是1M(當然這個可以通過設置-Xss屬性設置,但是要注意棧溢出問題)。如果每個請求都新建線程,1024個線程就會占用1個G內存,系統很容易崩潰。

第三、我們常用的多線程技術主要解決處理器單元內多個線程執行的問題,它的作用顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。假設一個服務器完成一項任務所需時間為:T1 創建線程時間,T2 在線程中執行任務的時間,T3 銷毀線程時間。在這種情況下經常會遇到T1+T3遠遠大于T2的情況,多線程反而成了負擔。

為了解決這些痛點,出現了線程池這個概念。

線程池做的工作主要是控制運行的線程的數量,處理過程中將任務加入隊列,然后在線程創建后啟動這些任務,如果先生超過了最大數量,超出的數量的線程排隊等候,等其他線程執行完畢,再從隊列中取出任務來執行。

他的主要特點為:線程復用、控制最大并發數、管理線程。

第一:降低資源消耗,通過重復利用自己創建的線程降低線程創建和銷毀造成的消耗。

第二: 提高響應速度,當任務到達時,任務可以不需要等到線程創建就能立即執行。

第三: 提高線程的可管理性。線程是稀缺資源,如果無限的創建,不僅會消耗資源,還會較低系統的穩定性,使用線程池可以進行統一分配,調優和監控。

java自帶的線程池工廠

java自帶有一個線程池工廠,工廠里面的線程池分了如下幾類:

Executors.newSingleThreadExecutor:創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當于單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那么會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。

Executors.newFixedThreadPool:創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那么線程池會補充一個新線程。

Executors.newCachedThreadPool:創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,那么就會回收部分空閑(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴于操作系統(或者說JVM)能夠創建的最大線程大小。

Executors.newScheduledThreadPool:創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求

Executors.newWorkStealingPool:這個是jdk1.8新增的線程池,適合處理比較耗時的工作任務。

實際使用的線程池

既然java1.8自帶了這么多線程池,我們平時生產中用那個呢?

抱歉,都不用。

為啥呢?

先看一眼線程池工作流程:

什么是線程池?線程池ThreadPoolExecutor使用及其原理又是什么?

 

如果正在運行的線程數量小于corePoolSize,那么馬上創建線程運行這個任務;如果正在運行的線程數量大于或等于corePoolSize,那么將這個任務放入隊列;如果這時候隊列滿了且正在運行的線程數量還小于maximumPoolSize,那么還是要創建非核心線程立刻運行這個任務;如果隊列滿了且正在運行的線程數量大于或等于maximumPoolSize,那么線程池會啟動飽和拒絕策略來執行。

然后咱再看看阿里規約:

什么是線程池?線程池ThreadPoolExecutor使用及其原理又是什么?

 

喏,阿里規約寫得很明白,這四個線程池有兩個不限制隊列長度,有兩個不限制線程數,這在極高并發下是非常危險的,比如阿里的雙十二,絕對秒炸。而且,沒有合適的拒絕策略,雖然這四個要么不限制隊列長,要么不限制線程數的線程池看起來都用不到,所以拒絕策略就是拋個異常就沒了。

這不坑爹呢。

雖然對于非互聯網公司貌似也夠用,最起碼簡單省事發面快,咳咳,被小時候安琪酵母的廣告洗腦了。

那我們自己寫吧,看了看上面工廠生產的前四個線程池,貌似都是用下面這個基礎函數生成的:

     public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

這幾個參數都是什么意思呢?

corePoolSize 線程池中的核心線程數maximumPoolSize 池中最大線程數keepAliveTime 當線程數超過核心線程數時,這個代表空線程在被終止前最長生存時間unit 最長存活時間的單位workQueue 線程池的工作隊列,這個隊列僅保存被Runnable任務execute方法提交的任務threadFactory 執行程序創建新線程時要使用的工廠(一般用默認Executors.defaultThreadFactory()即可)workQueue 拒絕策略,表示當線程隊列滿了并且工作線程大于等于線程池的最大顯示數(maxnumPoolSize)時如何來拒絕.

拒絕策略又有啥呢?

ThreadPoolExecutor.AbortPolicy:丟棄任務并拋出RejectedExecutionException異常。ThreadPoolExecutor.DiscardPolicy:丟棄任務,但是不拋出異常。ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新提交被拒絕的任務ThreadPoolExecutor.CallerRunsPolicy:由調用線程(提交任務的線程)處理該任務

最初直觀感受就是不能丟消息,用CallerRunsPolicy吧,主線程(暫且這么說吧,準確說是啟動線程池的線程)跟著做業務。然后就發現,主線程一旦運行任務,即使線程池里的線程跑完任務都不會再進任務,因為主線程被占住了,直到主線程跑完一次業務,才能繼續分配給線程池任務。問題很明顯,業務流程比較耗時,線程池的一旦干完活,啥都干不了,都等著主線程消費隊列的數據給新任務。其他三個策略,AbortPolicy直接拋異常,拋了能咋樣,還是不知道要干啥;DiscardOldestPolicy丟棄老任務,丟消息,否了;DiscardPolicy丟棄,肯定否了。

我們要不然自己實現試試?

自己創建線程池

那我們嘗試創建一個自己的線程池:

	int processors = Runtime.getRuntime().availableProcessors();
	int corePoolSize = processors+1;
	int maximumPoolSize = corePoolSize*2;
	ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
		corePoolSize,
		maximumPoolSize,
		5L,
		TimeUnit.MINUTES,
		new LinkedBlockingDeque<Runnable>(3),
		Executors.defaultThreadFactory(),
		(r, e) -> {
			if (!e.isShutdown()) {
				r.run();
            } else {
                LoggerFactory.getLogger(ThreadPoolTest.class).error("Task " + r.toString() + " rejected from " + e.toString());
            }
        }
	);

拒絕策略我簡單寫了點,在具體情況下是可以根據需要自己實現。

關于Java中Thread類的線程的小知識

我們在Thread類中,有一個State枚舉類,為什么只有runnable 而沒有running狀態呢?

    public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }

其實,這是因為現在的操作系統架構通常都是用所謂的“時間分片(time quantum or time slice)”方式進行搶占式(preemptive)輪轉調度(round-robin式)。更復雜的可能還會加入優先級(priority)的機制。

這個時間分片通常是很小的,一個線程一次最多只能在 cpu 上運行比如10-20ms 的時間(此時處于 running 狀態),時間片用后就要進行狀態保存然后被切換下來放入調度隊列的末尾等待再次調度(也即回到 ready 狀態)。如果期間進行了 I/O 的操作還會導致提前釋放時間分片,并進入等待隊列;或者是時間分片沒有用完就被搶占。

不計切換開銷(每次在1ms 以內)的話,相當于1秒內有50-100次切換。事實上時間片經常沒用完,線程就因為各種原因被中斷,實際發生的切換次數還會更多。

時間分片也是可配置的,如果不追求在多個線程間很快的響應,也可以把這個時間配置得大一點,以減少切換帶來的開銷。

通常,Java的線程狀態是服務于監控的,所以 Java 線程把調度委托給了操作系統,我們在虛擬機層面看到的狀態實質是對底層狀態的映射及包裝。cpu 線程切換這么快,區分 ready 與 running 也沒什么意義。因此,統一成為runnable 狀態是不錯的選擇。

作者:Solid-Snaker

原文鏈接:https://blog.csdn.net/jcSongle/article/details/106089405

分享到:
標簽:線程
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定