Kelas berikut membungkus ThreadPoolExecutor dan menggunakan Semaphore untuk memblokir maka antrian pekerjaan penuh:
public final class BlockingExecutor {
private final Executor executor;
private final Semaphore semaphore;
public BlockingExecutor(int queueSize, int corePoolSize, int maxPoolSize, int keepAliveTime, TimeUnit unit, ThreadFactory factory) {
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
this.executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, queue, factory);
this.semaphore = new Semaphore(queueSize + maxPoolSize);
}
private void execImpl (final Runnable command) throws InterruptedException {
semaphore.acquire();
try {
executor.execute(new Runnable() {
@Override
public void run() {
try {
command.run();
} finally {
semaphore.release();
}
}
});
} catch (RejectedExecutionException e) {
// will never be thrown with an unbounded buffer (LinkedBlockingQueue)
semaphore.release();
throw e;
}
}
public void execute (Runnable command) throws InterruptedException {
execImpl(command);
}
}
Kelas pembungkus ini didasarkan pada solusi yang diberikan dalam buku Java Concurrency in Practice oleh Brian Goetz. Solusi dalam buku ini hanya membutuhkan dua parameter konstruktor: an Executor
dan batas yang digunakan untuk semaphore. Ini ditunjukkan dalam jawaban yang diberikan oleh Fixpoint. Ada masalah dengan pendekatan itu: ia bisa berada dalam keadaan di mana utas kumpulan sibuk, antrean penuh, tetapi semafor baru saja merilis izin. (semaphore.release()
di blok terakhir). Dalam keadaan ini, tugas baru bisa mengambil izin yang baru saja dirilis, tetapi ditolak karena antrean tugas penuh. Tentu saja ini bukanlah sesuatu yang Anda inginkan; yang ingin Anda blokir dalam kasus ini.
Untuk mengatasi ini, kita harus menggunakan antrian tak terbatas , seperti yang disebutkan JCiP dengan jelas. Semaphore bertindak sebagai penjaga, memberikan efek ukuran antrian virtual. Ini memiliki efek samping yang memungkinkan bahwa unit dapat berisi maxPoolSize + virtualQueueSize + maxPoolSize
tugas. Mengapa demikian? Karena
semaphore.release()
di blok akhirnya. Jika semua utas pangkalan memanggil pernyataan ini pada saat yang sama, makamaxPoolSize
izin dilepaskan, memungkinkan jumlah tugas yang sama untuk memasuki unit. Jika kami menggunakan antrian terbatas, itu akan tetap penuh, sehingga tugas ditolak. Sekarang, karena kita tahu bahwa ini hanya terjadi ketika utas kumpulan hampir selesai, ini bukan masalah. Kami tahu bahwa utas kumpulan tidak akan diblokir, jadi tugas akan segera diambil dari antrean.
Anda dapat menggunakan antrian terbatas. Pastikan ukurannya sama virtualQueueSize + maxPoolSize
. Ukuran yang lebih besar tidak berguna, semaphore akan mencegah masuknya lebih banyak item. Ukuran yang lebih kecil akan mengakibatkan tugas ditolak. Kemungkinan tugas ditolak meningkat seiring dengan berkurangnya ukuran. Misalnya, Anda menginginkan eksekutor terikat dengan maxPoolSize = 2 dan virtualQueueSize = 5. Kemudian ambil semaphore dengan 5 + 2 = 7 izin dan ukuran antrian aktual 5 + 2 = 7. Jumlah tugas sebenarnya yang bisa di unit tersebut kemudian 2 + 5 + 2 = 9. Ketika pelaksana penuh (5 tugas dalam antrian, 2 di kumpulan utas, jadi 0 izin tersedia) dan SEMUA utas kumpulan melepaskan izin mereka, maka tepat 2 izin dapat diambil oleh tugas yang masuk.
Sekarang solusi dari JCiP agak rumit untuk digunakan karena tidak memberlakukan semua batasan ini (antrian tidak terbatas, atau dibatasi dengan batasan matematika tersebut, dll.). Saya pikir ini hanya berfungsi sebagai contoh yang baik untuk mendemonstrasikan bagaimana Anda dapat membangun kelas aman thread baru berdasarkan bagian-bagian yang sudah tersedia, tetapi bukan sebagai kelas dewasa yang dapat digunakan kembali. Saya tidak berpikir bahwa yang terakhir adalah niat penulisnya.