使用線程池(ThreadPoolExecutor)的好處是減少在創(chuàng)建和銷毀線程上所花的時間以及系統(tǒng)資源的開銷,解決資源不足的問題棍辕。如果不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導(dǎo)致消耗完內(nèi)存或者“過度切換”的問題还绘。 -- 阿里Java開發(fā)手冊
版本
JDK 1.8
本節(jié)目標(biāo)
- 理解線程池核心參數(shù)
- 理解線程池工作原理
- 理解線程池核心方法
線程池的核心參數(shù)和構(gòu)造方法
ctl
// 線程池核心變量楚昭,包含線程池的運行狀態(tài)和有效線程數(shù),利用二進制的位掩碼實現(xiàn)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
// 線程池狀態(tài)
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
// 獲取當(dāng)前線程池運行狀態(tài)
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 獲取當(dāng)前線程池有效線程數(shù)
private static int workerCountOf(int c) { return c & CAPACITY; }
// 打包ctl變量
private static int ctlOf(int rs, int wc) { return rs | wc; }
/*
* Bit field accessors that don't require unpacking ctl.
* These depend on the bit layout and on workerCount being never negative.
*/
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
JDK7 以后拍顷,線程池的狀態(tài)和有效線程數(shù)通過 ctl 這個變量表示(使用二進制的位掩碼來實現(xiàn)抚太,這里我們不深究),理解上述幾個方法作用即可昔案,不影響下面的源碼閱讀
關(guān)于線程池的五種狀態(tài)
RUNNING:接受新任務(wù)并處理隊列中的任務(wù)
SHUTDOWN :不接受新任務(wù)尿贫,但處理隊列中的任務(wù)
STOP :不接受新任務(wù),不處理隊列中的任務(wù)踏揣,并中斷正在進行的任務(wù)(中斷并不是強制的庆亡,只是修改了Thread的狀態(tài),是否中斷取決于Runnable 的實現(xiàn)邏輯)
TIDYING :所有任務(wù)都已終止捞稿,workerCount為0時又谋,線程池會過度到該狀態(tài)钝尸,并即將調(diào)用 terminate()
TERMINATED :terminated() 調(diào)用完成;線程池中止
線程池狀態(tài)的轉(zhuǎn)換
RUNNING => SHUTDOWN :調(diào)用 shutdown()
RUNNING / SHUTDOWN => STOP :調(diào)用 shutdownNow() (該方法會返回隊列中未執(zhí)行的任務(wù))
SHUTDOWN => TIDYING: 當(dāng)線程池和隊列都為空時
STOP => TIDYING:當(dāng)線程池為空時
TIDYING => TERMINATED:當(dāng) terminated() 調(diào)用完成時
構(gòu)造方法
線程池最終都是調(diào)用如下構(gòu)造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// 省略
}
核心參數(shù)
我們來看一下線程池中的核心參數(shù)都是什么作用
private final BlockingQueue<Runnable> workQueue; // 阻塞隊列搂根,用于緩存任務(wù)
private final ReentrantLock mainLock = new ReentrantLock(); // 線程池主鎖
private final HashSet<Worker> workers = new HashSet<Worker>(); // 工作線程集合
private final Condition termination = mainLock.newCondition(); // awaitTermination() 方法的等待條件
private int largestPoolSize; // 記錄最大線程池大小
private long completedTaskCount; //用來記錄線程池中曾經(jīng)出現(xiàn)過的最大線程數(shù)
private volatile ThreadFactory threadFactory; // 線程工廠珍促,用于創(chuàng)建線程
private volatile RejectedExecutionHandler handler; // 任務(wù)拒絕時的策略
private volatile long keepAliveTime; // 線程存活時間
// 當(dāng)線程數(shù)超過核心池數(shù)時,或允許核心池線程超時剩愧,該參數(shù)會起作用猪叙。否則一直會等待新的任務(wù)
private volatile boolean allowCoreThreadTimeOut; // 是否允許核心池線程超時
private volatile int corePoolSize; // 核心線程池數(shù)量
private volatile int maximumPoolSize; // 最大線程池數(shù)量
workQueue
這個隊列的作用,和之前的 Java 理解生產(chǎn)者-消費者設(shè)計模式 中講到的緩沖隊列仁卷,作用很相似穴翩,或者說線程池就是生產(chǎn)者消費者模式的一種實現(xiàn)。
關(guān)于 handler
- ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常锦积。
- ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù)芒帕,但是不拋出異常。
- ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務(wù)丰介,然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過程)
- ThreadPoolExecutor.CallerRunsPolicy:當(dāng)前任務(wù)自己決定
corePoolSize 和 maximumPoolSize
如果你對這兩個參數(shù)有疑問背蟆,看完下面的栗子你會清晰很多
下面我們來舉個栗子來更好的理解一下線程池
理解線程池工作原理
假如有一個工廠,工廠里面有10個工人哮幢,每個工人同時只能做一件任務(wù)带膀。
因此只要當(dāng)10個工人中有工人是空閑的,來了任務(wù)就分配給空閑的工人做橙垢;
當(dāng)10個工人都有任務(wù)在做時垛叨,如果還來了任務(wù),就把任務(wù)進行排隊等待柜某;
每個工人做完自己的任務(wù)后嗽元,會去任務(wù)隊列中領(lǐng)取新的任務(wù);
如果說新任務(wù)數(shù)目增長的速度遠遠大于工人做任務(wù)的速度(任務(wù)累積過多時)喂击,那么此時工廠主管可能會想補救措施剂癌,比如重新招4個臨時工人進來;
然后就將任務(wù)也分配給這4個臨時工人做惭等;
如果說著14個工人做任務(wù)的速度還是不夠珍手,此時工廠主管可能就要考慮不再接收新的任務(wù)或者拋棄前面的一些任務(wù)了。
當(dāng)這14個工人當(dāng)中有人空閑時辞做,而新任務(wù)增長的速度又比較緩慢琳要,工廠主管可能就考慮辭掉4個臨時工了,只保持原來的10個工人秤茅,畢竟請額外的工人是要花錢的稚补。
開始工廠的10個工人,就是 corePoolSize (核心池數(shù)量)框喳;
當(dāng)10個人都在工作時 (核心池達到 corePoolSize)课幕,任務(wù)排隊等待時厦坛,會緩存到 workQueue 中;
當(dāng)任務(wù)累積過多時(達到 workQueue 最大值時)乍惊,找臨時工杜秸;
14個臨時工,就是 maximumPoolSize (數(shù)量)润绎;
如果此時工作速度還是不夠撬碟,線程池這時會考慮拒絕任務(wù),具體由拒絕策略決定
理解線程池核心方法
execute()
線程池中所有執(zhí)行任務(wù)的方法有關(guān)的方法莉撇,都會調(diào)用 execute()呢蛤。如果你理解了上述的小例子,再來看這個會清晰很多
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
分析execute()
- step 1
1)首先檢查當(dāng)前有效線程數(shù) 是否小于 核心池數(shù)量
if (workerCountOf(c) < corePoolSize)
2)如果滿足上述條件棍郎,則嘗試向核心池添加一個工作線程 (addWorker() 第二個參數(shù)決定了是添加核心池其障,還是最大池)
if (addWorker(command, true))
3)如果成功則退出方法,否則將執(zhí)行 step2
- step 2
1)如果當(dāng)前線程池處于運行狀態(tài) && 嘗試向緩沖隊列添加任務(wù)
if (isRunning(c) && workQueue.offer(command))
2)如果線程池正在運行并且緩沖隊列添加任務(wù)成功涂佃,進行 double check(再次檢查)
3)如果此時線程池非運行狀態(tài) => 移除隊列 => 拒絕當(dāng)前任務(wù)励翼,退出方法
(這么做是為了,當(dāng)線程池不可用時及時回滾)
if (! isRunning(recheck) && remove(command))
reject(command);
4)如果當(dāng)前有效線程數(shù)為0巡李,則創(chuàng)建一個無任務(wù)的工作線程(此時這個線程會去隊列中獲取任務(wù))
- step 3
1)當(dāng)無法無法向核心池和隊列中添加任務(wù)時抚笔,線程池會再嘗試向最大池中添加一個工作線程,如果失敗則拒絕該任務(wù)
else if (!addWorker(command, false))
reject(command);
圖解execute()
根據(jù)上述的步驟畫了如下的這個圖侨拦,希望能幫助大家更好的理解
addWorker()
在分析execute() 方法時,我們已經(jīng)知道了 addWorker() 的作用了辐宾,可以向核心池或者最大池添加一個工作線程狱从。我們來看一下這個方法都做了什么
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
這個方法代碼看似很復(fù)雜,沒關(guān)系叠纹,我們一步一步來分析
- step 1
先看第一部分
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
這一部分代碼季研,主要是判斷,是否可以添加一個工作線程誉察。
在execute()中已經(jīng)判斷過if (workerCountOf(c) < corePoolSize)
了与涡,為什么還要再判斷?
因為在多線程環(huán)境中持偏,當(dāng)上下文切換到這里的時候驼卖,可能線程池已經(jīng)關(guān)閉了,或者其他線程提交了任務(wù)鸿秆,導(dǎo)致workerCountOf(c) > corePoolSize
1)首先進入第一個無限for循環(huán)酌畜,獲取ctl對象,獲取當(dāng)前線程的運行狀態(tài)卿叽,然后判斷
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
這個判斷的意義為桥胞,當(dāng)線程池運行狀態(tài) >= SHUTDOWN 時恳守,向添加一個工作線程必須同時滿足
rs == SHUTDOWN
firstTask == null
-
! workQueue.isEmpty()
三個條件,否則添加線程失敗
所以當(dāng)線程狀態(tài)為SHUTDOWN時贩虾,線程池允許添加一個無任務(wù)的工作線程去執(zhí)行隊列中的任務(wù)催烘。
2)進入第二個無限for循環(huán)
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
獲取當(dāng)前有效線程數(shù),if 有效線程數(shù) >= 容量 || 有效線程數(shù) >= 核心池數(shù)量/最大池數(shù)量缎罢,則return false; 添加線程失敗
如果有效線程數(shù)在合理范圍之內(nèi)伊群,嘗試使用 CAS 自增有效線程數(shù) (CAS 是Java中的樂觀鎖,不了解的小伙伴可以Google一下)屁使,樂觀鎖自增成功在岂,代表當(dāng)前無其他線程競爭,相當(dāng)于獲取到鎖了
如果自增成功蛮寂,break retry; 跳出這兩個循環(huán)蔽午,執(zhí)行下面的代碼
自增失敗,檢查線程池狀態(tài)酬蹋,如果線程池狀態(tài)發(fā)生變化及老,回到第一個for 繼續(xù)執(zhí)行;否則繼續(xù)在第二個for 中范抓;
- step 2
下面這部分就比較簡單了
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
1)創(chuàng)建工作線程對象Worker
骄恶;
2)加鎖,判斷當(dāng)前線程池狀態(tài)是否允許啟動線程匕垫;
如果可以僧鲁,將線程加入workers
(這個變量在需要遍歷所有工作線程時會用到),記錄最大值象泵,啟動線程寞秃;
3)如果線程啟動失敗,執(zhí)行addWorkerFailed(從workers
中移除該對象偶惠,有效線程數(shù)減一春寿,嘗試中止線程池)
Worker
Worker對象是線程池中的內(nèi)部類,線程的復(fù)用忽孽、線程超時都是在這實現(xiàn)的
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
// 這里我們只關(guān)心Run()绑改,省略了其他源碼,感興趣的同學(xué)可以自己看一下源碼
public void run() {
runWorker(this);
}
}
Worker 實現(xiàn)了 Runnable兄一,我們這里只關(guān)心 Worker 的run方法中做了什么厘线,關(guān)于 AbstractQueuedSynchronizer 有關(guān)的不在本文討論
下面我們分析一下runWorker()
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
// 通過該變量判斷是用戶任務(wù)拋出異常結(jié)束,還是線程池自然結(jié)束
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
1)
while (task != null || (task = getTask()) != null) {
// ...
}
不對地通過getTask() 從隊列中獲取任務(wù)瘾腰,可以間接通過getTask()的返回值控制線程的結(jié)束
2)
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
接下來這個判斷皆的,其實我是沒有太理解的,暫且認為是保證當(dāng)線程池STOP時蹋盆,線程一定會被打斷
3)執(zhí)行Runnable
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
beforeExecute(wt, task); 和 afterExecute(task, thrown); 默認是沒有實現(xiàn)的费薄,我們可以自己擴展
4)最后是當(dāng)跳出while循環(huán)后(getTask() == null
或者用戶任務(wù)拋出異常)硝全,會去執(zhí)行processWorkerExit(w, completedAbruptly);
線程退出工作(該方法會根據(jù)線程池狀態(tài),嘗試中止線程池楞抡。然后會考慮是結(jié)束當(dāng)前線程伟众,還是再新建一個工作線程,這里就不細說了)
我們再來看一下 getTask() 方法
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
1) 第一段不做解釋召廷,滿足該條件時凳厢,return null; 退出線程
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
2) 下面這段很有意思
int wc = workerCountOf(c);
// Are workers subject to culling?
// 是否允許線程超時
// 當(dāng)我們設(shè)置了允許核心池超時 或者 有效線程數(shù) > 核心池數(shù)量的時候
// 線程池會考慮為我們清除掉一些線程
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// (有效線程數(shù) > 最大線程池數(shù)量 || (允許超時 && 超時) )
// && (有效線程數(shù) > 1 || 或者隊列為空時)
if ((wc > maximumPoolSize || (timed && timedOut)) // timedOut 表示當(dāng)前線程超時,下文會說到
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
我在第一次看這段代碼的時候竞慢,傻傻的以為 timedOut 不是永遠為false嗎先紫,我以為JDK源碼怎么寫出這么個Bug。別忘了當(dāng)前的getTask()方法也是在一個無限循環(huán)里
3)
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
根據(jù) timed
筹煮,決定調(diào)用使用poll() 或者 take()遮精。
- poll 在隊列為空時會等待指定時間,如果這期間沒有獲取到元素败潦,則return null
- take 則在隊列為空時會一直等待本冲,直至隊列中被添加新的任務(wù),或者被打斷劫扒;
這兩個方法都會被shutdown() 或者 shutdownNow的 thread.interrupt()打斷檬洞;
如果被打斷則回到第一步
至此 execute() 方法所涉及的邏輯我們差不多分析完了
備注
線程池使用
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5));
executor.execute(() -> {
// 業(yè)務(wù)邏輯
});
executor.shutdown();
}
}
合理配置線程池的大小
一般需要根據(jù)任務(wù)的類型來配置線程池大小:
如果是CPU密集型任務(wù)沟饥,參考值可以設(shè)為 N+1 (N 為CPU核心數(shù))
如果是IO密集型任務(wù)添怔,參考值可以設(shè)置為2*N
當(dāng)然,這只是一個參考值贤旷,具體的設(shè)置還需要根據(jù)實際情況進行調(diào)整澎灸,比如可以先將線程池大小設(shè)置為參考值,再觀察任務(wù)運行情況和系統(tǒng)負載遮晚、資源利用率來進行適當(dāng)調(diào)整。