Latar belakang teknis kumpulan utas
Dalam pemrograman yang berorientasi pada objek, membuat dan menghancurkan objek memakan waktu, karena membuat objek membutuhkan sumber daya memori atau lebih banyak sumber daya lainnya. Ini bahkan lebih di Java, di mana mesin virtual akan mencoba melacak setiap objek sehingga dapat dikumpulkan sampah setelah objek dihancurkan.
Oleh karena itu, salah satu cara untuk meningkatkan efisiensi program layanan adalah dengan meminimalkan jumlah waktu menciptakan dan menghancurkan objek, terutama beberapa pembuatan dan penghancuran objek yang sangat memakan sumber daya. Cara menggunakan objek yang ada untuk melayani adalah masalah utama yang perlu diselesaikan. Bahkan, inilah alasan mengapa beberapa teknologi "sumber daya pengumpulan" diproduksi.
Sebagai contoh, banyak komponen umum yang biasa terlihat di Android umumnya tidak dapat dipisahkan dari konsep "kumpulan", seperti berbagai perpustakaan pemuatan gambar dan perpustakaan permintaan jaringan. Bahkan jika meaasge dalam mekanisme pesan Android menggunakan meaasge.obtain (), itu adalah objek dalam kumpulan meaasge yang digunakan, jadi konsep ini sangat penting. Teknologi pengumpulan utas yang diperkenalkan dalam artikel ini juga sesuai dengan ide ini.
Keuntungan kumpulan utas:
1. Reuse Thread di kumpulan utas untuk mengurangi overhead kinerja yang disebabkan oleh pembuatan objek dan penghancuran;
2. Ia dapat secara efektif mengontrol jumlah concurrency maksimum utas, meningkatkan pemanfaatan sumber daya sistem, dan menghindari persaingan sumber daya yang berlebihan dan menghindari penyumbatan;
3. Kemampuan untuk melakukan manajemen sederhana dari banyak utas, membuat penggunaan utas menjadi sederhana dan efisien.
Eksekutor Kerangka Kerja Thread
Kumpulan utas di Java diimplementasikan melalui kerangka kerja pelaksana. Kerangka Eksekutor mencakup kelas: pelaksana, pelaksana, ExecutorService, ThreadPoolExecutor, Callable dan Future, Futuretask Usage, dll.
Pelaksana: Hanya ada satu metode untuk semua antarmuka kumpulan utas.
Eksekutor Antarmuka Publik {void execute (runnable command); }ExecutorService: Menambahkan perilaku eksekutor adalah antarmuka paling langsung dari kelas implementasi pelaksana.
Pelaksana: Menyediakan serangkaian metode pabrik untuk membuat kumpulan utas, dan kumpulan utas yang dikembalikan semuanya mengimplementasikan antarmuka ExecutorService.
ThreadPoolExecutor: Kelas implementasi spesifik kumpulan utas. Berbagai kumpulan utas yang umumnya digunakan diimplementasikan berdasarkan kelas ini. Metode konstruksi adalah sebagai berikut:
Thread PublicPoolExecutor (int corePoolsize, int maximumpoolsize, Keepalivetime panjang, unit timeunit, blockingqueue <Runnable> workqueue) {this (corePoolsize, maximumpoolsize, stepalivetime, unit, workqueue, executors.defaultThreadFactory (), defauultime, unit, workqueue (executors.CorePoolsize: Jumlah utas inti di kumpulan utas, dan jumlah utas yang berjalan di kumpulan utas tidak akan pernah melebihi corePoolsize, dan dapat bertahan secara default. Anda dapat mengatur AllowCorethreadTimeout ke True, jumlah utas inti adalah 0, dan Keepalivetime mengontrol waktu batas waktu semua utas.
MaximumpoolSize: Jumlah maksimum utas yang diizinkan oleh kumpulan utas;
Keepalivetime: mengacu pada waktu batas waktu ketika benang idle berakhir;
Unit: adalah enum, mewakili unit Keepalivetime;
WorkQueue: Mewakili BlockingQueue <antrian yang dapat dijalankan yang menyimpan tugas.
BlockingQueue: BlockingQueue adalah alat yang terutama digunakan untuk mengontrol sinkronisasi utas di bawah java.util.concurrent. Jika blockqueue kosong, pengoperasian mengambil sesuatu dari blockingqueue akan diblokir dan tidak akan dibangunkan sampai blockingqueue memasuki benda itu. Demikian pula, jika blockingqueue penuh, operasi apa pun yang berupaya menyimpan barang -barang di dalamnya akan diblokir dan tidak akan terbangun untuk melanjutkan operasi sampai ada ruang di blockingqueue. Antrian memblokir sering digunakan dalam skenario produsen dan konsumen. Produsen adalah utas yang menambah elemen ke antrian, dan konsumen adalah utas yang mengambil elemen dari antrian. Antrian pemblokiran adalah wadah tempat produser menyimpan elemen, dan konsumen hanya mengambil elemen dari wadah. Kelas implementasi spesifik termasuk LinkedBlockingQueue, arrayblockingqueued, dll. Umumnya, pemblokiran internal dan bangun dicapai melalui kunci dan kondisi (belajar dan penggunaan kunci dan kondisi tampilan).
Proses kerja kumpulan utas adalah sebagai berikut:
Ketika kumpulan utas pertama kali dibuat, tidak ada utas di dalamnya. Antrian tugas dilewatkan sebagai parameter. Namun, bahkan jika ada tugas dalam antrian, kumpulan utas tidak akan segera melaksanakannya.
Saat menambahkan tugas dengan memanggil metode Execute (), kumpulan utas akan membuat penilaian berikut:
Jika jumlah utas yang berjalan kurang dari corePoolsize, buat utas untuk menjalankan tugas ini segera;
Jika jumlah utas yang berjalan lebih besar dari atau sama dengan corePoolsize, maka masukkan tugas ini ke dalam antrian;
Jika antrian penuh pada saat ini dan jumlah utas yang berjalan kurang dari MaximumpoolSize, maka Anda masih perlu membuat utas non-core untuk menjalankan tugas segera;
Jika antrian penuh dan jumlah utas yang berjalan lebih besar dari atau sama dengan MaximumpoolSize, kumpulan utas akan melempar pengecualian penolakan ExcutionException.
Ketika utas menyelesaikan tugas, dibutuhkan tugas berikutnya dari antrian untuk dieksekusi.
Ketika sebuah utas tidak ada hubungannya, dan setelah periode waktu tertentu (Keepalivetime), kumpulan utas akan menilai bahwa jika jumlah utas yang saat ini berjalan lebih besar dari corePoolsize, maka utas akan dihentikan. Jadi setelah semua tugas kolam benang selesai, pada akhirnya akan menyusut ke ukuran corePoolsize.
Pembuatan dan penggunaan kumpulan benang
Pool utas generasi menggunakan metode statis pelaksana kelas alat. Berikut ini adalah beberapa kumpulan benang umum.
SinglethreadExecutor: utas latar belakang tunggal (antrian buffer tidak terikat)
Public Static ExecutorService NewsingLetHreadExecutor () {return baru finalzableDelegatedExecutorService (ThreadPoolExecutor baru (1, 1, 0l, timeunit.milliseconds, LinkedBlockingQueue baru <Runnable> ()))); }Buat kumpulan utas tunggal. Kumpulan utas ini hanya memiliki satu utas inti yang berfungsi, yang setara dengan satu utas yang melakukan semua tugas dalam serial. Jika utas unik ini berakhir karena pengecualian, maka akan ada utas baru untuk menggantinya. Kumpulan utas ini memastikan bahwa perintah eksekusi semua tugas dieksekusi dalam urutan pengajuan tugas.
FixedThreadPool: Hanya kumpulan benang utas inti, dengan ukuran tetap (antrian buffer tidak terikat).
Public Static ExecutorService newfixedThreadPool (int nthreads) {
Return New ThreadPoolExecutor (nthreads, nthreads,
0L, Timeunit.Milliseconds,
baru LinkedBlockingQueue <Runnable> ());
}
Buat kumpulan utas ukuran tetap. Setiap kali tugas dikirimkan, utas dibuat sampai utas mencapai ukuran maksimum kumpulan utas. Ukuran kumpulan benang tetap sama setelah mencapai nilai maksimumnya. Jika utas berakhir karena pengecualian eksekusi, kumpulan utas akan menambahkan utas baru.
CachedThreadPool: Pool utas tak terbatas, dapat melakukan daur ulang utas otomatis.
Public Static ExecutorService newCachedThreadPool () {return new ThreadPoolExecutor (0, integer.max_value, 60l, timeunit.seconds, synchronousqueue baru <Runnable> ()); }Jika ukuran kumpulan utas melebihi utas yang diperlukan untuk memproses tugas, beberapa utas idle (tidak ada eksekusi tugas dalam 60 detik) akan didaur ulang. Ketika jumlah tugas meningkat, kumpulan utas ini dapat dengan cerdas menambahkan utas baru untuk memproses tugas. Kumpulan utas ini tidak membatasi ukuran kumpulan utas, yang sepenuhnya tergantung pada ukuran utas maksimum yang dapat dibuat oleh sistem operasi (atau JVM). Synchronousqueue adalah antrian pemblokiran dengan buffer 1.
TerjadwalThreadPool: Kumpulan utas inti dengan kumpulan utas inti tetap, ukuran tidak terbatas. Kumpulan utas ini mendukung kebutuhan untuk melakukan tugas secara berkala dan berkala.
Public Static ExecutorService NewsCheduledThreadPool (int corepoolsize) {return new jadwaltHreadpool (corepoolsize, integer.max_value, default_keepalive_millis, milidetik, delayedworkqueue baru ()); }Buat kumpulan utas yang melakukan tugas secara berkala. Jika idle, kumpulan utas non-core akan didaur ulang dalam waktu default_keepalivemillis.
Ada dua metode yang paling umum digunakan untuk mengirimkan tugas di kumpulan benang:
menjalankan:
ExecutorService.Execute (runnable runnable);
Kirim:
Futuretask Task = ExecutorService.submit (runnable runnable);
Futuretask <T> Tugas = ExecutorService.submit (runnable runnable, t hasil);
Futuretask <T> Tugas = ExecutorService.submit (Callable <T> Callable);
Hal yang sama berlaku untuk implementasi pengiriman (dapat dipanggil yang dapat dipanggil) dan hal yang sama berlaku untuk mengirimkan (runnable runnable).
PUBLIK <T> Masa Depan <T> Kirim (Tugas Callable <T>) {if (Task == NULL) Lempar NullPointerException baru (); Futuretask <t> ftask = newTaskFor (tugas); Execute (fTask); return fTask;}Dapat dilihat bahwa pengiriman adalah tugas yang mengembalikan hasilnya, dan akan mengembalikan objek Futuretask, sehingga hasilnya dapat diperoleh melalui metode get (). Panggilan terakhir untuk mengirimkan juga dieksekusi (runnable runnable). Kirim hanya merangkum objek yang dapat dipanggil atau dapat dijalankan ke objek Futuretask. Karena FutureTask adalah runnable, dapat dieksekusi dalam Execute. Untuk bagaimana objek yang dapat dipanggil dan runnable dienkapsulasi ke dalam objek Futuretask, lihat penggunaan Callable, Future, Futuretask.
Prinsip Implementasi Pool Thread
Jika Anda hanya berbicara tentang penggunaan kumpulan utas, maka blog ini tidak memiliki nilai besar. Paling-paling, itu hanya proses membiasakan diri dengan API terkait pelaksana. Proses implementasi kumpulan utas tidak menggunakan kata kunci yang disinkronkan, tetapi menggunakan antrian volatile, kunci dan sinkron (pemblokiran), kelas terkait atom, futuretask, dll., Karena yang terakhir memiliki kinerja yang lebih baik. Proses pemahaman dapat mempelajari gagasan kontrol bersamaan dalam kode sumber dengan baik.
Keuntungan dari pengumpulan utas yang disebutkan di awal dirangkum sebagai berikut:
Utas penggunaan kembali
Kontrol jumlah maksimum konkurensi
Kelola utas
1. Proses Multiplexing Thread
Untuk memahami prinsip multiplexing utas, Anda harus terlebih dahulu memahami siklus hidup utas.
Selama siklus hidup utas, ia harus melalui lima negara: baru, dapat dikeluarkan, berjalan, diblokir dan mati.
Thread membuat utas baru melalui yang baru. Proses ini adalah untuk menginisialisasi beberapa informasi utas, seperti nama utas, ID, grup yang menjadi milik utas, dll., Yang dapat dianggap hanya objek biasa. Setelah menelepon start thread (), mesin virtual Java membuat metode panggilan panggilan dan penghitung program untuk itu, dan pada saat yang sama, HasbeenSarted menjadi true, dan kemudian akan ada pengecualian saat memanggil metode start.
Utas dalam keadaan ini tidak mulai berjalan, tetapi hanya berarti bahwa utas dapat berjalan. Adapun saat utas mulai berjalan, itu tergantung pada penjadwal di JVM. Ketika utas memperoleh CPU, metode run () akan dipanggil. Jangan hubungi thread's run () Metode sendiri. Kemudian, beralih di antara waktu siap-memblokir sesuai dengan penjadwalan CPU, sampai metode run () berakhir atau metode lain menghentikan utas dan memasuki keadaan mati.
Oleh karena itu, prinsip penerapan utas penggunaan kembali adalah menjaga utas tetap hidup (siap, berjalan atau memblokir). Selanjutnya, mari kita lihat bagaimana ThreadPoolExecutor mengimplementasikan kembali Thread Reuse.
Kelas Pekerja Utama di ThreadPoolExecutor mengontrol penggunaan utas. Lihatlah kode yang disederhanakan dari kelas pekerja, jadi mudah dimengerti:
private final class Worker implements Runnable {final Thread thread;Runnable firstTask;Worker(Runnable firstTask) {this.firstTask = firstTask;this.thread = getThreadFactory().newThread(this);}public void run() {runWorker(this);}final void runWorker(Worker w) {Runnable task = w.firsttask; w.firsttask = null; while (tugas! = null || (tugas = getTask ())! = null) {task.run ();}}Pekerja adalah runnable dan memiliki utas pada saat yang sama. Utas ini adalah utas yang akan dibuka. Saat membuat objek pekerja baru, objek utas baru dibuat pada saat yang sama, dan pekerja itu sendiri diteruskan ke dalam tthread sebagai parameter. Dengan cara ini, ketika metode start () dari utas dipanggil, metode run () pekerja benar -benar berjalan. Kemudian di runworker (), ada beberapa waktu loop, yang terus mendapatkan objek runnable dari gettask () dan mengeksekusinya secara berurutan. Bagaimana gettask () mendapatkan objek yang dapat dijalankan?
Masih kode yang disederhanakan:
private runnable getTask () {if (beberapa kasus khusus) {return null; } Runnable r = workqueue.take (); return r;}Workqueue ini adalah antrian blockingqueue yang menyimpan tugas saat menginisialisasi threadpoolexecutor. Antrian menyimpan tugas yang dapat dijalankan. Karena BlockingQueue adalah antrian blocking, blockingqueue.take () menjadi kosong, dan memasuki keadaan menunggu sampai objek baru dalam blockingqueue ditambahkan untuk membangunkan benang yang diblokir. Oleh karena itu, secara umum, metode run () utas tidak akan berakhir, tetapi akan terus melaksanakan tugas yang dapat dijalankan dari workqueue, yang mencapai prinsip penggunaan kembali benang.
2. Kontrol jumlah maksimum konkurensi
Jadi kapan runnable dimasukkan ke dalam workqueue? Kapan pekerja dibuat? Kapan utas dalam pekerja dipanggil start () untuk membuka utas baru untuk menjalankan metode pekerja run ()? Analisis di atas menunjukkan bahwa runworker () pada pekerja melakukan tugas satu demi satu, secara serial, jadi bagaimana konkurensi memanifestasikan dirinya?
Sangat mudah untuk berpikir bahwa Anda akan melakukan beberapa tugas di atas saat Anda mengeksekusi (runnable runnable). Mari kita lihat bagaimana hal itu dilakukan di Execute.
menjalankan:
Kode yang disederhanakan
public void execute (runnable command) {if (command == null) lempar nullPointerException baru (); int c = ctl.get (); // Jumlah utas saat ini <corepoolsizeif (workerCountof (c) <corepoolsize) {// Langsung memulai utas baru. if (addworker (perintah, true)) return; c = ctl.get ();} // Jumlah utas aktif> = corePoolSize // runstate berjalan && antrian tidak penuh jika (c) && status workquee (command) {int recheck = ctl. (!!!AddWorker:
Kode yang disederhanakan
Private Boolean AddWorker (runnable firstTask, boolean core) {int wc = workercountof (c); if (wc> = (core? corePoolSize: maximumpoolSize)) {return false;} w = pekerja baru (firstTask); utas terakhir t = w.thread; t.start ();Menurut kode, mari kita lihat situasi yang disebutkan di atas untuk menambahkan tugas selama pekerjaan kumpulan utas:
* Jika jumlah utas yang berjalan kurang dari CorePoolsize, buat utas untuk menjalankan tugas ini segera;
* Jika jumlah utas yang berjalan lebih besar dari atau sama dengan corePoolsize, maka masukkan tugas ini dalam antrian;
* Jika antrian penuh pada saat ini dan jumlah utas yang berjalan kurang dari MaximumpoolSize, maka Anda masih perlu membuat utas non-inti untuk menjalankan tugas segera;
* Jika antrian penuh dan jumlah utas yang berjalan lebih besar dari atau sama dengan MaximumpoolSize, kumpulan utas akan melempar pengecualian penolakan ExecutionException.
Inilah sebabnya mengapa Android's Asynctask dieksekusi secara paralel dan melebihi jumlah maksimum tugas dan lemparan RejectExecutionException. Untuk detailnya, silakan merujuk ke versi terbaru dari interpretasi kode sumber asynctask dan sisi gelap asynctask
Jika utas baru berhasil dibuat melalui addworker, start () dan gunakan FirstTask sebagai tugas pertama yang dieksekusi dalam run () pada pekerja ini.
Meskipun tugas masing -masing pekerja adalah pemrosesan serial, jika banyak pekerja dibuat, karena mereka berbagi workqueue, mereka akan diproses secara paralel.
Oleh karena itu, angka konkurensi maksimum dikendalikan sesuai dengan CorePoolsize dan MaximumpoolSize. Proses umum dapat diwakili oleh gambar di bawah ini.
Penjelasan dan gambar di atas dapat dipahami dengan baik.
Jika Anda terlibat dalam pengembangan Android dan akrab dengan prinsip -prinsip Handler, Anda mungkin berpikir bahwa gambar ini cukup akrab. Beberapa proses sangat mirip dengan yang digunakan oleh Handler, Looper, dan Meaasge. Handler.send (pesan) setara dengan dieksekusi (runnuble). Antrian meaasge yang dipertahankan dalam looper setara dengan blockingqueue. Namun, Anda perlu mempertahankan antrian ini dengan sinkronisasi. Fungsi loop () dalam loop loop untuk mengambil meaasge dari antrian meaasge dan runwork () pada pekerja terus -menerus dapat dikeluarkan dari blockingqueue.
3. Kelola utas
Melalui kumpulan utas, kami dapat mengelola penggunaan kembali utas, mengontrol nomor konkurensi, dan menghancurkan proses. Utas penggunaan kembali dan kontrol konkurensi telah dibahas di atas, dan proses manajemen utas telah diselingi di dalamnya, yang mudah dimengerti.
Ada variabel CTL AtomicInteger di ThreadPoolExecutor. Dua konten disimpan melalui variabel ini:
Jumlah semua utas. Setiap utas berada dalam keadaan di mana 29 bit yang lebih rendah disimpan dan 3 bit runstate yang lebih tinggi disimpan. Nilai yang berbeda diperoleh melalui operasi bit.
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));//Get the thread's state private static int runStateOf(int c) {return c & ~CAPACITY;}//Get the number of Workers private static int workerCountOf(int c) {return c & CAPACITY;}//Get the number of Workers private static int workerCountOf(int c) {return c & Kapasitas;} // Istilah apakah utas tersebut menjalankan private static boolean isrunning (int c) {return c <shutdown;}Di sini, proses shutdown kumpulan utas terutama dianalisis dengan shutdown dan shutdownnow (). Pertama -tama, kumpulan utas memiliki lima negara bagian untuk mengontrol penambahan dan eksekusi tugas. Tiga jenis utama berikut ini diperkenalkan:
Status Menjalankan: Kumpulan utas berjalan secara normal, dan dapat menerima tugas baru dan memproses tugas dalam antrian;
Status shutdown: Tidak ada tugas baru yang diterima, tetapi tugas dalam antrian akan dieksekusi;
Status berhenti: Tidak ada tugas baru yang diterima lagi, dan shutdown tugas tidak diproses dalam antrian. Metode ini akan mengatur RunState untuk shutdown, dan akan menghentikan semua utas idle, sedangkan utas yang masih berfungsi tidak terpengaruh, sehingga tugas tugas dalam antrian akan dieksekusi.
Metode shutdownnow menetapkan RunState untuk berhenti. Perbedaan antara metode shutdown, metode ini akan mengakhiri semua utas, sehingga tugas dalam antrian tidak akan dieksekusi.
Ringkasan: Melalui analisis kode sumber ThreadPoolExecutor, kami memiliki pemahaman umum tentang proses membuat kumpulan utas, menambahkan tugas, dan mengeksekusi kumpulan utas. Jika Anda terbiasa dengan proses ini, akan lebih mudah menggunakan kumpulan benang.
Beberapa kontrol konkurensi yang dipelajari darinya dan penggunaan pemrosesan tugas model konsumen produsen akan sangat membantu untuk memahami atau menyelesaikan masalah terkait lainnya di masa depan. Misalnya, mekanisme pawang di Android, dan antrian messager di Looper juga boleh menggunakan blookqueue untuk menanganinya. Ini adalah keuntungan membaca kode sumber.
Di atas adalah informasi yang menyortir kumpulan utas Java. Kami akan terus menambahkan informasi yang relevan di masa mendatang. Terima kasih atas dukungan Anda untuk situs web ini!