Sumber kumpulan menggunakan Rangka Kerja Apache's Commons Pool
Pengumpulan sumber (juga disebut penyatuan objek) di antara beberapa klien adalah teknik yang digunakan untuk mempromosikan penggunaan semula objek dan untuk mengurangkan overhead untuk membuat sumber daya baru, menghasilkan kinerja dan hasil yang lebih baik. Bayangkan aplikasi pelayan Java tugas berat yang menghantar beratus-ratus pertanyaan SQL dengan membuka dan menutup sambungan untuk setiap permintaan SQL. Atau pelayan Web yang melayani ratusan permintaan HTTP, menangani setiap permintaan dengan memunculkan utas yang terpisah. Atau bayangkan membuat instance penghurai XML untuk setiap permintaan untuk menguraikan dokumen tanpa menggunakan kembali contohnya. Ini adalah beberapa senario yang memerlukan pengoptimuman sumber yang digunakan.
Penggunaan sumber boleh menjadi kritikal kadang-kadang untuk aplikasi tugas berat. Beberapa laman web terkenal telah ditutup kerana ketidakmampuan mereka menangani beban berat. Sebilangan besar masalah yang berkaitan dengan beban berat dapat ditangani, pada tahap makro, dengan menggunakan kemampuan pengelompokan dan pengimbangan beban. Kekhawatiran tetap ada di tingkat aplikasi sehubungan dengan penciptaan objek yang berlebihan dan ketersediaan sumber pelayan yang terhad seperti memori, CPU, utas, dan sambungan pangkalan data, yang dapat mewakili kemacetan yang berpotensi dan, ketika tidak digunakan secara optimal, menurunkan seluruh pelayan.
Dalam beberapa situasi, kebijakan penggunaan pangkalan data dapat memberlakukan batasan pada jumlah sambungan serentak. Juga, aplikasi luaran dapat menentukan atau menyekat jumlah sambungan terbuka serentak. Contoh biasa ialah pendaftaran domain (seperti Verisign) yang menghadkan bilangan sambungan soket aktif yang tersedia untuk pendaftar (seperti BulkRegister). Pengumpulan sumber telah terbukti menjadi salah satu pilihan terbaik dalam menangani jenis masalah ini dan, sampai batas tertentu, juga membantu dalam mempertahankan tahap layanan yang diperlukan untuk aplikasi perusahaan.
Sebilangan besar vendor pelayan aplikasi J2EE menyediakan penyatuan sumber sebagai bahagian tidak terpisahkan dari wadah Web dan EJB (Enterprise JavaBean) mereka. Untuk sambungan pangkalan data, vendor pelayan biasanya menyediakan pelaksanaan DataSource
antara muka, yang berfungsi bersamaan dengan ConnectionPoolDataSource
pelaksanaan vendor pemacu JDBC (Java Database Connectivity) . The ConnectionPoolDataSource
pelaksanaan berfungsi sebagai sambungan kilang pengurus sumber untuk dikumpulkan java.sql.Connection
objek. Begitu juga, contoh kacang sesi tanpa status, kacang berdasarkan pesanan, dan kacang entiti digabungkan dalam bekas EJB untuk mendapatkan hasil dan prestasi yang lebih tinggi. Instance parser XML juga merupakan kandidat untuk penyatuan, kerana penciptaan instance parser memakan banyak sumber sistem.
Pelaksanaan penyatuan sumber terbuka yang berjaya adalah DBCP framework Commons Pool, komponen penyatuan sambungan pangkalan data dari Apace Software Foundation yang banyak digunakan dalam aplikasi perusahaan kelas produksi. Dalam artikel ini, saya membincangkan secara ringkas kerangka dalaman Commons Pool dan menggunakannya untuk melaksanakan kumpulan utas.
Mari lihat dahulu kerangka yang disediakan.
Kerangka Kolam Renang Commons
Rangka kerja Commons Pool menawarkan pelaksanaan dasar dan mantap untuk mengumpulkan objek sewenang-wenangnya. Beberapa implementasi disediakan, tetapi untuk tujuan artikel ini, kami menggunakan implementasi yang paling umum, yaitu GenericObjectPool
. Ini menggunakan sebuah CursorableLinkedList
, yang merupakan implementasi daftar ganda-terkait (bagian dari Koleksi Jakarta Commons), sebagai infrastruktur dasar untuk menahan objek yang dikumpulkan.
Di atas, kerangka menyediakan satu set antara muka yang menyediakan kaedah kitaran hidup dan kaedah penolong untuk mengurus, memantau, dan memperluas kumpulan.
Antaramuka org.apache.commons.PoolableObjectFactory
menentukan kaedah kitaran hidup berikut, yang terbukti penting untuk menerapkan komponen penyatuan:
// Creates an instance that can be returned by the pool public Object makeObject() {} // Destroys an instance no longer needed by the pool public void destroyObject(Object obj) {} // Validate the object before using it public boolean validateObject(Object obj) {} // Initialize an instance to be returned by the pool public void activateObject(Object obj) {} // Uninitialize an instance to be returned to the pool public void passivateObject(Object obj) {}
Seperti yang anda dapati dengan tandatangan kaedah, antara muka ini terutama berkaitan dengan yang berikut:
makeObject()
: Melaksanakan penciptaan objekdestroyObject()
: Melaksanakan pemusnahan objekvalidateObject()
: Mengesahkan objek sebelum digunakanactivateObject()
: Melaksanakan kod inisialisasi objekpassivateObject()
: Melaksanakan kod uninitialization objek
Antara muka teras org.apache.commons.ObjectPool
lain— —mendefinisikan kaedah berikut untuk mengurus dan memantau kumpulan:
// Obtain an instance from my pool Object borrowObject() throws Exception; // Return an instance to my pool void returnObject(Object obj) throws Exception; // Invalidates an object from the pool void invalidateObject(Object obj) throws Exception; // Used for pre-loading a pool with idle objects void addObject() throws Exception; // Return the number of idle instances int getNumIdle() throws UnsupportedOperationException; // Return the number of active instances int getNumActive() throws UnsupportedOperationException; // Clears the idle objects void clear() throws Exception, UnsupportedOperationException; // Close the pool void close() throws Exception; //Set the ObjectFactory to be used for creating instances void setFactory(PoolableObjectFactory factory) throws IllegalStateException, UnsupportedOperationException;
Pelaksanaan ObjectPool
antaramuka mengambil PoolableObjectFactory
sebagai argumen dalam konstruktornya, sehingga mendelegasikan pembuatan objek ke subkelasnya. Saya tidak banyak bercakap mengenai corak reka bentuk di sini kerana itu bukan fokus kami. Bagi pembaca yang berminat untuk melihat gambar rajah kelas UML, sila lihat Sumber.
Seperti yang disebutkan di atas, kelas org.apache.commons.GenericObjectPool
hanya satu pelaksanaan org.apache.commons.ObjectPool
antara muka. Kerangka kerja ini juga menyediakan implementasi untuk kumpulan objek yang dikunci, menggunakan antarmuka org.apache.commons.KeyedObjectPoolFactory
dan org.apache.commons.KeyedObjectPool
, di mana seseorang dapat mengaitkan kumpulan dengan kunci (seperti dalam HashMap
) dan dengan itu menguruskan beberapa kumpulan.
Kunci strategi penggabungan yang berjaya bergantung pada bagaimana kita mengkonfigurasi kumpulan. Kolam yang dikonfigurasi dengan buruk boleh menjadi babi sumber, jika parameter konfigurasi tidak diselaraskan dengan baik. Mari lihat beberapa parameter penting dan tujuannya.
Perincian konfigurasi
Kolam boleh dikonfigurasi menggunakan GenericObjectPool.Config
kelas, yang merupakan kelas dalaman yang statik. Sebagai alternatif, kita hanya boleh menggunakan GenericObjectPool
kaedah setter untuk menetapkan nilai.
Senarai berikut memperincikan beberapa parameter konfigurasi yang tersedia untuk GenericObjectPool
pelaksanaan:
maxIdle
: Jumlah maksimum tidur di kolam renang, tanpa objek tambahan dilepaskan.minIdle
: Jumlah minimum tidur di kolam renang, tanpa dibuat objek tambahan.maxActive
: Jumlah maksimum kejadian aktif di kumpulan.timeBetweenEvictionRunsMillis
: Bilangan milisaat untuk tidur antara larian utas pengusir objek terbiar. Apabila negatif, tidak ada benang pengusir objek terbiar yang akan dijalankan. Gunakan parameter ini hanya apabila anda mahu thread pengusir berjalan.minEvictableIdleTimeMillis
: Jangka masa minimum objek, jika aktif, boleh duduk dalam kumpulan sebelum layak diusir oleh pengusir objek terbiar. Sekiranya nilai negatif diberikan, tidak ada objek yang diusir kerana waktu senggang sahaja.testOnBorrow
: Apabila "benar", objek disahkan. Sekiranya objek gagal disahkan, ia akan diturunkan dari kumpulan, dan kumpulan akan berusaha meminjam yang lain.
Nilai optimum harus diberikan untuk parameter di atas untuk mencapai prestasi dan hasil maksimum. Oleh kerana corak penggunaannya bervariasi dari aplikasi ke aplikasi, sesuaikan kumpulan dengan berbagai kombinasi parameter untuk mencapai solusi yang optimal.
Untuk memahami lebih lanjut mengenai kolam renang dan dalamannya, mari kita laksanakan kumpulan utas.
Keperluan kolam benang yang dicadangkan
Andaikan kita diberitahu untuk merancang dan menerapkan komponen kumpulan utas untuk penjadwal pekerjaan untuk memicu pekerjaan pada jadwal yang ditentukan dan melaporkan penyelesaiannya dan, mungkin, hasil pelaksanaannya. Dalam senario seperti itu, objektif kumpulan utas kami adalah mengumpulkan sejumlah utas prasyarat dan melaksanakan tugas yang dijadualkan dalam utas bebas. Keperluannya diringkaskan seperti berikut:
- Benang harus dapat menggunakan kaedah kelas sewenang-wenangnya (pekerjaan yang dijadualkan)
- Benang harus dapat mengembalikan hasil pelaksanaan
- Benang harus dapat melaporkan penyelesaian tugas
Keperluan pertama memberi ruang untuk pelaksanaan yang digabungkan secara longgar kerana tidak memaksa kita untuk melaksanakan antara muka seperti Runnable
. Ini juga memudahkan integrasi. Kami dapat melaksanakan syarat pertama kami dengan memberikan maklumat berikut:
- Nama kelas
- Nama kaedah yang akan digunakan
- Parameter yang akan diteruskan ke kaedah
- Jenis parameter parameter yang dilalui
Keperluan kedua membolehkan pelanggan menggunakan utas untuk menerima hasil pelaksanaan. Pelaksanaan yang mudah adalah menyimpan hasil pelaksanaan dan menyediakan kaedah aksesor seperti getResult()
.
Keperluan ketiga agak berkaitan dengan syarat kedua. Melaporkan penyelesaian tugas juga dapat berarti bahawa klien sedang menunggu untuk mendapatkan hasil pelaksanaan. Untuk menangani kemampuan ini, kami dapat menyediakan beberapa bentuk mekanisme panggilan balik. Mekanisme panggilan balik yang paling mudah dapat dilaksanakan dengan menggunakan java.lang.Object
s wait()
dan notify()
semantik. Sebagai alternatif, kita boleh menggunakan corak Observer , tetapi buat masa ini mari kita memperbaikinya. Anda mungkin tergoda untuk menggunakan kaedah java.lang.Thread
kelas join()
, tetapi itu tidak akan berjaya kerana benang yang digabungkan tidak pernah menyelesaikan run()
kaedahnya dan terus berjalan selagi kolam itu memerlukannya.
Sekarang kita sudah mempersiapkan keperluan kita dan idea kasar mengenai bagaimana melaksanakan kumpulan utas, inilah masanya untuk melakukan pengekodan sebenar.
Pada peringkat ini, rajah kelas UML kami mengenai reka bentuk yang dicadangkan kelihatan seperti gambar di bawah.

Melaksanakan kumpulan benang
Objek utas yang akan kita kumpulkan sebenarnya adalah pembungkus di sekitar objek utas. Mari panggil pembungkus WorkerThread
kelas, yang memanjangkan java.lang.Thread
kelas. Sebelum kita dapat memulakan pengekodan WorkerThread
, kita mesti melaksanakan syarat kerangka. Seperti yang kita lihat sebelumnya, kita mesti melaksanakan PoolableObjectFactory
, yang bertindak sebagai kilang, untuk membuat WorkerThread
s yang dapat dikumpulkan . Setelah kilang siap, kami melaksanakan ThreadPool
dengan memanjangkan GenericObjectPool
. Kemudian, kita selesai WorkerThread
.
Melaksanakan antara muka PoolableObjectFactory
Kami bermula dengan PoolableObjectFactory
antara muka dan cuba menerapkan kaedah kitaran hidup yang diperlukan untuk kumpulan utas kami. Kami menulis kelas kilang ThreadObjectFactory
seperti berikut:
public class ThreadObjectFactory implements PoolableObjectFactory{
public Object makeObject() { return new WorkerThread(); } public void destroyObject(Object obj) { if (obj instanceof WorkerThread) { WorkerThread rt = (WorkerThread) obj; rt.setStopped(true);//Make the running thread stop } } public boolean validateObject(Object obj) { if (obj instanceof WorkerThread) { WorkerThread rt = (WorkerThread) obj; if (rt.isRunning()) { if (rt.getThreadGroup() == null) { return false; } return true; } } return true; } public void activateObject(Object obj) { log.debug(" activateObject..."); }
public void passivateObject(Object obj) { log.debug(" passivateObject..." + obj); if (obj instanceof WorkerThread) { WorkerThread wt = (WorkerThread) obj; wt.setResult(null); //Clean up the result of the execution } } }
Mari kita ikuti setiap kaedah secara terperinci:
Kaedah makeObject()
mencipta WorkerThread
objek. Untuk setiap permintaan, kolam diperiksa untuk melihat apakah objek baru akan dibuat atau objek yang ada harus digunakan kembali. Sebagai contoh, jika permintaan tertentu adalah permintaan pertama dan kumpulan kosong, ObjectPool
pelaksanaan memanggil makeObject()
dan menambahkan WorkerThread
ke kolam.
Kaedah destroyObject()
mengeluarkan WorkerThread
objek dari kolam dengan menetapkan bendera Boolean dan dengan itu menghentikan benang berjalan. Kami akan melihat bahagian ini lagi kemudian, tetapi perhatikan bahawa kita sekarang mengawal bagaimana objek kita dimusnahkan.