Sumber daya pool menggunakan Apache's Commons Pool Framework

Menyatukan sumber daya (juga disebut penyatuan objek) di antara beberapa klien adalah teknik yang digunakan untuk mempromosikan penggunaan kembali objek dan untuk mengurangi biaya overhead pembuatan sumber daya baru, menghasilkan kinerja dan throughput yang lebih baik. Bayangkan aplikasi server Java tugas berat yang mengirimkan ratusan kueri SQL dengan membuka dan menutup koneksi untuk setiap permintaan SQL. Atau server Web yang melayani ratusan permintaan HTTP, menangani setiap permintaan dengan memunculkan utas terpisah. Atau bayangkan membuat instance pengurai XML untuk setiap permintaan untuk mengurai dokumen tanpa menggunakan kembali instance tersebut. Ini adalah beberapa skenario yang menjamin pengoptimalan sumber daya yang digunakan.

Penggunaan sumber daya terkadang terbukti penting untuk aplikasi tugas berat. Beberapa Situs Web terkenal telah ditutup karena ketidakmampuannya menangani beban berat. Sebagian besar masalah yang terkait dengan beban berat dapat ditangani, pada tingkat makro, menggunakan kemampuan pengelompokan dan penyeimbangan beban. Kekhawatiran tetap pada tingkat aplikasi sehubungan dengan pembuatan objek yang berlebihan dan ketersediaan sumber daya server yang terbatas seperti memori, CPU, utas, dan koneksi database, yang dapat menunjukkan potensi kemacetan dan, jika tidak digunakan secara optimal, menurunkan seluruh server.

Dalam beberapa situasi, kebijakan penggunaan database bisa memberlakukan batas jumlah koneksi bersamaan. Selain itu, aplikasi eksternal dapat menentukan atau membatasi jumlah koneksi terbuka bersamaan. Contoh tipikal adalah registri domain (seperti Verisign) yang membatasi jumlah koneksi soket aktif yang tersedia untuk pendaftar (seperti BulkRegister). Sumber daya penggabungan telah terbukti menjadi salah satu opsi terbaik dalam menangani jenis masalah ini dan, sampai batas tertentu, juga membantu dalam mempertahankan tingkat layanan yang diperlukan untuk aplikasi perusahaan.

Sebagian besar vendor server aplikasi J2EE menyediakan pengumpulan sumber daya sebagai bagian integral dari wadah Web dan EJB (Enterprise JavaBean) mereka. Untuk koneksi database, vendor server biasanya menyediakan implementasi DataSourceantarmuka, yang bekerja bersama dengan ConnectionPoolDataSourceimplementasi vendor driver JDBC (Java Database Connectivity) . The ConnectionPoolDataSourcepelaksanaan berfungsi sebagai pabrik koneksi manajer sumber daya untuk dikumpulkan java.sql.Connectionbenda. Demikian pula, instance EJB dari kacang sesi tanpa status, kacang berbasis pesan, dan kacang entitas dikumpulkan dalam wadah EJB untuk hasil dan kinerja yang lebih tinggi. Instance pengurai XML juga merupakan kandidat untuk penggabungan, karena pembuatan instance parser menghabiskan banyak sumber daya sistem.

Implementasi pengumpulan sumber daya sumber terbuka yang berhasil adalah DBCP kerangka kerja Commons Pool, komponen penyatuan koneksi database dari Apace Software Foundation yang banyak digunakan dalam aplikasi perusahaan kelas produksi. Dalam artikel ini, saya secara singkat membahas internal kerangka kerja Pool Commons dan kemudian menggunakannya untuk mengimplementasikan kumpulan utas.

Pertama-tama, mari kita lihat apa yang disediakan framework.

Kerangka Commons Pool

Kerangka kerja Pool Commons menawarkan implementasi dasar dan kuat untuk menggabungkan objek arbitrer. Beberapa implementasi disediakan, tetapi untuk tujuan artikel ini, kami menggunakan implementasi yang paling umum, file GenericObjectPool. Ia menggunakan CursorableLinkedList, yang merupakan implementasi daftar tertaut ganda (bagian dari Koleksi Jakarta Commons), sebagai struktur data yang mendasari untuk menahan objek yang dikumpulkan.

Di atas, kerangka kerja menyediakan sekumpulan antarmuka yang menyediakan metode siklus proses dan metode pembantu untuk mengelola, memantau, dan memperluas kumpulan.

Antarmuka org.apache.commons.PoolableObjectFactorymendefinisikan metode siklus hidup berikut, yang terbukti penting untuk mengimplementasikan komponen penggabungan:

 // 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 dapat Anda lihat dari tanda tangan metode, antarmuka ini terutama berhubungan dengan yang berikut:

  • makeObject(): Menerapkan pembuatan objek
  • destroyObject(): Menerapkan penghancuran objek
  • validateObject(): Validasi objek sebelum digunakan
  • activateObject(): Menerapkan kode inisialisasi objek
  • passivateObject(): Menerapkan kode uninitialization objek

Antarmuka inti org.apache.commons.ObjectPoollainnya— —menentukan metode berikut untuk mengelola 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;

The ObjectPoolimplementasi antarmuka ini mengambil PoolableObjectFactorysebagai argumen di konstruktor, dengan demikian mendelegasikan pembuatan objek untuk subclass. Saya tidak banyak bicara tentang pola desain di sini karena itu bukan fokus kami. Untuk pembaca yang tertarik melihat diagram kelas UML, lihat Sumberdaya.

Seperti disebutkan di atas, kelas org.apache.commons.GenericObjectPoolhanyalah salah satu implementasi dari org.apache.commons.ObjectPoolantarmuka. Kerangka kerja ini juga menyediakan implementasi untuk kumpulan objek yang dikunci, menggunakan antarmuka org.apache.commons.KeyedObjectPoolFactorydan org.apache.commons.KeyedObjectPool, di mana seseorang dapat mengaitkan kumpulan dengan kunci (seperti dalam HashMap) dan dengan demikian mengelola beberapa kumpulan.

Kunci strategi penyatuan yang sukses bergantung pada cara kita mengonfigurasi kumpulan. Kumpulan yang dikonfigurasi dengan buruk dapat menjadi sumber daya, jika parameter konfigurasi tidak disetel dengan baik. Mari kita lihat beberapa parameter penting dan tujuannya.

Detail konfigurasi

Pool dapat dikonfigurasi menggunakan GenericObjectPool.Configkelas, yang merupakan kelas dalam statis. Alternatifnya, kita bisa menggunakan GenericObjectPoolmetode setter untuk mengatur nilai.

Daftar berikut merinci beberapa parameter konfigurasi yang tersedia untuk GenericObjectPoolimplementasi:

  • maxIdle: Jumlah maksimum kejadian tidur di kumpulan, tanpa pelepasan objek tambahan.
  • minIdle: Jumlah minimum kejadian tidur di kumpulan, tanpa objek tambahan yang dibuat.
  • maxActive: Jumlah maksimum instance aktif di kumpulan.
  • timeBetweenEvictionRunsMillis: Jumlah milidetik untuk tidur di antara jalannya thread evictor objek diam. Jika negatif, tidak ada utas pengusir objek diam yang akan berjalan. Gunakan parameter ini hanya ketika Anda ingin utas evictor dijalankan.
  • minEvictableIdleTimeMillis: Jumlah waktu minimum suatu objek, jika aktif, dapat menganggur di kumpulan sebelum memenuhi syarat untuk digusur oleh pengusir objek menganggur. Jika nilai negatif diberikan, tidak ada objek yang dikeluarkan karena waktu idle saja.
  • testOnBorrow: Saat "true", objek divalidasi. Jika objek gagal validasi, itu akan dihapus dari pool, dan pool akan mencoba untuk meminjam yang lain.

Nilai optimal harus disediakan untuk parameter di atas untuk mencapai kinerja dan keluaran maksimum. Karena pola penggunaan bervariasi dari satu aplikasi ke aplikasi lainnya, sesuaikan kumpulan dengan kombinasi parameter yang berbeda untuk sampai pada solusi optimal.

Untuk memahami lebih lanjut tentang kumpulan dan internalnya, mari kita menerapkan kumpulan utas.

Persyaratan kumpulan benang yang diusulkan

Misalkan kita diberitahu untuk merancang dan mengimplementasikan komponen kumpulan utas untuk penjadwal pekerjaan untuk memicu pekerjaan pada jadwal yang ditentukan dan melaporkan penyelesaian dan, mungkin, hasil dari eksekusi. Dalam skenario seperti itu, tujuan kumpulan utas kami adalah untuk mengumpulkan prasyarat jumlah utas dan menjalankan pekerjaan terjadwal di utas independen. Persyaratan tersebut dirangkum sebagai berikut:

  • Utas harus dapat memanggil metode kelas arbitrer apa pun (tugas terjadwal)
  • Utas harus dapat mengembalikan hasil eksekusi
  • Utas harus dapat melaporkan penyelesaian tugas

Persyaratan pertama menyediakan ruang lingkup untuk implementasi yang digabungkan secara longgar karena tidak memaksa kita untuk mengimplementasikan antarmuka seperti Runnable. Itu juga memudahkan integrasi. Kami dapat menerapkan persyaratan pertama kami dengan menyediakan utas dengan informasi berikut:

  • Nama kelas
  • Nama metode yang akan dipanggil
  • Parameter yang akan diteruskan ke metode
  • Jenis parameter dari parameter yang diteruskan

Persyaratan kedua memungkinkan klien menggunakan utas untuk menerima hasil eksekusi. Implementasi sederhana adalah menyimpan hasil eksekusi dan menyediakan metode pengakses seperti getResult().

Persyaratan ketiga agak terkait dengan persyaratan kedua. Melaporkan penyelesaian tugas juga bisa berarti bahwa klien sedang menunggu untuk mendapatkan hasil eksekusi. Untuk menangani kemampuan ini, kami dapat menyediakan beberapa bentuk mekanisme panggilan balik. Mekanisme callback paling sederhana dapat diimplementasikan menggunakan java.lang.Object's wait()dan notify()semantik. Alternatifnya, kita bisa menggunakan pola Observer , tapi untuk sekarang mari kita buat semuanya tetap sederhana. Anda mungkin tergoda untuk menggunakan metode java.lang.Threadkelas join(), tetapi itu tidak akan berhasil karena thread yang dikumpulkan tidak pernah menyelesaikan run()metodenya dan terus berjalan selama kumpulan membutuhkannya.

Sekarang setelah persyaratan kita siap dan gambaran kasar tentang bagaimana menerapkan kumpulan utas, sekarang saatnya untuk melakukan pengkodean nyata.

Pada tahap ini, diagram kelas UML kami dari desain yang diusulkan terlihat seperti gambar di bawah ini.

Menerapkan kumpulan utas

Objek utas yang akan kita kumpulkan sebenarnya adalah pembungkus di sekitar objek utas. Mari kita panggil pembungkus WorkerThreadkelas, yang memperluas java.lang.Threadkelas. Sebelum kita dapat memulai pengkodean WorkerThread, kita harus menerapkan persyaratan kerangka kerja. Seperti yang kita lihat sebelumnya, kita harus mengimplementasikan PoolableObjectFactory, yang berfungsi sebagai pabrik, untuk membuat poolable kita WorkerThread. Setelah pabrik siap, kami menerapkannya ThreadPooldengan memperluas GenericObjectPool. Kemudian, kami menyelesaikan file WorkerThread.

Mengimplementasikan antarmuka PoolableObjectFactory

Kami mulai dengan PoolableObjectFactoryantarmuka dan mencoba menerapkan metode siklus hidup yang diperlukan untuk kumpulan utas kami. Kami menulis kelas pabrik ThreadObjectFactorysebagai 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 telusuri setiap metode secara mendetail:

Metode makeObject()menciptakan WorkerThreadobjek. Untuk setiap permintaan, kumpulan diperiksa untuk melihat apakah objek baru akan dibuat atau objek yang sudah ada akan digunakan kembali. Misalnya, jika permintaan tertentu adalah permintaan pertama dan kumpulan kosong, ObjectPoolimplementasi memanggil makeObject()dan menambahkan WorkerThreadke kumpulan.

Metode destroyObject()menghapus WorkerThreadobjek dari kolam dengan menyetel bendera Boolean dan dengan demikian menghentikan thread yang sedang berjalan. Kita akan melihat bagian ini lagi nanti, tetapi perhatikan bahwa kita sekarang mengambil kendali atas bagaimana objek kita dihancurkan.