Bagaimana saya bisa mengatasi batasan ini di ThreadPoolExecutor
mana antrian harus dibatasi dan penuh sebelum lebih banyak utas akan dimulai.
Saya yakin saya akhirnya menemukan solusi yang agak elegan (mungkin sedikit hacky) untuk batasan ini dengan ThreadPoolExecutor
. Ini melibatkan memperluas LinkedBlockingQueue
untuk memilikinya kembali false
untuk queue.offer(...)
saat ini sudah ada beberapa tugas antri. Jika utas saat ini tidak mengikuti tugas yang diantrekan, TPE akan menambahkan utas tambahan. Jika kumpulan sudah di utas maksimal, maka RejectedExecutionHandler
akan dipanggil. Ini adalah pawang yang kemudian memasukkan put(...)
ke dalam antrian.
Memang aneh menulis antrian di mana offer(...)
bisa kembali false
dan put()
tidak pernah diblokir jadi itu bagian hacknya. Tapi ini bekerja dengan baik dengan penggunaan antrian TPE jadi saya tidak melihat ada masalah dengan melakukan ini.
Berikut kodenya:
// extend LinkedBlockingQueue to force offer() to return false conditionally
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
private static final long serialVersionUID = -6903933921423432194L;
@Override
public boolean offer(Runnable e) {
// Offer it to the queue if there is 0 items already queued, else
// return false so the TPE will add another thread. If we return false
// and max threads have been reached then the RejectedExecutionHandler
// will be called which will do the put into the queue.
if (size() == 0) {
return super.offer(e);
} else {
return false;
}
}
};
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1 /*core*/, 50 /*max*/,
60 /*secs*/, TimeUnit.SECONDS, queue);
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
// This does the actual put into the queue. Once the max threads
// have been reached, the tasks will then queue up.
executor.getQueue().put(r);
// we do this after the put() to stop race conditions
if (executor.isShutdown()) {
throw new RejectedExecutionException(
"Task " + r + " rejected from " + e);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
});
Dengan mekanisme ini, ketika saya mengirimkan tugas ke antrian, ThreadPoolExecutor
keinginan:
- Skala jumlah utas hingga ukuran inti awalnya (di sini 1).
- Tawarkan ke antrean. Jika antrian kosong maka antrian akan ditangani oleh thread yang ada.
- Jika antrian sudah memiliki 1 atau lebih elemen,
offer(...)
akan mengembalikan false.
- Jika false dikembalikan, naikkan jumlah utas di kumpulan hingga mencapai jumlah maksimal (di sini 50).
- Jika di maks maka itu memanggil
RejectedExecutionHandler
- The
RejectedExecutionHandler
menempatkan maka tugas ke dalam antrian untuk diproses oleh thread pertama yang tersedia dalam rangka FIFO.
Meskipun dalam kode contoh saya di atas, antrian tidak dibatasi, Anda juga dapat mendefinisikannya sebagai antrian berbatas. Misalnya, jika Anda menambahkan kapasitas 1000 LinkedBlockingQueue
maka itu akan:
- skala utas hingga maks
- lalu antri hingga penuh dengan 1000 tugas
- kemudian blokir penelepon hingga tersedia ruang untuk antrian.
Juga, jika Anda perlu menggunakan offer(...)
dalam
RejectedExecutionHandler
maka Anda bisa menggunakan offer(E, long, TimeUnit)
metode sebagai gantinya dengan Long.MAX_VALUE
sebagai batas waktu.
Peringatan:
Jika Anda mengharapkan tugas ditambahkan ke eksekutor setelah dimatikan, maka Anda mungkin ingin lebih pintar membuang RejectedExecutionException
kebiasaan kami RejectedExecutionHandler
ketika layanan-pelaksana telah dimatikan. Terima kasih kepada @RaduToader karena telah menunjukkan hal ini.
Edit:
Perubahan lain untuk jawaban ini adalah menanyakan TPE jika ada utas yang tidak digunakan dan hanya mengantrekan item jika ada. Anda harus membuat kelas yang benar untuk ini dan menambahkan ourQueue.setThreadPoolExecutor(tpe);
metode di atasnya.
Maka offer(...)
metode Anda mungkin terlihat seperti:
- Periksa untuk melihat apakah
tpe.getPoolSize() == tpe.getMaximumPoolSize()
dalam hal ini panggil saja super.offer(...)
.
- Lain jika
tpe.getPoolSize() > tpe.getActiveCount()
kemudian panggil super.offer(...)
karena tampaknya ada utas yang menganggur.
- Jika tidak kembali
false
ke garpu utas lain.
Mungkin ini:
int poolSize = tpe.getPoolSize();
int maximumPoolSize = tpe.getMaximumPoolSize();
if (poolSize >= maximumPoolSize || poolSize > tpe.getActiveCount()) {
return super.offer(e);
} else {
return false;
}
Perhatikan bahwa metode get pada TPE mahal karena mereka mengakses volatile
bidang atau (dalam kasus getActiveCount()
) mengunci TPE dan menjalankan daftar utas. Selain itu, ada kondisi balapan di sini yang dapat menyebabkan tugas diantrekan dengan tidak semestinya atau utas lain bercabang ketika ada utas yang tidak aktif.