如果每當一個請求到達就創建一個新執行緒,開銷是相當大的,在實際使用中,每個請求創建新執行緒的服務器在創建和銷毀執行緒上花費的時間和消耗的系統資源,甚至可能要比花在處理實際的用戶請求的時間和資源要多得多,
除了創建和銷毀執行緒的開銷之外,活動的執行緒也需要消耗系統資源,如果在一個JVM里創建太多的執行緒,可能會導致系統由于過度消耗記憶體或“切換過度”而導致系統資源不足,為了防止資源不足,服務器應用程式需要一些辦法來限制任何給定時刻處理的請求數目,盡可能減少創建和銷毀執行緒的次數,特別是一些資源耗費比較大的執行緒的創建和銷毀,盡量利用已有物件來進行服務,這就是“池化資源”技術產生的原因,
執行緒池主要用來解決執行緒生命周期開銷問題和資源不足問題,通過對多個任務重用執行緒,執行緒創建的開銷就被分攤到了多個任務上了,而且由于在請求到達時執行緒已經存在,所以消除了執行緒創建所帶來的延遲,這樣,就可以立即為請求服務,使應用程式回應更快,另外,通過適當地調整執行緒池中的執行緒數目可以防止出現資源不足的情況,
Java中的執行緒池ThreadPoolExecutor
1、ThreadPoolExecutor
首先執行緒池有幾個關鍵的配置:核心執行緒數、最大執行緒數、空閑存活時間、作業佇列、拒絕策略
作業流程:
- 默認情況下執行緒不會預創建,所以是來任務之后才會創建執行緒(設定prestartAllCoreThreads可以預創建核心執行緒),
- 當核心執行緒滿了之后不會新建執行緒,而是把任務堆積到作業佇列中,
- 如果作業佇列放不下了,然后才會新增執行緒,直至達到最大執行緒數,
- 如果作業佇列滿了,然后也已經達到最大執行緒數了,這時候來任務會執行拒絕策略,
- 如果執行緒空閑時間超過空閑存活時間,并且執行緒執行緒數是大于核心執行緒數的則會銷毀執行緒,直到執行緒數等于核心執行緒數(設定allowCoreThreadTimeOut 可以回收核心執行緒),
1、1 作業佇列

用來存盤Runnable

上面的是繼承了這個阻塞佇列的,
我們可以選一個來看:ArrayBlockingQueue:
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
第一個引數是用來裝Runnable的陣列大小,第二個引數是是否公平,如果是公平的話,就是正常的佇列,先來先運行,非公平的話,就會有很多種情況,通常是用來設定ReentrantLock中的公平鎖和非公平鎖,
notEmpty = lock.newCondition(); //當陣列為空的時候,代表著佇列中沒有任務,要等待任務進入佇列,
notFull = lock.newCondition(); //當陣列滿的時候,任務不能進入佇列,就要進行這個信號量的等待,
我們在分析一下:任務怎樣添加到佇列:
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
如果當前陣列內元素的個數等于陣列長度的話,也就表示著佇列已經放滿了,不能再放入任務,這個時候,我們的notFull就要進行等待,我們先不管notFull什么時候signal(釋放),
呼叫了enqueue(任務進隊):
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
這里有一個notEmpty.signal:因為添加了一個任務,表示有任務可以消費了,這樣就要通知一下出隊操作,
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
如果佇列中沒有任務的話,我們就要進行等待,notEmpty.await()等待,如果有任務的話,就要進行出隊dequeue(),
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
當任務出隊了,這個時候表示佇列可以存放元素了,notFull.signal(),
1、2 拒接策略
通常有四種:

我們從名字上都可以執行緒數達到最大執行緒跟佇列滿的時候的一些策略:
比如:
1、CallerRunsPolicy:執行緒數達到最大執行緒跟佇列滿的時候,呼叫呼叫者執行緒來執行這個任務,比如再main執行緒中開啟的這個執行緒池,就會讓main執行緒來執行這個任務,

我們也是可以看到是r.run(),通常要啟動一個執行緒我們都是r.start()的,但是這個不是,就是正常的方法呼叫,
2、AbortPolicy: 執行緒數達到最大執行緒跟佇列滿的時候,直接拋出例外,

直接拋出例外,
3、DiscardPolicy:執行緒數達到最大執行緒跟佇列滿的時候,直接拋棄想要添加的任務,

不進行任何操作,也就是把新來的任務丟棄,
4、DiscardOldestPolicy:丟棄佇列中最古老的任務,

丟棄原來的舊任務,然后添加新的任務,添加新任務的時候需要進行三步操作:
也就是execute()方法里面的三步判斷:

- 如果任務為null,直接拋出例外,
- 如果當前作業的執行緒少于核心執行緒的話,就進行添加作業執行緒,
- 如果一個任務可以成功排隊,那么我們仍然需要仔細檢查是否應該添加一個執行緒(因為現有執行緒自上次檢查后就死掉了)或該池自進入此方法后就關閉了,因此,我們重新檢查狀態,并在必要時回滾排隊(如果已停止),或者在沒有執行緒的情況下啟動新執行緒,
- 3.如果我們無法將任務排隊,則嘗試添加一個新執行緒,如果失敗,則表明我們已關倍訓已飽和,因此拒絕該任務,
1、3 生產執行緒的工廠

使用的是默認的工廠,
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
// 宣告安全管理器
SecurityManager s = System.getSecurityManager();
// 得到執行緒組
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
// 執行緒名前綴,例如 "pool-1-thread-"
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
/**
* 用于創建一個執行緒
*/
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
// 設定執行緒t為非守護執行緒
if (t.isDaemon())
t.setDaemon(false);
// 設定執行緒t的優先級為5
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
2、執行緒運行的作業流程
1.執行緒池判斷核心執行緒是否已經滿了,否則會創建執行緒執行任務,是 進入下一個流程
2.執行緒池判斷作業佇列是否滿了,否 把將要執行的任務加入佇列,是 進入下一個流程
3.執行緒池判斷執行緒池是否滿了,否 創建執行緒執行任務,是進入下一個流程
4.執行緒池滿了,按照策略處理無法執行的任務

通常是這樣的作業流程,
3、常用的幾個執行緒池
- newFixedThreadPool:最大執行緒等于核心執行緒:佇列是鏈表

- newWorkStealingPool
jdk8出的,比較特殊,可以自己查看資料,

- newSingleThreadExecutor

只有一個核心執行緒,但是當這個核心執行緒掛了的時候,會再生成另一個來進行代替,能保證任務是按順序執行的,
- newCachedThreadPool

能看到都是非核心執行緒,存活時間為60s,然后任務佇列是沒有存盤空間的
- newScheduledThreadPool

有核心執行緒和最大執行緒,并且不一定相等,延遲時間是通過延遲佇列來進行的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/271520.html
標籤:其他
上一篇:銀行家演算法(作業系統)
