スレッドプールのピット-インスタンスメンバーまたはメソッドのローカル変数としてのエラー
この文書の目次: 1. 概要 2. 検証 3. 剖析 4. 小結
1.概要
スレッドプールは、スレッドを多重化し、スレッド作成の破棄時間とリソース消費を削減し、プログラムタスクのスループットを向上させることができます.
スレッドがグローバルで使用されるリソースに属するように、スレッドプールも一般的にグローバルであり、アプリケーションプロセス全体のスレッド多重化を効率的に管理します.デザイナーは、通常、スレッドプールをクラスの静的メンバーまたは単一のメンバーとして、プロセス全体のライフサイクルで生存します.
しかし、このようなコードは例外的に見られた.
たとえば、メソッドボディにローカル変数として配置されます.
または、ライフサイクルの短いオブジェクトにメンバー変数として挿入します.
これらのスレッドプールの使用は正常に見え、深刻な問題が隠されています.
オブジェクトインスタンスが使用されなくなったり、メソッドが実行されなくなったりすると、スレッドが解放され、スレッドプールが閉じられますか?
スレッドプールによって表現が異なります.コアスレッド数が設定されているかどうかを主に見ます.コアスレッド数、例えば コアスレッド数、例えば
2.検証
Demoを設計して検証します. JDKが提供する単一スレッドプールを選択します.このスレッドプールのコアスレッド数は1です. スレッドプールは、オブジェクトのメンバー変数として使用されます. このクラスのインスタンスオブジェクトはメソッドボディで実行され、外部は を参照していない.最終呼び出し しばらく待ってから、プロセスのスタック情報を印刷し、関連するクラスを表示します.
上記のようにSimpleClassを作成しました.
そしてmainメソッドで実行
しばらくすると、JDKのツールを使用して、現在生存しているオブジェクト情報を取得します. jps印刷取得プロセス番号4540 は、
コンソールには、次のようなレコードが表示されます.
しばらくの間、アクティブコールGCを加えると、10個のSimpleClassインスタンスはほぼ回収されたが、10個のThreadPoolExecutorインスタンスは依然として存在する.
これは、コアスレッドを持つThreadPoolExecutorが、スレッドのタイムアウトをアクティブに解放したり設定したりしていない場合、メンバー変数に配置すると、オブジェクトインスタンスの漏洩が発生します.
同様に,メソッド体に局所変数を置くという問題もある.
3.剖析
どうしてこんな現象があるの?
スレッドプールが回収できないのは、スレッドプールの参照が内部クラスWorkerによって保持されているためです.一方,Workerとスレッドは1つ1つに対応しており,Threadの強化であるため,本質的にはスレッドが解放されていないためである.
では、タスクキューは空で、外部からもタスクが来ていません.スレッドはなぜまだ解放されていませんか.
ThreadPoolExecutorの
スレッド終了スレッドプールの状態>=STOP getTask空のタスク を取得
最初の条件は、スレッドプールの状態がSTOPに達するには、
2つ目の条件は、getTaskが空のタスクを取得し、getTaskのコードを見続けます.
タスクキューは、タスクを取得するための2つの方法を提供するブロックキューBlockingQueueを使用します. pollでは、タイムアウト時間を設定でき、タイムアウト後に空のタスクが得られます. take、タスクが現れるまでブロックします.
上のgetTaskメソッドから、現在のスレッド数がコアスレッドより大きい場合、pollが呼び出され、タイムアウト後に空のタスクに戻ります. 現在のスレッド数がコアスレッド以下であり、 他の場合、takeブロック待ちを呼び出す.
単一のコアスレッドのスレッドプールを使用していますが、タスクがない場合、コアスレッドは
4.まとめ
上記のようにスレッドプールを設定すると,スレッドプールのローカルアプリケーションとして理解できる.
ローカルスレッドプールでできることは、グローバルスレッドプールでもできるので、このような方法はお勧めしません.また、グローバル・インスタンスのスレッド・プールは、ライフサイクルとプロセスが一致するため、スレッド・プールを閉じる問題を考慮する必要はありません.
ビジネスシーンでこのように使用しなければならない場合、スレッドプールにコアスレッドがある場合は、オブジェクトの漏洩を防ぐために2つのことに注意してください.コアスレッドにタイムアウト時間を設定します. は、スレッドプールを閉じるために
1.概要
スレッドプールは、スレッドを多重化し、スレッド作成の破棄時間とリソース消費を削減し、プログラムタスクのスループットを向上させることができます.
スレッドがグローバルで使用されるリソースに属するように、スレッドプールも一般的にグローバルであり、アプリケーションプロセス全体のスレッド多重化を効率的に管理します.デザイナーは、通常、スレッドプールをクラスの静的メンバーまたは単一のメンバーとして、プロセス全体のライフサイクルで生存します.
しかし、このようなコードは例外的に見られた.
たとえば、メソッドボディにローカル変数として配置されます.
private static void sampleFunc() {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
...
}
});
}
}
または、ライフサイクルの短いオブジェクトにメンバー変数として挿入します.
public class SampleClass {
...
private final ExecutorService mExecutor = Executors.newFixedThreadPool(2);
...
}
これらのスレッドプールの使用は正常に見え、深刻な問題が隠されています.
オブジェクトインスタンスが使用されなくなったり、メソッドが実行されなくなったりすると、スレッドが解放され、スレッドプールが閉じられますか?
スレッドプールによって表現が異なります.コアスレッド数が設定されているかどうかを主に見ます.
newCachedThreadPool
が設定されていない場合、オンラインスレッドプールのスレッド空き時間が60 sに達すると、スレッドは閉じられ、すべてのスレッドが閉じた後、スレッドプールもそれに応じて回収を閉じます.newSingleThreadExecutor
およびnewFixedThreadPool
が設定されている場合、アクティブに閉じるか、コアスレッドのタイムアウト時間を設定していないと、コアスレッドは閉じられず、このスレッドプールは回収されません.2.検証
Demoを設計して検証します.
System.gc
アクティブ回収上記のようにSimpleClassを作成しました.
public class SimpleClass {
private final int mIndex;
private Executor mExecutors = Executors.newSingleThreadExecutor();
public SimpleClass(int index) {
mIndex = index;
}
public void runTask() {
mExecutors.execute(new Runnable() {
@Override
public void run() {
System.out.println("[" + mIndex + "] execute");
}
});
}
}
そしてmainメソッドで実行
public class TestThreadLife {
public static void main(String[] args) {
test();
System.gc();
}
private static void test() {
for (int i = 0; i < 10; i++) {
new SimpleClass(i).runTask();
}
}
}
しばらくすると、JDKのツールを使用して、現在生存しているオブジェクト情報を取得します.
jmap -histo 4540
を呼び出してスタック内のオブジェクト情報を読み出す.コンソールには、次のようなレコードが表示されます.
E:\code\workspace-demo\TestJava>jmap -histo 4540
num #instances #bytes class name
----------------------------------------------
...
10: 20 7520 java.lang.Thread
...
52: 10 480 java.util.concurrent.LinkedBlockingQueue
53: 10 480 java.util.concurrent.ThreadPoolExecutor$Worker
54: 30 480 java.util.concurrent.locks.ReentrantLock
...
218: 1 16 com.intellij.rt.execution.application.AppMain$1
219: 1 16 concurrent.threadpool.TestThreadLife.SimpleClass
...
Total 9740 33368472
しばらくの間、アクティブコールGCを加えると、10個のSimpleClassインスタンスはほぼ回収されたが、10個のThreadPoolExecutorインスタンスは依然として存在する.
これは、コアスレッドを持つThreadPoolExecutorが、スレッドのタイムアウトをアクティブに解放したり設定したりしていない場合、メンバー変数に配置すると、オブジェクトインスタンスの漏洩が発生します.
同様に,メソッド体に局所変数を置くという問題もある.
3.剖析
どうしてこんな現象があるの?
スレッドプールが回収できないのは、スレッドプールの参照が内部クラスWorkerによって保持されているためです.一方,Workerとスレッドは1つ1つに対応しており,Threadの強化であるため,本質的にはスレッドが解放されていないためである.
では、タスクキューは空で、外部からもタスクが来ていません.スレッドはなぜまだ解放されていませんか.
ThreadPoolExecutorの
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();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
スレッド終了
processWorkerExit
を実行するには、次のような状況が必要です.最初の条件は、スレッドプールの状態がSTOPに達するには、
shutdown
またはshutdownNow
メソッドを呼び出す必要があります.2つ目の条件は、getTaskが空のタスクを取得し、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;
}
}
}
タスクキューは、タスクを取得するための2つの方法を提供するブロックキューBlockingQueueを使用します.
上のgetTaskメソッドから、
allowCoreThreadTimeOut
メソッドが呼び出されてコアスレッドがタイムアウトして閉じることができる場合もpollが呼び出され、タイムアウト後に空のタスクに戻る.単一のコアスレッドのスレッドプールを使用していますが、タスクがない場合、コアスレッドは
getTask
にあります.ブロックキューBlockingQueueを呼び出すtake
メソッドは、取得待ちタスクをブロックし、スレッドプールに含まれるコアスレッドが閉じられずに回収されます.4.まとめ
上記のようにスレッドプールを設定すると,スレッドプールのローカルアプリケーションとして理解できる.
ローカルスレッドプールでできることは、グローバルスレッドプールでもできるので、このような方法はお勧めしません.また、グローバル・インスタンスのスレッド・プールは、ライフサイクルとプロセスが一致するため、スレッド・プールを閉じる問題を考慮する必要はありません.
ビジネスシーンでこのように使用しなければならない場合、スレッドプールにコアスレッドがある場合は、オブジェクトの漏洩を防ぐために2つのことに注意してください.
shutdown
またはshutdownNow
をアクティブに呼び出す.