Pelaksana newCachedThreadPool () vs newFixedThreadPool ()

1. Gambaran keseluruhan

Ketika datang ke implementasi kumpulan utas, perpustakaan standard Java menyediakan banyak pilihan untuk dipilih. Kolam benang tetap dan cache cukup banyak terdapat di antara pelaksanaan tersebut.

Dalam tutorial ini, kita akan melihat bagaimana kumpulan benang berfungsi di bawah tudung dan kemudian membandingkan pelaksanaan ini dan kes penggunaannya.

2. Kolam Benang Cached

Mari kita lihat bagaimana Java membuat kumpulan utas cache ketika kita memanggil Executors.newCachedThreadPool () :

public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); }

Kumpulan benang cache menggunakan "handoff segerak" untuk mengantri tugas baru. Idea asas penyerahan segerak adalah mudah tetapi tidak intuitif: Seseorang boleh mengantri item jika dan hanya jika benang lain mengambil item itu pada masa yang sama. Dengan kata lain, yang SynchronousQueue tidak boleh memegang apa-apa tugas sekalipun.

Anggap tugas baru masuk. Sekiranya ada thread terbiar yang menunggu di barisan, maka pengeluar tugas menyerahkan tugas ke utas itu. Jika tidak, kerana barisan sentiasa penuh, pelaksana membuat utas baru untuk menangani tugas itu .

Kumpulan cache dimulakan dengan benang sifar dan berpotensi berkembang untuk mempunyai utas Integer.MAX_VALUE . Secara praktikal, satu-satunya batasan untuk kumpulan benang cache adalah sumber sistem yang ada.

Untuk mengurus sumber sistem dengan lebih baik, kumpulan utas cache akan mengeluarkan utas yang tidak digunakan selama satu minit.

2.1. Gunakan Kes

Konfigurasi kumpulan utas cache menyimpan utas (dengan itu namanya) untuk jangka masa yang pendek untuk menggunakannya semula untuk tugas lain. Hasilnya, ia berfungsi paling baik apabila kita berurusan dengan sebilangan besar tugas jangka pendek.

Kuncinya di sini adalah "munasabah" dan "berumur pendek". Untuk menjelaskan perkara ini, mari kita menilai senario di mana kumpulan cache tidak sesuai. Di sini kita akan menyerahkan satu juta tugas yang masing-masing memerlukan 100 saat mikro untuk diselesaikan:

Callable task = () -> { long oneHundredMicroSeconds = 100_000; long startedAt = System.nanoTime(); while (System.nanoTime() - startedAt  task).collect(toList()); var result = cachedPool.invokeAll(tasks);

Ini akan menghasilkan banyak utas yang diterjemahkan ke penggunaan memori yang tidak masuk akal, dan lebih buruk lagi, banyak pertukaran konteks CPU. Kedua-dua anomali ini akan merosakkan prestasi keseluruhan dengan ketara.

Oleh itu, kita harus mengelakkan kumpulan utas ini ketika waktu pelaksanaan tidak dapat diramalkan, seperti tugas yang terikat pada IO.

3. Kolam Thread yang diperbaiki

Mari lihat bagaimana kolam benang tetap berfungsi di bawah tudung:

public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); }

Berbanding dengan kumpulan utas cache, yang satu ini menggunakan barisan tanpa had dengan bilangan utas yang tidak pernah habis . Oleh itu, bukannya bilangan utas yang semakin meningkat, kumpulan utas tetap berusaha untuk melaksanakan tugas masuk dengan jumlah utas yang tetap . Apabila semua utas sibuk, maka pelaksana akan mengantri tugas baru. Dengan cara ini, kita mempunyai lebih banyak kawalan terhadap penggunaan sumber program kita.

Hasilnya, kumpulan utas tetap lebih sesuai untuk tugas dengan masa pelaksanaan yang tidak dapat diramalkan.

4. Persamaan yang Tidak Disukai

Setakat ini, kami hanya menghitung perbezaan antara kumpulan benang cache dan tetap.

Di samping itu, kedua-duanya menggunakan AbortPolicy sebagai dasar ketepuan mereka . Oleh itu, kami mengharapkan pelaksana ini membuang pengecualian apabila mereka tidak dapat menerima dan bahkan mengantrekan tugas lagi.

Mari lihat apa yang berlaku di dunia nyata.

Kumpulan benang cache akan terus menghasilkan benang yang semakin banyak dalam keadaan yang melampau, jadi, secara praktikal, mereka tidak akan mencapai titik tepu . Begitu juga, kumpulan utas tetap akan terus menambah lebih banyak tugas dalam barisan mereka. Oleh itu, kolam tetap juga tidak akan sampai ke titik tepu .

Oleh kerana kedua kolam tidak akan tepu, ketika bebannya sangat tinggi, mereka akan menghabiskan banyak memori untuk membuat utas atau tugas beratur. Menambah penghinaan terhadap kecederaan itu, kumpulan benang cache juga akan mengalami banyak pertukaran konteks pemproses.

Bagaimanapun, untuk mempunyai lebih banyak kawalan terhadap penggunaan sumber, sangat disarankan untuk membuat ThreadPoolExecutor tersuai :

var boundedQueue = new ArrayBlockingQueue(1000); new ThreadPoolExecutor(10, 20, 60, SECONDS, boundedQueue, new AbortPolicy()); 

Di sini, kumpulan utas kami dapat memuat hingga 20 utas dan hanya dapat mengantri hingga 1000 tugas. Juga, apabila ia tidak dapat menerima beban lagi, ia akan membuang pengecualian.

5. Kesimpulan

Dalam tutorial ini, kami telah melihat kod sumber JDK untuk melihat betapa berbezanya cara Pelaksana bekerja. Kemudian, kami membandingkan kumpulan benang tetap dan cache dan kes penggunaannya.

Pada akhirnya, kami cuba menangani penggunaan sumber yang tidak terkawal dari kolam tersebut dengan kumpulan benang khusus.